Compare commits

..
423 Commits
Author SHA1 Message Date
Pedro Pinto 6d7290db63 Updated release notes. 2019-09-17 16:56:28 -04:00
Pedro Pinto 0d0cf11157 Fixed matching error in sim 2019-09-17 16:55:24 -04:00
Eric Harding 7b833087e5 Merged in SP-233-wilson (pull request #342)
RTM: SP-233 wilson
2019-09-16 13:05:41 +00:00
Eric Harding 12f1e026d0 Update release notes 2019-09-16 09:05:10 -04:00
Eric Harding 762ce06f7c Merge branch 'prod' of bitbucket.org:bluetradesys/spark into SP-233-wilson 2019-09-16 09:00:14 -04:00
Eric Harding 9052051872 Increase initial logging queue size. 128 is too small 2019-09-13 15:44:26 -04:00
Eric Harding ab5b4e17ad SP-233 SDK sample should avoid async due to hidden complexity 2019-09-13 15:44:12 -04:00
Pedro Pinto 4295165427 Updated release notes. 2019-09-12 09:46:26 -04:00
Taha Afzal 9a1199a058 Better comments 2019-09-11 16:07:15 -04:00
Pedro Pinto 62f1911f35 Merge branch 'SP-220-raw-day-pos' into prod 2019-09-11 13:56:33 -04:00
Pedro Pinto d75c8af729 Updated release notes. 2019-09-11 13:53:03 -04:00
Pedro Pinto da95ece9fc Merge branch 'SP-221-sound-alerts' into prod 2019-09-11 13:28:42 -04:00
Pedro Pinto deca2bedb5 Made GMI sftp settings empty for prod use. 2019-09-11 09:55:24 -04:00
John Colvin de64bd843c was adding account name twice to position tool tip in ladder 2019-09-09 10:40:21 -05:00
John Colvin 79a8a29bac LadderView.PositionToolTipConverter - restructure impl a bit, change comments 2019-09-05 19:07:00 -05:00
John Colvin 6aaed0e76e simplify LadderView.PositionToolTipConverter impl 2019-09-05 18:57:41 -05:00
John Colvin 8072eece66 SP-220 pr edit: added comment explaining confusing position display formatting 2019-09-05 18:37:33 -05:00
John Colvin 043d826442 GreaterThanTrigger IEquatable impl 2019-09-05 17:36:51 -05:00
John Colvin 3bf9fa08e5 SP-221 Deleting an alert with 'Not Between' causes the shell to crash 2019-09-05 17:32:19 -05:00
John Colvin 79a7b8504e SP-220 mview row raises invalidated event view when raw day pos changes 2019-09-05 17:01:50 -05:00
John Colvin ffd2dd22c2 SP-220 fix raw day pos tooltip in ladder 2019-09-05 16:58:46 -05:00
John Colvin 469e4636e3 overload ValueConverter.create for 4 generic input args 2019-09-05 16:53:22 -05:00
John Colvin 15b45b7494 Ladder was hiding the position button when only raw day pos was checked 2019-09-05 14:07:33 -05:00
Pedro Pinto d7a98a8a95 Updated release notes. 2019-09-05 11:44:49 -04:00
John Colvin f0a144e786 Merged in raw-day-pos (pull request #336)
RTM Raw day pos

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2019-09-05 15:22:53 +00:00
Jake Norbut c45f131dc0 mview settings manager should use mv grid defaults and not pnl 2019-09-05 10:15:35 -05:00
Jake Norbut b5a227f7aa I don't think we need this now that we have John's IxxxMigrations rules 2019-09-05 10:14:41 -05:00
Michael Harding b4f2de327a test updates 2019-09-05 10:08:20 -04:00
John Colvin 70a1f0305e export mview 'sanitize' logic as ISettingsMigrationRules 2019-09-04 17:09:50 -05:00
John Colvin 6bb7348b54 edit TradeAlertsManualTest 2019-09-04 15:40:35 -05:00
John Colvin 1cfba93216 SP-210 Trade Alerts Manual Test 2019-09-04 15:12:06 -05:00
Kevin Darby ba33d9a657 Merge branch 'master' of bitbucket.org:bluetradesys/spark 2019-09-04 15:11:14 -04:00
Kevin Darby 5b2a6215a7 fixing tests 2019-09-04 15:09:56 -04:00
Eric Harding 9edb5625c1 Dorman Algo : Use the lower value when between entries in the step table 2019-09-04 14:41:59 -04:00
Kevin Darby a31ad90be4 shorten prorata parameter names by removing 'enable_' 2019-09-04 14:01:20 -04:00
Kevin Darby bddebfc25b fix some tests 2019-09-04 14:00:54 -04:00
John Colvin f184ec5ecf SP-218 add raw day pos column to mview 2019-09-04 11:45:23 -05:00
John Colvin b0a1d4f448 stack MarketViewView.Columns enum values vertically 2019-09-04 11:45:23 -05:00
John Colvin 5bcb75b15c SP-217 add raw day pos to ladder (expose via optional setting) 2019-09-04 11:45:23 -05:00
John Colvin 24ce565051 SP-213 add raw day pos column to original pnl window 2019-09-04 11:45:23 -05:00
John Colvin d3aa696afb GridSettings 'add_missing_columns' extension 2019-09-04 10:30:35 -05:00
John Colvin e33028669d UserSettingsHub imports/applies IPAndLGridSettingsMigrationRules 2019-09-04 10:30:35 -05:00
John Colvin 81700b03c8 add ISettingsMigrationRule 2019-09-04 10:30:35 -05:00
John Colvin 775ff3e2db PAndLWindow default settings was missing 'has day trades' col 2019-09-03 16:47:12 -05:00
Pedro Pinto 9f48b648b7 Added an option to run our FIX simulator standalone.
This is needed to run wire to wire speed trials.
2019-09-03 14:29:24 -04:00
Pedro Pinto f0376843db Updated CME logon file, made it so its always copied if in dev 2019-09-03 14:23:10 -04:00
Kevin Darby cb52c14db1 disable span by default 2019-09-03 10:17:14 -04:00
Taha Afzal 2b53a051d3 Presod local fills are deleted at startup, Modified the SOD test to test for this 2019-09-01 13:00:15 -04:00
Taha Afzal 768853bbe5 Merge branch 'master' of bitbucket.org:bluetradesys/spark 2019-08-30 18:30:15 -04:00
Taha Afzal cc40c523f9 Local fills are not applied to the SOD, Admin fills are applied to the SOD (if they havent been overwritten by the base) 2019-08-30 18:30:11 -04:00
Pedro Pinto c7fd8deabe Updated release notes. 2019-08-30 16:59:37 -04:00
Pedro Pinto 6a896586ce Added MD sequence number to order FIX. 2019-08-30 16:55:23 -04:00
Pedro Pinto c4f4a8ff4e Updated release notes. 2019-08-30 11:29:07 -04:00
Pedro Pinto 1cc594d3a4 Merged in SP-91-position-migration (pull request #331)
SP-91 position migration
2019-08-30 15:22:39 +00:00
Pedro Pinto 6725762fa1 Merged master 2019-08-30 11:20:18 -04:00
Eric Harding 2dd2b4e9b8 Merged in api_support (pull request #335)
Api support
2019-08-29 20:59:07 +00:00
Eric Harding 6c78a949bb Merge branch 'master' of bitbucket.org:bluetradesys/spark into api_support 2019-08-29 16:51:00 -04:00
Eric Harding ce0beafac3 CR feedback 2019-08-29 16:43:43 -04:00
Michael Harding b2f27e0a58 Manual fills updated after 1970 2019-08-29 16:24:52 -04:00
John Colvin a9152ff365 Merged in sound-alerts (pull request #332)
RTM Sound alerts
2019-08-29 19:59:31 +00:00
Michael Harding 9ea581367b Add timestamp to Position management 2019-08-29 15:58:33 -04:00
John Colvin b761e24dbc accidentally changed 'Recreate' icon (set it back to Beaker) 2019-08-29 15:51:04 -04:00
John Colvin ca8a148108 Merge remote-tracking branch 'origin/master' into sound-alerts-draft 2019-08-29 15:03:00 -04:00
John Colvin 53586d19df add comment abt accessing dispatcher in ThreadedWindowLauncher 2019-08-29 14:47:39 -04:00
Pedro Pinto 095e4bc325 Updated release notes. 2019-08-29 14:46:36 -04:00
John Colvin 5b8a5af7cd ThreadedWindowLauncher exits loop after the timeout 2019-08-29 14:42:58 -04:00
Pedro Pinto 6a8f2a4868 Updated release notes 2019-08-29 14:36:40 -04:00
John Colvin b7c9d282ea SP-212 (Alerts Feedback) - cancel button dismisses dialog 2019-08-29 14:18:15 -04:00
John Colvin 8cc61628e3 SP-212 (Alerts Feedback) - Initial dialog is too large 2019-08-29 14:15:44 -04:00
Jake Norbut 6f91d1a890 Merged in SP-211 (pull request #333)
RTM - Launcher window will always show a title in taskbar preview
2019-08-29 18:12:24 +00:00
John Colvin 548328dc41 regular settings dialog can use BTSDialog style (now that icon height/margin looks good) 2019-08-29 13:56:28 -04:00
John Colvin 12c864368d better icon margin/height in BTSDialog (Style) Template 2019-08-29 13:52:22 -04:00
John Colvin dd60692b28 better alerts icon 2019-08-29 13:35:43 -04:00
Michael Harding 9fc4b369bf Clear security dropdown 2019-08-29 13:22:53 -04:00
Eric Harding d12200e977 Allow a larger timeout for rpc connect in bts api 2019-08-29 13:17:57 -04:00
Jake Norbut 38941d9bd9 Launcher window will always show a title in taskbar preview 2019-08-29 10:36:11 -05:00
Pedro Pinto f59f6a54ce Merge branch 'SP-91-position-migration' of bitbucket.org:bluetradesys/spark into SP-91-position-migration 2019-08-29 11:29:30 -04:00
Pedro Pinto 727898fc9c Changed fills to use exchange provided timestamp. 2019-08-29 11:29:12 -04:00
Eric Harding f7b5eeb04c Increase default message queue size on windows 2019-08-29 10:49:38 -04:00
Michael Harding 908f924e28 Manual fills feedback - simple items 2019-08-29 10:33:40 -04:00
Taha Afzal 0ff2fbc4ff Modifying tests according to the limit changes 2019-08-28 22:24:54 -04:00
Taha Afzal 82535c915a Merge branch 'SP-91-position-migration' of bitbucket.org:bluetradesys/spark into SP-91-position-migration 2019-08-28 20:15:20 -04:00
Taha Afzal 038137d002 Cleaned up SOD time and modified the test 2019-08-28 20:15:15 -04:00
Pedro Pinto 031dd3779d Fixed windows build. 2019-08-28 19:04:08 -04:00
Pedro Pinto 5c552f4e50 Fixed windows build 2019-08-28 18:47:15 -04:00
Eric Harding 64d43be34a Add error checking to order sample 2019-08-28 15:56:27 -04:00
Eric Harding d544511993 Additional managed init cleanup 2019-08-28 15:47:36 -04:00
Pedro Pinto 5c2617d065 Merge branch 'master' into SP-91-position-migration 2019-08-28 14:35:43 -04:00
Pedro Pinto 27dfc14085 Updated release notes. 2019-08-28 14:32:24 -04:00
Pedro Pinto 8f43c2e172 Merge branch 'SP-175-bg_color' 2019-08-28 14:31:00 -04:00
Eric Harding e030518d60 Config bug: if configserver is down but we proceed (for unit tests or something) we don't get the right values 2019-08-28 14:25:37 -04:00
Pedro Pinto f794537868 Updated release notes. 2019-08-28 14:21:45 -04:00
Eric Harding 7a4cb105d8 Merged in entitlement_logging (pull request #327)
RTM: Add entitlement logging to everyone's favorite super long md log
2019-08-28 18:20:24 +00:00
Eric Harding 06e4e34625 Merged in SP-197 (pull request #328)
RTM  SP-197 Move onixs license file into license.h

Approved-by: Kevin Darby <kevin.darby@bluetradesys.com>
2019-08-28 18:19:47 +00:00
Jake Norbut 78be61c9ba PR suggestion - changing ISelectionHighlightListener.on_selection_fill's parameter method to be SolidColorBrush so we avoid casting 2019-08-28 13:11:48 -05:00
Jake Norbut d80045d7c0 PR suggestion - adding new _default_enable_background_color const rather than having random falses strewn throughout; good style catch John 2019-08-28 13:07:20 -05:00
Pedro Pinto 725a3dd76b Updated release notes. 2019-08-28 13:57:16 -04:00
Pedro Pinto 81e8acb459 Merge branch 'SP-174' 2019-08-28 13:54:19 -04:00
Pedro Pinto 9cc5a38213 Updated release notes 2019-08-28 13:40:01 -04:00
Pedro Pinto dd28d5375c Merge branch 'SP-163' 2019-08-28 13:32:03 -04:00
John Colvin 46c37059b2 TradeSource should not recycle List in .Scan extension 2019-08-28 13:25:09 -04:00
Jake Norbut b0bfcc861a fixing OrderBook template as well while we're in here 2019-08-28 12:14:28 -05:00
John Colvin 1e353c2a21 ThreadedWindowLauncher max_thread_count = Environment.ProcessorCount 2019-08-28 12:55:49 -04:00
Pedro Pinto e283d05d81 Merge branch 'prod' 2019-08-28 12:50:05 -04:00
Jake Norbut aaf90c5293 PR feedback - Hardening the RowSelectionAdorner 2019-08-28 11:42:09 -05:00
Michael Harding 4f5f73101d Merge branch 'SP-91-position-migration' into SP-150 2019-08-28 12:37:33 -04:00
Michael Harding e1a5c1e6cd Hide GMI upload 2019-08-28 12:35:53 -04:00
Eric Harding 2ee0f0be2c buildspark.cmd should touch CMakeLists so it doesn't get lonely 2019-08-28 12:26:41 -04:00
Eric Harding 5bdaab0581 Add BTS.init() to init_spark lib and provide args 2019-08-28 12:26:03 -04:00
Jake Norbut 707b5cf016 PR suggestion - adding Flags to enum declaration 2019-08-28 11:03:25 -05:00
Jake Norbut f415cddf0b Merged in SP-208 (pull request #330)
SP-208 - no nullref when clearing empty discrete filter

Approved-by: Kevin Darby <kevin.darby@bluetradesys.com>
2019-08-28 15:52:57 +00:00
Jake Norbut 8c36b498a4 proposed release notes 2019-08-28 10:45:45 -05:00
John Colvin c03263ef0b SettingsModule - put global alerts menu item on top of workspaces 2019-08-28 11:14:15 -04:00
Jake Norbut 94baf15d01 Per John, adding this line back to repopulate items once again 2019-08-28 10:02:37 -05:00
John Colvin c36541cee0 GlobalAlertsDialog SizeToContent=Width 2019-08-28 10:39:07 -04:00
Jake Norbut 14aad6ece8 SP-208 - no nullref when clearing empty discrete filter 2019-08-28 08:48:34 -05:00
Michael Harding 088185d5ce Fix revert 2019-08-28 09:42:17 -04:00
Jake Norbut 08478a2663 don't skip new bool when serializing settings to disk 2019-08-28 08:01:23 -05:00
John Colvin 9fd88f2a86 model 'ToggleSwitch' style after windows 10 settings dialog toggle buttons 2019-08-28 00:30:48 -04:00
John Colvin 83b8f14aa0 added ShowGlobalAlerts SparkCommand to SettingsShellModule 2019-08-28 00:30:47 -04:00
John Colvin aba09e769b added GlobalAlertsDaemon 2019-08-28 00:30:46 -04:00
John Colvin 8de1fa00aa added GlobalAlertsView/VM 2019-08-28 00:30:46 -04:00
John Colvin d1960850e8 added Global\TradeAlertSettings 2019-08-27 18:48:39 -04:00
John Colvin b1714cbd83 SoundNotification IEquatable/Equals impl 2019-08-27 18:48:38 -04:00
John Colvin ed3a1a735e added TradeSource factory methods (creates IObservable<IReadOnlyList<TradeInfo>>) 2019-08-27 18:48:37 -04:00
John Colvin b6d1eb9773 set DIYGrid BorderBrush\Thickness TemplateBindings 2019-08-27 18:48:37 -04:00
John Colvin d39d7b8f47 added ColumnBase default constructor, made 'key' prop mutable so columns can be defined in xaml (snatched from pnl branch) 2019-08-27 18:48:36 -04:00
John Colvin fb4ffaeeb9 DIYGrid.SyncWithColumnGeometry impl (snatched from pnl branch) 2019-08-27 18:48:36 -04:00
John Colvin 19e4079864 add 'LoggerContext' (snatched from pnl branch) 2019-08-27 18:48:36 -04:00
John Colvin a38fb38f33 added DoublePredicateVM (will use in GlobalAlertsVM) 2019-08-27 18:48:36 -04:00
Jake Norbut 20df8a6597 if show_background, then set the background from the color popup, else default to the Fallback background color, transparent 2019-08-27 16:50:47 -05:00
Jake Norbut f90ef6e12c changing can we set the background color override to check for can_override_background_color 2019-08-27 16:49:30 -05:00
Jake Norbut b528e1c1ec background color default should be transparent and not black 2019-08-27 16:48:14 -05:00
Michael Harding 97a2ff9adf Sort securities 2019-08-27 17:30:11 -04:00
Jake Norbut d52ec704fb bind Foreground to font_brush 2019-08-27 15:14:49 -05:00
Jake Norbut e2d065f6b3 allow font color to change on bid/ask implied/direct/merged cols 2019-08-27 15:12:21 -05:00
Michael Harding 6454ea16ae Indicate validation errors in tab 2019-08-27 15:54:29 -04:00
Jake Norbut 298f9261b4 allow overrideable settings to not be overridden 2019-08-27 14:49:43 -05:00
Michael Harding 09805de03e Validate fills before allowing save 2019-08-27 15:23:17 -04:00
Pedro Pinto a0d3447a0d Merge branch 'SP-91-position-migration' of bitbucket.org:bluetradesys/spark into SP-91-position-migration 2019-08-27 14:05:24 -04:00
Pedro Pinto 14be87e22d Added unit test to validate is_expired logic. 2019-08-27 14:05:05 -04:00
Pedro Pinto e61f514c0b Fixed GMI test. 2019-08-27 14:01:06 -04:00
Michael Harding 5e84af4f8f Manual fills validation and style improvements 2019-08-27 13:51:52 -04:00
Pedro Pinto 5f75609d8d SP-180, Lean Redeploying After Detach/Attach 2019-08-27 13:33:34 -04:00
Pedro Pinto bd0e697ea3 Made the SFTP client use the name of the target in its temp file. 2019-08-27 13:28:07 -04:00
John Colvin 67d62f0d8c IPropertyWrapperVM.sync_with_models extension 2019-08-27 13:16:48 -04:00
John Colvin 1712069530 TradeMessage.price extension 2019-08-27 13:16:48 -04:00
John Colvin 2b12e7fee1 INotificationService.link_to_trades method/impl 2019-08-27 13:16:48 -04:00
John Colvin 0fb69c8beb PredicateBuilder.and_safe factory method (handles null args gracefully) 2019-08-27 13:16:47 -04:00
John Colvin 6f68c0f6a1 PredicateBuilder.greater/less_than_or_equal_to factory methods 2019-08-27 13:16:47 -04:00
John Colvin a9df42deec added SparkTag/Command to show GlobalAlerts dialog (not yet implemented) 2019-08-27 13:16:47 -04:00
John Colvin 258f4f394d added NotificationProcessor.process_items (handles multiple items rather than 1) 2019-08-27 13:10:38 -04:00
John Colvin d42ebd7ea5 added ExceptionAssert.Between method 2019-08-27 13:10:37 -04:00
John Colvin d9bb667351 added AppendTextConverter 2019-08-27 13:10:37 -04:00
John Colvin 8ef97d012a EnumHelper.split_flags requires all bits to match and be non-zero 2019-08-27 13:10:36 -04:00
John Colvin 76cc77485a added Side.to_aggressor extension 2019-08-27 13:10:36 -04:00
John Colvin 6572c7344d move TradeInfo into its own file 2019-08-27 13:10:36 -04:00
John Colvin 25dfa9a0a2 IEquatable impl for IPredicate impls 2019-08-27 13:10:36 -04:00
John Colvin 50f6ce9fb4 fix 'TOutter' typo 2019-08-27 13:10:36 -04:00
John Colvin cba1675da7 remove AnonymousMutablePredicate (don't need it) 2019-08-27 13:10:35 -04:00
John Colvin 3720a7728f added ThreadedWindowLauncher (will be used to launch GlobalAlertsDialog) 2019-08-27 13:10:35 -04:00
John Colvin ce26c2acaf reduce ComboBox default padding 2019-08-27 13:08:18 -04:00
Michael Harding 631964e8bd Add manual fills before validating 2019-08-27 12:41:09 -04:00
Jake Norbut 4ea32e06a9 fix ActionMenu binding in SimpleGrid template
(cherry picked from commit 3b25e181fa)
2019-08-26 16:26:42 -05:00
Jake Norbut 3b25e181fa fix ActionMenu binding in SimpleGrid template 2019-08-26 16:22:33 -05:00
Taha Afzal 3bd4dce5cb Spread positions limits can be optionally not enforced 2019-08-26 16:34:51 -04:00
Eric Harding 880dac06b9 SP-197 factor common code between onixs cme/ice 2019-08-26 15:31:44 -04:00
Pedro Pinto cb6e004931 SP-206, expiration predicate takes SOD into account 2019-08-26 14:00:17 -04:00
Kevin Darby c19aae91a6 added checkbox for ignoreSpreadPositionLimits 2019-08-26 13:54:19 -04:00
Kevin Darby 2194ea027f added default for ignoreSpreadPositionLimits 2019-08-26 13:33:59 -04:00
Taha Afzal ded84cfb1c Merge branch 'SP-91-position-migration' of bitbucket.org:bluetradesys/spark into SP-91-position-migration 2019-08-26 13:12:21 -04:00
Taha Afzal 0464b61de5 Optionally ignore spread posiiton limits 2019-08-26 13:12:15 -04:00
Michael Harding 7cbfc4d7d7 Merge branch 'SP-91-position-migration' into SP-150 2019-08-26 10:56:04 -04:00
Eric Harding af83516e38 Add user to KEYCODE logging 2019-08-26 10:22:48 -04:00
Michael Harding 8f4f0649a6 Comment out position tab 2019-08-26 09:32:40 -04:00
Taha Afzal 68834e2a6d SP-186 2019-08-25 15:49:16 -04:00
Pedro Pinto 3181501210 Allowed GMI parser to skip option positions. Fixed default conf file 2019-08-23 17:25:39 -04:00
Pedro Pinto 0cec54dd62 Merge branch 'master' into SP-91-position-migration 2019-08-23 16:28:07 -04:00
Pedro Pinto 1766feebcb Fixed SP-157, starting up with 0 fills is no longer a problem. 2019-08-23 16:25:15 -04:00
Eric Harding e2d10bc518 SP-197 Move onixs license file into license.h 2019-08-23 15:07:30 -04:00
Eric Harding aacc5d8d27 add entitlement logging to everyone's favorite super long md log 2019-08-23 11:50:59 -04:00
Eric Harding 0ddf145ef9 Merged in settingsmanager_bugfix (pull request #319)
RTM: Settingsmanager : handle more than 9 users
2019-08-23 01:50:48 +00:00
Eric Harding e7ee32b3df SP-199 Apply cash_per_point overrides in SecurityMapper 2019-08-22 12:48:40 -04:00
Eric Harding fc41203f28 merge master 2019-08-22 12:40:35 -04:00
Pedro Pinto 13009a6d90 Fixed clang build 2019-08-22 12:00:05 -04:00
Pedro Pinto fa55a61ef3 Merge branch 'prod' of bitbucket.org:bluetradesys/spark into prod 2019-08-22 11:43:08 -04:00
Pedro Pinto e832252669 Merge branch 'master' of bitbucket.org:bluetradesys/spark 2019-08-22 11:41:30 -04:00
Pedro Pinto 31980f7c14 Updated release notes. 2019-08-22 11:41:14 -04:00
Pedro Pinto d63ee80f94 Fixed up internal tool only pp uses. 2019-08-22 11:40:53 -04:00
Jake Norbut ca69e11fff Merged in SP-190 (pull request #325)
use Spreader AlgoFormat when security is synthetic

Approved-by: John Colvin <john.colvin@bluetradesys.com>
2019-08-22 15:38:17 +00:00
Eric Harding 9d202906f0 Merged in SP-184-add-display-factor-mapping (pull request #326)
SP-184 add display factor mapping

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-08-22 15:27:04 +00:00
Pedro Pinto 968dd5a4d3 Merge branch 'prod' 2019-08-22 11:03:09 -04:00
Pedro Pinto 30cb1fd800 Updated externals subrepo pointer 2019-08-22 11:02:35 -04:00
Pedro Pinto 38eb122bbc Updated docker subrepo pointer 2019-08-22 11:01:51 -04:00
Eric Harding e2e31d310f SP-184 Update SOM price mult 2019-08-22 10:55:02 -04:00
Eric Harding 6890a61b26 Add SOM display factor to defaults 2019-08-22 08:51:29 -04:00
Eric Harding 36828a979f Add std::optional stand-in for _WIN32 2019-08-21 18:40:26 -04:00
Eric Harding 57bf60e6df Extract parse_enum<T> for parsing protobuf enum to optional<T> 2019-08-21 18:40:26 -04:00
Eric Harding 8e3a83d314 SP-184 Revenge of Mapping Part IV 2019-08-21 18:40:26 -04:00
Jake Norbut dc43d25c53 use Spreader AlgoFormat when security is synthetic 2019-08-21 15:54:23 -05:00
Jake Norbut 068e762f44 PR feedback - Workspaces are now Topmost true; gret suggestion Eric! 2019-08-21 11:28:19 -05:00
Jake Norbut 845fa99bd0 swapping these statements so no NullRef 2019-08-21 10:48:48 -05:00
John Colvin 2b5403bbd5 Merged in sorted-wrapper-crash (pull request #324)
SP-188 DiscreteValuesFilterVM SortedWrapper crash
2019-08-21 15:34:50 +00:00
John Colvin c9ff3e1bb3 SP-188 DiscreteValuesFilterVM SortedWrapper crash 2019-08-21 10:21:18 -05:00
Pedro Pinto c60ada3c78 Merge branch 'SP-91-position-migration' of bitbucket.org:bluetradesys/spark into SP-91-position-migration 2019-08-20 20:18:14 -04:00
Taha Afzal ed7790bc0c Admin day positions hooked up 2019-08-20 16:10:12 -04:00
Jake Norbut 63434516f8 quoting workspace names in confirmation dialogs 2019-08-20 14:44:14 -05:00
Jake Norbut 8d5715d510 adding comment 2019-08-20 14:37:29 -05:00
Jake Norbut e8b1786411 splitting date and time in UI into separate columns; formatting DateTime is now done in Style 2019-08-20 14:24:07 -05:00
Jake Norbut b2be4c58c4 Workspaces ComboBox now has a table-like appearance 2019-08-20 13:43:10 -05:00
Jake Norbut 380d263c5e stylizing ComboBox for a 2 column layout 2019-08-20 13:01:09 -05:00
Jake Norbut 9a13fb3626 adding LastSaveTime to VM for use in UI 2019-08-20 13:00:39 -05:00
Pedro Pinto 924eb31d1d Merged 2019-08-20 13:36:22 -04:00
Jake Norbut a8cd07e4d4 relocating commands here, adding confirmations, etc 2019-08-20 10:16:21 -05:00
Jake Norbut aa3dda2c57 adding DisplayName property that displays last save info if available 2019-08-20 10:15:34 -05:00
Jake Norbut 4e78938827 adding last_save_lldatetime ulong to proto; populing said field in save_workspace 2019-08-20 10:14:45 -05:00
Jake Norbut 02062ee07b removing commands 2019-08-20 10:13:40 -05:00
Jake Norbut ed10545d8a Removing no longer valid VisualState changes 2019-08-20 10:12:41 -05:00
Jake Norbut 1335813031 bringing order to Workspaces dialog UI 2019-08-20 10:10:30 -05:00
Michael Harding cacf3933be GMI file Upload 2019-08-20 11:09:28 -04:00
Taha Afzal f7b89b3d39 Flling the AdminServiceUtils::get_clearing_account_map stub 2019-08-19 17:13:00 -04:00
Taha Afzal d86e7c4ad2 Adding price multiplier to GMI_SymbolMap 2019-08-19 16:17:49 -04:00
Pedro Pinto 0d910d6f6e Cleaned up docs. 2019-08-19 10:33:08 -04:00
Pedro Pinto f1fe8226b5 Cleaned column references in money parser. 2019-08-19 10:07:25 -04:00
Pedro Pinto d4cd9d5399 Cleaned column references in GMI parser. 2019-08-19 10:00:44 -04:00
Pedro Pinto 3a8d3e0104 Use CTPRIC consistently as the source of trade price. 2019-08-19 10:00:22 -04:00
Taha Afzal a053609cc9 Merge branch 'SP-91-position-migration' of bitbucket.org:bluetradesys/spark into SP-91-position-migration 2019-08-16 22:13:41 -04:00
Taha Afzal 6b311883a5 Fixed the bug which was preventing the GMI imported rows to be to shown in the PNL UI 2019-08-16 22:13:36 -04:00
Pedro Pinto c8e3258544 Broke the update cycle between money file download and admin changes. 2019-08-16 21:35:37 -04:00
Taha Afzal a24e814182 Added more logs 2019-08-16 19:33:54 -04:00
Taha Afzal 417413e8c0 Merge branch 'SP-91-position-migration' of bitbucket.org:bluetradesys/spark into SP-91-position-migration 2019-08-16 17:46:13 -04:00
Taha Afzal 1090422fa2 base pos tests 2019-08-16 17:46:09 -04:00
Pedro Pinto ff753b0550 Added an option to map GLBX to SIM in GMI import. 2019-08-16 16:25:46 -04:00
Taha Afzal 74f2e2ac50 Merge branch 'SP-91-position-migration' of bitbucket.org:bluetradesys/spark into SP-91-position-migration 2019-08-16 15:07:53 -04:00
Taha Afzal ffbed31b33 Added a basic base position text, and logs when base positions ar eupdated 2019-08-16 15:07:49 -04:00
Kevin Darby 4992d2dab6 Implemented set_cash_balance_batch
made defaults/position_cfg build
2019-08-16 14:55:34 -04:00
Taha Afzal 88dbfd4346 Merge branch 'SP-91-position-migration' of bitbucket.org:bluetradesys/spark into SP-91-position-migration 2019-08-16 13:40:10 -04:00
Taha Afzal 2c1cf5f2e0 tests for sd 2019-08-16 13:39:58 -04:00
Pedro Pinto 3d6e6ea7db Money file download code complete. 2019-08-16 12:34:02 -04:00
Pedro Pinto 73d1f67dcd Added helper class to share download logic. 2019-08-16 11:29:09 -04:00
Pedro Pinto ebb427dc90 Merge branch 'SP-91-position-migration' of bitbucket.org:bluetradesys/spark into SP-91-position-migration 2019-08-16 09:59:03 -04:00
Pedro Pinto 74bed6275a Added skelleton for money file download. 2019-08-16 09:50:15 -04:00
Taha Afzal 20f65a9196 Name changed (unnormalized to raw) 2019-08-16 09:45:36 -04:00
Taha Afzal 69208bf347 Merge branch 'SP-91-position-migration' of bitbucket.org:bluetradesys/spark into SP-91-position-migration 2019-08-15 23:43:59 -04:00
Taha Afzal 7c9eac2803 More PositionMap tests 2019-08-15 23:43:53 -04:00
Pedro Pinto 2039be54df Added money file parser. 2019-08-15 22:00:53 -04:00
Pedro Pinto 221f24827b Refactored. 2019-08-15 20:47:50 -04:00
Jake Norbut 5e0f1d450c removing unused property 2019-08-15 15:49:26 -05:00
Jake Norbut ec4f88c68f having row selection adorner reactor to selection changed events rather than key|mouse up|down 2019-08-15 15:48:59 -05:00
Pedro Pinto cad611104b Moved some files around. 2019-08-15 16:37:48 -04:00
Jake Norbut fe179d44f2 RowSelectionAdorner now responds to KeyUp (CTRL-A for example) 2019-08-15 15:15:07 -05:00
Jake Norbut c4866253b8 need a KeyUp here 2019-08-15 15:14:33 -05:00
Pedro Pinto 932b185769 Merge branch 'SP-91-position-migration' of bitbucket.org:bluetradesys/spark into SP-91-position-migration 2019-08-15 16:13:32 -04:00
Pedro Pinto a3e4576165 Allowed for multiple records for the same security in gmi file. 2019-08-15 16:13:09 -04:00
Jake Norbut 0a1cf0198d sending my NullableColorOpacityProto to /dev/null 2019-08-15 14:20:59 -05:00
Jake Norbut 6b97d55927 adding show_background_color to main GridSettingsVM so that OB bg color settings are kept hidden for now; misc comment 2019-08-15 14:09:47 -05:00
Taha Afzal 2780d6da0b Basic tests 2019-08-15 15:08:20 -04:00
Jake Norbut 2e831aa90e fixing alpha in column-level background override 2019-08-15 13:27:29 -05:00
Jake Norbut ad819dca68 can just use rgb_color() now that Popup is in alpha enabled mode 2019-08-15 13:27:04 -05:00
Jake Norbut 14f2ae481a selection highlight and background color picker popupsnow make use of the new alpha hotness 2019-08-15 10:34:31 -05:00
Jake Norbut c222181730 ColorOpacityProto can now spit out a Color with the appropriate alpha bytes 2019-08-15 10:33:42 -05:00
Jake Norbut d59f7a5bb1 new alpha-compliant ColorPickerPopupVM from John 2019-08-15 10:32:32 -05:00
Jake Norbut 0d4f97a023 visiblity of bg color Column-level override UI is now linked to GridSettings enable_background_color as well 2019-08-15 09:11:47 -05:00
Jake Norbut 6603e8ad86 another sanitization spot 2019-08-15 08:37:44 -05:00
Kevin Darby 632a86f670 Merge branch 'prod' of bitbucket.org:bluetradesys/spark 2019-08-15 09:35:33 -04:00
Taha Afzal cdbda8250f 'Fixing' the test (the test is broken, fill really fix it in the subsequent push) 2019-08-14 22:14:54 -04:00
Taha Afzal be6c2f8b35 Fixing merge conflict 2019-08-14 21:48:07 -04:00
Taha Afzal ddaf029cc7 Cleaned up the SODPosition file and added scaffolding for tests 2019-08-14 21:45:24 -04:00
Pedro Pinto fae994c057 Fixed a bug in gmi parser. 2019-08-14 21:03:06 -04:00
Pedro Pinto d72796394d Made GMI parser more configurable and more articulate. 2019-08-14 20:54:03 -04:00
Pedro Pinto a3dc6ebc2e Dropped non existing docs from SDK build. 2019-08-14 19:43:35 -04:00
Pedro Pinto 8278a20b95 Adjusted to new GMI file format. 2019-08-14 19:17:34 -04:00
Pedro Pinto 88401f0c27 Merge branch 'SP-91-position-migration' of bitbucket.org:bluetradesys/spark into SP-91-position-migration 2019-08-14 18:58:36 -04:00
Pedro Pinto 16493a9919 Made download more robust. 2019-08-14 18:58:03 -04:00
Jake Norbut 5c9c75463c GridSettings background_color should be a uint (can be tranparent by deafult), and not a NullableColorProto; fml 2019-08-14 17:39:03 -05:00
Jake Norbut 5f6742f1db adding enable_background_color to GridSettings and all the right spots 2019-08-14 16:48:08 -05:00
Taha Afzal ccb050c8b8 SP-182, Implement clearing to bts account lookup in AdminServiceUtils 2019-08-14 16:55:40 -04:00
John Colvin 43fd07dcfa Merged in SOM-ticks (pull request #320)
SPARK-1715 Handle fractional numerator tick sizes

Approved-by: Kevin Darby <kevin.darby@bluetradesys.com>
2019-08-14 20:43:00 +00:00
John Colvin efcb4f471c fix config toggle for som ticks 2019-08-14 15:40:23 -05:00
John Colvin d2f2e03e64 [SPARK-1715] config toggle for som ticks fix 2019-08-14 15:31:30 -05:00
Jake Norbut ea2aee5044 utilzing new argb 2019-08-14 15:04:40 -05:00
John Colvin cfeba74900 SPARK-1715 Handle fractional numerator tick sizes 2019-08-14 14:50:42 -05:00
Jake Norbut 9a76d9c857 argb-ness from John 2019-08-14 14:21:58 -05:00
Jake Norbut 231f596ad7 switching to NullableColorProto in wrap up attempt 2019-08-14 14:17:33 -05:00
Jake Norbut 41bc4f7950 re-generated conf with SOM and latest from CME ftp 2019-08-14 11:51:07 -05:00
Jake Norbut 963bd15b35 adding SOM manual list; CME does not publish SOM data in their ftp 2019-08-14 11:50:24 -05:00
Jake Norbut 3c7dbc0489 RowSelectionAdorner now implements IDisposable 2019-08-14 11:17:50 -05:00
Jake Norbut 3046871cf4 removing background_overlay property from HyperLinkColumn, et al 2019-08-14 10:59:52 -05:00
Jake Norbut dd9ca350af RowSelectionAdorner bounds check should use ScrollContentPresenter for X point 2019-08-14 10:51:06 -05:00
Jake Norbut b292f50d99 Removing Background selection/active highlighting from DIYGridRowStyle 2019-08-14 10:16:48 -05:00
Jake Norbut 910cf20473 sanitize existing persisted grid state 2019-08-14 10:05:46 -05:00
Jake Norbut 5ac8a46a6c DIYGrid makes use of new RowSelectionAdorner 2019-08-14 09:22:49 -05:00
Jake Norbut decdae424d new Adorner to convey row selection 2019-08-14 09:22:02 -05:00
Taha Afzal 4228dcab95 Added unnormalized_day position data to PositionUnit 2019-08-13 20:03:58 -04:00
Eric Harding d99a2f5b88 Settingsmanager : handle more than 9 users 2019-08-13 17:24:27 -04:00
Kevin Darby b080d4a9c4 Merged in master (pull request #318)
Spark 2.8

Approved-by: Kevin Darby <kevin.darby@bluetradesys.com>
2019-08-13 19:19:47 +00:00
pfisher 282e10cba3 fixed update.txt test 2019-08-13 12:04:11 -04:00
Eric Harding dd67252173 Merge branch 'SP-183' of bitbucket.org:bluetradesys/spark into SP-91-position-migration 2019-08-13 11:51:26 -04:00
Jake Norbut 540559de3e Adding sanitization routine for MarketViewWindowFactory 2019-08-12 16:38:15 -05:00
Michael Harding 19871752ee Merged SP-91-position-migration into SP-150 2019-08-12 16:47:09 -04:00
Eric Harding 4801a16977 Re-enable api unit tests 2019-08-12 14:15:09 -04:00
Jake Norbut 7aaae817ac higher level changes as a result of previous checkin 2019-08-12 13:02:48 -05:00
Jake Norbut d6499464cf fleshing out per Column bg color override 2019-08-12 13:01:34 -05:00
Eric Harding 52d30249fe Allow empty updates in ApiServer 2019-08-12 13:52:39 -04:00
Jake Norbut a14d449b33 this brush needs to be frozen, because of course it does 2019-08-09 15:06:25 -05:00
Jake Norbut 14a86d7b29 changing RenderColumn to use ColumnBase's background_brush; removed background property 2019-08-09 14:09:19 -05:00
Jake Norbut 19729a276d adding background color to ColumnBase as every column should be able to eventually override the grid default bg 2019-08-09 14:08:03 -05:00
Jake Norbut 0f1135d5d6 adding background color to settings and related infrastruture 2019-08-09 14:07:04 -05:00
Pedro Pinto 146b5bdd8a Merge branch 'SP-91-position-migration' of bitbucket.org:bluetradesys/spark into SP-91-position-migration 2019-08-09 14:14:26 -04:00
Pedro Pinto 1783feffe7 Adds on demand gmi parsing and fixes a few bugs. 2019-08-09 14:13:27 -04:00
Kevin Darby fa1b436fb3 added sticky button release notes 2019-08-09 10:53:02 -04:00
Eric Harding 38964f4d10 SP-150 Decode http multipart form data in WebServer.cpp 2019-08-08 16:39:56 -04:00
Eric Harding 637d7384b1 Add Http log subsystem for spard web server 2019-08-08 16:39:56 -04:00
Kevin Darby 191518d8bc Merged in fix_algo_docs (pull request #316)
removing duplicate mark down describing algos

Approved-by: Michael Harding <michael.harding@bluetradesys.com>
2019-08-08 20:26:11 +00:00
Kevin Darby 50768750d6 removing duplicate mark down describing algos 2019-08-08 16:14:49 -04:00
Michael Harding b3f8976922 Merge branch 'SP-91-position-migration' into SP-150 2019-08-08 15:53:53 -04:00
Michael Harding dfe906ea64 Inhibit margin polling and unsub on tab change 2019-08-08 15:52:36 -04:00
Jake Norbut 450ef7dd56 Merged in SP-128 (pull request #312)
SP-128 - Allow for configurable stickyness on Algo Favorites

Approved-by: Kevin Darby <kevin.darby@bluetradesys.com>
2019-08-08 19:26:03 +00:00
Jake Norbut 89a4f9264a LadderSaveState now at version 2; implementing upgrade mechanism for migrating existing favorite algos to stickied algos 2019-08-08 13:03:26 -05:00
Jake Norbut 55ade15574 provide upgrade mechanism when loading IShellWindow 2019-08-08 13:01:19 -05:00
Kevin Darby 36b8b35e47 Merged in fix_tooltips2 (pull request #315)
fix non-dynamic and non-generic tooltips

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2019-08-08 16:18:29 +00:00
Kevin Darby 23bb872052 fix non-dynamic and non-generic tooltips 2019-08-08 12:09:48 -04:00
Eric Harding 1af8247a69 Merge branch 'SP-91-position-migration' of bitbucket.org:bluetradesys/spark into SP-150 2019-08-08 11:37:27 -04:00
Eric Harding de569e2564 Merge branch 'master' of bitbucket.org:bluetradesys/spark into SP-91-position-migration 2019-08-08 11:36:58 -04:00
Pedro Pinto fc24c768d8 Added local defaults for gmi file download. 2019-08-07 17:14:19 -04:00
Pedro Pinto 7aee876d54 Merge branch 'SP-91-position-migration' of bitbucket.org:bluetradesys/spark into SP-91-position-migration 2019-08-07 17:03:57 -04:00
Pedro Pinto 3e3e519403 Changed gmi download to only work on specific time windows. 2019-08-07 17:03:26 -04:00
Michael Harding 3e40bf9fd3 Remove uncommited fills, update numbers 2019-08-07 16:53:17 -04:00
Michael Harding 315ca96d4f Make uncommited manual fills editable 2019-08-07 15:35:18 -04:00
Eric Harding 9d1c0d7f44 Merge branch 'master' of bitbucket.org:bluetradesys/spark into SP-150 2019-08-07 15:24:24 -04:00
Michael Harding 97b2f2df75 Handle deleted users in manual fills table 2019-08-07 13:21:43 -04:00
Michael Harding 92a9127160 Better number validation in the spark admin 2019-08-07 12:51:00 -04:00
Michael Harding 388d491a0a Fix empty symbol col on manual fills 2019-08-07 12:26:24 -04:00
Pedro Pinto 2ec7aa5f3f Changed gmi download to pick oldest file rather than guess its name. 2019-08-06 19:34:15 -04:00
Pedro Pinto 6148a08b62 Updated dev tool. 2019-08-06 15:30:49 -04:00
Pedro Pinto e914ca9c15 Merge branch 'SP-91-position-migration' of bitbucket.org:bluetradesys/spark into SP-91-position-migration 2019-08-06 15:30:12 -04:00
Eric Harding 17c2a91a0f SP-169 sftp downloads with libssh2 2019-08-06 14:27:12 -04:00
Pedro Pinto 09d75af8c3 Improved algo API documentation slightly. 2019-08-06 14:18:22 -04:00
Jake Norbut bf8df1d396 always push Limit onto the algo stack; henceforth known as the anchor 2019-08-06 10:24:59 -05:00
Michael Harding 97e1277b6a Manual Fills get data from streams 2019-08-06 11:00:15 -04:00
Michael Harding c198965a49 Fix enums in DynamicService.ts 2019-08-06 09:45:47 -04:00
Taha Afzal a10a49cf4f Cleanup, removing some decaying code which was making for bad readibility 2019-08-05 22:49:13 -04:00
Taha Afzal cfec77bb96 Removed unused cons variables 2019-08-05 22:25:19 -04:00
Taha Afzal 03e2433201 Comments added and some cleanup for the position update related changes to SecurityLimitNode and ProductLimitNode 2019-08-05 19:38:43 -04:00
John Colvin d064af817c remove unreferenced '_sticky_algos' field 2019-08-05 15:51:21 -05:00
John Colvin 7b3c4b76bc LadderVM/OrderPropertiesEditorVM sticky/stack behavior 2019-08-05 15:36:57 -05:00
John Colvin 5de431f279 make LadderVM.send_order_command an IAsyncCommand 2019-08-05 15:36:57 -05:00
John Colvin 7b63d951f0 ShellSettingsDialogVM profilevm resets 'is_dirty' whenever the internal vm's resultant value equals the target value 2019-08-05 15:36:57 -05:00
John Colvin 018887980b LadderSettingsVM 'sticky buttons' impl reacts to FavoriteAlgoItem's value (algo) changes 2019-08-05 15:06:39 -05:00
John Colvin 8b4d649bd4 ExecuteAsync should return the Task directly (no need to await internally) 2019-08-05 15:06:39 -05:00
Jake Norbut 441d3c886b fixing improper use of is 2019-08-05 15:06:39 -05:00
Jake Norbut 472e32f558 removing empty new lines; leftovers from Dispatcher debugging 2019-08-05 15:06:39 -05:00
Jake Norbut 472c9252f9 adding Stay Enabled (sticky) checkbox to Favorites UI 2019-08-05 15:06:39 -05:00
Jake Norbut 98a9ad3ef2 reset order type after non-sticky order entry 2019-08-05 15:06:39 -05:00
Jake Norbut 7b237fedea adding sticky-related bits 2019-08-05 15:06:39 -05:00
Jake Norbut 7281aa8ff1 adding is_sticky property 2019-08-05 15:06:39 -05:00
Jake Norbut d915e2f064 setting algo_def_vm.is_sticky via CollectionChanged event (hmm) 2019-08-05 15:06:39 -05:00
Jake Norbut beb5d855b1 Dispatcher debuggering with John; think does not appear to work since the if condition is never met, but commiting this part of the pair-debugging experience 2019-08-05 15:06:39 -05:00
Jake Norbut 120b86aaae popuplating stickied algo keys for persistence/restoration state 2019-08-05 15:06:39 -05:00
Jake Norbut 44eeff98e7 adding is_sticky member to which the view will Databind 2019-08-05 15:06:39 -05:00
Jake Norbut dc420a097a adding stickied_algos ProtoMember for persistence 2019-08-05 15:06:39 -05:00
Jake Norbut 69ee85dea9 misc - adding OnDispatcher after discussing with John 2019-08-05 15:06:39 -05:00
Taha Afzal c42bc45f24 PositionServiceServer uses the manual fills state in PositionMap 2019-08-05 15:55:01 -04:00
Taha Afzal 37a8ae6677 zero_out_pos_unit was not zering out backwards compat doubles 2019-08-05 14:00:40 -04:00
Taha Afzal a271abf3c4 Merge branch 'SP-91-position-migration' of bitbucket.org:bluetradesys/spark into SP-91-position-migration 2019-08-05 10:48:07 -04:00
Taha Afzal 76f582613b Comments mainly 2019-08-05 10:47:58 -04:00
Eric Harding e15d7babbd Fix client build errors 2019-08-05 10:23:34 -04:00
Eric Harding 469914c6c9 SP-150 file upload decoder 2019-08-04 21:17:22 -04:00
Taha Afzal edba0e1cd7 Positions entries published by the PositionService are never removed, if a baseposition is absent, the enrties are 0'd out 2019-08-04 19:27:02 -04:00
Taha Afzal ac1c5566ac For backwards compat, PositionUnit's account is populated 2019-08-02 18:48:46 -04:00
Taha Afzal 4564e630db Merge branch 'SP-91-position-migration' of bitbucket.org:bluetradesys/spark into SP-91-position-migration 2019-08-02 17:51:02 -04:00
Taha Afzal 5c77fa2799 PositionMap keeps a SODPosition around to resolve sod fills 2019-08-02 17:50:54 -04:00
Michael Harding db234c7d06 fix web build after merge 2019-08-02 15:24:21 -04:00
Pedro Pinto 1bb4ce91cf Merge branch 'SP-91-position-migration' of bitbucket.org:bluetradesys/spark into SP-91-position-migration 2019-08-02 14:45:58 -04:00
Pedro Pinto d1a3857119 Removed some remaining conflicts 2019-08-02 14:42:49 -04:00
Eric Harding 6dd63156cc SP-169 Use sftp command for sftp instead of scp 2019-08-02 13:59:19 -04:00
Pedro Pinto 01f178fe3b Partial merge 2019-08-02 13:18:15 -04:00
Pedro Pinto 53672e1875 Added as of timestamp to base position call. 2019-08-02 11:27:13 -04:00
Pedro Pinto e7a20252a0 GMI parser parses. 2019-08-02 11:14:15 -04:00
Pedro Pinto 497884fe00 Added sod helper. 2019-08-02 10:02:11 -04:00
Pedro Pinto f7aa2c5bec Removed unusued timestamp for base pos call 2019-08-02 09:13:32 -04:00
Pedro Pinto 5c75507f42 Updated sub repos pointers 2019-08-02 09:06:46 -04:00
Pedro Pinto ba1b459b0b More gmi parser files. 2019-08-02 09:06:46 -04:00
Pedro Pinto bac7ea06f2 Fixed some issues with GMI import. 2019-08-02 09:06:46 -04:00
Pedro Pinto 26c577f94b GMI parsing is code complete. 2019-08-02 09:06:46 -04:00
Taha Afzal 4fc59c2ca8 Base Position incorporation but untested 2019-08-02 00:10:54 -04:00
Taha Afzal 3a7fbe2ddf Line got deleted in FillStore when removing call to SOD 2019-08-01 14:08:22 -04:00
Taha Afzal 2bfc3148d1 merging 2019-08-01 13:50:54 -04:00
Taha Afzal 72734bb15f Moving PositionServiceServer data into PositionMap (the Service Server was getting too long and confusing) 2019-08-01 13:22:38 -04:00
Eric Harding 1dd72abc9a SP-169 sftp download 2019-08-01 12:30:58 -04:00
Pedro Pinto 6dfcb970b8 Fixed build 2019-07-31 14:23:15 -04:00
Pedro Pinto 72aab5bbcb Merged 2019-07-31 14:16:55 -04:00
Pedro Pinto 0afededebf Added bones of base position service. 2019-07-31 14:12:36 -04:00
Michael Harding 8c8e4dd0e8 Delete fills... theoretically 2019-07-30 16:35:25 -04:00
Pedro Pinto 17769cbced Added skelleton for BasePositionService. 2019-07-30 15:58:15 -04:00
Pedro Pinto 5cb1220d05 Added stubs for base position service. 2019-07-30 14:41:39 -04:00
Pedro Pinto 37ba9f7b0d Merge branch 'SP-91-position-migration' of bitbucket.org:bluetradesys/spark into SP-91-position-migration 2019-07-30 13:58:19 -04:00
Pedro Pinto 9a1bf1bbae Removed SOD from fill store. 2019-07-30 13:45:11 -04:00
Michael Harding a404bbf654 Add Day position management table 2019-07-30 10:06:56 -04:00
Michael Harding 0b34457259 SP-97: updates to existing manual fills 2019-07-30 10:06:56 -04:00
Taha Afzal 5939b983dd Hooked up the new Limiter call set_filled_position 2019-07-29 18:33:22 -04:00
Taha Afzal c0b0ffd646 Merge branch 'SP-91-position-migration' of bitbucket.org:bluetradesys/spark into SP-91-position-migration 2019-07-29 16:59:22 -04:00
Taha Afzal 21a48ba0ad moving PositionUtilities into lib, removing 2 unused functions in Limiter 2019-07-29 16:59:12 -04:00
Eric Harding 9a21d03311 Merged master into SP-150 2019-07-29 14:59:08 -04:00
Eric Harding 46d6ca9d2c SP-152 Handle stream unsubscribe with IDisposable 2019-07-29 13:06:31 -04:00
Taha Afzal 6ef274d5d6 Merge branch 'SP-91-position-migration' of bitbucket.org:bluetradesys/spark into SP-91-position-migration 2019-07-29 13:01:10 -04:00
Taha Afzal 36d507b3ca comments 2019-07-29 13:00:58 -04:00
Pedro Pinto 54820107db Updated externals pointer. 2019-07-29 12:58:16 -04:00
Pedro Pinto 38bf521132 Position based limits in order manager code complete. 2019-07-29 12:58:16 -04:00
Pedro Pinto 4aa0d878a6 Updated cmakedeps. 2019-07-29 12:58:16 -04:00
Eric Harding 03bbf2f4a8 SP-152 unsubscribe support 2019-07-29 12:37:28 -04:00
Taha Afzal f2d284cadb Merge branch 'SP-91-position-migration' of bitbucket.org:bluetradesys/spark into SP-91-position-migration 2019-07-29 11:31:36 -04:00
Taha Afzal 9f1ac3ee80 Added a proto for an unresolved File row to be imported as a SOD 2019-07-29 11:31:29 -04:00
Eric Harding 6b81dcef7d SP-152 fix F# type definition 2019-07-29 11:29:49 -04:00
Eric Harding 1f0371e870 SP-152 c++ side 2019-07-26 17:37:38 -04:00
Eric Harding 322f1164be SP-152 typescript/f# half 2019-07-26 17:35:54 -04:00
Eric Harding c6cae3064d SP-150 add server endpoint for file upload 2019-07-23 12:25:01 -04:00
Michael Harding 7c13474e31 SP-97: Position Management tab and add fills 2019-07-22 14:50:26 -04:00
Taha Afzal 06c1f60598 update_position called is routed from Limiter to ProductNode 2019-07-02 18:23:06 -04:00
Taha Afzal 37414d6c78 Added function signature in Limiter to acceot positions 2019-07-01 14:58:11 -04:00
Taha Afzal 3057f8233d Merge branch 'SP-24-loss-limit' into SP-91-position-migration 2019-07-01 11:52:11 -04:00
Taha Afzal e000d34766 Converting PositionUtilities into a class (vs functions) and big fixes 2019-06-30 09:46:04 -04:00
Taha Afzal 87a8665273 Added support for Admin fills to PositionService 2019-06-28 19:18:58 -04:00
325 changed files with 9790 additions and 3761 deletions

No files matched your search

+1 -2
View File
@@ -32,5 +32,4 @@ trace_data/
/src/lib/orderman/perf_analysis/last_pretty_log.csv
/src/lib/orderman/perf_analysis/log.csv
/doc/user/public/*
.ionide/*
src/web/ui/.ionide/symbolCache.db
.ionide/*
+1 -1
View File
@@ -1 +1 @@
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Algos/_index.md;content/bts_spark/Glossary/index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Help/index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Launcher/index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Logging-In-to-the-System/index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/index.md;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/My-Windows/index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Settings/index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Spreader/index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/index.md;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/_index.md;content/bts_spark/Help/_index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Launcher/_index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Logging-In-to-the-System/_index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Market/_index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/_index.md;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/Settings/_index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Spreader/_index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Trading/OrderTypes/index.md;content/bts_spark/Trading/_index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
+46
View File
@@ -4,6 +4,51 @@ weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## 2.9.2
### Bug Fixes
- Fixed an issue with the exchange simulator that could lead to crossed markets
## 2.9.1
### Bug Fixes
- Fixed an issue with trade sound alerts, where in some cases the use of a "In Between" condition could lead to a UI failure.
- Fixed an issue with the display of the new raw position column in the market view window.
- Fixed the popup label that describes the raw position information in the ladder
### Internal Changes
- Avoided shipping production configuration defaults that lead to trying to download a position file for the development sftp server.
### Api
- C# API sample now uses synchronous calls.
## 2.9.0
### User Visible Changes
- Improved Workspace management User Interface
- Added the ability to change grid background colors
- The Spark UI Window group now has a title in the Windows task bar
- Added sound alerts for trade events, see Launcher -> Settings -> Alerts
- Allowed composite algos that are attached to existing orders to identify fills that were already present prior to attachment
- Added import facilities for GMI position files
- Added the ability for risk managers to enter manual fills
- Added "raw" P&L columns, that is columns that *do not* include spread legs in their accounting
### Bug Fixes
- Fixed the settings action menu (Load/Save/Reset to Factory)
- Fixed the method used to determine if a security is expired to take into account the start of day time
### API Changes
- Fixed an issue in the Client SDK that could lead to occasional failures to connect to the spark server as well as excessive memory usage.
- Added the ability for algo orders to distinguish fills existing prior to launch
### Internal Changes
- Changed how we packaged certain third-party components
- Added logging of entitlement status
- Tagged outgoing orders with sequence number of marketdata or FIX event that triggered them
## 2.8.3
### Bug Fixes
- Fixed a bug that could cause an OrderBook's Security column filter to be blank
- Fixed a bug that could cause a UI failure when clearing a filter selection on a filtering widget
## 2.8.2
### Bug Fixes
- Added proper display factors for SOM spreads
- Fixed a bug that could cause a UI failure when trying to change settings in a synthetic ladder
## 2.8.1
### Bug fixes
- Fixed an error in the UI grids filtering widget
## 2.8.0
### User Visible Changes
- Added ProRata and Scratch algos
@@ -16,6 +61,7 @@ insert_anchor_links = "right"
selected by default
- Increased width of login panel to better show login error messages
- Added optional 'Maker/Taker' pricing to Spreader algo
- Algo buttons in ladder can now be made to deactivate after launching an algo or remain 'sticky'
### Internal changes
- Made it easier to generate simple UI's for new order types
### API Changes
-1
View File
@@ -70,7 +70,6 @@ namespace mbts.ui.shell
{
Log.Gen.info("Application.OnExit");
if (_runtime != null) _runtime.Dispose();
utils.BTSNativeMethods.finit_spark_lib();
base.OnExit(e);
Environment.Exit(0);
}
@@ -0,0 +1,68 @@
using bts.api;
using bts.shell;
using mbts;
using mbts.persist;
using mbts.spark;
using mbts.ui;
using mbts.ui.spark;
using mbts.ui.utilities;
using System;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Windows;
using Disposable = mbts.Disposable;
namespace bts {
[Export(typeof(IShellDaemon))]
class GlobalAlertsDaemon : IShellDaemon, IDisposable {
const string
// used for logging/diagnostic purposes
_window_name = "Global Alerts Dialog",
// actual window title
_window_title = "Alerts";
readonly ThreadedWindowLauncher _launcher;
readonly UserSettingsHub _hub;
readonly IBus _bus;
readonly INotificationService _notification_service;
IDisposable _dispose;
[ImportingConstructor]
public GlobalAlertsDaemon(IBus bus, UserSettingsHub hub, INotificationService notification_service) {
_bus = bus;
_hub = hub;
_notification_service = notification_service;
_launcher = new ThreadedWindowLauncher(_window_name);
_dispose = Disposable.Create(
bus.subscribe(SparkTags.ShowGlobalAlertsTag)
.Subscribe(_ => _launcher.show(_view_factory)),
_hub.subscribe_default_settings()
.SubscribeEach(s => new CompositeDisposable(
s.alerts.trade_alerts.Where(t => t.is_enabled).Select(
a => _notification_service.link_to_trades(
a.predicate.security_criteria, a.predicate.create(), a.sound)))));
}
Window _view_factory() {
var vm = new GlobalAlertsVM();
vm.load_from(_hub.current.alerts);
vm.request_commit += commit;
var sec_sub = _bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m => vm.security_map = m);
var win = new GlobalAlertsDialog {
Title = "Alerts",
DataContext = vm
};
win.Closed += (s, e) => Disposable.dispose(ref sec_sub);
return win;
void commit() {
var s = _hub.current.with(alerts: vm.create());
_hub.save_default(s).ignore();
}
}
public void Dispose() =>
Disposable.dispose(ref _dispose);
}
}
+4
View File
@@ -40,6 +40,7 @@ namespace mbts.ui {
EditDocumentAltGeometryKey = nameof(EditDocumentAltGeometryKey),
EditDocumentGeometryKey = nameof(EditDocumentGeometryKey),
GearGeometryKey = nameof(GearGeometryKey),
HornGeometryKey = nameof(HornGeometryKey),
LetterAKey = nameof(LetterAKey),
LetterDKey = nameof(LetterDKey),
OrdersGeometryKey = nameof(OrdersGeometryKey),
@@ -48,6 +49,7 @@ namespace mbts.ui {
PlayGeometryKey = nameof(PlayGeometryKey),
ProhibitedGeometryKey = nameof(ProhibitedGeometryKey),
RubberStampGeometryKey = nameof(RubberStampGeometryKey),
SpeakerGeometryKey = nameof(SpeakerGeometryKey),
StarGeometryKey = nameof(StarGeometryKey),
ToolsGeometryKey = nameof(ToolsGeometryKey),
TrashGeometryKey = nameof(TrashGeometryKey),
@@ -168,6 +170,7 @@ namespace mbts.ui {
public static ImageSource FilterActive { get { return load_resource_and_freeze("FilterActive"); } }
public static ImageSource FilterError { get { return load_resource_and_freeze("FilterError"); } }
public static ImageSource Gear => create_icon(GearGeometryKey);
public static ImageSource Horn => create_icon(HornGeometryKey);
public static ImageSource Import => load_resource_and_freeze("Import");
public static ImageSource Knight { get { return load_resource_and_freeze("Knight"); } }
public static ImageSource Payment => create_icon(PaymentGeometryKey);
@@ -176,6 +179,7 @@ namespace mbts.ui {
public static ImageSource RubberStamp => create_icon(RubberStampGeometryKey);
public static ImageSource RedoCircular => create_icon(UndoCircularGeometryKey, Brushes.Black, null, new ScaleTransform(-1, 1));
public static ImageSource Spawn { get { return load_resource_and_freeze("Spawn"); } }
public static ImageSource Speaker => create_icon(SpeakerGeometryKey);
public static ImageSource Star => create_icon(StarGeometryKey);
public static ImageSource Target { get { return load_resource_and_freeze("Target"); } }
public static ImageSource Tetris { get { return load_resource_and_freeze("Tetris"); } }
@@ -274,6 +274,10 @@
M16.000015,9.6000061C12.500012,9.6000061 9.5999844,12.5 9.5999844,16 9.5999844,19.5 12.500012,22.399994 16.000015,22.399994 19.500019,22.399994 22.400015,19.5 22.400015,16 22.400015,12.5 19.500019,9.6000061 16.000015,9.6000061z M14.199995,0L17.800005,0C18.199999,0,18.599993,0.29998779,18.599993,0.79998779L18.599993,4.7999878C19.900013,5.1000061,21.000019,5.6000061,22.099997,6.2999878L25.000023,3.3999939C25.099999,3.2999878 25.300011,3.1999817 25.500025,3.1999817 25.700007,3.1999817 25.900019,3.2999878 26.000025,3.3999939L28.600033,6C28.900021,6.2999878,28.900021,6.7999878,28.600033,7.1000061L25.700007,10C26.400019,11.100006,26.900019,12.299988,27.200007,13.5L31.20001,13.5C31.600037,13.5,31.999998,13.799988,31.999998,14.299988L31.999998,17.899994C31.999998,18.299988,31.700012,18.699982,31.20001,18.699982L27.200007,18.699982C26.900019,20,26.400019,21.100006,25.700007,22.199982L28.600033,25.100006C28.700009,25.199982 28.799984,25.399994 28.799984,25.600006 28.799984,25.799988 28.700009,26 28.600033,26.100006L26.000025,28.600006C25.900019,28.699982 25.700007,28.799988 25.500025,28.799988 25.300011,28.799988 25.099999,28.699982 25.000023,28.600006L22.099997,25.699982C21.000019,26.399994,19.800007,26.899994,18.599993,27.199982L18.599993,31.199982C18.599993,31.600006,18.300005,32,17.800005,32L14.199995,32C13.8,32,13.400006,31.699982,13.400006,31.199982L13.400006,27.199982C12.099987,26.899994,11.000011,26.399994,9.9000027,25.699982L7.0000064,28.600006C6.9000003,28.699982 6.6999881,28.799988 6.5000059,28.799988 6.2999937,28.799988 6.099981,28.699982 6.0000054,28.600006L3.3999967,26C3.0999784,25.699982,3.0999784,25.199982,3.3999967,24.899994L6.2999937,22C5.5999805,20.899994,5.0999801,19.699982,4.7999923,18.5L0.79998828,18.5C0.39999409,18.5,-1.933513E-07,18.199982,0,17.699982L0,14.100006C-1.933513E-07,13.699982,0.2999879,13.299988,0.79998828,13.299988L4.7999923,13.299988C5.0999801,12,5.5999805,10.899994,6.2999937,9.7999878L3.3999967,7C3.2999908,6.8999939 3.1999845,6.6999817 3.1999845,6.5 3.1999845,6.2999878 3.2999908,6.1000061 3.3999967,6L6.0000054,3.3999939C6.099981,3.2999878 6.2999937,3.1999817 6.5000059,3.1999817 6.6999881,3.1999817 6.9000003,3.2999878 7.0000064,3.3999939L9.9000027,6.2999878C11.000011,5.6000061,12.199993,5.1000061,13.400006,4.7999878L13.400006,0.79998779C13.400006,0.29998779,13.8,0,14.199995,0z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.HornGeometryKey}">
M4.0499969,7.0870913C2.9519978,7.0870913 2.0249984,8.4780906 2.0249987,10.125091 2.0249984,11.771091 2.9519978,13.162091 4.0499969,13.162091 5.1479961,13.162091 6.0749954,11.771091 6.0749955,10.125091 6.0749954,8.4780906 5.1479961,7.0870913 4.0499969,7.0870913z M29.975016,3.316155C23.883831,8.2016557,11.829888,8.9691842,8.1184387,9.0896781L8.0151367,9.0928462 8.0175586,9.1059306C8.0716033,9.4352315 8.0999939,9.7760909 8.0999942,10.125091 8.0999939,10.473966 8.0716033,10.814716 8.0175586,11.143921L8.0098858,11.185364 8.193316,11.195448C12.163719,11.420399,23.994581,12.458921,29.975016,17.005057z M31.176758,0.00019051835C31.280359,-0.0022594665 31.391857,0.01889403 31.514975,0.065146674 31.908983,0.21413228 31.999985,0.59114302 31.999985,1.0121596L31.999985,19.236969C31.999985,19.653958 31.913988,20.028956 31.525991,20.179955 31.405998,20.226952 31.450981,20.250023 31.32699,20.250023 31.050991,20.250023 30.778992,20.13601 30.583986,19.925928 25.229074,14.140572 9.6001122,13.269665 7.2865356,13.172559L7.2813506,13.172345 7.1739591,13.343056C6.4304952,14.468533 5.3060585,15.187091 4.0499969,15.187091 1.8169985,15.187091 0,12.916091 0,10.125091 0,7.3330911 1.8169985,5.0620912 4.0499969,5.0620912 5.3060585,5.0620912 6.4304952,5.78065 7.1739591,6.9063042L7.2852713,7.0832756 7.3087114,7.0828188C9.6077337,7.036146 25.141474,6.4939421 30.567997,0.34315929 30.75913,0.12521249 30.948835,0.005580858 31.176758,0.00019051835z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.OrdersGeometryKey}">
M50.14375,33.389126C50.416863,33.394688,50.653496,33.587773,50.705998,33.864617L50.015995,45.114388C49.810295,46.291462,49.130394,45.097489,49.130394,45.097489L47.65659,42.640538C42.747776,49.635197 34.633152,54.221001 25.433924,54.221001 14.884193,54.221001 5.7437552,48.204125 1.2449617,39.419205 0.80087067,38.552021 0.21358696,37.705738 0.052086769,36.736958 -0.49735315,33.490925 3.4507786,32.901036 4.8049124,34.432404 5.3674143,35.067692 6.034036,36.16407 6.5079177,37.026051 10.106778,43.799416 17.2321,48.413819 25.433924,48.413819 32.656544,48.413819 39.026364,44.826695 42.903977,39.351506L40.229367,38.854115C40.229367,38.854115,38.677464,38.647118,39.742466,37.996033L50.024895,33.398526C50.064972,33.391365,50.104733,33.388333,50.14375,33.389126z M23.030291,9.0310004L27.889918,9.0310004 27.889918,13.028299C30.539786,13.205498,32.324932,13.958096,33.464161,14.528294L33.964176,14.775793 32.417435,20.038878 31.706417,19.68068C30.579987,19.11958 28.839241,18.244583 26.149073,18.244583 24.006916,18.244583 22.723183,19.122181 22.723183,20.590876 22.723183,21.894272 23.943314,22.804369 27.551308,24.187165 32.706444,26.124659 34.911,28.702853 34.911,32.817341 34.911,36.892929 32.090527,40.05172 27.673611,40.989117L27.673611,45.540001 22.767485,45.540001 22.767485,41.330414C20.252919,41.155815,17.695551,40.424118,16.101811,39.404721L15.683999,39.137619 17.221739,33.731438 17.969059,34.180634C19.088988,34.851235 21.402748,35.97873 24.340325,35.97873 26.734886,35.97873 28.276728,34.921635 28.276728,33.291336 28.276728,31.986641 27.547409,30.853846 23.986016,29.57255 20.083815,28.144253 16.095511,26.04406 16.095511,21.239174 16.095511,17.280985 18.737379,14.291395 23.030291,13.329098z M25.268765,0C35.819447,2.7347414E-07 44.961449,6.0155495 49.458798,14.804573 49.902695,15.669173 50.490094,16.516972 50.651287,17.48427 51.198281,20.734265 47.255726,21.318962 45.901341,19.789065 45.336346,19.153567 44.669552,18.051968 44.19566,17.19517 40.5957,10.421881 33.473275,5.8072398 25.268765,5.8072393 18.047545,5.8072398 11.678013,9.3918336 7.8003562,14.867073L10.474627,15.367072C10.474627,15.367072,12.026909,15.574172,10.962921,16.223872L0.67933281,20.826762C0.36037083,20.880062,0.057029126,20.669165,0.00099972012,20.354063L0.69093342,9.1118043C0.89552094,7.9296158,1.5738537,9.124924,1.5738539,9.124924L3.0452772,11.583379C7.9566038,4.584462,16.073666,2.7347414E-07,25.268765,0z
</sys:String>
@@ -327,6 +331,11 @@
M33.5,4h-6v10h6V4z M31.5,12h-2V6h2V12z
</sys:String>
</x:Array>
<sys:String x:Key="{x:Static mbts:Icons.SpeakerGeometryKey}">
M18,12.839666L18,19.159448 18.029955,19.131576C18.848108,18.314159 19.332083,17.192702 19.332083,15.999873 19.332083,14.806357 18.848108,13.684685 18.029955,12.867527z M21.738388,4.748316C21.868353,4.7515661 21.999973,4.7804414 22.126221,4.8376924 26.511095,6.8177201 29.344013,11.199782 29.344013,15.999849 29.344013,20.799915 26.511095,25.181977 22.126221,27.162004 21.992224,27.223004 21.853228,27.251006 21.715233,27.251006 21.333244,27.251006 20.970255,27.032003 20.803259,26.662997 20.575266,26.15899 20.799259,25.566981 21.302244,25.339978 24.973139,23.681956 27.34407,20.015905 27.34407,15.999849 27.34407,11.983792 24.973139,8.3177406 21.302244,6.6597177 20.799259,6.4327146 20.575266,5.8407065 20.803259,5.3366992 20.973503,4.9594442 21.348494,4.7385656 21.738388,4.748316z M16,3.1854247L6.6530151,11.257323C6.4719848,11.414303,6.2399902,11.500301,6,11.500301L1.9999998,11.500301 1.9999998,21.500148 6,21.500148C6.2219848,21.500148,6.4370117,21.573144,6.6110229,21.709129L16,28.964023z M17.000252,1.186163E-08C17.141373,-2.1778678E-05 17.283264,0.02997845 17.416016,0.091477852 17.770996,0.25450047 18,0.60843792 18,1.0004608L18,10.368904 18.170259,10.465408C20.111607,11.612996 21.331976,13.716092 21.331976,15.999873 21.331976,18.282748 20.111607,20.38576 18.170259,21.53334L18,21.629842 18,31.000003C18,31.382016 17.78302,31.728995 17.440002,31.896962 17.301025,31.965991 17.150024,31.99999 17,31.99999 16.78302,31.99999 16.567017,31.929982 16.388977,31.791007L5.65802,23.500117 0.99999966,23.500117C0.44702145,23.500117,-3.9348969E-08,23.053104,3.5527137E-15,22.500133L3.5527137E-15,10.500316C-3.9348969E-08,9.9473459,0.44702145,9.5003311,0.99999966,9.5003311L5.6279907,9.5003311 16.346985,0.24345344C16.531998,0.083467316,16.765053,3.6392792E-05,17.000252,1.186163E-08z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.StarGeometryKey}">
M1540.22,2082.07L1546.95,2102.78 1568.73,2102.78 1551.11,2115.58 1557.84,2136.29 1540.22,2123.49 1522.6,2136.29 1529.33,2115.58 1511.71,2102.78 1533.49,2102.78 1540.22,2082.07z
</sys:String>
@@ -36,6 +36,7 @@ namespace mbts.spark {
readonly MyOrderSummarizer _order_summarizer = new MyOrderSummarizer();
readonly MDDisplayHelper.SizeFormatter _size_formatter = MDDisplayHelper.get_formatter(MarketSizeDisplay.get_default());
Property<uint> _bid_direct_size, _bid_merged_size, _ask_direct_size, _ask_merged_size;
double _raw_day_position;
string _bid_merged_size_text, _ask_merged_size_text;
ulong _bbo_ref, _last_ref;
@@ -89,6 +90,10 @@ namespace mbts.spark {
_dispose = Disposable.Create(
_orders_model.on_security_root_orders(security.Id).OnDispatcher().Subscribe(_on_orders),
_position.OnDispatcher().Subscribe(v => _animation.animate_position(v.newval > v.oldval)),
_orders_model.on_security_positions(security.Id).OnDispatcher().Subscribe(t => {
if (Set(ref _raw_day_position, t.state.Values.Sum(p => p.Item2.raw_day_pos()), nameof(raw_day_position)))
raise_invalidated();
}),
_inside_bids.OnDispatcher().Subscribe(v => _animation.animate_working_bids_inside(v.newval)),
_inside_asks.OnDispatcher().Subscribe(v => _animation.animate_working_asks_inside(v.newval)),
_day_change.OnDispatcher().Subscribe(v => _animation.animate_day_change(v.newval > v.oldval)),
@@ -102,6 +107,7 @@ namespace mbts.spark {
public bool inside_bids => _inside_bids.value;
public bool inside_asks => _inside_asks.value;
public int position => _position.value;
public double raw_day_position => _raw_day_position;
public double last_price => _last_price.Value;
public uint last_size => _last_size.value;
public double bid_price => _bid_price.Value;
@@ -10,7 +10,6 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Windows.Data;
using System.Windows.Media;
using static bts.shell.MarketViewWindowFactory;
@@ -42,12 +41,13 @@ namespace mbts.spark {
add_col(Columns.Last, "Last", r => r.format_price(r.last_price), nameof(Row.last_price), DefaultColumnWidths.Price, last_price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UIntMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.Position, "Pos", r => r.position.ToString(), nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.RawDayPosition, "Raw Day Pos", r => SparkUtils.fmt_position(r.raw_day_position), nameof(Row.raw_day_position), DefaultColumnWidths.Size, raw_day_position_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_working_orders_col(Columns.WorkingBids, working_col_header, nameof(Row.working_bids_text), nameof(Row.working_best_bid), nameof(Row.working_bids_click), working_bid_volume_filter, BTSColors.MarketData.bid, System.Windows.HorizontalAlignment.Right);
add_col(Columns.BidImpliedSize, implied_size_col_header, r => r.bid_implied_size.ToString(), nameof(Row.bid_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.BidDirectSize, direct_size_col_header, r => r.bid_direct_size.ToString(), nameof(Row.bid_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_merged_size_col(Columns.BidMergedSize, merged_size_col_header, nameof(Row.bid_merged_size), nameof(Row.bid_merged_size_text), nameof(Row.bid_merged_size_click), bid_merged_size_filter, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), nameof(Row.bid_price_click), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid_brush, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), nameof(Row.ask_price_click), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask_brush, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), nameof(Row.bid_price_click), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), nameof(Row.ask_price_click), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_merged_size_col(Columns.AskMergedSize, merged_size_col_header, nameof(Row.ask_merged_size), nameof(Row.ask_merged_size_text), nameof(Row.ask_merged_size_click), ask_merged_size_filter, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.AskDirectSize, direct_size_col_header, r => r.ask_direct_size.ToString(), nameof(Row.ask_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.AskImpliedSize, implied_size_col_header, r => r.ask_implied_size.ToString(), nameof(Row.ask_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
@@ -77,18 +77,18 @@ namespace mbts.spark {
horizontal_alignment = h_alignment
});
void add_hl_price_col(Columns col_key, string header, string prop, string command_prop, IDataGridColumnFilter filter, Color foreground, Brush background, System.Windows.HorizontalAlignment h_alignment) =>
add_hl_col(col_key, header, price_binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.MViewPrice, foreground, background, DefaultSelectionHighlight, h_alignment);
void add_hl_price_col(Columns col_key, string header, string prop, string command_prop, IDataGridColumnFilter filter, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment) =>
add_hl_col(col_key, header, price_binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.MViewPrice, foreground, background, h_alignment);
void add_hl_merged_size_col(Columns col_key, string header, string sort_prop, string text_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.ImpliedDirectMergedSize, foreground, null, null, h_alignment);
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.ImpliedDirectMergedSize, foreground, null, h_alignment);
void add_hl_working_orders_col(Columns col_key, string header, string text_prop, string sort_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.IntMinMax, working_orders_col_width, foreground, null, null, h_alignment);
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.IntMinMax, working_orders_col_width, foreground, null, h_alignment);
void add_hl_col(
Columns col_key, string header, BindingBase binding, string prop, string command_prop,
IDataGridColumnFilter filter, string filter_template, double width, Color foreground, Brush background, Brush background_overlay, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
IDataGridColumnFilter filter, string filter_template, double width, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
columns.Add(new HyperlinkElementColumn<Row, Columns>(
col_key, binding, new Binding(command_prop), filter, filter_template) {
// it's necessary to have this id populated for grid settings
@@ -96,8 +96,7 @@ namespace mbts.spark {
Header = header,
sort_member_path = prop,
font_color_override = foreground,
background = background,
background_overlay = background_overlay,
background_color_override = background,
Width = DefaultColumnWidths.Price,
horizontal_alignment = h_alignment
});
@@ -111,14 +110,8 @@ namespace mbts.spark {
//
// make font-color immutable for Side, Size, (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
var col_settings_flags = new Dictionary<string, GridColumnSettingsFeatureFlags> {
{ Columns.Bid.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.Ask.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.BidImpliedSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.BidDirectSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.BidMergedSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.AskImpliedSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.AskDirectSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.AskMergedSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.Bid.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor },
{ Columns.Ask.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor},
{ Columns.WorkingAsks.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.WorkingBids.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor }
};
@@ -151,6 +144,7 @@ namespace mbts.spark {
session_low_filter.load_state(filters.session_low);
session_high_filter.load_state(filters.session_high);
position_filter.load_state(filters.position);
raw_day_position_filter.load_state(filters.raw_day_position);
working_bid_volume_filter.load_state(filters.working_bid_volume);
working_ask_volume_filter.load_state(filters.working_ask_volume);
volume_filter.load_state(filters.volume);
@@ -162,7 +156,7 @@ namespace mbts.spark {
bid_price_filter, bid_merged_size_filter, bid_direct_size_filter, bid_implied_size_filter,
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),
session_low_filter, session_high_filter, position_filter, raw_day_position_filter, working_bid_volume_filter, working_ask_volume_filter, volume_filter, security_key_filter),
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
WorkLoop.Start("MarketView", _refresh)
);
@@ -230,6 +224,7 @@ namespace mbts.spark {
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_low_filter { get; } = new DoubleMinMax<Row>(r => r.session_low, nameof(Row.session_low));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_high_filter { get; } = new DoubleMinMax<Row>(r => r.session_high, nameof(Row.session_high));
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> position_filter { get; } = new IntMinMax<Row>(r => r.position, nameof(Row.position));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> raw_day_position_filter { get; } = new DoubleMinMax<Row>(r => r.raw_day_position, nameof(Row.raw_day_position));
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_bid_volume_filter { get; } = new IntMinMax<Row>(r => r.working_bid_volume, nameof(Row.working_bid_volume));
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_ask_volume_filter { get; } = new IntMinMax<Row>(r => r.working_ask_volume, nameof(Row.working_ask_volume));
public IDataGridColumnPersistentFilter<Row, ULongMinMaxState> volume_filter { get; } = new ULongMinMax<Row>(r => r.volume, nameof(Row.volume));
@@ -261,6 +256,7 @@ namespace mbts.spark {
session_low = session_low_filter.get_state(),
session_high = session_high_filter.get_state(),
position = position_filter.get_state(),
raw_day_position = raw_day_position_filter.get_state(),
working_bid_volume = working_bid_volume_filter.get_state(),
working_ask_volume = working_ask_volume_filter.get_state(),
volume = volume_filter.get_state(),
@@ -8,8 +8,34 @@ namespace mbts.ui.spark {
/// Interaction logic for MarketSheetsView.xaml
/// </summary>
partial class MarketViewView : UserControl {
public enum Columns { Security, Volume, Last, LastSize, Position, WorkingBids, Bid, BidMergedSize, BidDirectSize, BidImpliedSize, Ask, AskMergedSize, AskDirectSize, AskImpliedSize, WorkingAsks, Settlement, DayChange, SessionLow, SessionHigh, SecurityKey,
MarketDescription, ClearedAlias, ProductDescription, HubAlias, StripName, MicCode
public enum Columns {
Security,
Volume,
Last,
LastSize,
Position,
RawDayPosition,
WorkingBids,
Bid,
BidMergedSize,
BidDirectSize,
BidImpliedSize,
Ask,
AskMergedSize,
AskDirectSize,
AskImpliedSize,
WorkingAsks,
Settlement,
DayChange,
SessionLow,
SessionHigh,
SecurityKey,
MarketDescription,
ClearedAlias,
ProductDescription,
HubAlias,
StripName,
MicCode
}
public MarketViewView() {
@@ -33,7 +33,7 @@ namespace mbts.spark {
_selected_security_criteria_backing,
_sorted_selected_security_criteria_backing;
public MarketViewWindow(MarketViewWindowFactory factory, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state, WindowSettingsManager<GridSettings> settings_manager)
public MarketViewWindow(MarketViewWindowFactory factory, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state, IWindowSettingsManager<GridSettings> settings_manager)
: base("Market View", bus, factory, SparkCommands.ShowMarketView.icon) {
_orders_model = orders_model;
_state = state;
@@ -1,4 +1,6 @@
using bts.api;
using bts.proto;
using bts.settings;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
@@ -7,6 +9,7 @@ using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
@@ -35,13 +38,8 @@ namespace bts.shell {
void _show(SecurityCriteria criteria) =>
_bus.post(SparkTags.ShowWindow, _create(StartupData.Default(criteria)));
IShellWindow _create(StartupData state) {
var settings_manager = new WindowSettingsManager<GridSettings>(_settings_hub,
state.grid,
get: us => us.market_view_grid,
set: (us, gs) => us.with(provided_market_view_gridsettings: gs));
return new MarketViewWindow(this, _bus, _orders_model, state, settings_manager);
}
IShellWindow _create(StartupData state) =>
new MarketViewWindow(this, _bus, _orders_model, state, _settings_hub.create_mview_settings_manager(state.grid));
[ProtoContract]
public class StartupData {
@@ -68,7 +66,29 @@ namespace bts.shell {
_sorting_criteria = sort;
}
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
public static GridSettings sanitize(GridSettings s) {
if (s == null) {
return null;
}
// column upgrade
var columns = s.columns.ToList();
var bid_col_index = columns.FindIndex(x => x.id == col_id(Columns.Bid));
var ask_col_index = columns.FindIndex(x => x.id == col_id(Columns.Ask));
if (bid_col_index > -1 && ask_col_index > -1) {
if (!columns[bid_col_index].background_color_override.HasValue) {
columns[bid_col_index] = columns[bid_col_index].with(background_color_override: new NullableColorProto(BTSColors.MarketData.bid.ToUint()));
}
if (!columns[ask_col_index].background_color_override.HasValue) {
columns[ask_col_index] = columns[ask_col_index].with(background_color_override: new NullableColorProto(BTSColors.MarketData.ask.ToUint()));
}
}
// and enable background color upgrade
return s.with(columns, enable_background_color: true);
}
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion, enable_background_color: true);
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criteria ?? (_sorting_criteria = DefaultColumns.default_sorting_criterion.ToList());
@@ -82,12 +102,13 @@ namespace bts.shell {
public static GridColumnSettings last => new GridColumnSettings(col_id(Columns.Last), DefaultColumnHeaders.Last, width: DefaultColumnWidths.Price);
public static GridColumnSettings last_size => new GridColumnSettings(col_id(Columns.LastSize), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings position => new GridColumnSettings(col_id(Columns.Position), DefaultColumnHeaders.Pos, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings raw_day_position => new GridColumnSettings(col_id(Columns.RawDayPosition), DefaultColumnHeaders.RawDayPos, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right, is_visible: false);
public static GridColumnSettings working_bids => new GridColumnSettings(col_id(Columns.WorkingBids), DefaultColumnHeaders.Working, font_color_override: BTSColors.MarketData.bid, width: DefaultColumnWidths.Working, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings bid_implied_size => new GridColumnSettings(col_id(Columns.BidImpliedSize), DefaultColumnHeaders.ImpliedSize, font_color_override: BTSColors.MarketData.bid, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings bid_direct_size => new GridColumnSettings(col_id(Columns.BidDirectSize), DefaultColumnHeaders.DirectSize, font_color_override: BTSColors.MarketData.bid, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings bid_merged_size => new GridColumnSettings(col_id(Columns.BidMergedSize), DefaultColumnHeaders.MergedSize, font_color_override: BTSColors.MarketData.bid, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings bid => new GridColumnSettings(col_id(Columns.Bid), DefaultColumnHeaders.Bid, font_color_override: BTSColors.header_text, width:DefaultColumnWidths.MViewPrice, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings ask => new GridColumnSettings(col_id(Columns.Ask), DefaultColumnHeaders.Ask, font_color_override: BTSColors.header_text, width: DefaultColumnWidths.MViewPrice, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings bid => new GridColumnSettings(col_id(Columns.Bid), DefaultColumnHeaders.Bid, font_color_override: BTSColors.header_text, width:DefaultColumnWidths.MViewPrice, horizontal_alignment: System.Windows.HorizontalAlignment.Right, background_color_override: BTSColors.MarketData.bid);
public static GridColumnSettings ask => new GridColumnSettings(col_id(Columns.Ask), DefaultColumnHeaders.Ask, font_color_override: BTSColors.header_text, width: DefaultColumnWidths.MViewPrice, horizontal_alignment: System.Windows.HorizontalAlignment.Right, background_color_override: BTSColors.MarketData.ask);
public static GridColumnSettings ask_merged_size => new GridColumnSettings(col_id(Columns.AskMergedSize), DefaultColumnHeaders.MergedSize, font_color_override: BTSColors.MarketData.ask, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings ask_direct_size => new GridColumnSettings(col_id(Columns.AskDirectSize), DefaultColumnHeaders.DirectSize, font_color_override: BTSColors.MarketData.ask, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings ask_implied_size => new GridColumnSettings(col_id(Columns.AskImpliedSize), DefaultColumnHeaders.ImpliedSize, font_color_override: BTSColors.MarketData.ask, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
@@ -119,6 +140,7 @@ namespace bts.shell {
yield return last;
yield return last_size;
yield return position;
yield return raw_day_position;
yield return working_bids;
yield return bid_implied_size;
yield return bid_direct_size;
@@ -173,6 +195,8 @@ namespace bts.shell {
public DoubleMinMaxState session_high;
[ProtoMember(12)]
public IntMinMaxState position;
[ProtoMember(27)]
public DoubleMinMaxState raw_day_position;
[ProtoMember(13)]
public IntMinMaxState working_bid_volume;
[ProtoMember(14)]
@@ -202,5 +226,46 @@ namespace bts.shell {
[ProtoMember(26)]
public string mic_code;
}
// migration rules
[Export(typeof(IMarketViewGridSettingsMigrationRule))]
class AddMissingColumnsMigrationRule : IMarketViewGridSettingsMigrationRule {
public bool apply(GridSettings settings, out GridSettings new_settings) =>
settings.add_missing_columns(StartupData.DefaultGridSettings.columns, out new_settings);
}
[Export(typeof(IMarketViewGridSettingsMigrationRule))]
class InitBidAskBackgroundOverrideMigrationRule : IMarketViewGridSettingsMigrationRule {
public bool apply(GridSettings s, out GridSettings new_settings) {
var columns = s.columns.ToList();
var bid_col_index = columns.FindIndex(x => x.id == col_id(Columns.Bid));
var ask_col_index = columns.FindIndex(x => x.id == col_id(Columns.Ask));
bool applied = false;
if (bid_col_index > -1 && ask_col_index > -1) {
if (!columns[bid_col_index].background_color_override.HasValue) {
columns[bid_col_index] = columns[bid_col_index].with(background_color_override: new NullableColorProto(BTSColors.MarketData.bid.ToUint()));
applied = true;
}
if (!columns[ask_col_index].background_color_override.HasValue) {
columns[ask_col_index] = columns[ask_col_index].with(background_color_override: new NullableColorProto(BTSColors.MarketData.ask.ToUint()));
applied = true;
}
}
new_settings = applied ? s.with(columns) : default;
return applied;
}
}
[Export(typeof(IMarketViewGridSettingsMigrationRule))]
class InitBackgroundOverrideMigrationRule : IMarketViewGridSettingsMigrationRule {
public bool apply(GridSettings settings, out GridSettings new_settings) {
if (!settings.enable_background_color) {
new_settings = settings.with(enable_background_color: true);
return true;
}
new_settings = default;
return false;
}
}
}
}
}
@@ -7,16 +7,6 @@ using System.Linq;
using System.Reactive.Linq;
namespace mbts.spark {
struct TradeInfo {
public readonly SecurityImmutable security;
public readonly TradeMessageImmutable trade;
public TradeInfo(SecurityImmutable security, TradeMessageImmutable trade) {
this.security = security;
this.trade = trade;
}
}
class TimeAndSalesModel : IDisposable {
internal readonly SecurityCriteria security_criteria;
@@ -137,7 +137,7 @@ namespace mbts.spark {
public Row(TimeAndSalesVM parent, SecurityImmutable security, TradeMessageImmutable trade) : base(security) {
_trade_key = trade.Key;
time = ((ulong)trade.Time).lldatetime().ToLocalTime();
price = trade.Mantissa * Math.Pow(10, trade.Exponent);
price = trade.price();
size = trade.Size;
aggressor_side = trade.AggressorSide;
show_ladder = new DelegateCommand(() => parent._show_ladder_async(this).ignore());
@@ -26,7 +26,10 @@ namespace bts.ui {
public SettingsShellModule(IBus bus, UserSettingsHub settings_hub, WorkspacesModel ws) {
// todo: settings dialog
ws_model = ws;
var actions_list = new List<SparkCommand>(2) { SparkCommands.ShowWorkspaces };
var actions_list = new List<SparkCommand>(2) {
SparkCommands.ShowGlobalAlerts,
SparkCommands.ShowWorkspaces
};
if (Configuration.get<bts.config.toggles.ToastNotifications>().enabled) {
actions_list.Add(SparkCommands.ShowSettings);
}
@@ -10,7 +10,7 @@ using System.Reactive.Disposables;
namespace mbts.spark {
static class AlgoParamRowsHelper {
public static IDisposable connect(AlgoDefinitionVM algo, ObservableAggregateCollection<AlgoParamRowVM> rows, bool include_algo_type_rows) =>
algo == null ? null : new CompositeDisposable(algo.observe_algo_root().SubscribeEach(a => {
algo == null ? null : new CompositeDisposable(algo.observe_algo_root().OnDispatcher().SubscribeEach(a => {
rows.clear_collections();
if (a == null)
return Disposable.empty;
@@ -77,7 +77,7 @@ namespace mbts.spark {
include_algo_type_rows ?
(ObservableCollection<AlgoParamRowVM>)new ObservableAppendCollection<AlgoParamRowVM>(child_rows, selector_wrapper) :
child_rows,
s.subscribe_selected_algo().SubscribeEach(a => {
s.subscribe_selected_algo().OnDispatcher().SubscribeEach(a => {
s.value = a;
child_rows.clear_collections();
return _wrap_rows(a, selector_wrapper, child_rows, include_algo_type_rows);
@@ -194,20 +194,27 @@ namespace mbts.spark {
readonly bts.persist.algo.AlgoPersistedKey[] _favorite_algos;
[ProtoMember(2)]
public readonly int column_count;
[ProtoMember(3)]
readonly bts.persist.algo.AlgoPersistedKey[] _stickied_algos;
public LadderFavoriteAlgosSettings(AlgoKey[] favorite_algos, int column_count)
: this(ArrayHelper.safe_convert(favorite_algos, k => new bts.persist.algo.AlgoPersistedKey(k.persisted)), column_count) { }
public LadderFavoriteAlgosSettings(AlgoKey[] favorite_algos, int column_count, AlgoKey[] stickied_algos)
: this(ArrayHelper.safe_convert(favorite_algos, k => new bts.persist.algo.AlgoPersistedKey(k.persisted)),
column_count,
ArrayHelper.safe_convert(stickied_algos, k => new bts.persist.algo.AlgoPersistedKey(k.persisted))) { }
LadderFavoriteAlgosSettings(bts.persist.algo.AlgoPersistedKey[] favorite_algos, int column_count) {
LadderFavoriteAlgosSettings(bts.persist.algo.AlgoPersistedKey[] favorite_algos, int column_count, bts.persist.algo.AlgoPersistedKey[] stickied_algos) {
_favorite_algos = favorite_algos;
this.column_count = column_count;
_stickied_algos = stickied_algos;
}
public IReadOnlyList<AlgoKey> favorite_algos => ArrayHelper.safe_convert(_favorite_algos, k => k.to_algo_key());
public IReadOnlyList<AlgoKey> stickied_algos => ArrayHelper.safe_convert(_stickied_algos, k => k.to_algo_key());
public bool Equals(LadderFavoriteAlgosSettings other) =>
column_count == other.column_count &&
favorite_algos.SequenceEqual(other.favorite_algos);
favorite_algos.SequenceEqual(other.favorite_algos) &&
stickied_algos.SequenceEqual(other.stickied_algos);
public override bool Equals(object obj) =>
EqualityComparer.equals_obj(this, obj);
@@ -215,7 +222,8 @@ namespace mbts.spark {
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
column_count,
EqualityComparer.aggregate_hash_limited(favorite_algos));
EqualityComparer.aggregate_hash_limited(favorite_algos),
EqualityComparer.aggregate_hash_limited(stickied_algos));
public static bool operator ==(LadderFavoriteAlgosSettings a, LadderFavoriteAlgosSettings b) => a.Equals(b);
public static bool operator !=(LadderFavoriteAlgosSettings a, LadderFavoriteAlgosSettings b) => !a.Equals(b);
@@ -273,7 +281,7 @@ namespace mbts.spark {
order_entry_panel_initial_width = _default_order_entry_panel_width;
zoom_factor = _default_zoom_factor;
use_defaults = true;
favorite_algos = new LadderFavoriteAlgosSettings(null, 2);
favorite_algos = new LadderFavoriteAlgosSettings(null, 2, null);
show_warn_on_cross = _default_show_warn_on_cross;
}
@@ -442,6 +450,8 @@ namespace mbts.spark {
public bool account_position_visible { get; set; }
[ProtoMember(50), DefaultValue(true)]
public bool day_position_visible { get; set; }
[ProtoMember(56)]
public bool raw_day_position_visible { get; set; }
[ProtoMember(37), DefaultValue(true)]
public bool size_presets_visible { get; set; }
[ProtoMember(38), DefaultValue(true)]
@@ -562,6 +572,7 @@ namespace mbts.spark {
order_type_visible == other.order_type_visible &&
account_position_visible == other.account_position_visible &&
day_position_visible == other.day_position_visible &&
raw_day_position_visible == other.raw_day_position_visible &&
size_presets_visible == other.size_presets_visible &&
cancel_buys_visible == other.cancel_buys_visible &&
cancel_sells_visible == other.cancel_sells_visible &&
@@ -11,6 +11,7 @@ using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
@@ -22,6 +23,7 @@ using LadderSettings = mbts.spark.LadderSettings;
namespace bts.shell {
class LadderWindowFactory : ShellWindowFactory<LadderSaveState> {
const string _window_type = "ladder";
const int _version_where_stickied_algos_introduced = 2;
readonly LadderConfig _ladder_conf;
readonly IBus _bus;
readonly IWindowManager _wm;
@@ -38,7 +40,7 @@ namespace bts.shell {
OrdersModel orders_model,
MBOService mbo)
: base(owner, "SparkLadder") {
: base(owner, "SparkLadder", 2) {
_bus = bus;
_wm = wm;
_settings_hub = settings_hub;
@@ -60,6 +62,16 @@ namespace bts.shell {
return null;
}
protected override Task<IShellWindow> try_upgrade_state(Stream load_stream, int version) {
var state = ProtoBuf.Serializer.DeserializeWithLengthPrefix<LadderSaveState>(load_stream, ProtoBuf.PrefixStyle.Fixed32);
if (version < _version_where_stickied_algos_introduced) {
// We want to assign any existing favorite algos to stickied algos upon setting upgrade
var favorite_algos_array = state.settings.favorite_algos.favorite_algos.ToArray();
state.settings.favorite_algos = new LadderFavoriteAlgosSettings(favorite_algos_array, state.settings.favorite_algos.column_count, favorite_algos_array);
}
return load(state);
}
LadderSaveState _get_state(LadderVM vm, uint id) {
LadderSettings s = vm.get_save_state();
if (id != 0) {
@@ -2,6 +2,7 @@
using bts.api;
using bts.api.orderman;
using bts.collections;
using bts.persist.algo;
using bts.shell.orderbook;
using bts.utils;
using mbts.collections;
@@ -71,6 +72,7 @@ namespace mbts.spark {
_order_type_visibility = new OrderEntryPanelItemVisibilityVM("Order Type"),
_account_position_visibility = new OrderEntryPanelItemVisibilityVM("Account Position"),
_day_position_visibility = new OrderEntryPanelItemVisibilityVM("Day Position"),
_raw_day_position_visibility = new OrderEntryPanelItemVisibilityVM("Raw Day Position"),
_size_preset_visibility = new OrderEntryPanelItemVisibilityVM("Size Presets"),
_auto_clear_visibility = new OrderEntryPanelItemVisibilityVM("Auto Clear"),
_clear_visibility = new OrderEntryPanelItemVisibilityVM("Clear"),
@@ -136,7 +138,7 @@ namespace mbts.spark {
c.PropertyChanged -= on_prop_changed;
}
AccountTuple? account_factory(KeyValuePair<uint, string> kv) => new AccountTuple(kv.Key, kv.Value);
AlgoDefinitionVM algo_factory(AlgoData data) => AlgoDefinitionVM.create(_model, data);
AlgoDefinitionVM algo_factory(AlgoData data) => AlgoDefinitionVM.create(_model, data, is_synthetic ? AlgoFormats.Spreader : AlgoFormats.Exchange);
}
public event Action result_changed;
@@ -238,6 +240,7 @@ namespace mbts.spark {
yield return _order_type_visibility;
yield return _account_position_visibility;
yield return _day_position_visibility;
yield return _raw_day_position_visibility;
yield return _size_preset_visibility;
yield return _clear_visibility;
yield return _auto_clear_visibility;
@@ -280,6 +283,7 @@ namespace mbts.spark {
yield return _order_type_visibility;
yield return _account_position_visibility;
yield return _day_position_visibility;
yield return _raw_day_position_visibility;
yield return _size_preset_visibility;
yield return _auto_clear_visibility;
yield return _clear_visibility;
@@ -342,6 +346,7 @@ namespace mbts.spark {
_order_type_visibility.show = settings.order_type_visible;
_account_position_visibility.show = settings.account_position_visible;
_day_position_visibility.show = settings.day_position_visible;
_raw_day_position_visibility.show = settings.raw_day_position_visible;
_size_preset_visibility.show = settings.size_presets_visible;
_clear_visibility.show = settings.clear_visible;
_auto_clear_visibility.show = settings.auto_clear_visible;
@@ -414,6 +419,7 @@ namespace mbts.spark {
clone.order_type_visible = _order_type_visibility.show;
clone.account_position_visible = _account_position_visibility.show;
clone.day_position_visible = _day_position_visibility.show;
clone.raw_day_position_visible = _raw_day_position_visibility.show;
clone.size_presets_visible = _size_preset_visibility.show;
clone.auto_clear_visible = _auto_clear_visibility.show;
clone.clear_visible = _clear_visibility.show;
@@ -723,12 +729,15 @@ namespace mbts.spark {
readonly IObservableCollection<AlgoDefinitionVM> _algo_definitions;
readonly ObservableCollection<FavoriteAlgoItem> _raw_items = new ObservableCollection<FavoriteAlgoItem>();
int _column_count = 2;
HashSet<AlgoPersistedKey> _sticky_algo_keys;
IDisposable _sticky_algos_hook;
public FavoriteAlgosVM(IObservableCollection<AlgoDefinitionVM> algo_definitions) {
_algo_definitions = algo_definitions;
items = new ObservableAggregateCollection<FavoriteAlgoItem>(_raw_items, new ObservableCollection<FavoriteAlgoItem> { FavoriteAlgoItem.AddButtonPlaceholderItem });
add_item_cmd = new DelegateCommand(() => _raw_items.Add(_item_factory()));
remove_item_cmd = new DelegateCommand<FavoriteAlgoItem>(fai => _raw_items.Remove(fai));
_dispose = Disposable.Create(() => Disposable.dispose(ref _sticky_algos_hook));
}
public ObservableCollection<FavoriteAlgoItem> items { get; }
@@ -745,6 +754,17 @@ namespace mbts.spark {
fai.selector.try_select(k);
return fai;
}));
_sticky_algo_keys = new HashSet<AlgoPersistedKey>(value.stickied_algos.Where(k => k.is_persisted).Select(x => new AlgoPersistedKey(x.persisted)));
Disposable.replace(ref _sticky_algos_hook,
ObservableAutoWrapper.connect_item_hooks(_raw_items, x =>
x.selector.subscribe_value().Subscribe(algo => {
if (algo != null &&
algo.key.is_persisted &&
(x.is_sticky = _sticky_algo_keys.Remove(new AlgoPersistedKey(algo.key.persisted))) &&
_sticky_algo_keys.Count == 0)
//
Disposable.dispose(ref _sticky_algos_hook);
})));
}
public LadderFavoriteAlgosSettings create() =>
@@ -753,7 +773,13 @@ namespace mbts.spark {
.Where(k => k.HasValue && k.Value.is_persisted)
.Select(k => k.Value)
.ToArray(),
column_count);
column_count,
_raw_items
.Where(fai => fai.is_sticky)
.Select(fai => fai.algo_definition?.key ?? fai.selector.desired_key)
.Where(k => k.HasValue && k.Value.is_persisted)
.Select(k => k.Value)
.ToArray());
FavoriteAlgoItem _item_factory() =>
new FavoriteAlgoItem(_algo_definitions, new ListCollectionView<AlgoDefinitionVM>(_algo_definitions) {
@@ -766,6 +792,7 @@ namespace mbts.spark {
public static readonly FavoriteAlgoItem AddButtonPlaceholderItem = new _AddButtonPlaceholderItem();
internal readonly KeyedItemSelector<AlgoDefinitionVM, AlgoKey> selector;
bool _is_sticky;
protected FavoriteAlgoItem() { }
@@ -776,6 +803,7 @@ namespace mbts.spark {
public ICollectionView<AlgoDefinitionVM> algo_definitions_view { get; }
public AlgoDefinitionVM algo_definition { get => selector.value; set => selector.value = value; }
public bool is_sticky { get => _is_sticky; set => Set(ref _is_sticky, value); }
class _AddButtonPlaceholderItem : FavoriteAlgoItem, ITemplateSelectorKey {
public string key => LadderSettingsView.FavoriteAlgoAddButtonTemplateKey;
@@ -1,9 +1,11 @@
using bts.api;
using bts.api.orderman;
using bts.collections;
using bts.shell;
using bts.spark;
using bts.utils;
using mbts.co2;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.shell;
using mbts.utils;
@@ -32,8 +34,9 @@ namespace mbts.spark {
_show_tif, _show_order_type, _show_size_preset, _show_auto_clear, _show_clear, _show_cancel_buys,
_show_cancel_sells, _show_cancel_all, _show_zoom;
readonly PropertyAutoWrapper<LadderSettings, Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
double _position, _day_position;
bool _show_account_position, _show_day_position;
readonly HashSet<AlgoKey> _sticky_algos = new HashSet<AlgoKey>();
double _position, _day_position, _raw_day_position;
bool _show_account_position, _show_day_position, _show_raw_day_position;
ulong _misc_time = 0;
ulong _last_trade_time = 0;
IEqualityComparer<double> _equality;
@@ -106,7 +109,10 @@ namespace mbts.spark {
(_show_cancel_all = bool_prop(nameof(show_cancel_all), s => s.cancel_all_visible)).set,
(_show_zoom = bool_prop(nameof(show_zoom), s => s.zoom_slider_visible)).set,
s => {
if (Set(ref _show_account_position, s.account_position_visible, nameof(show_account_position)) | Set(ref _show_day_position, s.day_position_visible, nameof(show_day_position))) {
if (
Set(ref _show_account_position, s.account_position_visible, nameof(show_account_position)) |
Set(ref _show_day_position, s.day_position_visible, nameof(show_day_position)) |
Set(ref _show_raw_day_position, s.raw_day_position_visible, nameof(show_raw_day_position))) {
RaisePropertyChanged(nameof(position_display_text));
}
size_calculator.buttons = s.calculator_buttons;
@@ -132,8 +138,8 @@ namespace mbts.spark {
//
//_window_height = backing(nameof(window_height), settings_manager.subscribe_current().OnDispatcher().Select(s => s.zoom_factor), (scale) => scale * 600);
//_window_width = backing(nameof(window_width), settings_manager.subscribe_current().OnDispatcher().Select(s => s.zoom_factor), (scale) => scale * 420);
position_click_command = new DelegateCommand(() => size_calculator.value = DoubleHelper.safe_convert_to_uint(Math.Abs(_show_day_position ? day_position : position)));
send_order_command = new DelegateCommand<(double price, Side? side, MouseButton button)>(send_order);
position_click_command = new DelegateCommand(() => size_calculator.value = DoubleHelper.safe_convert_to_uint(Math.Abs(_show_day_position ? day_position : (_show_raw_day_position ? _raw_day_position : position))));
send_order_command = new AsyncCommand<(double price, Side? side, MouseButton button)>(send_order_async);
cancel_command = new AsyncCommand<LadderModel.CancelType>(_model.cancel_orders, true);
show_book_command = new DelegateCommand(_show_book);
show_ticket_command = new DelegateCommand(_show_ticket);
@@ -223,17 +229,25 @@ namespace mbts.spark {
.SelectManyInSeries(a => _model.order_gateway.on_security_positions(security.Id)
.Select(table => table.state.Values.FirstOrDefault(pos => pos.Item2.Account == a, tup => tup.Item2, (bts.api.position.PositionUnitImmutable?)null)));
return Disposable.Create(
settings.OnDispatcher().Subscribe(s => order_properties_vm.on_favorite_algos(s.favorite_algos)),
settings.OnDispatcher().Subscribe(s => {
_sticky_algos.SetItems(s.favorite_algos.stickied_algos);
order_properties_vm.on_favorite_algos(s.favorite_algos);
}),
order_properties_vm,
position_src.OnDispatcher().Subscribe(pos => {
double total, day;
double total, day, raw_day;
if (pos.HasValue) {
total = pos.Value.total_pos();
day = pos.Value.day_pos();
raw_day = pos.Value.raw_day_pos();
} else {
total = day = double.NaN;
total = day = raw_day = double.NaN;
}
if ((Set(ref _position, total, nameof(position)) | Set(ref _day_position, day, nameof(day_position))) && (show_day_position || show_account_position))
if ((
Set(ref _position, total, nameof(position)) |
Set(ref _day_position, day, nameof(day_position)) |
Set(ref _raw_day_position, raw_day, nameof(raw_day_position))
) && (show_day_position || show_account_position || show_raw_day_position))
RaisePropertyChanged(nameof(position_display_text));
}));
}
@@ -284,6 +298,7 @@ namespace mbts.spark {
public double settlement => _settlement.Value;
public double position => _position;
public double day_position => _day_position;
public double raw_day_position => _raw_day_position;
public ulong volume => _volume.Value;
public bool auto_clear { get => _auto_clear.Value; set => _auto_clear.set(value); }
public CalculatorLogic size_calculator { get; } = new CalculatorLogic(0);
@@ -310,6 +325,7 @@ namespace mbts.spark {
public bool show_order_type => _show_order_type.view;
public bool show_account_position => _show_account_position;
public bool show_day_position => _show_day_position;
public bool show_raw_day_position => _show_raw_day_position;
public bool show_size_preset => _show_size_preset.view;
public bool show_auto_clear => _show_auto_clear.view;
public bool show_clear => _show_clear.view;
@@ -321,12 +337,18 @@ namespace mbts.spark {
public double zoom_factor { get => _zoom_factor.Value; set => _zoom_factor.set(value); }
public string position_display_text {
get {
if (show_day_position) {
string day_pos_text = SparkUtils.fmt_position(day_position);
return show_account_position ? $"{day_pos_text} ({SparkUtils.fmt_position(position)})" : day_pos_text;
} else if (show_account_position) {
return SparkUtils.fmt_position(position);
} else return string.Empty;
string rv = null;
if (show_day_position)
rv = SparkUtils.fmt_position(day_position);
if (show_raw_day_position) {
var raw_day_text = SparkUtils.fmt_position(raw_day_position);
rv = rv == null ? raw_day_text : $"{rv} / {raw_day_text}";
}
if (show_account_position) {
var acc_text = SparkUtils.fmt_position(position);
rv = rv == null ? SparkUtils.fmt_position(position) : $"{rv} ({acc_text})";
}
return rv ?? "";
}
}
//public double window_height { get => _window_height.Value; set => _window_height.set(value); }
@@ -400,20 +422,19 @@ namespace mbts.spark {
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)) {
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);
send_order_async(args.side.Value, size, args.price).ignore();
public Task send_order_async((double price, Side? side, MouseButton button) args) {
if (order_properties_vm != null && args.button != MouseButton.Middle) {
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 (!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);
return send_order_async(args.side.Value, size, args.price);
}
}
}
return Task.CompletedTask;
}
public async Task send_order_async(Side side, uint size, double price) {
@@ -433,6 +454,13 @@ namespace mbts.spark {
await _model.order_gateway.send_order(order, price, size);
if (auto_clear)
size_calculator.value = 0;
var fav_algo = order_properties_vm.selected_favorite_algo_definition;
if (fav_algo != null && !_sticky_algos.Contains(fav_algo.key) && !order_properties_vm.try_pop_selected_algo_definition()) {
var key = new AlgoKey(OrderDataHelper.get_default_algo(OrderDataHelper.get_algo_format(security.ContractCase)));
if (order_properties_vm.try_get_algo_definition(key, out var r)) {
order_properties_vm.selected_algo_definition = r;
}
}
}
void _update_header() {
@@ -805,6 +805,7 @@
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="c1"/>
</Grid.ColumnDefinitions>
<Button Command="{Binding DataContext.remove_item_cmd, RelativeSource={RelativeSource AncestorType=ItemsControl}}" CommandParameter="{Binding}" Style="{StaticResource RoundCloseButton}"/>
<ComboBox Grid.Column="1" ItemsSource="{Binding algo_definitions_view}" SelectedItem="{Binding algo_definition}">
@@ -814,6 +815,7 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<CheckBox Grid.Column="2" VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding is_sticky}"> Stay Enabled?</CheckBox>
</Grid>
</DataTemplate>
</u:KeyedTemplateSelector.Default>
@@ -823,6 +825,7 @@
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="c1"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" HorizontalAlignment="Stretch" Command="{Binding DataContext.add_item_cmd, RelativeSource={RelativeSource AncestorType=ItemsControl}}">Add</Button>
</Grid>
@@ -476,6 +476,7 @@
</converters:MultiValueConverterPipe>
</MultiBinding.Converter>
<Binding Path="show_day_position"/>
<Binding Path="show_raw_day_position"/>
<Binding Path="show_account_position"/>
</MultiBinding>
</Button.Visibility>
@@ -486,6 +487,7 @@
<MultiBinding Converter="{x:Static spark:LadderView.PositionToolTipConverter}">
<Binding Path="order_properties_vm.selected_account"/>
<Binding Path="show_day_position"/>
<Binding Path="show_raw_day_position"/>
<Binding Path="show_account_position"/>
</MultiBinding>
</TextBlock.Text>
@@ -18,32 +18,60 @@ namespace mbts.spark {
MaxZoomFactor = 1.5;
public static IMultiValueConverter PositionToolTipConverter =
ValueConverter.create<AccountTuple?, bool, bool, string>((account, show_day_pos, show_total_pos) => {
const string day_pos_text = "day", total_pos_text = "total";
ValueConverter.create<AccountTuple?, bool, bool, bool, string>((account, show_day_pos, show_raw_day_pos, show_total_pos) => {
const string day_pos_text = "day", raw_day_pos_text = "raw day", total_pos_text = "total";
if (!account.HasValue)
return string.Empty;
var sb = new StringBuilderEx(account.Value.name);
string primary_pos;
if (show_day_pos) {
primary_pos = day_pos_text;
if (show_total_pos) {
sb.Add(" '", primary_pos, " ");
sb.Add("(", total_pos_text, ")' ");
} else {
sb.Add(" ", primary_pos, " ");
}
} else if (show_total_pos) {
primary_pos = total_pos_text;
sb.Add(" ", primary_pos, " ");
/// Note: 'day,' and 'raw day,' are interchangeable wrt the string format, 'total' however is treated differently.
/// Different Formats:
// No parts are visible:
// null
//
// 1 part 'foo' is visible:
// "<account> foo position
// click to copy to size"
//
// 2 parts 'day' and 'raw day' are visible:
// "<account> 'day / raw day' position
// click to copy day to size"
//
// 2 parts 'day' and 'total' are visible:
// "<account> 'day (total)' position
// click to copy day to size"
//
string primary_pos = null;
// part count is incremented in the 'add_part' method
int part_count = 0;
var sb = new StringBuilderEx();
if (show_day_pos) add_part(day_pos_text, " / ");
if (show_raw_day_pos) add_part(raw_day_pos_text, " / ");
if (show_total_pos) add_part(total_pos_text, " (", ")");
//
if (part_count > 1) {
// This is a complex expression (showing more than 1 value) - wrap it in single quotes.
// Explicitly state which value will be copied to the size input.
sb.Insert(0, "'");
sb.Add("'");
sb.Add(" position\n(click to copy ", primary_pos, " to size)");
} else {
// unexpected code path (the button for which this tooltip is destined should be invisible in this case), but no need to throw an exception.
return string.Empty;
// Only showing 1 value, no need for single quotes,
// nor an explicit statement that indicates which value will be copied to the size input.
sb.Add(" position\n(click to copy to size)");
}
sb.Add("position\n(click to copy ");
if (show_day_pos && show_total_pos)
sb.Add(primary_pos, " position ");
sb.Add("to size)");
return sb.ToString();
void add_part(string part_name, string separator = null, string separator_suffix = null) {
if (part_count++ > 0) {
sb.Add(separator);
sb.Add(part_name);
sb.Add(separator_suffix);
} else {
primary_pos = part_name;
sb.Add($"{account.Value.name} {part_name}");
}
}
});
internal LadderView(LadderVM vm) {
@@ -1,15 +1,15 @@
using bts.api;
using bts.api.orderman;
using bts.settings;
using bts.spark;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.spark;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
@@ -40,13 +40,14 @@ namespace bts.shell {
_bus.post(SparkTags.ShowWindow, w);
}
IShellWindow _create(PAndLStartupData state) {
var settings_manager = new WindowSettingsManager<GridSettings>(_settings_hub,
state.grid,
get: us => us.pnl_grid,
set: (us, gs) => us.with(provided_pnl_gridsettings: gs));
return new PAndLWindow(_orders_model, this, _bus, state, SparkCommands.ShowPAndL.icon, settings_manager);
}
IShellWindow _create(PAndLStartupData state) =>
new PAndLWindow(
_orders_model,
this,
_bus,
state,
SparkCommands.ShowPAndL.icon,
_settings_hub.create_pandl_settings_manager(state.grid));
[ProtoContract]
public class PAndLStartupData {
@@ -81,7 +82,7 @@ namespace bts.shell {
[ProtoMember(25)]
public GridSettings grid;
static class DefaultColumns {
public static class DefaultColumns {
public static GridColumnSettings account => new GridColumnSettings(col_id(Columns.Account), DefaultColumnHeaders.Account, width: DefaultColumnWidths.Account);
public static GridColumnSettings exchange => new GridColumnSettings(col_id(Columns.Exchange), DefaultColumnHeaders.Exchange, false, width: DefaultColumnWidths.Exchange);
@@ -92,10 +93,12 @@ namespace bts.shell {
public static GridColumnSettings start_pnl => new GridColumnSettings(col_id(Columns.StartPNL), DefaultColumnHeaders.StartPnL, false, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings day_long_pnl => new GridColumnSettings(col_id(Columns.DayLong), DefaultColumnHeaders.DayLong, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings day_short_pnl => new GridColumnSettings(col_id(Columns.DayShort), DefaultColumnHeaders.DayShort, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings raw_day_net_position => new GridColumnSettings(col_id(Columns.RawDayPos), DefaultColumnHeaders.RawDayPos, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right, is_visible: false);
public static GridColumnSettings day_pnl => new GridColumnSettings(col_id(Columns.DayPNL), DefaultColumnHeaders.DayPnL, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings total_net_pos => new GridColumnSettings(col_id(Columns.TotalNetPos), DefaultColumnHeaders.TotalNetPos, false, width: DefaultColumnWidths.Total_Net_Pos, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings total_pnl => new GridColumnSettings(col_id(Columns.TotalPNL), DefaultColumnHeaders.TotalPnL, false, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings pnl_price => new GridColumnSettings(col_id(Columns.PNLPrice), DefaultColumnHeaders.PnLPrice, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings has_day_trades => new GridColumnSettings(col_id(Columns.TradedToday), "Traded Today", width: 100, horizontal_alignment: System.Windows.HorizontalAlignment.Center);
public static IEnumerable<SortingCriteria> default_sorting_criterion {
get {
@@ -117,10 +120,12 @@ namespace bts.shell {
yield return start_pnl;
yield return day_long_pnl;
yield return day_short_pnl;
yield return raw_day_net_position;
yield return day_pnl;
yield return total_net_pos;
yield return total_pnl;
yield return pnl_price;
yield return has_day_trades;
}
}
@@ -145,6 +150,8 @@ namespace bts.shell {
public UIntMinMaxState day_long_pos;
[ProtoMember(8)]
public UIntMinMaxState day_short_pos;
[ProtoMember(16)]
public DoubleMinMaxState raw_day_net_pos;
[ProtoMember(9)]
public DoubleMinMaxState day_pnl;
[ProtoMember(10)]
@@ -162,7 +169,11 @@ namespace bts.shell {
public bool[] has_day_trades;
}
// migration rules
[Export(typeof(IPAndLGridSettingsMigrationRule))]
class AddMissingColumnsMigrationRule : IPAndLGridSettingsMigrationRule {
public bool apply(GridSettings settings, out GridSettings new_settings) =>
settings.add_missing_columns(PAndLStartupData.DefaultGridSettings.columns, out new_settings);
}
}
}
@@ -4,6 +4,7 @@ using bts.api.orderman;
using bts.collections;
using bts.spark;
using mbts;
using mbts.co2;
using mbts.collections;
using mbts.spark;
using mbts.ui;
@@ -13,6 +14,7 @@ using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
namespace bts.shell {
interface INotificationService {
@@ -22,25 +24,48 @@ namespace bts.shell {
IDisposable link_to_activity(INotificationPredicate<Activity> toast_notification_predicate);
IDisposable link_to_order(IPredicate<OrderSecurity> predicate, SoundNotification sound);
IDisposable link_to_order(INotificationPredicate<OrderSecurity> toast_notification_predicate);
// Note: security criteria should not be incorporated in the predicate's 'trigger' impl.
// It is easier/cleaner if the INotificationService incorporates the criteria internally.
IDisposable link_to_trades(SecurityCriteria security_criteria, IPredicate<TradeInfo> predicate, SoundNotification sound);
}
[Export(typeof(INotificationService))]
partial class NotificationService : INotificationService, IDisposable {
// activity notifications
readonly NotificationProcessor<Activity> _activity_notifications;
// order notifications
readonly NotificationProcessor<OrderSecurity> _order_notifications;
// trade notifications
readonly NotificationProcessor<TradeInfo> _trade_notifications;
readonly MDConsumer _consumer;
readonly Subject<IEnumerable<SecurityCriteria>> _trade_criteria_subject = new Subject<IEnumerable<SecurityCriteria>>();
readonly RefCountMap<SecurityCriteria> _trade_criteria_refs = new RefCountMap<SecurityCriteria>();
readonly object _trade_lock = new object();
//
readonly DataCache _data_cache = new DataCache();
readonly LoggerContext _log = new LoggerContext(Log.Ui, "Notification Service ", null);
IDisposable _dispose;
[ImportingConstructor]
public NotificationService(IBus bus, INotifier notifier, OrdersModel orders_model) {
public NotificationService(IBus bus, INotifier notifier, OrdersModel orders_model, MDConsumer consumer) {
this.notifier = notifier;
_activity_notifications = new NotificationProcessor<Activity>(notifier);
_order_notifications = new NotificationProcessor<OrderSecurity>(notifier);
_trade_notifications = new NotificationProcessor<TradeInfo>(notifier);
_consumer = consumer;
_dispose = Disposable.Create(
bus.subscribe(SparkTags.LogActivity).Subscribe(_on_activity),
orders_model.on_orders.Subscribe(_on_orders)
);
orders_model.on_orders.Subscribe(_on_orders),
TradesSource.create(
consumer,
bus.subscribe(SparkTags.SecuritiesTag).BufferLatest(TimeSpan.FromSeconds(2))
.Table(s => s.security.Id)
.Where(t => t.has_changes())
.Select(m => MappedDictionary.create(m.state, smd => smd.security)),
_trade_criteria_subject,
TimeSpan.FromSeconds(0.2))
.Subscribe(_trade_notifications.process_items));
}
[Export(typeof(INotificationServiceDataCache))]
@@ -51,8 +76,42 @@ namespace bts.shell {
public IDisposable link_to_activity(INotificationPredicate<Activity> toast_notification_predicate) => _activity_notifications.link(toast_notification_predicate);
public IDisposable link_to_order(IPredicate<OrderSecurity> predicate, SoundNotification sound) => _order_notifications.link(predicate, sound);
public IDisposable link_to_order(INotificationPredicate<OrderSecurity> toast_notification_predicate) => _order_notifications.link(toast_notification_predicate);
public IDisposable link_to_trades(SecurityCriteria security_criteria, IPredicate<TradeInfo> predicate, SoundNotification sound) {
if (predicate == null) {
_log.warn("cannot create trade alert sound because the specified predicate is null.");
return Disposable.empty;
}
if (!security_criteria.is_defined()) {
// there are simply too many trades to support universal-product predicates, therefor a product filter must be specified (aka SecrityCriteria).
_log.warn("cannot create trade alert sound because the specified security criteria is not defined.");
return Disposable.empty;
}
// This criteria predicate may be redundant, but it safe guards against a case
// where the criteria has not been incorporated into the predicate,
// as there doesn't seem to be a great way to require this in the interface.
// Note: verifying the criteria twice (instead of once) should not hurt on perf at all.
predicate = predicate.and<TradeInfo>(PredicateBuilder.create<TradeInfo>(t => ContractPickerModel.security_in_criteria(security_criteria, t.security)));
var link = _trade_notifications.link(predicate, sound);
lock (_trade_lock) {
if (_trade_criteria_refs.increment_adds_key(security_criteria))
_reset_trade_readers();
}
return Disposable.Create(() => {
Disposable.dispose(ref link);
lock (_trade_lock) {
if (_trade_criteria_refs.decrement_removes_key(security_criteria))
_reset_trade_readers();
}
});
}
public void Dispose() => Disposable.dispose(ref _dispose);
/// should only call this within a locked scope (<see cref="_trade_lock"/>)
void _reset_trade_readers() =>
_trade_criteria_subject.OnNext(_trade_criteria_refs.Keys);
void _on_activity(Activity activity) =>
_activity_notifications.process_item(activity);
@@ -128,6 +187,31 @@ namespace bts.shell {
if (sounds.Count > 0 || toast.Count > 0)
_notifier.notify(toast, sounds);
}
public void process_items(IEnumerable<T> source) {
var sounds = new HashSet<SoundNotification>();
var toast = new HashSet<IToastNotification>();
var temp_sound_triggers = _sound_trigger_refs;
if (temp_sound_triggers.Count > 0) {
foreach (var v in source) {
foreach (var trigger_kv in temp_sound_triggers) {
if (trigger_kv.Key.trigger(v))
sounds.AddRange(trigger_kv.Value.Keys);
}
}
}
var temp_toast_triggers = _toast_trigger_refs;
if (temp_toast_triggers.Count > 0) {
foreach (var v in source) {
foreach (var trigger_kv in temp_toast_triggers) {
if (trigger_kv.Key.trigger(v) is IToastNotification tn && tn.pass)
toast.Add(tn);
}
}
}
if (sounds.Count > 0 || toast.Count > 0)
_notifier.notify(toast, sounds);
}
}
}
}
@@ -1,9 +1,7 @@
using mbts;
using mbts.ui.utility;
using bts.utils;
using ProtoBuf;
using System;
using System.ComponentModel;
using System.Text;
namespace bts.shell {
enum LicenseType {
@@ -33,7 +31,7 @@ namespace bts.shell {
}
[ProtoContract]
class SoundNotification {
class SoundNotification : IEquatable<SoundNotification> {
SystemSound? _system_sound;
string _uri_sound;
@@ -81,6 +79,19 @@ namespace bts.shell {
[ProtoMember(2)]
public string uri_sound { get => _uri_sound; set => _set_ref_type(ref _uri_sound, value); }
public bool Equals(SoundNotification other) =>
other != null &&
_system_sound == other._system_sound &&
_uri_sound == other._uri_sound;
public override bool Equals(object obj) =>
Equals(obj as SoundNotification);
public override int GetHashCode() =>
EqualityComparer.auto_phase(
_system_sound.GetHashCode(),
_uri_sound?.GetHashCode() ?? 0);
void _set<T>(ref T? field, T? value) where T : struct {
if (value != null) {
_clear_values();
+44 -15
View File
@@ -631,25 +631,54 @@ namespace mbts.spark.utilities {
return def;
}
}
static CustomOrderDefinition.CustomOneofCase[] algo_config(
Func<bts.config.AlgoPermissions, List<string>> selector)
{
var conf = mbts.config.Configuration.get<bts.config.AlgoPermissions>();
return selector(conf).Select(x =>
{
return (CustomOrderDefinition.CustomOneofCase)System.Enum.Parse(typeof(CustomOrderDefinition.CustomOneofCase), x, true);
}).ToArray();
}
public static readonly CustomOrderDefinition.CustomOneofCase[]
// exchange_order_types are the types which can be submitted on an exchange security
exchange_order_types = algo_config(x => x.exchange_order_types),
spreader_order_types = algo_config(x => x.spreader_order_types),
attachable_order_types = algo_config(x => x.attachable_order_types),
outright_order_types = algo_config(x => x.outright_order_types);
exchange_order_types = new[] {
CustomOrderDefinition.CustomOneofCase.Market,
CustomOrderDefinition.CustomOneofCase.Limit,
CustomOrderDefinition.CustomOneofCase.Stop,
CustomOrderDefinition.CustomOneofCase.StopLimit,
CustomOrderDefinition.CustomOneofCase.GlbxIceberg,
CustomOrderDefinition.CustomOneofCase.Iceberg,
CustomOrderDefinition.CustomOneofCase.Lean,
CustomOrderDefinition.CustomOneofCase.Join,
CustomOrderDefinition.CustomOneofCase.Cover,
CustomOrderDefinition.CustomOneofCase.Top,
CustomOrderDefinition.CustomOneofCase.Offset,
CustomOrderDefinition.CustomOneofCase.Splitter,
CustomOrderDefinition.CustomOneofCase.Scratch,
CustomOrderDefinition.CustomOneofCase.Prorata,
},
spreader_order_types = new[] {
CustomOrderDefinition.CustomOneofCase.Spreader,
CustomOrderDefinition.CustomOneofCase.Iceberg,
CustomOrderDefinition.CustomOneofCase.Lean,
CustomOrderDefinition.CustomOneofCase.Join,
CustomOrderDefinition.CustomOneofCase.Cover,
CustomOrderDefinition.CustomOneofCase.Top,
CustomOrderDefinition.CustomOneofCase.Splitter,
CustomOrderDefinition.CustomOneofCase.Scratch,
CustomOrderDefinition.CustomOneofCase.Prorata,
},
attachable_order_types = new[] {
CustomOrderDefinition.CustomOneofCase.Iceberg,
CustomOrderDefinition.CustomOneofCase.Lean,
CustomOrderDefinition.CustomOneofCase.Join,
CustomOrderDefinition.CustomOneofCase.Cover,
CustomOrderDefinition.CustomOneofCase.Top,
CustomOrderDefinition.CustomOneofCase.Offset,
CustomOrderDefinition.CustomOneofCase.Scratch,
CustomOrderDefinition.CustomOneofCase.Prorata,
},
outright_order_types = new[] {
CustomOrderDefinition.CustomOneofCase.Market,
CustomOrderDefinition.CustomOneofCase.Limit,
CustomOrderDefinition.CustomOneofCase.Stop,
CustomOrderDefinition.CustomOneofCase.StopLimit,
CustomOrderDefinition.CustomOneofCase.GlbxIceberg,
};
public static readonly CustomOrderDefinition.CustomOneofCase[] all_order_types = exchange_order_types.Union(spreader_order_types).ToArray();
public static void copy_values(CustomOrderDefinitionImmutable source, ref CustomOrderDefinition target) =>
@@ -60,6 +60,7 @@ namespace mbts.spark {
readonly ListCollectionView<AlgoDefinitionVM> _algo_definitions_view, _favorite_algo_definitions_view;
readonly TableUpdateBuffer<AlgoData, AlgoKey> _definitions_buffer = new TableUpdateBuffer<AlgoData, AlgoKey>(d => d.key);
readonly ObservableAggregateCollection<AlgoParamRowVM> _algo_param_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
readonly AlgoDefinitionVM the_anchor_algo_def_vm;
IReadOnlyDictionary<AlgoKey, AlgoDefinitionMetadata> _algo_metadata = DictionaryHelper.empty<AlgoKey, AlgoDefinitionMetadata>();
IReadOnlyDictionary<AlgoKey, int> _favorite_algos_index = DictionaryHelper.empty<AlgoKey, int>();
IDisposable _algo_param_rows_hook;
@@ -68,6 +69,7 @@ namespace mbts.spark {
public OrderPropertiesEditorVM(OrdersModel model, IObservable<SecurityMD> security_md_source, IObservable<InlineOrderProperties> order_properties) {
_model = model;
the_anchor_algo_def_vm = AlgoDefinitionVM.create(_model, OrderDataHelper.Defaults.AlgoData(CustomOrderDefinition.CustomOneofCase.Limit));
var security_source = security_md_source.Select(smd => smd.security);
_security = backing(nameof(security), security_source, SecurityDataHelper.Defaults.SecurityImmutable);
security_source = _security.observe_current(true);
@@ -237,11 +239,15 @@ namespace mbts.spark {
void _selected_algo_definition_Changed(AlgoDefinitionVM old_value, AlgoDefinitionVM new_value, SelectionReason reason) {
Disposable.dispose(ref _algo_param_rows_hook);
if (old_value != null)
// Taha feels that we should always revert to a Limit order either when the user explicity unchecks a sticky button
// or when a non-sticky button is pressed. Rather than completely remove the stack at this time (b/c it may be come
// back in the future), let's just always push a specfic def_vm.key onto it [Limit] ;)
//_selected_algo_stack.Push(old_value.key);
_selected_algo_stack.Push(the_anchor_algo_def_vm.key);
if (new_value != null) {
if (use_algo_param_grid)
_algo_param_rows_hook = _connect_algo_param_rows(new_value, _algo_param_rows);
if (reason != SelectionReason.Explicit)
_selected_algo_stack.Push(new_value.key);
}
if (open_algo_popup_on_select) {
if (reason == SelectionReason.UserSelectedNewValue && new_value?.algo_root is IAlgoVM ar && ar.can_expand)
@@ -277,6 +283,18 @@ namespace mbts.spark {
get => _selected_algo_definition.selected_item;
set => _selected_algo_definition.selected_item = value;
}
public bool try_get_algo_definition(AlgoKey a, out AlgoDefinitionVM result) =>
_algo_definitions.TryGetValue(a, out result);
public bool try_pop_selected_algo_definition() {
if (_selected_algo_stack.TryPop(out AlgoKey k) && _algo_definitions.TryGetValue(k, out AlgoDefinitionVM vm)) {
_selected_algo_definition.select_explicit(vm);
return true;
}
return false;
}
public AlgoDefinitionVM selected_favorite_algo_definition {
get => selected_algo_definition?.is_favorite ?? false ? selected_algo_definition : null;
set {
@@ -353,6 +371,7 @@ namespace mbts.spark {
d.favorite_index = _get_favorite_algo_index(d.key);
}
favorite_algos_column_count = value.column_count;
RaisePropertyChanged(nameof(selected_favorite_algo_definition));
}
static IDisposable _connect_algo_param_rows(AlgoDefinitionVM def, ObservableAggregateCollection<AlgoParamRowVM> rows) =>
@@ -371,4 +390,4 @@ namespace mbts.spark {
algo_definitions_view.Refresh();
}
}
}
}
+9
View File
@@ -1,5 +1,6 @@
using bts.api;
using bts.api.orderman;
using bts.co2;
using bts.spark;
using bts.utils;
using mbts.spark.utilities;
@@ -328,5 +329,13 @@ namespace mbts.spark {
return string.Empty;
}
}
public static AggressorSide to_aggressor(this Side side) {
switch (side) {
case Side.Buy: return AggressorSide.Buy;
case Side.Sell: return AggressorSide.Sell;
default: return AggressorSide.None;
}
}
}
}
@@ -61,6 +61,17 @@ namespace mbts {
}
}
public static DoublePredicate create(PredicateTypeOneofCase type, double min, double max) {
var rv = new DoublePredicate();
switch (type) {
case PredicateTypeOneofCase.GreaterThan: rv.greater_than = min; break;
case PredicateTypeOneofCase.LessThan: rv.less_than = max; break;
case PredicateTypeOneofCase.InsideRange: rv.inside_range = new DoubleRange(min, max); break;
case PredicateTypeOneofCase.OutsideRange: rv.outside_range = new DoubleRange(min, max); break;
}
return rv;
}
public IPredicate<double> create() {
switch (predicate_type_case) {
case PredicateTypeOneofCase.GreaterThan: return PredicateBuilder.greater_than(_greater_than.Value);
+179 -49
View File
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
namespace mbts {
interface IPredicate {
@@ -34,9 +35,15 @@ namespace mbts {
public static IPredicate<T> greater_than<T>(T value) =>
new GreaterThanTrigger<T>(value, Comparer<T>.Default);
public static IPredicate<T> greater_than_or_equal_to<T>(T value) =>
new GreaterThanOrEqualToTrigger<T>(value, Comparer<T>.Default);
public static IPredicate<T> less_than<T>(T value) =>
greater_than(value).negate();
public static IPredicate<T> less_than_or_equal_to<T>(T value) =>
greater_than_or_equal_to(value).negate();
public static IPredicate<T> inside_range<T>(T minimum, T maximum) =>
greater_than(minimum).and<T>(less_than(maximum));
@@ -67,6 +74,14 @@ namespace mbts {
public static IPredicate<T> not_in_set<T>(ISet<T> set) =>
in_set(set).negate();
public static IPredicate<T> and_safe<T>(this IPredicate<T> first, IPredicate<T> second) {
if (first == null)
return second;
if (second == null)
return first;
return and<T>(first, second);
}
public static IPredicate<T> and<T>(this IPredicate<T> first, IPredicate<T> second) =>
new AndTrigger<T>(first, second);
@@ -88,7 +103,7 @@ namespace mbts {
public static IPredicate<T> state_transition<T>(Func<T, bool> predicate) {
// avoid false-positive in the first callback
bool last_result = true;
return new AnonymousMutableTrigger<T>(v => {
return new AnonymousTrigger<T>(v => {
bool curr_result = predicate(v);
if (last_result == curr_result) {
return false;
@@ -97,7 +112,7 @@ namespace mbts {
}
#region Implementation
class EqualsTrigger<T> : IPredicate<T> {
class EqualsTrigger<T> : IPredicate<T>, IEquatable<EqualsTrigger<T>> {
readonly T _value;
readonly IEqualityComparer<T> _eq_comparer;
@@ -106,12 +121,26 @@ namespace mbts {
_eq_comparer = eq_comparer;
}
public bool trigger(T value) => _eq_comparer.Equals(value, _value);
public bool trigger(T value) =>
_eq_comparer.Equals(value, _value);
public override string ToString() => $"EqualsTrigger ({typeof(T)})";
public override string ToString() =>
$"EqualsTrigger ({typeof(T)})";
public bool Equals(EqualsTrigger<T> other) =>
other != null &&
// check equality with both IEqualityComparers
_eq_comparer.Equals(_value, other._value) &&
other._eq_comparer.Equals(_value, other._value);
public override bool Equals(object obj) =>
Equals(obj as EqualsTrigger<T>);
public override int GetHashCode() =>
_eq_comparer.GetHashCode(_value);
}
class AndTrigger<T> : IPredicate<T> {
class AndTrigger<T> : IPredicate<T>, IEquatable<AndTrigger<T>> {
readonly IPredicate<T> _first, _second;
public AndTrigger(IPredicate<T> first, IPredicate<T> second) {
@@ -119,18 +148,28 @@ namespace mbts {
_second = second;
}
public bool trigger(T value) => _first.trigger(value) && _second.trigger(value);
public bool trigger(T value) =>
_first.trigger(value) &&
_second.trigger(value);
public override string ToString() => $"AndTrigger ({_first}, {_second})";
public bool Equals(AndTrigger<T> other) =>
other != null &&
_first.Equals(other._first) &&
_second.Equals(other._second);
public override string ToString() =>
$"AndTrigger ({_first}, {_second})";
public override bool Equals(object obj) =>
obj is AndTrigger<T> t && _first.Equals(t._first) && _second.Equals(t._second);
Equals(obj as AndTrigger<T>);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(_first.GetHashCode(), _second.GetHashCode());
EqualityComparer.aggregate_hash(
_first.GetHashCode(),
_second.GetHashCode());
}
class AndTrigger<T1, T2> : IPredicate<(T1, T2)> {
class AndTrigger<T1, T2> : IPredicate<(T1, T2)>, IEquatable<AndTrigger<T1, T2>> {
readonly IPredicate<T1> _first;
readonly IPredicate<T2> _second;
@@ -139,18 +178,28 @@ namespace mbts {
_second = second;
}
public bool trigger((T1, T2) value) => _first.trigger(value.Item1) && _second.trigger(value.Item2);
public bool trigger((T1, T2) value) =>
_first.trigger(value.Item1) &&
_second.trigger(value.Item2);
public bool Equals(AndTrigger<T1, T2> other) =>
other != null &&
_first.Equals(other._first) &&
_second.Equals(other._second);
public override bool Equals(object obj) =>
obj is AndTrigger<T1, T2> t && _first.Equals(t._first) && _second.Equals(t._second);
Equals(obj as AndTrigger<T1, T2>);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(_first.GetHashCode(), _second.GetHashCode());
EqualityComparer.aggregate_hash(
_first.GetHashCode(),
_second.GetHashCode());
public override string ToString() => $"AndTrigger ({_first}, {_second})";
public override string ToString() =>
$"AndTrigger ({_first}, {_second})";
}
class OrTrigger<T> : IPredicate<T> {
class OrTrigger<T> : IPredicate<T>, IEquatable<OrTrigger<T>> {
readonly IPredicate<T> _first, _second;
public OrTrigger(IPredicate<T> first, IPredicate<T> second) {
@@ -160,16 +209,24 @@ namespace mbts {
public bool trigger(T value) => _first.trigger(value) || _second.trigger(value);
public bool Equals(OrTrigger<T> other) =>
other != null &&
_first.Equals(other._first) &&
_second.Equals(other._second);
public override bool Equals(object obj) =>
obj is OrTrigger<T> t && _first.Equals(t._first) && _second.Equals(t._second);
Equals(obj as OrTrigger<T>);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(_first.GetHashCode(), _second.GetHashCode());
EqualityComparer.aggregate_hash(
_first.GetHashCode(),
_second.GetHashCode());
public override string ToString() => $"OrTrigger ({_first}, {_second})";
public override string ToString() =>
$"OrTrigger ({_first}, {_second})";
}
class OrTrigger<T1, T2> : IPredicate<(T1, T2)> {
class OrTrigger<T1, T2> : IPredicate<(T1, T2)>, IEquatable<OrTrigger<T1, T2>> {
readonly IPredicate<T1> _first;
readonly IPredicate<T2> _second;
@@ -178,18 +235,28 @@ namespace mbts {
_second = second;
}
public bool trigger((T1, T2) value) => _first.trigger(value.Item1) || _second.trigger(value.Item2);
public bool trigger((T1, T2) value) =>
_first.trigger(value.Item1) ||
_second.trigger(value.Item2);
public bool Equals(OrTrigger<T1, T2> other) =>
other != null &&
_first.Equals(other._first) &&
_second.Equals(other._second);
public override bool Equals(object obj) =>
obj is OrTrigger<T1, T2> t && _first.Equals(t._first) && _second.Equals(t._second);
Equals(obj as OrTrigger<T1, T2>);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(_first.GetHashCode(), _second.GetHashCode());
EqualityComparer.aggregate_hash(
_first.GetHashCode(),
_second.GetHashCode());
public override string ToString() => $"OrTrigger ({_first}, {_second})";
public override string ToString() =>
$"OrTrigger ({_first}, {_second})";
}
class GreaterThanTrigger<T> : IPredicate<T> {
class GreaterThanTrigger<T> : IPredicate<T>, IEquatable<GreaterThanTrigger<T>> {
readonly T _value;
readonly IComparer<T> _comparer;
@@ -198,57 +265,120 @@ namespace mbts {
_comparer = comparer;
}
public bool trigger(T value) => _comparer.Compare(value, _value) > 0;
public bool trigger(T value) =>
_comparer.Compare(value, _value) > 0;
public override string ToString() => $"GreaterThanTrigger ({_value})";
public bool Equals(GreaterThanTrigger<T> other) =>
other != null &&
_comparer.Equals(other._comparer) &&
_comparer.Compare(_value, other._value) == 0;
public override bool Equals(object obj) =>
Equals(obj as GreaterThanTrigger<T>);
public override int GetHashCode() =>
_value.GetHashCode();
public override string ToString() =>
$"GreaterThanTrigger ({_value})";
}
class InSetTrigger<T>: IPredicate<T> {
class GreaterThanOrEqualToTrigger<T> : IPredicate<T>, IEquatable<GreaterThanOrEqualToTrigger<T>> {
readonly T _value;
readonly IComparer<T> _comparer;
public GreaterThanOrEqualToTrigger(T value, IComparer<T> comparer) {
_value = value;
_comparer = comparer;
}
public bool trigger(T value) =>
_comparer.Compare(value, _value) >= 0;
public bool Equals(GreaterThanOrEqualToTrigger<T> other) =>
other != null &&
_comparer.Equals(other._comparer) &&
_comparer.Compare(_value, other._value) == 0;
public override bool Equals(object obj) =>
Equals(obj as GreaterThanOrEqualToTrigger<T>);
public override int GetHashCode() =>
_value.GetHashCode();
public override string ToString() =>
$"GreaterThanTrigger ({_value})";
}
class InSetTrigger<T> : IPredicate<T>, IEquatable<InSetTrigger<T>> {
readonly ISet<T> _set;
public InSetTrigger(ISet<T> set) =>
_set = set;
public bool trigger(T value) => _set.Contains(value);
public bool trigger(T value) =>
_set.Contains(value);
public bool Equals(InSetTrigger<T> other) =>
other != null &&
// check SetEquals with both sets (IEqualityComparers may differ)
_set.SetEquals(other._set) &&
other._set.SetEquals(_set);
public override bool Equals(object obj) =>
obj is InSetTrigger<T> t && _set.SetEquals(t._set);
public override int GetHashCode() =>
_set.GetHashCode();
EqualityComparer.auto_phase(_set.Take(8));
public override string ToString() => $"InSetTrigger (Count: {_set.Count}, type: {typeof(T)})";
public override string ToString() =>
$"InSetTrigger (Count: {_set.Count}, type: {typeof(T)})";
}
class WrapperTrigger<TOutter, TInner> : IPredicate<TOutter> {
class WrapperTrigger<TOuter, TInner> : IPredicate<TOuter>, IEquatable<WrapperTrigger<TOuter, TInner>> {
readonly IPredicate<TInner> _inner_trigger;
readonly Func<TOutter, TInner> _selector;
readonly Func<TOuter, TInner> _selector;
public WrapperTrigger(IPredicate<TInner> inner_trigger, Func<TOutter, TInner> selector) {
public WrapperTrigger(IPredicate<TInner> inner_trigger, Func<TOuter, TInner> selector) {
_inner_trigger = inner_trigger;
_selector = selector;
}
public bool trigger(TOutter value) => _inner_trigger.trigger(_selector(value));
public bool trigger(TOuter value) =>
_inner_trigger.trigger(_selector(value));
public bool Equals(WrapperTrigger<TOuter, TInner> other) =>
other != null &&
_selector.Equals(other._selector) &&
_inner_trigger.Equals(other._inner_trigger);
public override bool Equals(object obj) =>
obj is WrapperTrigger<TOutter, TInner> t && _inner_trigger.Equals(t._inner_trigger) && _selector.Equals(t._selector);
Equals(obj as WrapperTrigger<TOuter, TInner>);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(_inner_trigger.GetHashCode(), _selector.GetHashCode());
EqualityComparer.aggregate_hash(
_inner_trigger.GetHashCode(),
_selector.GetHashCode());
public override string ToString() => $"WrapperTrigger (outter-type {typeof(TOutter)}, inner-trigger: {_inner_trigger})";
public override string ToString() =>
$"WrapperTrigger (outer-type {typeof(TOuter)}, inner-trigger: {_inner_trigger})";
}
class NegateTrigger<T> : IPredicate<T> {
class NegateTrigger<T> : IPredicate<T>, IEquatable<NegateTrigger<T>> {
readonly IPredicate<T> _trigger;
public NegateTrigger(IPredicate<T> trigger) =>
_trigger = trigger;
public bool trigger(T value) => !_trigger.trigger(value);
public bool trigger(T value) =>
!_trigger.trigger(value);
public bool Equals(NegateTrigger<T> other) =>
other != null &&
_trigger.Equals(other._trigger);
public override bool Equals(object obj) =>
obj is NegateTrigger<T> t && _trigger.Equals(t._trigger);
Equals(obj as NegateTrigger<T>);
public override int GetHashCode() =>
-_trigger.GetHashCode();
@@ -256,22 +386,22 @@ namespace mbts {
public override string ToString() => $"NegateTrigger ({_trigger})";
}
class AnonymousTrigger<T> : IPredicate<T> {
class AnonymousTrigger<T> : IPredicate<T>, IEquatable<AnonymousTrigger<T>> {
readonly Func<T, bool> _trigger;
public AnonymousTrigger(Func<T, bool> trigger) =>
_trigger = trigger;
public bool trigger(T value) => _trigger(value);
public bool Equals(AnonymousTrigger<T> other) =>
other != null &&
_trigger.Equals(other._trigger);
public override string ToString() => $"AnonymousTrigger ({typeof(T)})";
}
public bool trigger(T value) =>
_trigger(value);
class AnonymousMutableTrigger<T> : IPredicate<T> {
public AnonymousMutableTrigger(Func<T, bool> predicate) => this.predicate = predicate;
public Func<T, bool> predicate { get; set; }
public bool trigger(T value) => predicate(value);
public override string ToString() =>
$"AnonymousTrigger ({typeof(T)})";
}
#endregion
}
}
}
@@ -0,0 +1,33 @@
using bts.proto;
using mbts.ui.shell;
namespace mbts.spark {
class DoublePredicateVM : ViewModelBase, IValueFactory<DoublePredicate> {
double _minimum, _maximum;
DoublePredicate.PredicateTypeOneofCase _type;
public DoublePredicate.PredicateTypeOneofCase type { get => _type; set => Set(ref _type, value); }
public double minimum { get => _minimum; set => Set(ref _minimum, value); }
public double maximum { get => _maximum; set => Set(ref _maximum, value); }
public DoublePredicate create() =>
DoublePredicate.create(type, minimum, maximum);
public void load_from(DoublePredicate value) {
if (value == null)
return;
type = value.predicate_type_case;
switch (type) {
case DoublePredicate.PredicateTypeOneofCase.GreaterThan: minimum = value.greater_than.Value; break;
case DoublePredicate.PredicateTypeOneofCase.LessThan: maximum = value.less_than.Value; break;
case DoublePredicate.PredicateTypeOneofCase.InsideRange: _load_range(value.inside_range.Value); break;
case DoublePredicate.PredicateTypeOneofCase.OutsideRange: _load_range(value.outside_range.Value); break;
}
}
void _load_range(DoubleRange range) {
minimum = range.minimum;
maximum = range.maximum;
}
}
}
+1 -1
View File
@@ -338,7 +338,7 @@
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="Padding" Value="6,3,5,3"/>
<Setter Property="Padding" Value="3,3,1,3"/>
<Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
<Setter Property="ScrollViewer.PanningMode" Value="Both"/>
<Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
+14
View File
@@ -0,0 +1,14 @@
using bts.api;
using bts.co2;
namespace mbts.spark {
struct TradeInfo {
public readonly SecurityImmutable security;
public readonly TradeMessageImmutable trade;
public TradeInfo(SecurityImmutable security, TradeMessageImmutable trade) {
this.security = security;
this.trade = trade;
}
}
}
@@ -0,0 +1,9 @@
using bts.co2;
using System;
namespace mbts.spark {
static class TradeMessageExtensions {
public static double price(this TradeMessageImmutable trade) =>
trade.Mantissa * Math.Pow(10, trade.Exponent);
}
}
+94
View File
@@ -0,0 +1,94 @@
using bts.api;
using bts.api.core;
using bts.co2;
using bts.utils;
using mbts.co2;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reactive.Linq;
namespace mbts.spark {
readonly struct Product : IEquatable<Product> {
public readonly string symbol;
public readonly SecurityType type;
public readonly Venue exchange;
public Product(string symbol, SecurityType type, Venue exchange) {
this.symbol = symbol;
this.type = type;
this.exchange = exchange;
}
public static Product from_security(SecurityImmutable security) =>
new Product(security.Symbol, security.security_type(), security.Exchange);
public bool Equals(Product other) =>
symbol == other.symbol &&
type == other.type &&
exchange == other.exchange;
public override bool Equals(object obj) =>
obj is Product other && Equals(other);
public override int GetHashCode() =>
EqualityComparer.auto_phase(
symbol?.GetHashCode() ?? 0,
type.GetHashCode(),
exchange.GetHashCode());
public static bool operator ==(Product a, Product b) => a.Equals(b);
public static bool operator !=(Product a, Product b) => !a.Equals(b);
}
static class TradesSource {
public static IEnumerable<Product> aggregate_security_criteria(IEnumerable<SecurityImmutable> securities, IEnumerable<SecurityCriteria> security_criterion) =>
securities.Where(s => security_criterion.Any(sc => ContractPickerModel.security_in_criteria(sc, s)))
.Select(Product.from_security)
.Distinct();
public static IObservable<IReadOnlyList<TradeInfo>> create(MDConsumer consumer, IObservable<IReadOnlyDictionary<uint, SecurityImmutable>> security_map, IObservable<IEnumerable<SecurityCriteria>> security_criterion, TimeSpan poll_interval) =>
security_criterion.CombineLatest(security_map, (criterion, map) => (criterion, map))
.Scan((channels: ImmutableDictionary<Product, ChannelConsumer>.Empty, active: new List<ChannelConsumer>(0)), (channel_data, data) => {
var (criterion, map) = data;
var (channels, active) = channel_data;
var products = aggregate_security_criteria(map.Values, criterion);
active = new List<ChannelConsumer>();
foreach (var p in products) {
if (!channels.TryGetValue(p, out var c)) {
c = new ChannelConsumer(consumer.get_channel(p.symbol, p.type, p.exchange), map.Values.Where(s => Product.from_security(s).Equals(p)));
channels = channels.Add(p, c);
}
active.Add(c);
}
return (channels, active);
}).SelectManyInSeries(channel_data => {
var (channels, active) = channel_data;
return Observable.Interval(poll_interval)
.Select(_ => active.SelectMany(c => c.read_trades()).ToList());
});
class ChannelConsumer {
readonly MDConsumerChannel _channel;
readonly Dictionary<uint, SecurityImmutable> _securities;
long _cursor = 0;
public ChannelConsumer(MDConsumerChannel channel) =>
_channel = channel;
public ChannelConsumer(MDConsumerChannel channel, IEnumerable<SecurityImmutable> securities) {
_channel = channel;
_securities = securities.ToDictionaryCrushed(s => s.Id);
}
public IEnumerable<TradeInfo> read_trades() {
foreach (var tm in _channel.read_trades(ref _cursor)) {
if (tm != null && _securities.TryGetValue(tm.Key, out SecurityImmutable sec))
yield return new TradeInfo(sec, new TradeMessageImmutable(tm));
}
}
}
}
}
@@ -107,6 +107,9 @@ namespace bts.spark {
public static SparkCommand ShowActivityLog =>
SparkCommand.create("Activity Log", Icons.Wrench, SparkTags.ShowActivityLog);
public static SparkCommand ShowGlobalAlerts =>
SparkCommand.create("Alerts", Icons.Speaker, SparkTags.ShowGlobalAlertsTag);
public static PartialCommand<string> ShowReportIssue =>
SparkCommand.create("Report a Problem", Icons.Support, SparkTags.Support);
+2
View File
@@ -81,6 +81,8 @@ namespace mbts.spark
public static ITag<uint> SyntheticDeleteTag { get; } = new SparkTag<uint>(nameof(SyntheticDeleteTag));
public static ITag<Nothing> ShowGlobalAlertsTag { get; } = new SparkTag<Nothing>(nameof(ShowGlobalAlertsTag));
public class Nothing { Nothing() { } }
internal class SparkTag<T> : ITag<T> {
@@ -34,13 +34,14 @@ namespace bts.shell {
Disposable.dispose(ref _dispose);
}
public abstract Task<IShellWindow> load(T state);
protected virtual Task<IShellWindow> try_upgrade_state(Stream load_stream, int version) => null;
public Task<IShellWindow> load_window(Stream load_stream, int version) {
if (version == this.version) {
var t = ProtoBuf.Serializer.DeserializeWithLengthPrefix<T>(load_stream, ProtoBuf.PrefixStyle.Fixed32);
return load(t);
}
return null;
return try_upgrade_state(load_stream, version);
}
public void save(T data, Stream output) {
var header = new ShellSerializationHeader() {
@@ -0,0 +1,29 @@
using bts.utils;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
namespace mbts.spark {
[ProtoContract]
class GlobalAlertSettings : IEquatable<GlobalAlertSettings> {
[ProtoMember(1)]
TradeAlert[] _trade_alerts;
public GlobalAlertSettings() { }
public GlobalAlertSettings(IEnumerable<TradeAlert> trade_alerts) =>
_trade_alerts = trade_alerts.ToArray();
public IReadOnlyList<TradeAlert> trade_alerts => _trade_alerts ?? Array.Empty<TradeAlert>();
public bool Equals(GlobalAlertSettings other) =>
other != null &&
trade_alerts.SequenceEqual(other.trade_alerts);
public override bool Equals(object obj) =>
Equals(obj as GlobalAlertSettings);
public override int GetHashCode() =>
EqualityComparer.auto_phase(trade_alerts);
}
}
@@ -0,0 +1,121 @@
using bts.api;
using bts.shell;
using bts.utils;
using ProtoBuf;
using System;
using System.Collections.Generic;
namespace mbts.spark {
[ProtoContract]
readonly struct TradeAlert : IEquatable<TradeAlert> {
[ProtoMember(1)]
public readonly TradeAlertPredicate predicate;
[ProtoMember(2)]
public readonly SoundNotification sound;
[ProtoMember(3)]
public readonly bool is_enabled;
public TradeAlert(
TradeAlertPredicate predicate,
SoundNotification sound = null,
bool is_enabled = false) {
this.predicate = predicate;
this.sound = sound;
this.is_enabled = is_enabled;
}
public TradeAlert with(
TradeAlertPredicate? predicate = null,
SoundNotification sound = null,
bool? is_enabled = null) =>
//
new TradeAlert(
predicate ?? this.predicate,
sound ?? this.sound,
is_enabled ?? this.is_enabled);
public bool Equals(TradeAlert other) =>
predicate.Equals(other.predicate) &&
sound.safe_equals(other.sound) &&
is_enabled == other.is_enabled;
public override bool Equals(object obj) =>
obj is TradeAlert other && Equals(other);
public override int GetHashCode() =>
EqualityComparer.auto_phase(
predicate.GetHashCode(),
sound?.GetHashCode() ?? 0,
is_enabled.GetHashCode());
public static bool operator ==(TradeAlert a, TradeAlert b) => a.Equals(b);
public static bool operator !=(TradeAlert a, TradeAlert b) => !a.Equals(b);
}
[ProtoContract]
readonly struct TradeAlertPredicate : IEquatable<TradeAlertPredicate> {
[ProtoMember(1)]
public readonly SecurityCriteria security_criteria;
[ProtoMember(2)]
public readonly DoublePredicate quantity;
[ProtoMember(3)]
public readonly DoublePredicate price;
[ProtoMember(4)]
readonly Side[] _side;
public TradeAlertPredicate(
SecurityCriteria security_criteria = null,
DoublePredicate quantity = null,
DoublePredicate price = null,
Side[] side = null) {
this.security_criteria = security_criteria;
this.quantity = quantity;
this.price = price;
this._side = side;
}
public IReadOnlyList<Side> side => _side ?? Array.Empty<Side>();
public IPredicate<TradeInfo> create() {
IPredicate<TradeInfo> p = null;
// security criteria
var criteria = security_criteria;
if (criteria.is_defined()) {
p = p.and_safe(PredicateBuilder.create<TradeInfo>(t => ContractPickerModel.security_in_criteria(criteria, t.security)));
}
// quantity
var qpred = quantity?.create();
if (qpred != null)
p = p.and_safe(PredicateBuilder.wrap<TradeInfo, double>(qpred, t => t.trade.Size));
// price
var ppred = price?.create();
if (ppred != null)
p = p.and_safe(PredicateBuilder.wrap<TradeInfo, double>(ppred, t => t.trade.price()));
// side
if (_side != null && _side.Length == 1) {
var agg_side = _side[0].to_aggressor();
p = p.and_safe(PredicateBuilder.create<TradeInfo>(t => agg_side == t.trade.AggressorSide));
}
return p;
}
public bool Equals(TradeAlertPredicate other) =>
security_criteria.safe_equals(other.security_criteria) &&
quantity == other.quantity &&
price == other.price &&
EqualityComparer.safe_sequence_equals(_side, other._side);
public override bool Equals(object obj) =>
obj is TradeAlertPredicate other && Equals(other);
public override int GetHashCode() =>
EqualityComparer.auto_phase(
security_criteria?.GetHashCode() ?? 0,
quantity.GetHashCode(),
price.GetHashCode(),
_side != null && _side.Length == 1 ? (int)_side[0] : 0);
public static bool operator ==(TradeAlertPredicate a, TradeAlertPredicate b) => a.Equals(b);
public static bool operator !=(TradeAlertPredicate a, TradeAlertPredicate b) => !a.Equals(b);
}
}
@@ -0,0 +1,82 @@
using mbts.ui.settings;
using System;
using System.Collections.Generic;
namespace bts.settings {
interface ISettingsMigrationRule<T> {
bool apply(T settings, out T new_settings);
}
// Migration rules of type ISettingsMigrationRule<T>, where 'T' is a common type, such as GridSettings,
// should be exposed through explicitly named interfaces that inherit ISettingsMigrationRule<T>.
// This avoids magic const names in the MEF import/export contracts, which would otherwise be needed to
// determine the various windows to which such rules apply.
interface IPAndLGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
interface IMarketViewGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
static class SettingsMigrationHelper {
public static bool apply<T>(T settings, IEnumerable<ISettingsMigrationRule<T>> migration_rules, out T new_settings) {
bool applied = false;
foreach (var r in migration_rules) {
if (r.apply(settings, out var migrated)) {
settings = migrated;
applied = true;
}
}
new_settings = applied ? settings : default;
return applied;
}
public static void apply_quiet<T>(ref T settings, IEnumerable<ISettingsMigrationRule<T>> migration_rules) {
if (apply(settings, migration_rules, out var new_settings))
settings = new_settings;
}
public static ISettingsMigrationRule<T> merge<T>(this IEnumerable<ISettingsMigrationRule<T>> rules) =>
new MergeWrapper<T>(rules);
public static ISettingsMigrationRule<TOuter> wrap<TInner, TOuter>(this ISettingsMigrationRule<TInner> rule, Func<TOuter, TInner> get, Func<TOuter, TInner, TOuter> set) =>
new SelectorWrapper<TInner, TOuter>(rule, get, set);
class MergeWrapper<T> : ISettingsMigrationRule<T> {
readonly IEnumerable<ISettingsMigrationRule<T>> _rules;
public MergeWrapper(IEnumerable<ISettingsMigrationRule<T>> rules) =>
_rules = rules;
public bool apply(T settings, out T new_settings) {
bool applied = false;
foreach (var r in _rules) {
if (r.apply(settings, out var migrated)) {
settings = migrated;
applied = true;
}
}
new_settings = applied ? settings : default;
return applied;
}
}
class SelectorWrapper<TInner, TOuter> : ISettingsMigrationRule<TOuter> {
readonly ISettingsMigrationRule<TInner> _rule;
readonly Func<TOuter, TInner> _get;
readonly Func<TOuter, TInner, TOuter> _set;
public SelectorWrapper(ISettingsMigrationRule<TInner> rule, Func<TOuter, TInner> get, Func<TOuter, TInner, TOuter> set) {
_rule = rule;
_get = get;
_set = set;
}
public bool apply(TOuter settings, out TOuter new_settings) {
var ival = _get(settings);
if (_rule.apply(ival, out var new_ival)) {
new_settings = _set(settings, new_ival);
return true;
}
new_settings = default;
return false;
}
}
}
}
@@ -56,6 +56,11 @@ namespace mbts.spark {
GridSettings _pnl_grid;
[ProtoMember(10)]
bts.shell.orderbook.OrderBookGridSettings _order_book_grid;
// Placeholder for PNLSettings (will be merged to master soon)
// [ProtoMember(13)]
// PNLSettings _pnl;
[ProtoMember(14)]
GlobalAlertSettings _alerts;
public UserSettings() { }
@@ -73,6 +78,8 @@ namespace mbts.spark {
public GridSettings pnl_grid => _pnl_grid ?? (_pnl_grid = bts.shell.PAndLWindowFactory.PAndLStartupData.DefaultGridSettings);
public bts.shell.orderbook.OrderBookGridSettings order_book_grid => _order_book_grid ?? (_order_book_grid = bts.shell.orderbook.OrderBookStartupData.Default.grid_settings_wrapper());
public GlobalAlertSettings alerts => _alerts ?? (_alerts = new GlobalAlertSettings());
public UserSettings with(LadderSettings ladder = null,
LadderSettingsProfile[] ladder_profiles = null,
NotificationSettings notification = null,
@@ -81,7 +88,8 @@ namespace mbts.spark {
GridSettings provided_fills_gridsettings = null,
GridSettings provided_synthetic_management_gridsettings = null,
GridSettings provided_pnl_gridsettings = null,
bts.shell.orderbook.OrderBookGridSettings order_book_grid = null) {
bts.shell.orderbook.OrderBookGridSettings order_book_grid = null,
GlobalAlertSettings alerts = null) {
var us = new UserSettings(ladder_profiles ?? _ladder_profiles) {
_ladder = ladder ?? _ladder,
_notification = notification ?? _notification,
@@ -89,8 +97,9 @@ namespace mbts.spark {
_time_and_sales_grid = time_and_sales ?? _time_and_sales_grid,
_fills_grid = provided_fills_gridsettings ?? _fills_grid,
_synthetic_management_grid = provided_synthetic_management_gridsettings ?? _synthetic_management_grid,
_pnl_grid = provided_pnl_gridsettings ?? _pnl_grid,
_pnl_grid = provided_pnl_gridsettings ?? _pnl_grid,
_order_book_grid = order_book_grid ?? _order_book_grid,
_alerts = alerts ?? _alerts
};
us._last_ladder_profile_id = Math.Max(_last_ladder_profile_id, us.ladder_profiles.Select(p => p.id).MaxOrDefault());
return us;
@@ -98,8 +107,9 @@ namespace mbts.spark {
public static class FileExtensions {
public const string
GRID = "grid",
LADDER = "ladder",
GRID = "grid";
PNL = "pnl";
}
}
@@ -1,6 +1,8 @@
using bts;
using bts.settings;
using bts.utils;
using mbts.spark;
using mbts.ui.settings;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
@@ -22,16 +24,30 @@ namespace mbts.persist {
UserSettings _default_settings;
readonly BehaviorSubject<UserSettings> _default_settings_subject = new BehaviorSubject<UserSettings>(UserSettings.factory_defaults);
readonly object _default_settings_lock = new object();
readonly IEnumerable<IPAndLGridSettingsMigrationRule> _pandl_migration_rules;
readonly IEnumerable<IMarketViewGridSettingsMigrationRule> _mview_migration_rules;
[ImportingConstructor]
public UserSettingsHub(IUserPersist persist) {
public UserSettingsHub(
IUserPersist persist,
[ImportMany]IEnumerable<IPAndLGridSettingsMigrationRule> pandl_migration_rules,
[ImportMany]IEnumerable<IMarketViewGridSettingsMigrationRule> mview_migration_rules) {
//
_persist = persist;
_pandl_migration_rules = pandl_migration_rules ?? Enumerable.Empty<IPAndLGridSettingsMigrationRule>();
_mview_migration_rules = mview_migration_rules ?? Enumerable.Empty<IMarketViewGridSettingsMigrationRule>();
init();
async void init() {
UserSettings s;
try {
s = await _persist.get<UserSettings>(NAME_SPACE, DEFAULT_KEY);
if (s != null) {
var wrapped_rules = new[] {
_pandl_migration_rules.merge().wrap<GridSettings, UserSettings>(us => us.pnl_grid, (us, gs) => us.with(provided_pnl_gridsettings: gs)),
_mview_migration_rules.merge().wrap<GridSettings, UserSettings>(us => us.market_view_grid, (us, gs) => us.with(provided_market_view_gridsettings: gs))
};
SettingsMigrationHelper.apply_quiet(ref s, wrapped_rules);
// TODO: move ladder sanitization logic into ISettingsMigrationRule
s.ladder.sanitize();
// ensure valid named ladder set (w/ distinct, non-zero ids)
var ladder_profile_map = new Dictionary<uint, LadderSettingsProfile>();
@@ -78,6 +94,20 @@ namespace mbts.persist {
}
}
public IWindowSettingsManager<GridSettings> create_pandl_settings_manager(GridSettings instance_settings) =>
_create_settings_manager(
instance_settings,
_pandl_migration_rules,
us => us.pnl_grid,
(us, gs) => us.with(provided_pnl_gridsettings: gs));
public IWindowSettingsManager<GridSettings> create_mview_settings_manager(GridSettings instance_settings) =>
_create_settings_manager(
instance_settings,
_mview_migration_rules,
us => us.market_view_grid,
(us, gs) => us.with(provided_market_view_gridsettings: gs));
public async Task save_default(UserSettings value) {
// notify observers before persisting so the changes are reflected immediately in the ui
lock (_default_settings_lock) {
@@ -86,6 +116,12 @@ namespace mbts.persist {
await _persist_async();
}
IWindowSettingsManager<T> _create_settings_manager<T>(T instance_settings, IEnumerable<ISettingsMigrationRule<T>> migration_rules, Func<UserSettings, T> get, Func<UserSettings, T, UserSettings> set) where T : class {
if (instance_settings != null && migration_rules.merge().apply(instance_settings, out var new_settings))
instance_settings = new_settings;
return new WindowSettingsManager<T>(this, instance_settings, get, set);
}
async Task _persist_async() {
try {
await _persist.set(NAME_SPACE, DEFAULT_KEY, _default_settings);
@@ -59,6 +59,22 @@ namespace mbts.spark {
interface ITemplatedPropertyWrapperVM<T> : IPropertyWrapperVM<T>, ITemplateSelectorKey { }
static class ValueFactoryUtils {
public static void sync_with_models<TValueFactory, TModel>(this IList<TValueFactory> target, IEnumerable<TModel> models) where TValueFactory : IValueFactory<TModel>, new() {
target.sync_with_models(
models,
m => {
var vf = new TValueFactory();
vf.load_from(m);
return vf;
},
(vf, m) => {
vf.load_from(m);
return vf;
});
}
}
static class PropertyWrapperVMUtils {
public static T create_assigned<T>(this IPropertyWrapperVM<T> vm) where T : new() =>
vm.create_assigned(() => new T());
@@ -22,6 +22,7 @@ namespace bts.proto {
}
public Color color => _color.rgb_color();
public Color argb_color => Color.FromArgb((byte)(MathUtils.within_range(0, 1, _opacity) * 255.0), color.R, color.G, color.B);
public double opacity => _opacity;
public ColorOpacityProto with(uint? color = null, double? opacity = null) =>
@@ -16,14 +16,8 @@ namespace bts.proto {
public NullableColorProto(uint value) =>
_value = value;
public Color? value {
get {
if (_value.HasValue)
return _value.Value.rgb_color();
return null;
}
}
public Color? value => _value?.rgb_color();
public Color? argb => _value?.argb_color();
public bool Equals(NullableColorProto other) =>
EqualityComparer.nullable_equals(_value, other._value);
+3
View File
@@ -105,6 +105,9 @@
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Button.Border" Color="Transparent"/>
<SolidColorBrush x:Key="ComboBox.Static.Glyph" Color="#FF606060"/>
<!--Toggle Switch-->
<Color x:Key="ToggleSwitchOnColor">#FF4182F6</Color>
<!--ListBox-->
<Color x:Key="ListBoxItem.Selected.Active.Background.Color">#3D25A0DA</Color>
<Color x:Key="ListBoxItem.Selected.Active.Border.Color">#FF26A0DA</Color>
@@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using bts.utils;
using mbts.ui.extensions;
namespace mbts.ui.controls.adorners {
public class RowSelectionAdorner : Adorner, DIYGrid.ISelectionHighlightListener, IDisposable {
Brush _fill = DIYGrid.DefaultSelectionHighlight;
Pen _stroke = DIYGrid.DefaultSelectionStroke;
DIYGridRowsPresenter _diy_rp;
ScrollViewer _diy_scroll;
ScrollContentPresenter _diy_scp;
List<IDisposable> _disposables = new List<IDisposable>();
#region Fill
public static readonly DependencyProperty FillProperty =
WPFHelper.create_dp<Brush, RowSelectionAdorner>(
nameof(Fill),
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._fill = v,
DIYGrid.DefaultSelectionHighlight);
public SolidColorBrush Fill {
get => GetValue(FillProperty) as SolidColorBrush;
set => SetValue(FillProperty, value);
}
#endregion
#region Stroke
public static readonly DependencyProperty StrokeProperty =
WPFHelper.create_dp<Pen, RowSelectionAdorner>(
nameof(Stroke),
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._stroke = v,
DIYGrid.DefaultSelectionStroke);
public Pen Stroke {
get => GetValue(StrokeProperty) as Pen;
set => SetValue(StrokeProperty, value);
}
#endregion
internal RowSelectionAdorner(DIYGridRowsPresenter adorned_element, ScrollViewer scroll_viewer) : base(adorned_element) {
// we want our clicks to go through to the underlying grid
IsHitTestVisible = false;
_diy_rp = adorned_element;
_diy_scroll = scroll_viewer;
_diy_scp = _diy_rp.descendants_of_type<ScrollContentPresenter>().empty_if_null().FirstOrDefault();
// We actually don't need mouse up/down or key up/down when we can just respond to selection changes
_disposables.Add(((System.Windows.Controls.Primitives.Selector)_diy_rp).SubscribeSelectionChanged((s, e) => InvalidateVisual()));
// so that we update the highlight as the grid scrolls
_disposables.Add(_diy_scroll.SubscribeScrollChanged((s, e) => InvalidateVisual()));
}
protected override void OnRender(DrawingContext drawingContext) {
base.OnRender(drawingContext);
if (AdornedElement is ListBox lb && lb.SelectedItems != null && lb.SelectedItems.Count > 0) {
foreach(var lbi in lb.SelectedItems) {
if (_diy_rp.ItemContainerGenerator.ContainerFromItem(lbi) is FrameworkElement row && row.IsVisible) {
var top_left = lb.GetChildTopLeft(row);
var top_is_within_bounds = top_left.Y >= 0;
// Note that the top of the row can still technically be visible but we wont show
// the adorner rectangle b/c the bottom of the row will be obscured by the scrollbar
var bottom_is_within_bounds = lb.GetChildBounds(row).BottomRight.Y <= _diy_scroll.ActualHeight;
if (top_is_within_bounds && bottom_is_within_bounds) {
// we use the scroll content presenter in the X point calculation instead of the row
// because of the OB which has a right side panel that takes up some space over
// row width
drawingContext.DrawRectangle(_fill,
_stroke,
new Rect(top_left, new Point(top_left.X + _diy_scp.ActualWidth, top_left.Y + row.ActualHeight)));
}
}
}
}
}
public void on_selection_fill(SolidColorBrush selection_fill) {
Fill = selection_fill;
Stroke = new Pen(selection_fill.Color.to_solid_brush(), 1);
InvalidateVisual();
}
public void Dispose() {
_disposables.DisposeAll();
}
}
}
@@ -1,7 +1,9 @@
using bts.proto;
using bts.utils;
using mbts.spark;
using mbts.ui.controls.color_picker;
using mbts.ui.shell;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
@@ -38,16 +40,42 @@ namespace mbts.ui.controls {
class ColorPickerPopupVM : ViewModelBase, IValueFactory<SolidColorBrush> {
readonly Property<Color> _color;
readonly Property<double> _opacity;
ObservableCollection<ColorPaletteVM> _palettes;
bool _bind_opacity_to_alpha;
IDisposable _opacity_alpha_binding;
public ColorPickerPopupVM() {
_color = backing(nameof(color), Colors.Black);
_opacity = backing(nameof(opacity), 1.0);
palettes = new ObservableCollection<ColorPaletteVM>(ColorPalette.all_presets().Select(p => new ColorPaletteVM(p)));
}
public Color color { get => _color.Value; set => _color.set(value); }
public double opacity { get => _opacity.Value; set => _opacity.set(value); }
public ObservableCollection<ColorPaletteVM> palettes { get; }
public ObservableCollection<ColorPaletteVM> palettes => _palettes ?? (_palettes = new ObservableCollection<ColorPaletteVM>(ColorPalette.all_presets().Select(p => new ColorPaletteVM(p))));
public bool bind_opacity_to_alpha {
get => _bind_opacity_to_alpha;
set {
if (_bind_opacity_to_alpha != value) {
Disposable.dispose(ref _opacity_alpha_binding);
_bind_opacity_to_alpha = value;
if (value) {
_opacity.Changed += opacity_changed;
_color.Changed += color_changed;
color_changed(default, color);
void opacity_changed(double oldval, double newval) {
_color.Changed -= color_changed;
_color.set(color.with((byte)(MathUtils.within_range(0, 1, newval) * 255.0)));
_color.Changed += color_changed;
}
void color_changed(Color oldval, Color newval) {
_opacity.Changed -= opacity_changed;
_opacity.set(newval.A / 255.0);
_opacity.Changed += opacity_changed;
}
}
}
}
}
public SolidColorBrush create() => new SolidColorBrush(color) { Opacity = opacity }.frozen();
public void load_from(SolidColorBrush brush) {
@@ -304,11 +304,6 @@ namespace mbts.ui.controls {
}
drawingContext.Pop();
}
if (bg != null) {
var bgo = GetBackgroundOverlay(this);
if (bgo != null)
drawingContext.DrawRectangle(bgo, null, rect);
}
}
bool _point_horizontally_over_text(double x) {
@@ -1,9 +1,8 @@
using mbts.spark;
using bts;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui.settings;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Reflection;
@@ -11,7 +10,7 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace mbts.ui.controls {
namespace mbts.ui.controls {
// Column types are nested inside the DIYGrid
partial class DIYGrid {
public static class DefaultColumnWidths {
@@ -113,6 +112,7 @@ namespace mbts.ui.controls {
StartPnL = "Start PnL",
DayLong = "Day Long",
DayShort = "Day Short",
RawDayPos = "Raw Day Pos",
DayPnL = "Day PnL",
TotalNetPos = "Total Net Pos",
TotalPnL = "Total PnL",
@@ -165,7 +165,7 @@ namespace mbts.ui.controls {
}
public interface ISelectionHighlightListener {
void on_selection_fill(Brush selection_fill);
void on_selection_fill(SolidColorBrush selection_fill);
}
// To include extra data with your column type you are encouraged to derive from DIYGridColumn
@@ -173,16 +173,19 @@ namespace mbts.ui.controls {
//readonly ExternalProperty<FontWeight?> _font_weigh
FontWeight _fallback_font_weight;
Color _fallback_font_color;
Color _fallback_background_color;
double _fallback_font_size;
FontFamily _fallback_font_family;
FontWeight? _font_weight_override;
Color? _font_color_override;
Color? _background_color_override;
object _header_copy;
protected ColumnBase() {
var gs = GridSettings.Default;
_fallback_font_weight = gs.font_weight;
_fallback_font_color = gs.font_color.rgb_color();
_fallback_background_color = gs.background_color.argb_color();
_fallback_font_size = gs.font_size;
_fallback_font_family = new FontFamily(gs.font_family);
original_width = double.NaN;
@@ -215,6 +218,9 @@ namespace mbts.ui.controls {
public FontWeight font_weight => font_weight_override ?? _fallback_font_weight;
public Color font_color => font_color_override ?? _fallback_font_color;
public Brush font_brush => new SolidColorBrush(font_color);
public Color? background_color_override { get => _background_color_override; set => _set_font_param(ref _background_color_override, value); }
public Color? background_color => background_color_override ?? _fallback_background_color;
public Brush background_brush => background_color?.to_brush();
// TODO: find a better way to store the header-property in a thread-safe manner - a value for GridSettings.display_name, more specifically.
// Since save_state is called from a different thread in IShared/SlowWindows, it is not safe to access the DependencyProperty store from that method.
// By updating a local _header_copy field in the Header setter, the DependencyProperty lookup may be bypassed when appropriate (see create method below).
@@ -224,7 +230,7 @@ namespace mbts.ui.controls {
}
public GridColumnSettings create() =>
new GridColumnSettings(id, _header_copy?.ToString(), is_visible, is_collapsed, font_weight_override, font_color_override, ActualWidth, horizontal_alignment, horizontal_offset);
new GridColumnSettings(id, _header_copy?.ToString(), is_visible, is_collapsed, font_weight_override, font_color_override, ActualWidth, horizontal_alignment, horizontal_offset, background_color_override);
void _set_font_param<T>(ref T field, T value) {
field = value;
@@ -235,7 +241,7 @@ namespace mbts.ui.controls {
if (settings == null)
return;
using (ReflectionHelper.detect_property_changes(this, _raise_prop_changed, nameof(is_visible), nameof(is_collapsed), nameof(font_weight_override), nameof(font_weight),
nameof(font_color_override), nameof(font_color), nameof(font_brush), nameof(font_family), nameof(font_size), nameof(horizontal_alignment), nameof(horizontal_offset))) {
nameof(font_color_override), nameof(font_color), nameof(font_brush), nameof(font_family), nameof(font_size), nameof(horizontal_alignment), nameof(horizontal_offset), nameof(background_color_override), nameof(background_color), nameof(background_brush))) {
//
id = settings.id;
if (!string.IsNullOrEmpty(settings.display_name))
@@ -246,6 +252,7 @@ namespace mbts.ui.controls {
font_color_override = settings.font_color_override;
horizontal_alignment = settings.horizontal_alignment;
horizontal_offset = settings.horizontal_offset;
background_color_override = settings.background_color_override;
}
if (original_width != settings.width) {
original_width = settings.width;
@@ -255,11 +262,12 @@ namespace mbts.ui.controls {
}
// 'fallback' font-params (aka 'grid-level' font-params)
public void set_fallback_font_params(FontFamily font_family, double font_size, Color font_color, FontWeight font_weight) {
using (ReflectionHelper.detect_property_changes(this, _raise_prop_changed, nameof(font_family), nameof(font_size), nameof(font_color), nameof(font_weight))) {
public void set_fallback_font_params(FontFamily font_family, double font_size, Color font_color, FontWeight font_weight, Color background_color) {
using (ReflectionHelper.detect_property_changes(this, _raise_prop_changed, nameof(font_family), nameof(font_size), nameof(font_color), nameof(font_weight), nameof(background_color), nameof(background_brush))) {
_fallback_font_family = font_family;
_fallback_font_size = font_size;
_fallback_font_color = font_color;
_fallback_background_color = background_color;
_fallback_font_weight = font_weight;
}
font_params_changed?.Invoke();
@@ -273,9 +281,25 @@ namespace mbts.ui.controls {
}
internal abstract class ColumnBase<TColKey> : ColumnBase, IKeyedColumn<TColKey>, IGridColumn {
TColKey _key;
bool _key_initialized;
// need a default constructor to support declaration in xaml
protected ColumnBase() { }
protected ColumnBase(TColKey key) => this.key = key;
public TColKey key { get; }
public TColKey key {
get => _key;
set {
if (Equals(_key, value))
return;
Log.assert(!_key_initialized, "Column key already initialized.");
_key_initialized = true;
_key = value;
on_key_initialized(value);
}
}
protected virtual void on_key_initialized(TColKey key) { }
}
}
}
}
@@ -7,7 +7,7 @@ using System.Windows.Media;
namespace mbts.ui.controls {
partial class DIYGrid {
internal class HyperlinkElementColumn<TRowVM, TKey> : FrameworkElementColumnBase<TKey>, IFrameworkElementColumn, IFilterableColumn, IKeyedColumn<TKey>, ISelectionHighlightListener {
internal class HyperlinkElementColumn<TRowVM, TKey> : FrameworkElementColumnBase<TKey>, IFrameworkElementColumn, IFilterableColumn, IKeyedColumn<TKey> {
static readonly Thickness _default_padding = new Thickness(6, 0, 6, 0);
readonly BindingBase _binding, _command_binding;
@@ -22,27 +22,17 @@ namespace mbts.ui.controls {
public IDataGridColumnFilter filter { get; }
public string filter_template { get; }
public Thickness padding { get; set; } = _default_padding;
public Brush background { get; set; }
public Brush background_overlay { get; set; } = DefaultSelectionHighlight;
public void on_selection_fill(Brush selection_fill) =>
background_overlay = selection_fill;
public override FrameworkElement view_factory() {
var hl = new HyperlinkCell { Foreground = font_brush, Background = background, Padding = padding };
var hl = new HyperlinkCell { Padding = padding };
hl.SetBinding(HyperlinkCell.ForegroundProperty, new Binding(nameof(font_brush)) { Source = this });
hl.SetBinding(HyperlinkCell.TextProperty, _binding);
hl.SetBinding(HyperlinkCell.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
hl.SetBinding(HyperlinkCell.FontSizeProperty, new Binding(nameof(font_size)) { Source = this });
hl.SetBinding(HyperlinkCell.HorizontalAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
hl.SetBinding(HyperlinkCell.CommandProperty, _command_binding);
hl.SetBinding(HyperlinkCell.WidthProperty, new Binding(nameof(ActualWidth)) { Source = this });
if (background_overlay != null) {
hl.SetBinding(BackgroundOverlayProperty, new Binding(nameof(ListBoxItem.IsSelected)) {
Converter = new AnonymousValueConverter<bool, Brush>(val => val ? background_overlay : null),
Mode = BindingMode.OneWay,
RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(ListBoxItem), 1)
});
}
hl.SetBinding(HyperlinkCell.BackgroundProperty, new Binding(nameof(background_brush)) { Source = this });
return hl;
}
}
@@ -30,7 +30,7 @@ namespace mbts.ui.controls {
Func<TRowVM, Brush> get_brush = null)
: base() {
_get_text = get_text;
Func<TRowVM, Brush> get_default_background_brush = (TRowVM _) => this.background;
Func<TRowVM, Brush> get_default_background_brush = (TRowVM _) => this.background_brush;
_get_background_brush = get_brush ?? get_default_background_brush;
this.filter = filter;
this.filter_template = filter_template;
@@ -42,7 +42,6 @@ namespace mbts.ui.controls {
public IDataGridColumnFilter filter { get; }
public string filter_template { get; }
public Brush background { get; set; }
public VerticalAlignment vertical_alignment { get; set; } = VerticalAlignment.Center;
public Thickness padding { get; set; } = DefaultCellPadding;
public GlyphContext glyph_context { get; private set; }
@@ -1,9 +1,9 @@
using bts;
using bts.utils;
using mbts.spark.filters;
using mbts.ui.controls.adorners;
using mbts.ui.extensions;
using mbts.ui.utility;
using bts.utils;
using System;
using System.Collections;
using System.Collections.Generic;
@@ -12,14 +12,13 @@ using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using mbts.utils;
using System.Reactive.Linq;
namespace mbts.ui.controls {
interface IHandleGridSorting {
@@ -126,6 +125,7 @@ namespace mbts.ui.controls {
double _row_height = DIYGrid.DefaultRowHeight;
DIYGrid _grid;
DragRectAdorner _drag_rect_adorner;
RowSelectionAdorner _rsa;
ScrollViewer _scroll_viewer;
Point? _drag_start;
double _drag_start_vertical_drift;
@@ -148,12 +148,18 @@ namespace mbts.ui.controls {
_drag_rect_adorner.SetResourceReference(DragRectAdorner.StrokeProperty, DIYGrid.SelectionStrokeKey);
_drag_rect_adorner.attach();
}
_rsa = new RowSelectionAdorner(this, _scroll_viewer);
_rsa.attach();
};
Unloaded += (s, e) => {
if (_drag_rect_adorner != null) {
_drag_rect_adorner.detach();
Disposable.dispose(ref _drag_rect_adorner);
}
if (_rsa != null) {
_rsa.detach();
Disposable.dispose(ref _rsa);
}
};
void on_row_height_changed(double v) => _row_height = v;
}
@@ -261,6 +267,12 @@ namespace mbts.ui.controls {
o.Resources[SelectionStrokeKey] = new Pen(o.SelectionHighlight.Color.to_solid_brush().frozen(), 1).frozen();
foreach (var col in o.Columns.empty_if_null().OfType<ISelectionHighlightListener>())
col.on_selection_fill(v);
var ad_layer = System.Windows.Documents.AdornerLayer.GetAdornerLayer(o);
if (ad_layer != null) {
foreach (var ad in ad_layer.descendants_of_type<ISelectionHighlightListener>()) {
ad.on_selection_fill(v);
}
}
}, (o, v) => v as SolidColorBrush ?? DefaultSelectionHighlight,
DefaultSelectionHighlight);
@@ -562,6 +574,11 @@ namespace mbts.ui.controls {
public static readonly DependencyProperty HeaderTotalSizeHackProperty = DependencyProperty.Register("HeaderTotalSizeHack", typeof(double), typeof(DIYGrid), new FrameworkPropertyMetadata(0.0, dp_invalidate_column_layout));
public double HeaderTotalSizeHack {
get => (double)GetValue(HeaderTotalSizeHackProperty);
set => SetValue(HeaderTotalSizeHackProperty, value);
}
private static void dp_invalidate_column_layout(DependencyObject d, DependencyPropertyChangedEventArgs unused) {
var grid = d as DIYGrid;
if (grid != null) {
@@ -594,6 +611,61 @@ namespace mbts.ui.controls {
// Public Properties
#region Public Properties
#region ColumnKey
public static readonly DependencyProperty ColumnKeyProperty =
DependencyProperty.RegisterAttached("ColumnKey",
typeof(object),
typeof(DIYGrid),
new PropertyMetadata((d, e) => {
if (!(d is UIElement child))
return;
if (VisualTreeHelper.GetParent(child) is Grid panel) {
if (GetSyncWithColumnGeometrySource(d) is DIYGrid diy_grid || panel.try_find_ancestor_of_type(out diy_grid))
ColumnGeometrySyncHelper.reset_panel_child(child, diy_grid, e.NewValue);
}
}));
public static object GetColumnKey(DependencyObject d) => d.GetValue(ColumnKeyProperty);
public static void SetColumnKey(DependencyObject d, object value) => d.SetValue(ColumnKeyProperty, value);
#endregion
#region SyncWithColumnGeometrySource
public static readonly DependencyProperty SyncWithColumnGeometrySourceProperty =
DependencyProperty.RegisterAttached(
"SyncWithColumnGeometrySource",
typeof(DIYGrid),
typeof(DIYGrid),
new PropertyMetadata(ColumnGeometrySyncHelper.reset_sync));
public static DIYGrid GetSyncWithColumnGeometrySource(DependencyObject d) => d.GetValue(SyncWithColumnGeometrySourceProperty) as DIYGrid;
public static void SetSyncWithColumnGeometrySource(DependencyObject d, DIYGrid value) => d.SetValue(SyncWithColumnGeometrySourceProperty, value);
#endregion
#region SyncWithColumnGeometry
public static readonly DependencyProperty SyncWithColumnGeometryProperty =
DependencyProperty.RegisterAttached(
"SyncWithColumnGeometry",
typeof(bool),
typeof(DIYGrid),
new PropertyMetadata(false, ColumnGeometrySyncHelper.reset_sync));
public static bool GetSyncWithColumnGeometry(DependencyObject d) => (bool)d.GetValue(SyncWithColumnGeometryProperty);
public static void SetSyncWithColumnGeometry(DependencyObject d, bool value) => d.SetValue(SyncWithColumnGeometryProperty, value);
static readonly DependencyPropertyKey SyncWithColumnGeometryHookPropertyKey =
DependencyProperty.RegisterAttachedReadOnly(
"SyncWithColumnGeometryHook",
typeof(IDisposable),
typeof(DIYGrid),
new PropertyMetadata(FrameworkElementLifeDependencyHelper.DisposeOld));
internal static IDisposable GetSyncWithColumnGeometryHook(DependencyObject d) =>
d.GetValue(SyncWithColumnGeometryHookPropertyKey.DependencyProperty) as IDisposable;
static void SetSyncWithColumnGeometryHook(DependencyObject d, IDisposable value) =>
d.SetValue(SyncWithColumnGeometryHookPropertyKey, value);
#endregion
public static readonly DependencyProperty BackgroundOverlayProperty =
DependencyProperty.RegisterAttached(
"BackgroundOverlay",
@@ -783,7 +855,7 @@ namespace mbts.ui.controls {
/// Compute the geometry of all visible (or partially visible columns) columns
/// This effectively replaces the entire Measure/Arrange pass of the grid rows (and enables column virtualization)
/// Mouse events will be hit tested against this as well
internal IEnumerable<ColumnGeometry> GetColumnGeometry() {
internal IEnumerable<ColumnGeometry> GetColumnGeometry(bool include_obscured_cols = false) {
if (_header_scroll_viewer != null && ActualWidth > 0) {
double frozen_offset = 0;
int vis_frozen_col_count = _get_visible_frozen_column_count();
@@ -805,7 +877,7 @@ namespace mbts.ui.controls {
double left = Math.Max(offset, frozen_offset);
double right = offset + col.ActualWidth;
// If right < frozen_offset column is completely obscured
if (right > frozen_offset && left <= ActualWidth) {
if (include_obscured_cols || (right > frozen_offset && left <= ActualWidth)) {
// fully visible
yield return new ColumnGeometry() {
column = col,
@@ -883,6 +955,98 @@ namespace mbts.ui.controls {
}
#endregion
static class ColumnGeometrySyncHelper {
public static void reset_panel_child(UIElement child, DIYGrid diy_grid) =>
reset_panel_child(child, diy_grid, GetColumnKey(child));
public static void reset_panel_child(UIElement child, DIYGrid diy_grid, object col_key) {
if (col_key == null)
return;
var key_str = col_key.ToString();
if (diy_grid._visible_columns.TryGetFirst(c => string.Equals(c.id, key_str, StringComparison.CurrentCultureIgnoreCase), out var target_col, out int target_pos)) {
// set clip to null in case this child was hidden before (see 'else' block).
child.Clip = null;
Grid.SetColumn(child, target_pos);
} else {
// hide element by setting clip to 0x0 rect, rather than visibility to collapsed,
// to avoid incidentally breaking visibility bindings, triggers, animations, etc.
child.Clip = new RectangleGeometry(new Rect(0, 0, 0, 0));
}
}
public static void reset_sync(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (!GetSyncWithColumnGeometry(d))
SetSyncWithColumnGeometryHook(d, null);
if (!(d is Grid g))
return;
var hook = new DisposableContainer();
if (g.IsLoaded)
hook.replace(impl());
SetSyncWithColumnGeometryHook(d, Disposable.Create(g.SubscribeLoaded(on_loaded), hook));
//
void on_loaded(object sender, RoutedEventArgs loaded_e) {
if (GetSyncWithColumnGeometry(d))
hook.replace(impl());
}
IDisposable impl() {
// Avoid resetting children on every collection change, as that could be very noisy and hurt perf.
// In the visible columns CollectionChanged handler, call reset_children inside a Dispatcher.InvokeAsync request
// to effectively wait for potential batch-changes to finish.
bool resetting_children = false;
if (!(GetSyncWithColumnGeometrySource(d) is DIYGrid diy_grid) && !g.try_find_ancestor_of_type(out diy_grid))
return null;
g.SetBinding(Grid.WidthProperty, new Binding(nameof(HeaderTotalSizeHack)) { Source = diy_grid });
reset_children();
on_layout_changed(diy_grid.GetColumnGeometry(true).ToList());
diy_grid.ColumnLayoutChanged += on_layout_changed;
diy_grid._visible_columns.CollectionChanged += on_vis_columns_changed;
return Disposable.Create(() => {
diy_grid.ColumnLayoutChanged -= on_layout_changed;
diy_grid._visible_columns.CollectionChanged -= on_vis_columns_changed;
});
//
void on_vis_columns_changed(object sender, NotifyCollectionChangedEventArgs vce) {
if (!resetting_children) {
// avoid resetting children on every collection change, as that could be very noisy and hurt perf.
resetting_children = true;
diy_grid.Dispatcher.InvokeAsync(() => {
on_layout_changed(diy_grid.GetColumnGeometry().ToList());
reset_children();
resetting_children = false;
});
}
}
void on_layout_changed(List<ColumnGeometry> layout) {
// TODO: figure out how to make the ColumnLayoutChanged event lazy, or somehow avoid making 2 lists of columns
// (the layout param does not include obscured cols, so we have to create a new list of ColumnGeoemtry to satisfy our needs).
layout = (from col in diy_grid._visible_columns
join pos in diy_grid.GetColumnGeometry(true)
on col.id equals pos.column.id
select pos).ToList();
int n_removed = g.ColumnDefinitions.Count - layout.Count;
if (n_removed > 0)
g.ColumnDefinitions.RemoveRange(g.ColumnDefinitions.Count - n_removed, n_removed);
int i = 0;
for (; i < g.ColumnDefinitions.Count; i++) {
var col_pos = layout[i];
var col = g.ColumnDefinitions[i];
if (col.Width.GridUnitType == GridUnitType.Pixel && col.Width.Value == col_pos.visible_width)
continue;
col.Width = new GridLength(col_pos.visible_width, GridUnitType.Pixel);
}
for (; i < layout.Count; i++) {
g.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(layout[i].visible_width, GridUnitType.Pixel) });
}
}
void reset_children() {
foreach (UIElement child in g.Children)
reset_panel_child(child, diy_grid);
}
}
}
}
class DataContextHooks : IDisposable {
List<IDisposable> _hooks = new List<IDisposable>(4);
@@ -29,22 +29,6 @@
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
/>
</Border>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Selector.IsSelectionActive" Value="False"/>
<Condition Property="IsSelected" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static bts:DIYGrid.InactiveSelectionHighlightKey}}"/>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Selector.IsSelectionActive" Value="True"/>
<Condition Property="IsSelected" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="{Binding SelectionHighlight, RelativeSource={RelativeSource AncestorType=bts:DIYGrid}}"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
@@ -61,6 +45,9 @@
<Setter Property="IsTextSearchEnabled" Value="False"/>
</Style>
<Style TargetType="bts:DIYGrid">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Focusable" Value="False"/>
<Setter Property="SelectionMode" Value="Extended"/>
<Setter Property="ColumnHeaderStyle">
@@ -304,66 +291,72 @@
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type bts:DIYGrid}">
<Grid>
<Grid.ColumnDefinitions>
<!-- Frozen -->
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- fill the space above the frozen columns -->
<Rectangle Fill="{mbts:BTSColor Name=header}" Height="{Binding Path=ActualHeight, ElementName=_super_headers}" HorizontalAlignment="Stretch" Margin="0,0,-1,-1"/>
<!-- colums set in code behind -->
<GridViewHeaderRowPresenter x:Name="_header_frozen" Grid.Row="1" Grid.Column="0" AllowsColumnReorder="False" VerticalAlignment="Bottom" ColumnHeaderContainerStyle="{TemplateBinding ColumnHeaderStyle}" UseLayoutRounding="True"/>
<ScrollViewer
x:Name="_header_scroll_viewer"
Grid.Column="1"
Grid.ColumnSpan="2"
Grid.Row="0"
Grid.RowSpan="2"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden"
>
<!-- setting the bg color for this stackpanel is a work-around for the vertical white lines that appear on high-dpi displays between some columns -->
<!-- in reality, there is some high dpi rounding thing going on, but that's a battle for another day -->
<StackPanel Background="{mbts:BTSColor Name=header}">
<!-- We don't need super headers over the fixed columns -->
<StackPanel Orientation="Horizontal" x:Name="_super_headers"/>
<GridViewHeaderRowPresenter x:Name="_header_scroll" AllowsColumnReorder="False" ColumnHeaderContainerStyle="{TemplateBinding ColumnHeaderStyle}" UseLayoutRounding="True"/>
</StackPanel>
</ScrollViewer>
<bts:DIYGridRowsPresenter
x:Name="_row_presenter"
Grid.Row="2"
Grid.ColumnSpan="2"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
VirtualizingPanel.VirtualizationMode="Recycling"
AlternationCount="2"
Background="{TemplateBinding Background}"
BorderThickness="0"
ItemsSource="{TemplateBinding Rows}"
ItemTemplate="{TemplateBinding RowTemplate}"
SelectedItem="{Binding CurrentRow, RelativeSource={RelativeSource TemplatedParent}}"
SelectionMode="{TemplateBinding SelectionMode}"
>
<bts:DIYGridRowsPresenter.Resources>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource DIYGridRowStyle}"/>
</bts:DIYGridRowsPresenter.Resources>
<bts:DIYGridRowsPresenter.Template>
<ControlTemplate TargetType="{x:Type bts:DIYGridRowsPresenter}">
<ScrollViewer Focusable="False">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</ScrollViewer>
</ControlTemplate>
</bts:DIYGridRowsPresenter.Template>
</bts:DIYGridRowsPresenter>
<ScrollBar Grid.Row="3" Grid.Column="1" Orientation="Horizontal" LargeChange="10" x:Name="_scroll_horiz" Visibility="{TemplateBinding RowsHorizontalScrollBarVisibility}"/>
</Grid>
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
>
<Grid>
<Grid.ColumnDefinitions>
<!-- Frozen -->
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- fill the space above the frozen columns -->
<Rectangle Fill="{mbts:BTSColor Name=header}" Height="{Binding Path=ActualHeight, ElementName=_super_headers}" HorizontalAlignment="Stretch" Margin="0,0,-1,-1"/>
<!-- colums set in code behind -->
<GridViewHeaderRowPresenter x:Name="_header_frozen" Grid.Row="1" Grid.Column="0" AllowsColumnReorder="False" VerticalAlignment="Bottom" ColumnHeaderContainerStyle="{TemplateBinding ColumnHeaderStyle}" UseLayoutRounding="True"/>
<ScrollViewer
x:Name="_header_scroll_viewer"
Grid.Column="1"
Grid.ColumnSpan="2"
Grid.Row="0"
Grid.RowSpan="2"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden"
>
<!-- setting the bg color for this stackpanel is a work-around for the vertical white lines that appear on high-dpi displays between some columns -->
<!-- in reality, there is some high dpi rounding thing going on, but that's a battle for another day -->
<StackPanel Background="{mbts:BTSColor Name=header}">
<!-- We don't need super headers over the fixed columns -->
<StackPanel Orientation="Horizontal" x:Name="_super_headers"/>
<GridViewHeaderRowPresenter x:Name="_header_scroll" AllowsColumnReorder="False" ColumnHeaderContainerStyle="{TemplateBinding ColumnHeaderStyle}" UseLayoutRounding="True"/>
</StackPanel>
</ScrollViewer>
<bts:DIYGridRowsPresenter
x:Name="_row_presenter"
Grid.Row="2"
Grid.ColumnSpan="2"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
VirtualizingPanel.VirtualizationMode="Recycling"
AlternationCount="2"
Background="{TemplateBinding Background}"
BorderThickness="0"
ItemsSource="{TemplateBinding Rows}"
ItemTemplate="{TemplateBinding RowTemplate}"
SelectedItem="{Binding CurrentRow, RelativeSource={RelativeSource TemplatedParent}}"
SelectionMode="{TemplateBinding SelectionMode}"
>
<bts:DIYGridRowsPresenter.Resources>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource DIYGridRowStyle}"/>
</bts:DIYGridRowsPresenter.Resources>
<bts:DIYGridRowsPresenter.Template>
<ControlTemplate TargetType="{x:Type bts:DIYGridRowsPresenter}">
<ScrollViewer Focusable="False">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</ScrollViewer>
</ControlTemplate>
</bts:DIYGridRowsPresenter.Template>
</bts:DIYGridRowsPresenter>
<ScrollBar Grid.Row="3" Grid.Column="1" Orientation="Horizontal" LargeChange="10" x:Name="_scroll_horiz" Visibility="{TemplateBinding RowsHorizontalScrollBarVisibility}"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
@@ -60,6 +60,9 @@ namespace mbts.ui.settings {
[DefaultValue(_default_horizontal_offset)]
readonly double _horizontal_offset;
[ProtoMember(10)]
readonly NullableColorProto _background_color_override;
public GridColumnSettings() {
_is_visible = _default_is_visible;
_is_collapsed = _default_is_collapsed;
@@ -69,7 +72,7 @@ namespace mbts.ui.settings {
}
public GridColumnSettings(string id, string display_name = null, bool is_visible = _default_is_visible, bool is_collapsed = _default_is_collapsed, FontWeight? font_weight_override = null, Color? font_color_override = null,
double width = _default_width, HorizontalAlignment horizontal_alignment = _default_horizontal_alignment, double horizontal_offset = _default_horizontal_offset) {
double width = _default_width, HorizontalAlignment horizontal_alignment = _default_horizontal_alignment, double horizontal_offset = _default_horizontal_offset, Color? background_color_override = null) {
_id = id;
_display_name = display_name;
_is_visible = is_visible;
@@ -79,6 +82,7 @@ namespace mbts.ui.settings {
_width = width;
_horizontal_alignment = horizontal_alignment;
_horizontal_offset = horizontal_offset;
_background_color_override = new NullableColorProto(background_color_override);
}
public string id => _id;
@@ -90,10 +94,11 @@ namespace mbts.ui.settings {
public HorizontalAlignment horizontal_alignment => _horizontal_alignment;
public double horizontal_offset => _horizontal_offset;
public string display_name => _display_name;
public Color? background_color_override => _background_color_override.argb;
public GridColumnSettings with(
string display_name = null, bool? is_visible = null, bool? is_collapsed = null, NullableFontWeightProto? font_weight_override = null, NullableColorProto? font_color_override = null,
double? width = null, HorizontalAlignment? horizontal_alignment = null, double? horizontal_offset = null) =>
double? width = null, HorizontalAlignment? horizontal_alignment = null, double? horizontal_offset = null, NullableColorProto? background_color_override = null) =>
//
new GridColumnSettings(_id,
display_name ?? this.display_name,
@@ -103,7 +108,8 @@ namespace mbts.ui.settings {
(font_color_override ?? _font_color_override).value,
width ?? this.width,
horizontal_alignment ?? this.horizontal_alignment,
horizontal_offset ?? this.horizontal_offset);
horizontal_offset ?? this.horizontal_offset,
(background_color_override ?? _background_color_override).argb);
public bool Equals(GridColumnSettings other) =>
other != null &&
@@ -116,7 +122,8 @@ namespace mbts.ui.settings {
width == other.width &&
horizontal_alignment == other.horizontal_alignment &&
horizontal_offset == other.horizontal_offset &&
display_name == other.display_name;
display_name == other.display_name &&
background_color_override.Equals(other.background_color_override);
public override bool Equals(object obj) =>
Equals(obj as GridColumnSettings);
@@ -132,7 +139,8 @@ namespace mbts.ui.settings {
width.GetHashCode(),
horizontal_alignment.GetHashCode(),
horizontal_offset.GetHashCode(),
display_name.GetHashCode()
display_name.GetHashCode(),
background_color_override.GetHashCode()
);
public override string ToString() {
@@ -22,11 +22,13 @@ namespace mbts.spark {
/// </para>
/// For flag behavior implementation, see <see cref="GridSettingsVM{TRowData, TPredicateBuilder}.ColumnVM.apply_flags(GridColumnSettingsFeatureFlags)"/>
/// </summary>
[Flags]
enum GridColumnSettingsFeatureFlags {
None = 0,
ImmutableFontColor = 1,
ImmutableFontWeight = 1 << 1,
ImmutableHeader = 1 << 2
ImmutableHeader = 1 << 2,
ImmutableBackgroundColor = 1 << 3
}
partial class GridSettingsVM<TRowData, TPredicateData, TSettingsData> {
@@ -50,7 +52,9 @@ namespace mbts.spark {
_can_override_font_color = true,
_is_font_color_overridden,
_can_override_font_weight = true,
_is_font_weight_overridden;
_is_font_weight_overridden,
_can_override_background_color = true,
_is_background_color_overridden;
FontWeight _font_weight_override;
GridColumnSettings _settings;
@@ -78,6 +82,10 @@ namespace mbts.spark {
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); }
// background color override
public bool can_override_background_color { get => _can_override_background_color; private set => Set(ref _can_override_background_color, value); }
public bool is_background_color_overridden { get => _is_background_color_overridden; set => Set(ref _is_background_color_overridden, value); }
public ColorPickerPopupVM background_color_override { get; } = new ColorPickerPopupVM() { bind_opacity_to_alpha = true };
// animations
public ObservableEditableCollection<PredicateCellAnimationVM> predicate_animations { get; }
@@ -89,7 +97,9 @@ namespace mbts.spark {
is_font_weight_overridden ? new NullableFontWeightProto(font_weight_override) : default,
is_font_color_overridden ? new NullableColorProto(font_color_override.color) : default,
width,
horizontal_alignment);
horizontal_alignment,
background_color_override: is_background_color_overridden ?
new NullableColorProto(background_color_override.color) : default);
var animations_lookup = predicate_animations.ToFlatLookup(pa => pa.predicate.create_assigned(owner._predicate_data_factory), pa => pa.animations.Select(a => a.create()));
return (settings, animations_lookup);
}
@@ -111,6 +121,11 @@ namespace mbts.spark {
if (is_font_weight_overridden) {
_font_weight_override = _settings.font_weight_override.Value;
}
// background color
is_background_color_overridden = _settings.background_color_override.HasValue;
if (is_background_color_overridden) {
background_color_override.color = _settings.background_color_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));
@@ -129,6 +144,9 @@ namespace mbts.spark {
// font weight
can_override_font_weight = (flags & GridColumnSettingsFeatureFlags.ImmutableFontWeight) == 0;
is_font_weight_overridden &= can_override_font_weight;
// background color
can_override_background_color = (flags & GridColumnSettingsFeatureFlags.ImmutableBackgroundColor) == 0;
is_background_color_overridden &= can_override_background_color;
}
public override string ToString() => $"{display_name} ({id})";
@@ -1,4 +1,5 @@
using bts.proto;
using bts.collections;
using bts.proto;
using bts.utils;
using mbts.ui.controls;
using mbts.ui.converters;
@@ -6,6 +7,7 @@ using ProtoBuf;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
@@ -17,9 +19,14 @@ namespace mbts.ui.settings {
const double _default_font_size = 11;
const uint _default_font_color = 0;
const ushort _default_font_weight = 400;
const uint _default_background_color = 0x00FFFFFF; // Colors.Transparent
const bool _default_enable_background_color = false;
public static GridSettings Default =>
new GridSettings(selection_highlight: new ColorOpacityProto(FallbackValues.selection_highlight.Color, FallbackValues.selection_highlight.Opacity));
new GridSettings(
selection_highlight: new ColorOpacityProto(FallbackValues.selection_highlight.Color, FallbackValues.selection_highlight.Opacity),
background_color: _default_background_color, //new ColorOpacityProto(FallbackValues.background_color.Color, FallbackValues.background_color.Opacity));
enable_background_color: _default_enable_background_color);
// corrects any inappropriate values that would render the grid unusable (usually nulls)
// NOTE: if you add a property to GridSettings that could have such a value, please add a conditional-correction for it below)
@@ -39,6 +46,7 @@ namespace mbts.ui.settings {
public static Color font_color => _default_font_color.rgb_color();
public static FontWeight font_weight => FontWeight.FromOpenTypeWeight(_default_font_weight);
public static SolidColorBrush selection_highlight => DIYGrid.DefaultSelectionHighlight;
public static uint background_color => _default_background_color;
}
public static IValueConverter selection_highlight_converter => ValueConverter.create<GridSettings, SolidColorBrush>(gs => gs.selection_highlight.to_brush().frozen());
@@ -65,17 +73,26 @@ namespace mbts.ui.settings {
List<GridColumnSettings> _columns;
[ProtoMember(7)]
List<SortingCriteria> _sorting_criterion;
[ProtoMember(8)]
readonly uint _background_color;
[ProtoMember(9)]
[DefaultValue(false)]
readonly bool _enable_background_color;
public GridSettings() {
_font_family = _default_font_family;
_font_size = _default_font_size;
_font_weight = _default_font_weight;
_font_color = _default_font_color;
// new setting in proto defaulted here
_background_color = _default_background_color;
_enable_background_color = _default_enable_background_color;
}
public GridSettings(
string font_family = _default_font_family, double font_size = _default_font_size, FontWeight font_weight = default, uint font_color = _default_font_color,
ColorOpacityProto? selection_highlight = null, IEnumerable<GridColumnSettings> columns = null, IEnumerable<SortingCriteria> sorting_criterion = null) {
ColorOpacityProto? selection_highlight = null, IEnumerable<GridColumnSettings> columns = null, IEnumerable<SortingCriteria> sorting_criterion = null, uint background_color = _default_background_color, bool enable_background_color = _default_enable_background_color) {
//
_font_family = font_family;
_font_size = font_size;
@@ -84,6 +101,8 @@ namespace mbts.ui.settings {
_selection_highlight = selection_highlight ?? new ColorOpacityProto(0, 1);
_columns = new List<GridColumnSettings>(columns.empty_if_null());
_sorting_criterion = new List<SortingCriteria>(sorting_criterion.empty_if_null());
_background_color = background_color;
_enable_background_color = enable_background_color;
}
public string font_family => _font_family;
@@ -91,14 +110,17 @@ 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 uint background_color => _background_color;
public bool enable_background_color => _enable_background_color;
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 ?? (_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));
new GridSettings(_default_font_family, _default_font_size, FontWeights.Normal, _default_font_color, new ColorOpacityProto(DIYGrid.DefaultSelectionHighlight.Color, DIYGrid.DefaultSelectionHighlight.Opacity),
background_color: _default_background_color);
public GridSettings with(IEnumerable<GridColumnSettings> columns = null, IEnumerable<SortingCriteria> sorting_criterion = null, string font_family = null, double font_size = double.NaN) =>
public GridSettings with(IEnumerable<GridColumnSettings> columns = null, IEnumerable<SortingCriteria> sorting_criterion = null, string font_family = null, double font_size = double.NaN, bool? enable_background_color = null) =>
new GridSettings(
font_family ?? _font_family,
DoubleHelper.IsNaNOrInfinityOr(font_size, 0) ? _font_size : font_size,
@@ -106,7 +128,9 @@ namespace mbts.ui.settings {
_font_color,
_selection_highlight,
columns ?? _columns,
sorting_criterion ?? _sorting_criterion);
sorting_criterion ?? _sorting_criterion,
_background_color,
enable_background_color ?? _enable_background_color);
public GridSettings clone() => with();
@@ -119,8 +143,10 @@ namespace mbts.ui.settings {
selection_highlight == other.selection_highlight &&
EqualityComparer.safe_sequence_equals(columns, other.columns) &&
#pragma warning disable CS0612 // Type or member is obsolete
EqualityComparer.safe_sequence_equals(sorting_criterion, other.sorting_criterion);
EqualityComparer.safe_sequence_equals(sorting_criterion, other.sorting_criterion) &&
#pragma warning restore CS0612 // Type or member is obsolete
background_color == other.background_color &&
enable_background_color == other.enable_background_color;
public override bool Equals(object obj) =>
Equals(obj as GridSettings);
@@ -134,7 +160,37 @@ namespace mbts.ui.settings {
selection_highlight.GetHashCode(),
EqualityComparer.safe_aggregate_hash(columns),
#pragma warning disable CS0612 // Type or member is obsolete
EqualityComparer.safe_aggregate_hash(sorting_criterion));
EqualityComparer.safe_aggregate_hash(sorting_criterion),
#pragma warning restore CS0612 // Type or member is obsolete
background_color.GetHashCode(),
enable_background_color.GetHashCode());
}
static class GridSettingsExtensions {
public static bool add_missing_columns(this GridSettings grid, IEnumerable<GridColumnSettings> required_columns, out GridSettings result) {
var col_lookup = new Lazy<ILookup<string, GridColumnSettings>>(() => required_columns.ToLookup(c => c.id));
return add_missing_columns(grid, required_columns.Select(c => c.id), id => col_lookup.Value.TryGetValue(id, out var matches) ? matches.FirstOrDefault() : null, out result);
}
public static bool add_missing_columns(this GridSettings grid, IEnumerable<string> col_ids, Func<string, GridColumnSettings> col_factory, out GridSettings result) {
// add the new column (RawDayPos), if absent.
var cols_to_add = new List<GridColumnSettings>(0);
foreach (var col_id in col_ids) {
if (!grid.columns.Any(c => c.id == col_id)) {
var default_col = col_factory(col_id);
if (default_col != null)
cols_to_add.Add(default_col);
}
}
if (cols_to_add.Count > 0) {
var col_list = grid.columns.ToList();
col_list.AddRange(cols_to_add);
grid = grid.with(col_list);
result = grid;
return true;
}
result = grid;
return false;
}
}
}
@@ -30,7 +30,7 @@ namespace mbts.ui {
readonly DelegateProperty<FontFamily> _font_family;
readonly DelegateProperty<FontWeight> _font_weight;
readonly DelegateProperty<double> _font_size;
readonly DelegateProperty<SolidColorBrush> _font_color, _selection_highlight;
readonly DelegateProperty<SolidColorBrush> _font_color, _selection_highlight, _background_color;
readonly SetMap<IPredicate<TRowData>, AnimationDefinitionWrapper<TRow>> _animation_defs = new SetMap<IPredicate<TRowData>, AnimationDefinitionWrapper<TRow>>();
readonly Func<TPredicateData> _predicate_data_factory;
readonly Func<TPredicateData, IPredicate<TRowData>> _predicate_factory;
@@ -77,6 +77,7 @@ namespace mbts.ui {
_font_size = backing(nameof(font_size), () => grid_settings.font_size, nameof(font_size));
_font_color = backing(nameof(font_color), () => grid_settings.font_color.rgb_solid_brush().frozen(), nameof(grid_settings));
_selection_highlight = backing(nameof(selection_highlight), () => grid_settings.selection_highlight.to_brush().frozen(), nameof(grid_settings));
_background_color = backing(nameof(background_color), () => grid_settings.background_color.argb_color().to_solid_brush().frozen(), nameof(grid_settings));
}
public void Dispose() =>
@@ -88,6 +89,7 @@ namespace mbts.ui {
public double font_size => _font_size.Value;
public SolidColorBrush font_color => _font_color.Value;
public SolidColorBrush selection_highlight => _selection_highlight.Value;
public SolidColorBrush background_color => _background_color.Value;
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));
@@ -112,7 +114,7 @@ namespace mbts.ui {
var grid = value.general ?? GridSettings.Default;
_grid_settings.set(grid);
foreach (ColumnBase col in _columns)
col.set_fallback_font_params(new FontFamily(grid.font_family), grid.font_size, grid.font_color.rgb_color(), grid.font_weight);
col.set_fallback_font_params(new FontFamily(grid.font_family), grid.font_size, grid.font_color.rgb_color(), grid.font_weight, grid.background_color.argb_color());
_predicate_animations_data = value.predicate_animations;
_animation_defs.clear();
foreach (var pa in _predicate_animations_data) {
@@ -1,5 +1,6 @@
using bts.api;
using bts.collections;
using bts.proto;
using bts.shell;
using bts.utils;
using mbts.collections;
@@ -56,7 +57,7 @@ namespace mbts.spark {
readonly Property<FontWeight> _font_weight;
readonly Property<string> _font_display_name;
readonly Property<double> _font_size;
readonly Property<bool> _show_predicate_animations, _show_cell_predicate_animations;
readonly Property<bool> _show_predicate_animations, _show_cell_predicate_animations, _show_background_color;
readonly OrdersModel _orders_model;
readonly IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> _column_flags;
@@ -83,6 +84,7 @@ namespace mbts.spark {
_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);
_show_background_color = backing(nameof(show_background_color), false);
_current_column = backing<IColumnVM>(nameof(current_column));
predicate_animations = new ObservableEditableCollection<PredicateRowAnimationVM>(() =>
new PredicateRowAnimationVM(_create_predicate_vm(), _predicate_data_factory), true);
@@ -100,6 +102,8 @@ namespace mbts.spark {
public FontWeight font_weight { get => _font_weight.Value; set => _font_weight.set(value); }
public ColorPickerPopupVM font_color { get; } = new ColorPickerPopupVM();
public ColorPickerPopupVM selection_highlight { get; } = new ColorPickerPopupVM();
public ColorPickerPopupVM background_color { get; } = new ColorPickerPopupVM();
public bool show_background_color => _show_background_color.Value;
public double font_size { get => _font_size.Value; set => _font_size.set(value); }
public bool enable_sound_triggers => true;
public bool show_predicate_animations { get => _show_predicate_animations.Value; set => _show_predicate_animations.set(value); }
@@ -130,7 +134,8 @@ namespace mbts.spark {
foreach (IGrouping<TPredicateData, CellAnimation> g in animations)
animation_map.AddOrUpdate(g.Key, p => animation_tuple(c: g), (p, t) => t.cell_animations.AddRange(g));
}
return _create_settings_obj(new GridSettings(font_family.name(), font_size, font_weight, font_color.color.rgb_color(), selection_highlight.to_proto(), col_settings),
return _create_settings_obj(new GridSettings(font_family.name(), font_size, font_weight, font_color.color.rgb_color(), selection_highlight.to_proto(), col_settings,
background_color: background_color.color.with((byte)(MathUtils.within_range(0, 1, background_color.opacity) * 255.0)).rgb_color()),
animation_map.Select(kv => PredicateAnimations.create_data<TRowData, TPredicateData>(kv.Key, kv.Value.cell_animations, kv.Value.row_animations, kv.Value.sounds)));
//
(List<RowAnimation> row_animations, List<SoundNotification> sounds, List<CellAnimation> cell_animations) animation_tuple(IEnumerable<RowAnimation> r = null, IEnumerable<SoundNotification> s = null, IGrouping<TPredicateData, CellAnimation> c = null) =>
@@ -142,6 +147,14 @@ namespace mbts.spark {
_font_weight.set(sd.general.font_weight);
font_color.load_from(sd.general.font_color.rgb_solid_brush());
selection_highlight.load_from(sd.general.selection_highlight.to_brush());
_show_background_color.set(sd.general.enable_background_color);
if (sd.general.enable_background_color) {
// note that when the OB is moved to allow background colors
// we can just background_color.color instead of load_from
// so that we get all the alpha goodness similar to SimpleGridSettingsVM
// assuming bind_opacity_to_alpha on the picker.
background_color.load_from(sd.general.background_color.argb_color().to_solid_brush());
}
_font_size.set(sd.general.font_size);
var col_animations = new Dictionary<string, Dictionary<TPredicateData, List<CellAnimation>>>();
predicate_animations.Clear();
@@ -642,6 +642,8 @@
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
@@ -672,6 +674,16 @@
Visibility="{Binding DataContext.current_column.is_font_color_overridden, Converter={StaticResource BooleanToVisibilityConverter}, RelativeSource={RelativeSource AncestorType=usp:GridSettingsView}}"
/>
<ComboBox Grid.Row="8" Grid.Column="2" Style="{StaticResource DisabledPlaceholderComboBox}" Visibility="{Binding current_column.is_font_color_overridden, Converter={StaticResource FalseVisibilityConverter}}"/>
<CheckBox Grid.Row="10" VerticalAlignment="Center" IsEnabled="{Binding current_column.can_override_background_color}" IsChecked="{Binding current_column.is_background_color_overridden}" Visibility="{Binding show_background_color, Converter={StaticResource BoolToVisibility}}">Override Background Color</CheckBox>
<StackPanel Grid.Row="10" Grid.Column="2" Visibility="{Binding show_background_color, Converter={StaticResource BoolToVisibility}}">
<c:ColorPickerPopup
Grid.Row="10"
Grid.Column="2"
DataContext="{Binding current_column.background_color_override}"
Visibility="{Binding DataContext.current_column.is_background_color_overridden, Converter={StaticResource BooleanToVisibilityConverter}, RelativeSource={RelativeSource AncestorType=usp:GridSettingsView}}"
/>
<ComboBox Grid.Row="10" Grid.Column="2" Style="{StaticResource DisabledPlaceholderComboBox}" Visibility="{Binding current_column.is_background_color_overridden, Converter={StaticResource FalseVisibilityConverter}}"/>
</StackPanel>
</Grid>
</GroupBox>
<!--TODO: Make this visible when cell animations are ready-->
@@ -21,6 +21,9 @@ namespace mbts.ui {
if ((flags & GridColumnSettingsFeatureFlags.ImmutableFontWeight) != 0) {
stores_per_flag.Add(new ImmutableFontWeightOverrideStore());
}
if ((flags & GridColumnSettingsFeatureFlags.ImmutableBackgroundColor) != 0) {
stores_per_flag.Add(new ImmutableBackgroundColorOverrideStore());
}
return new CompositeImmutableSettingsStore(stores_per_flag, flags);
}
@@ -66,6 +69,19 @@ namespace mbts.ui {
_value = new NullableFontWeightProto(settings.font_weight_override);
}
class ImmutableBackgroundColorOverrideStore : ImmutableColumnSettingStore {
NullableColorProto _value;
public ImmutableBackgroundColorOverrideStore()
: base(GridColumnSettingsFeatureFlags.ImmutableBackgroundColor) { }
public override GridColumnSettings apply(GridColumnSettings settings) =>
settings.with(background_color_override: _value);
public override void store(GridColumnSettings settings) =>
_value = new NullableColorProto(settings.background_color_override);
}
class CompositeImmutableSettingsStore : ImmutableColumnSettingStore {
readonly IReadOnlyList<ImmutableColumnSettingStore> _stores;
public CompositeImmutableSettingsStore(IReadOnlyList<ImmutableColumnSettingStore> stores, GridColumnSettingsFeatureFlags flags)
@@ -21,7 +21,9 @@ namespace mbts.spark {
_can_override_font_color = true,
_is_font_color_overridden,
_can_override_font_weight = true,
_is_font_weight_overridden;
_is_font_weight_overridden,
_can_override_background_color = true,
_is_background_color_overridden;
FontWeight _font_weight_override;
GridColumnSettings _settings;
@@ -43,16 +45,23 @@ namespace mbts.spark {
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); }
// background color override
public bool can_override_background_color { get => _can_override_background_color; private set => Set(ref _can_override_background_color, value); }
public bool is_background_color_overridden { get => _is_background_color_overridden; set => Set(ref _is_background_color_overridden, value); }
public ColorPickerPopupVM background_color_override { get; } = new ColorPickerPopupVM();
public GridColumnSettings create() =>
_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,
can_override_font_weight ? (is_font_weight_overridden ? new NullableFontWeightProto(font_weight_override) : new NullableFontWeightProto()) : (NullableFontWeightProto?)null,
can_override_font_color ? (is_font_color_overridden ? new NullableColorProto(font_color_override.color) : new NullableColorProto()) : (NullableColorProto?)null,
width,
horizontal_alignment);
horizontal_alignment,
background_color_override:
can_override_background_color ? (is_background_color_overridden ? new NullableColorProto(background_color_override.color) : new NullableColorProto()) : (NullableColorProto ?)null
);
public void load_from(GridColumnSettings value) {
_settings = value;
@@ -71,6 +80,14 @@ namespace mbts.spark {
if (is_font_weight_overridden) {
_font_weight_override = _settings.font_weight_override.Value;
}
// background color
is_background_color_overridden = _settings.background_color_override.HasValue;
if (is_background_color_overridden) {
background_color_override.load_from(
new SolidColorBrush(_settings.background_color_override.Value) {
//Opacity = _settings.background_color_override.Value.opacity
});
}
}
public void apply_flags(GridColumnSettingsFeatureFlags flags) {
@@ -84,6 +101,9 @@ namespace mbts.spark {
// font weight
can_override_font_weight = (flags & GridColumnSettingsFeatureFlags.ImmutableFontWeight) == 0;
is_font_weight_overridden &= can_override_font_weight;
// background color
can_override_background_color = (flags & GridColumnSettingsFeatureFlags.ImmutableBackgroundColor) == 0;
is_background_color_overridden &= can_override_background_color;
}
public override string ToString() => $"{display_name} ({id})";
@@ -20,7 +20,7 @@ namespace mbts.ui {
readonly DelegateProperty<FontFamily> _font_family;
readonly DelegateProperty<FontWeight> _font_weight;
readonly DelegateProperty<double> _font_size;
readonly DelegateProperty<SolidColorBrush> _font_color, _selection_highlight;
readonly DelegateProperty<SolidColorBrush> _font_color, _selection_highlight, _background_color;
readonly IReadOnlyDictionary<string, ImmutableColumnSettingStore> _column_flags;
public SimpleGridSettingsManager(ObservableCollection<ColumnBase> columns)
@@ -35,6 +35,7 @@ namespace mbts.ui {
_font_size = backing(nameof(font_size), () => grid_settings.font_size, nameof(font_size));
_font_color = backing(nameof(font_color), () => grid_settings.font_color.rgb_solid_brush().frozen(), nameof(grid_settings));
_selection_highlight = backing(nameof(selection_highlight), () => grid_settings.selection_highlight.to_brush().frozen(), nameof(grid_settings));
_background_color = backing(nameof(background_color), () => grid_settings.background_color.argb_color().to_solid_brush().frozen(), nameof(grid_settings));
_row_vms = row_vms;
}
@@ -44,6 +45,7 @@ namespace mbts.ui {
public double font_size => _font_size.Value;
public SolidColorBrush font_color => _font_color.Value;
public SolidColorBrush selection_highlight => _selection_highlight.Value;
public SolidColorBrush background_color => _background_color.Value;
public SimpleGridSettingsVM create_vm(GridSettings settings) {
var vm = new SimpleGridSettingsVM(_column_flags.ToDictionary(kv => kv.Key, kv => kv.Value.flags));
@@ -86,7 +88,7 @@ namespace mbts.ui {
grid = grid ?? GridSettings.Default;
_grid_settings.set(grid);
foreach (ColumnBase col in _columns)
col.set_fallback_font_params(new FontFamily(grid.font_family), grid.font_size, grid.font_color.rgb_color(), grid.font_weight);
col.set_fallback_font_params(new FontFamily(grid.font_family), grid.font_size, grid.font_color.rgb_color(), grid.font_weight, grid.background_color.argb_color());
// ** in the unfortunate event that we botch backwards compatability, and the columns don't bear a 1:1 relationship with the column-settings
// don't add insult to injury by crashing **
if (grid_settings.columns != null) {
@@ -1,4 +1,5 @@
using bts.collections;
using bts.proto;
using bts.utils;
using mbts.ui;
using mbts.ui.controls;
@@ -36,7 +37,7 @@ namespace mbts.spark {
readonly Property<FontWeight> _font_weight;
readonly Property<string> _font_display_name;
readonly Property<double> _font_size;
readonly Property<bool> _show_predicate_animations, _show_cell_predicate_animations;
readonly Property<bool> _show_predicate_animations, _show_cell_predicate_animations, _show_background_color;
readonly DeferrableActionWrapper _update;
readonly IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> _column_flags;
@@ -48,6 +49,7 @@ namespace mbts.spark {
_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);
_show_background_color = backing(nameof(show_background_color), false);
_current_column = backing<ISimpleColumnVM>(nameof(current_column));
// update
_update = new DeferrableActionWrapper(() => result_changed?.Invoke());
@@ -78,7 +80,9 @@ namespace mbts.spark {
public FontFamily font_family { get => _font_family.Value; set => _font_family.set(value); }
public FontWeight font_weight { get => _font_weight.Value; set => _font_weight.set(value); }
public ColorPickerPopupVM font_color { get; } = new ColorPickerPopupVM();
public ColorPickerPopupVM selection_highlight { get; } = new ColorPickerPopupVM();
public ColorPickerPopupVM selection_highlight { get; } = new ColorPickerPopupVM(){ bind_opacity_to_alpha = true };
public ColorPickerPopupVM background_color { get; } = new ColorPickerPopupVM() { bind_opacity_to_alpha = true };
public bool show_background_color => _show_background_color.Value;
public double font_size { get => _font_size.Value; set => _font_size.set(value); }
public bool enable_sound_triggers => false;
public bool show_predicate_animations { get => _show_predicate_animations.Value; set => _show_predicate_animations.set(value); }
@@ -98,12 +102,20 @@ namespace mbts.spark {
get {
yield return font_color;
yield return selection_highlight;
yield return background_color;
yield return this;
}
}
// Note that one of the responsibilites of create() is via instance_vm.get_result() in the ShellSettingsDialogVM.export_to_custom_cmd,
// which is what handles saving .grid files. This means if you have introduced any optional args, say for example enable_background_color
// that is not yet a feature of all windows at this point in time, 8/28/2019, you need to be sure it's explicity set if the proto
// constructor parameter default has been initialized to different default value.
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()));
new GridSettings(font_family.name(), font_size, font_weight, font_color.color.rgb_color(), selection_highlight.to_proto(),
columns.Select(c => c.create()),
background_color: show_background_color ? background_color.color.rgb_color() : GridSettings.FallbackValues.background_color,
enable_background_color: show_background_color);
public void load_from(GridSettings value) {
if (value == null)
@@ -112,7 +124,11 @@ namespace mbts.spark {
_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());
selection_highlight.color = value.selection_highlight.argb_color;
_show_background_color.set(value.enable_background_color);
if (value.enable_background_color) {
background_color.color = value.background_color.argb_color();
}
_font_size.set(value.font_size);
var curr_col_id = current_column?.id;
columns.SetItems(value.columns.Select(col => {
@@ -110,10 +110,11 @@ namespace mbts.spark.filters {
class DynamicItemsFilter<TRow, TValue, TState> : FilterVMBase<TRow, TValue, TState>, IDisposable {
readonly IEqualityComparer<TValue> _equality_comparer;
readonly IComparer<TValue> _comparer;
readonly Func<ISet<TValue>, TState> _get_state;
readonly Func<TState, IObservable<IEnumerable<TValue>>> _load_state;
readonly HashSet<TValue> _items_set, _selected_items_set;
readonly ObservableCollection<TValue> _unsorted_items;
readonly ObservableCollection<TValue> _items = new ObservableCollection<TValue>();
bool _override_desired_selection;
IDisposable _desired_items_sub, _items_sub;
@@ -129,7 +130,7 @@ namespace mbts.spark.filters {
//
_items_sub = items.OnDispatcher().Subscribe(new_items => {
_items_set.SetItems(new_items.Concat(_selected_items_set));
_unsorted_items.sync_with(_items_set);
_sync_items();
});
}
@@ -148,7 +149,7 @@ namespace mbts.spark.filters {
_items_set.Remove(v);
foreach (var v in update.added)
_items_set.Add(v);
_unsorted_items.sync_with(_items_set);
_sync_items();
});
}
@@ -164,11 +165,12 @@ namespace mbts.spark.filters {
_get_state = get_state;
_load_state = load_state;
_equality_comparer = equality_comparer;
_comparer = comparer;
_selected_items_set = new HashSet<TValue>(_equality_comparer);
_items_set = new HashSet<TValue>(_equality_comparer);
_unsorted_items = new ObservableCollection<TValue>();
items = comparer != null ? ObservableAutoWrapper.create_sorted_wrapper(_unsorted_items, comparer) : ObservableAutoWrapper.create_read_only_wrapper(_unsorted_items);
items = ObservableAutoWrapper.create_read_only_wrapper(_items);
selected_items.CollectionChanged += _on_selected_items_CollectionChanged;
_sync_items();
}
public IReadOnlyObservableCollection<TValue> items { get; }
@@ -238,9 +240,11 @@ namespace mbts.spark.filters {
var copy = new HashSet<TValue>(_selected_items_set, _equality_comparer);
_selected_items_set.SetItems(dsi);
using (selected_items.SuspendHandler(_on_selected_items_CollectionChanged)) {
foreach (var item in _selected_items_set) {
if (_items_set.Add(item))
_unsorted_items.Add(item);
var to_add = _selected_items_set.Where(_items_set.Add);
if (_comparer != null) {
_items.InsertSorted(to_add, _comparer);
} else {
_items.AddRange(to_add);
}
selected_items.sync_with(_selected_items_set);
}
@@ -248,6 +252,14 @@ namespace mbts.spark.filters {
_raise_filter_changed();
}
}
void _sync_items() {
if (_comparer != null) {
_items.sync_with(_items_set.Ordered(_comparer));
} else {
_items.sync_with(_items_set);
}
}
}
class TableUpdateItemsFilter<TRow, TValue, TKey, TState> : FilterVMBase<TRow, TValue, TState>, IObserver<TableUpdate<TValue, TKey>> where TKey : struct, IEquatable<TKey> {
@@ -101,7 +101,8 @@ namespace mbts.ui.controls
case NotifyCollectionChangedAction.Replace:
if (e.OldItems != null) foreach (var item in e.OldItems) _set_is_selected(item, false);
if (e.NewItems != null) foreach (var item in e.NewItems) _set_is_selected(item, true); break;
case NotifyCollectionChangedAction.Reset: foreach (var item in ItemsSource) _set_is_selected(item, false); break;
case NotifyCollectionChangedAction.Reset:
if (ItemsSource != null) foreach (var item in ItemsSource) _set_is_selected(item, false); break;
}
_update_is_all_selected();
}
@@ -16,6 +16,9 @@ namespace mbts.ui.converters {
public static IMultiValueConverter create<TIn1, TIn2, TIn3, TOut>(Func<TIn1, TIn2, TIn3, TOut> convert) =>
new AnonymousMultiValueConverter<TIn1, TIn2, TIn3, TOut>(convert);
public static IMultiValueConverter create<TIn1, TIn2, TIn3, TIn4, TOut>(Func<TIn1, TIn2, TIn3, TIn4, TOut> convert) =>
new AnonymousMultiValueConverter<TIn1, TIn2, TIn3, TIn4, TOut>(convert);
}
static class ValueConverters {
@@ -92,4 +95,17 @@ namespace mbts.ui.converters {
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => throw new NotImplementedException();
}
class AnonymousMultiValueConverter<TIn1, TIn2, TIn3, TIn4, TOut> : IMultiValueConverter {
readonly Func<TIn1, TIn2, TIn3, TIn4, TOut> _convert;
public AnonymousMultiValueConverter(Func<TIn1, TIn2, TIn3, TIn4, TOut> convert) => _convert = convert;
public TOut ErrorValue { get; set; }
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) =>
ConvertUtils.try_convert_args(values, out TIn1 a, out TIn2 b, out TIn3 c, out TIn4 d) ? _convert(a, b, c, d) : ErrorValue;
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => throw new NotImplementedException();
}
}
@@ -0,0 +1,22 @@
using System;
using System.Globalization;
using System.Text;
using System.Windows.Data;
namespace mbts.ui.converters {
class AppendTextConverter : IValueConverter {
public string Prefix { get; set; }
public string Suffix { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
var sb = new StringBuilder(value?.ToString());
// don't need to check for null (Insert/Append support it)
sb.Insert(0, Prefix);
sb.Append(Suffix);
return sb.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
throw new NotImplementedException();
}
}
@@ -24,6 +24,9 @@ namespace mbts.ui.converters {
public string Format { get; set; }
public string NaNText { get; set; } = string.Empty;
public static string convert(double value, string format, string nan_text = "-") =>
double.IsNaN(value) ? nan_text : value.ToString(format);
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
var text = (value as string ?? string.Empty).Trim().ToLower();
if (text == string.Empty || text == "nan" || text == NaNText) return double.NaN;
@@ -156,6 +156,19 @@ namespace mbts.ui.extensions {
}
}
public static IDisposable SubscribePreviewKeyUp(this UIElement target, KeyEventHandler callback) {
target.PreviewKeyUp += callback;
return Disposable.Create(() => target.PreviewKeyUp -= callback);
}
public static IDisposable SubscribePreviewKeyUp(this UIElement target, Key key, Action callback) {
return SubscribePreviewKeyUp(target, on_preview_key_up);
void on_preview_key_up(object sender, KeyEventArgs e) {
if (e.Key == key)
callback();
}
}
public static IObservable<KeyEventArgs> SubscribePreviewKeyDown(this UIElement target) =>
Observable.Create<KeyEventArgs>(o => {
target.PreviewKeyDown += on_preview_key_down;
@@ -197,6 +210,13 @@ namespace mbts.ui.extensions {
}
}
public static class SelectorExtensions {
public static IDisposable SubscribeSelectionChanged(this System.Windows.Controls.Primitives.Selector target, SelectionChangedEventHandler callback) {
target.SelectionChanged += callback;
return Disposable.Create(() => target.SelectionChanged -= callback);
}
}
public static class ScrollViewerExtensions {
public static IDisposable SubscribeScrollChanged(this ScrollViewer target, ScrollChangedEventHandler callback) {
target.ScrollChanged += callback;
@@ -66,11 +66,18 @@ namespace mbts.ui {
}
}
// TODO: replace this method with 'argb_color'
// - before that, need to verify that no settings/colors become 'invisible' as a result.
public static Color rgb_color(this UInt32 color) {
var bytes = BitConverter.GetBytes(color);
return Color.FromRgb(bytes[2], bytes[1], bytes[0]);
}
public static Color argb_color(this uint color) {
var bytes = BitConverter.GetBytes(color);
return Color.FromArgb(bytes[3], bytes[2], bytes[1], bytes[0]);
}
public static SolidColorBrush to_brush(this BTSColor color) =>
new SolidColorBrush(color);
@@ -103,6 +110,9 @@ namespace mbts.ui {
return brush;
}
public static Color with(this Color color, byte alpha) =>
Color.FromArgb(alpha, color.R, color.G, color.B);
public static SolidColorBrush with(this SolidColorBrush brush, double opacity) {
brush.Opacity = opacity;
return brush;
@@ -236,12 +246,12 @@ namespace mbts.ui {
throw new NotImplementedException("This ICommand does not implement IRaiseCanExecuteChanged");
}
public static async Task ExecuteAsync(this ICommand command, object parameter) {
public static Task ExecuteAsync(this ICommand command, object parameter) {
ExceptionAssert.Argument.NotNull(command, nameof(command));
if (command is IAsyncCommand acmd)
await acmd.Execute(parameter);
return acmd.Execute(parameter);
else
await Task.Run(() => command.Execute(parameter));
return Task.Run(() => command.Execute(parameter));
}
public static void TryExecuteIfCan(this ICommand command, object parameter) {
+43 -51
View File
@@ -619,47 +619,47 @@
<!-- Toggleswitch -->
<Style TargetType="ToggleButton" x:Key="ToggleSwitch">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="Black" />
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="Margin" Value="2,0,2,0" />
<Setter Property="ToolTip" Value="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={StaticResource ToggleSwitchToolTipConverter}}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid Margin="{TemplateBinding Margin}">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="0.8" Duration="0:0:0.1" />
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ColorAnimation Storyboard.TargetName="background" Storyboard.TargetProperty="Background.Color" To="#c8ff44" Duration="0:0:0.5" />
<DoubleAnimation Storyboard.TargetName="background" Storyboard.TargetProperty="Opacity" To="0.7" BeginTime="0" Duration="0"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed"/>
<VisualState x:Name="Disabled">
<!--<Storyboard>
<ColorAnimation Storyboard.TargetName="background" Storyboard.TargetProperty="Background.Color" To="#555" Duration="0:0:0" />
</Storyboard>-->
<Storyboard>
<ColorAnimation Storyboard.TargetName="background" Storyboard.TargetProperty="Background.Color" To="#555" Duration="0:0:0" />
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="CheckStates">
<VisualState x:Name="Checked">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="lever" Storyboard.TargetProperty="(Canvas.Left)" To="40" Duration="0:0:0.2">
<DoubleAnimation Storyboard.TargetName="lever" Storyboard.TargetProperty="(Canvas.Left)" From="3" To="40" Duration="0:0:0.2">
<DoubleAnimation.EasingFunction>
<CubicEase EasingMode="EaseIn" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<StringAnimationUsingKeyFrames Storyboard.TargetName="text" Storyboard.TargetProperty="(TextBlock.Text)">
<DiscreteStringKeyFrame KeyTime="0" Value="On"/>
</StringAnimationUsingKeyFrames>
<ColorAnimation Storyboard.TargetName="background" Storyboard.TargetProperty="Background.Color" To="{StaticResource ToggleSwitchOnColor}" BeginTime="0" Duration="0"/>
<ColorAnimation Storyboard.TargetName="background" Storyboard.TargetProperty="BorderBrush.Color" To="{StaticResource ToggleSwitchOnColor}" BeginTime="0" Duration="0"/>
<ColorAnimation Storyboard.TargetName="text" Storyboard.TargetProperty="Foreground.Color" To="White" BeginTime="0" Duration="0"/>
<ColorAnimation Storyboard.TargetName="lever" Storyboard.TargetProperty="Fill.Color" To="White" BeginTime="0" Duration="0"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Unchecked">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="lever" Storyboard.TargetProperty="(Canvas.Left)" To="3" Duration="0:0:0.2">
<DoubleAnimation Storyboard.TargetName="lever" Storyboard.TargetProperty="(Canvas.Left)" From="40" To="3" Duration="0:0:0.2">
<DoubleAnimation.EasingFunction>
<CubicEase EasingMode="EaseIn" />
</DoubleAnimation.EasingFunction>
@@ -673,45 +673,37 @@
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="background"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="8"
Background="{TemplateBinding Background}"
Width="54" Height="18">
<Border
x:Name="background"
Width="54"
Height="18"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="8"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
Opacity="1"
>
<Border.Background>
<SolidColorBrush Color="Transparent"/>
</Border.Background>
<Border.BorderBrush>
<SolidColorBrush Color="Black"/>
</Border.BorderBrush>
<Canvas>
<Ellipse x:Name="lever" VerticalAlignment="Center" Width="9" Height="9" Fill="Black" Canvas.Top="3" Canvas.Left="3" />
<Ellipse x:Name="lever" VerticalAlignment="Center" Width="9" Height="9" Canvas.Top="3" Canvas.Left="3">
<Ellipse.Fill>
<SolidColorBrush Color="Black"/>
</Ellipse.Fill>
</Ellipse>
</Canvas>
</Border>
<TextBlock x:Name="text" Grid.Column="1" Text="Off" HorizontalAlignment="Center" VerticalAlignment="Center" />
<TextBlock x:Name="text" Grid.Column="1" FontWeight="SemiBold" Text="Off" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock.Foreground>
<SolidColorBrush Color="Black"/>
</TextBlock.Foreground>
</TextBlock>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="White" />
<Setter Property="Background" Value="#555" />
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="text" Property="Text" Value="On" />
<Setter TargetName="lever" Property="Canvas.Top" Value="4" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsChecked" Value="True" />
<Condition Property="IsEnabled" Value="True" />
</MultiTrigger.Conditions>
<Setter TargetName="background" Property="BorderThickness" Value="0" />
<Setter TargetName="background" Property="Background" Value="#080" />
<Setter Property="Foreground" Value="White" />
</MultiTrigger>
<Trigger Property="IsChecked" Value="{x:Null}">
<Setter TargetName="text" Property="Text" Value="" />
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter Property="Foreground" Value="DarkGray" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
@@ -21,6 +21,7 @@
<cv:ValueMap x:Key="ReverseVisibilityConverter" Default="{u:Visibility Collapsed}">
<cv:KeyValue Key="{u:Visibility Collapsed}" Value="{u:Visibility Visible}"/>
</cv:ValueMap>
<cv:BooleanToObjectConverter x:Key="ToggleSwitchToolTipConverter" ValueForFalse="Turn On" ValueForTrue="Turn Off"/>
<cv:EnumDescriptionConverter x:Key="EnumDescriptionConverter" />
<cv:EnumFlagsDescriptionConverter x:Key="EnumFlagsDescriptionConverter"/>
<cv:EnumerableDoubleStringConverter x:Key="EnumerableDoublesConverter" />
@@ -143,6 +143,7 @@
</Setter>
</Style>
<Color x:Key="HeaderBackgroundBorderColor">#FFAAD1FF</Color>
<Style x:Key="BTSDialog" TargetType="{x:Type Window}" BasedOn="{StaticResource BTSWindow}">
<Setter Property="MinHeight" Value="100" />
<Setter Property="MinWidth" Value="100" />
@@ -156,8 +157,10 @@
<RowDefinition Height="1" />
<RowDefinition />
</Grid.RowDefinitions>
<Image SnapsToDevicePixels="True" Height="20" VerticalAlignment="Center" HorizontalAlignment="Left" Stretch="Uniform" Source="{TemplateBinding Icon}" RenderOptions.BitmapScalingMode="HighQuality" />
<TextBlock Text="{TemplateBinding Title}" VerticalAlignment="Center" HorizontalAlignment="Stretch" Margin="22,2,2,2" x:Name="_title" />
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Stretch" Orientation="Horizontal">
<Image SnapsToDevicePixels="True" Height="14" Margin="4,2" VerticalAlignment="Center" HorizontalAlignment="Left" Stretch="Uniform" Source="{TemplateBinding Icon}" RenderOptions.BitmapScalingMode="HighQuality" />
<TextBlock Text="{TemplateBinding Title}" VerticalAlignment="Center" HorizontalAlignment="Stretch" x:Name="_title" />
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Background="{TemplateBinding Background}">
<Button Style="{StaticResource RoundMinimizeButton}" HorizontalAlignment="Right" Command="{x:Static SystemCommands.MinimizeWindowCommand}"
Visibility="{Binding Path=(u:WindowStyle.ShowMinimizeButton),RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource BoolToVisibility}}"/>
@@ -168,7 +171,7 @@
</StackPanel>
<Rectangle Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Rectangle.Fill>
<SolidColorBrush Color="#AAD1FF"/>
<SolidColorBrush Color="{DynamicResource HeaderBackgroundBorderColor}"/>
</Rectangle.Fill>
</Rectangle>
<AdornerDecorator Grid.Row="2">
+1 -1
View File
@@ -15,7 +15,7 @@
Topmost="{Binding Topmost}"
x:Name="window"
Background="{StaticResource CommonBackground}"
Title="{Binding Title}"
Title="BTS Spark"
d:DataContext="{d:DesignInstance shell:LauncherDesignTimeVM, IsDesignTimeCreatable=True}">
<Window.Resources>
<ResourceDictionary>
@@ -0,0 +1,42 @@
<Window
x:Class="mbts.ui.spark.GlobalAlertsDialog"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
u:WindowStyle.SizeToContent="WidthAndHeight"
u:WindowStyle.AllowResize="True"
Content="{Binding}"
Icon="{x:Static u:Icons.Speaker}"
Title="Alerts"
Style="{StaticResource BTSDialog}"
>
<Window.ContentTemplate>
<DataTemplate DataType="{x:Type usp:GlobalAlertsVM}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<usp:GlobalAlertsView/>
<c:DialogSkirt Grid.Row="1" Padding="{StaticResource DialogSkirtPadding}">
<c:DialogSkirt.RightItems>
<x:Array Type="{x:Type Button}">
<Button Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding apply_cmd}">
Apply
</Button>
<Button Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding ok_cmd}" IsDefault="True">
OK
</Button>
<Button Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding cancel_cmd}" IsCancel="True">
Cancel
</Button>
</x:Array>
</c:DialogSkirt.RightItems>
</c:DialogSkirt>
</Grid>
</DataTemplate>
</Window.ContentTemplate>
</Window>
@@ -0,0 +1,14 @@
using mbts.ui.utility;
using System.Windows;
namespace mbts.ui.spark {
/// <summary>
/// Interaction logic for GlobalAlertsDialog.xaml
/// </summary>
partial class GlobalAlertsDialog : Window {
public GlobalAlertsDialog() {
InitializeComponent();
WindowUtilities.listen_for_close_requests(this);
}
}
}
@@ -0,0 +1,246 @@
<UserControl
x:Class="mbts.ui.spark.GlobalAlertsView"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mbts="clr-namespace:mbts"
xmlns:sp="clr-namespace:mbts.spark"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:stg="clr-namespace:mbts.ui.settings"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<Grid>
<Grid.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Controls/DIYGrid/Settings/Animation/SoundAnimationView.xaml"/>
</ResourceDictionary.MergedDictionaries>
<sys:Double x:Key="SecurityWidth">130</sys:Double>
<sys:Double x:Key="DoublePredicateWidth">210</sys:Double>
<sys:Double x:Key="SideWidth">100</sys:Double>
<sys:Double x:Key="SoundWidth">163</sys:Double>
<Thickness x:Key="CellPadding">10,8</Thickness>
<!--Converters-->
<cv:AppendTextConverter x:Key="DropDownSharedSizeGroupConverter" Suffix="_DropDownWidth"/>
<AlternationConverter x:Key="AlternatingBackgroundConverter">
<SolidColorBrush Color="{u:BTSColor Grid.input_cell_background}"/>
<SolidColorBrush Color="{u:BTSColor Grid.alternate_row_background}"/>
</AlternationConverter>
<!--Styles-->
<Style x:Key="DisabledColumnHeader" TargetType="GridViewColumnHeader">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GridViewColumnHeader">
<Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{x:Static u:BTSColors.header_brush}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--Data Templates-->
<DataTemplate x:Key="DoublePredicateTypeTemplate">
<TextBlock Text="{Binding Converter={x:Static usp:GlobalAlertsView.DoublePredicateTypeDisplayNameConverter}}"/>
</DataTemplate>
<DataTemplate x:Key="ColumnHeaderTemplate">
<TextBlock Text="{Binding}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</DataTemplate>
<DataTemplate x:Key="None"/>
<DataTemplate x:Key="DisabledTickUpDown">
<c:TickUpDown IsEnabled="False">
<TextBlock Foreground="Gray" Margin="4,0,0,0">(N/A)</TextBlock>
</c:TickUpDown>
</DataTemplate>
<DataTemplate x:Key="TickUpDownDouble">
<c:TickUpDownDouble Value="{Binding}"/>
</DataTemplate>
<DataTemplate x:Key="DoubleMinimum" DataType="{x:Type sp:DoublePredicateVM}">
<c:TickUpDownDouble Value="{Binding minimum}"/>
</DataTemplate>
<DataTemplate x:Key="DoubleMaximum" DataType="{x:Type sp:DoublePredicateVM}">
<c:TickUpDownDouble Value="{Binding maximum}"/>
</DataTemplate>
<DataTemplate x:Key="DoubleRange" DataType="{x:Type sp:DoublePredicateVM}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<c:TickUpDownDouble Value="{Binding minimum}"/>
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="2,0" Text="and"/>
<c:TickUpDownDouble Grid.Column="2" Value="{Binding maximum}"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type sp:DoublePredicateVM}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="{Binding Path='(c:DIYGrid.ColumnKey)', RelativeSource={RelativeSource AncestorType=ContentControl}, Converter={StaticResource DropDownSharedSizeGroupConverter}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ComboBox ItemsSource="{u:EnumBindingSource {u:Nested mbts:DoublePredicate.PredicateTypeOneofCase}}" SelectedItem="{Binding type}" ItemTemplate="{StaticResource DoublePredicateTypeTemplate}"/>
<ContentControl Grid.Column="1" Margin="6,0,0,0" Content="{Binding}">
<ContentControl.ContentTemplate>
<Binding Path="type">
<Binding.Converter>
<cv:ValueMap>
<cv:KeyValue Key="{x:Static None, MemberType={u:Nested mbts:DoublePredicate.PredicateTypeOneofCase}}" Value="{StaticResource DisabledTickUpDown}"/>
<cv:KeyValue Key="{x:Static LessThan, MemberType={u:Nested mbts:DoublePredicate.PredicateTypeOneofCase}}" Value="{StaticResource DoubleMaximum}"/>
<cv:KeyValue Key="{x:Static GreaterThan, MemberType={u:Nested mbts:DoublePredicate.PredicateTypeOneofCase}}" Value="{StaticResource DoubleMinimum}"/>
<cv:KeyValue Key="{x:Static InsideRange, MemberType={u:Nested mbts:DoublePredicate.PredicateTypeOneofCase}}" Value="{StaticResource DoubleRange}"/>
<cv:KeyValue Key="{x:Static OutsideRange, MemberType={u:Nested mbts:DoublePredicate.PredicateTypeOneofCase}}" Value="{StaticResource DoubleRange}"/>
</cv:ValueMap>
</Binding.Converter>
</Binding>
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type stg:SoundAnimationVM}">
<c:SoundAnimationView Bus="{DynamicResource Bus}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type sys:Boolean}">
<CheckBox IsChecked="{Binding}"/>
</DataTemplate>
<DataTemplate x:Key="SecurityCriteriaTemplate">
<c:ContractPickerPopup
c:DIYGrid.ColumnKey="Security"
Margin="{StaticResource CellPadding}"
AllowEmpty="True"
CloseOnOK="True"
CloseOnCancel="True"
MaxWidth="180"
DataContext="{Binding predicate}"
SecurityMap="{Binding DataContext.security_map, RelativeSource={RelativeSource AncestorType=usp:GlobalAlertsView}}"
Header="{Binding security_criteria}"
ShowDialogSkirt="True"
ToolTip="{Binding security_criteria, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
OKClick="{x:Static c:ContractPickerPopup.FlushSelectedCriteriaHandler}"
SelectedCriteria="{Binding security_criteria, UpdateSourceTrigger=Explicit}"
>
<c:ContractPickerPopup.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}}" TextTrimming="CharacterEllipsis"/>
</DataTemplate>
</c:ContractPickerPopup.HeaderTemplate>
</c:ContractPickerPopup>
</DataTemplate>
<DataTemplate DataType="{x:Type usp:TradeAlertsVM}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<c:DIYGrid
Grid.Row="0"
Grid.IsSharedSizeScope="True"
BorderBrush="Black"
BorderThickness="1"
Rows="{Binding alerts}"
RowsHorizontalScrollBarVisibility="Collapsed"
SelectionMode="Single"
>
<c:DIYGrid.Columns>
<c:DIYGridColumnCollection>
<usp:GlobalAlertsColumn key="Delete" HeaderContainerStyle="{StaticResource DisabledColumnHeader}" Width="23" is_visible="True"/>
<usp:GlobalAlertsColumn key="IsEnabled" HeaderContainerStyle="{StaticResource DisabledColumnHeader}" Width="66" is_visible="True"/>
<usp:GlobalAlertsColumn key="Security" Header="Security" Width="{StaticResource SecurityWidth}" is_visible="True"/>
<usp:GlobalAlertsColumn key="Price" Header="Price Condition" Width="{StaticResource DoublePredicateWidth}" is_visible="True"/>
<usp:GlobalAlertsColumn key="Quantity" Header="Size Condition" Width="{StaticResource DoublePredicateWidth}" is_visible="True"/>
<usp:GlobalAlertsColumn key="Side" Header="Side" Width="{StaticResource SideWidth}" is_visible="True"/>
<usp:GlobalAlertsColumn key="Sound" Header="Sound" Width="{StaticResource SoundWidth}" is_visible="True"/>
</c:DIYGridColumnCollection>
</c:DIYGrid.Columns>
<c:DIYGrid.RowTemplate>
<DataTemplate DataType="{x:Type usp:TradeAlertVM}">
<Grid c:DIYGrid.SyncWithColumnGeometry="True">
<Button
c:DIYGrid.ColumnKey="Delete"
HorizontalAlignment="Center"
Command="{Binding DataContext.delete_alert_cmd, RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
CommandParameter="{Binding}"
Style="{StaticResource RoundCloseButton}"
ToolTip="Delete"
/>
<ToggleButton c:DIYGrid.ColumnKey="IsEnabled" IsChecked="{Binding is_enabled}" Style="{StaticResource ToggleSwitch}"/>
<c:ContractPickerPopup
c:DIYGrid.ColumnKey="Security"
Grid.Column="0"
Margin="{StaticResource CellPadding}"
AllowEmpty="True"
CloseOnOK="True"
CloseOnCancel="True"
MaxWidth="180"
DataContext="{Binding predicate}"
SecurityMap="{Binding DataContext.security_map, RelativeSource={RelativeSource AncestorType=usp:GlobalAlertsView}}"
Header="{Binding security_criteria}"
ShowDialogSkirt="True"
ToolTip="{Binding security_criteria, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
OKClick="{x:Static c:ContractPickerPopup.FlushSelectedCriteriaHandler}"
SelectedCriteria="{Binding security_criteria, UpdateSourceTrigger=Explicit}"
>
<c:ContractPickerPopup.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}}" TextTrimming="CharacterEllipsis"/>
</DataTemplate>
</c:ContractPickerPopup.HeaderTemplate>
</c:ContractPickerPopup>
<ContentControl c:DIYGrid.ColumnKey="Price" Margin="{StaticResource CellPadding}" Content="{Binding predicate.price}"/>
<ContentControl c:DIYGrid.ColumnKey="Quantity" Margin="{StaticResource CellPadding}" Content="{Binding predicate.quantity}"/>
<ComboBox
c:DIYGrid.ColumnKey="Side"
Margin="{StaticResource CellPadding}"
ItemsSource="{Binding predicate.side_options}"
SelectedItem="{Binding predicate.side}"
ItemTemplate="{StaticResource EnumFlagsDescriptionTemplate}"
/>
<Grid c:DIYGrid.ColumnKey="Sound" Margin="{StaticResource CellPadding}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ComboBox
ItemsSource="{Binding sound.system_sound_options}"
SelectedItem="{Binding sound.system_sound}"
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
HorizontalAlignment="Stretch"
/>
<Button
Grid.Column="1"
Margin="4,0,0,0"
HorizontalAlignment="Left"
Command="{x:Static c:SoundAnimationView.PreviewSoundCommand}"
CommandParameter="{Binding sound.system_sound}"
Style="{StaticResource RoundPlayButton}"
/>
</Grid>
<Rectangle Grid.Column="0" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
<Rectangle Grid.Column="1" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
<Rectangle Grid.Column="2" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
<Rectangle Grid.Column="3" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
<Rectangle Grid.Column="4" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
<Rectangle Grid.Column="5" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
</Grid>
</DataTemplate>
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
<TextBlock Grid.Row="1" HorizontalAlignment="Left" Margin="4,8">
<Hyperlink Command="{Binding add_alert_cmd}">
Click to Add Alert
</Hyperlink>
</TextBlock>
</Grid>
</DataTemplate>
<!--<DataTemplate DataType-->
</ResourceDictionary>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TabControl>
<TabItem Header="Trade Alerts" Content="{Binding trade_alerts}">
</TabItem>
</TabControl>
</Grid>
</UserControl>
@@ -0,0 +1,41 @@
using mbts.ui.controls;
using mbts.ui.converters;
using mbts.ui.utilities;
using System.Windows.Controls;
using System.Windows.Data;
namespace mbts.ui.spark {
/// <summary>
/// Interaction logic for ShellSettingsDialog.xaml
/// </summary>
partial class GlobalAlertsView : UserControl {
public static readonly IValueConverter
DoublePredicateTypeDisplayNameConverter = ValueConverter.create<DoublePredicate.PredicateTypeOneofCase, string>(t => {
switch (t) {
case DoublePredicate.PredicateTypeOneofCase.InsideRange: return "Between";
case DoublePredicate.PredicateTypeOneofCase.OutsideRange: return "Not Between";
case DoublePredicate.PredicateTypeOneofCase.None: return "(None)";
default: return DisplayNameHelper.camel_to_title_case(t.ToString());
}
});
public GlobalAlertsView() =>
InitializeComponent();
public enum Columns {
None = 0,
Delete,
IsEnabled,
Security,
Price,
Quantity,
Side,
Sound
}
}
class GlobalAlertsColumn : DIYGrid.ColumnBase<GlobalAlertsView.Columns> {
protected override void on_key_initialized(GlobalAlertsView.Columns key) =>
id = key.ToString();
}
}
@@ -0,0 +1,46 @@
using mbts.spark;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
namespace mbts.ui.spark {
class GlobalAlertsVM : ViewModelBase, IValueFactory<GlobalAlertSettings>, IRequestViewClose {
IReadOnlyDictionary<uint, SecurityMD> _security_map = DictionaryHelper.empty<uint, SecurityMD>();
public GlobalAlertsVM() {
ok_cmd = new DelegateCommand(() => {
apply();
RequestClose?.Invoke(this, EventArgs.Empty);
});
apply_cmd = new DelegateCommand(apply);
cancel_cmd = new DelegateCommand(() => RequestClose?.Invoke(this, EventArgs.Empty));
void apply() =>
request_commit?.Invoke();
}
public event Action request_commit;
public event EventHandler RequestClose;
public TradeAlertsVM trade_alerts { get; } = new TradeAlertsVM();
public ICommand apply_cmd { get; }
public ICommand ok_cmd { get; }
public ICommand cancel_cmd { get; }
public IReadOnlyDictionary<uint, SecurityMD> security_map {
get => _security_map;
set {
_security_map = value;
RaisePropertyChanged();
}
}
public GlobalAlertSettings create() =>
new GlobalAlertSettings(trade_alerts.create().ToArray());
public void load_from(GlobalAlertSettings value) {
trade_alerts.load_from(value.trade_alerts);
}
}
}
@@ -0,0 +1,28 @@
using bts.shell;
using mbts.ui.shell;
using mbts.ui.utility;
using System.Collections.Generic;
namespace mbts.spark {
class SoundNotificationVM : DisposableViewModelBase {
SoundNotification.SystemSound _system_sound;
public SoundNotificationVM() {
}
public SoundNotification.SystemSound system_sound { get => _system_sound; set => Set(ref _system_sound, value); }
public IReadOnlyList<SoundNotification.SystemSound> system_sound_options => EnumHelper.get_values<SoundNotification.SystemSound>();
public SoundNotification create() =>
new SoundNotification {
system_sound = system_sound
};
public void load_from(SoundNotification value) {
switch (value.sound_type_case) {
case SoundNotification.SoundTypeOneofCase.System: system_sound = value.system_sound.Value; break;
case SoundNotification.SoundTypeOneofCase.Uri: /*todo*/ break;
}
}
}
}
@@ -0,0 +1,39 @@
using bts.api;
using mbts.spark;
using mbts.ui.shell;
using mbts.ui.utility;
using System.Collections.Generic;
using System.Linq;
namespace mbts.ui.spark {
class TradeAlertPredicateVM : ViewModelBase, IValueFactory<TradeAlertPredicate> {
SecurityCriteria _security_criteria;
Side _side;
public SecurityCriteria security_criteria { get => _security_criteria; set => Set(ref _security_criteria, value); }
public DoublePredicateVM price { get; } = new DoublePredicateVM();
public DoublePredicateVM quantity { get; } = new DoublePredicateVM();
public Side side { get => _side; set => Set(ref _side, value); }
public IEnumerable<Side> side_options {
get {
yield return Side.Buy;
yield return Side.Sell;
yield return Side.Buy | Side.Sell;
}
}
public void load_from(TradeAlertPredicate value) {
security_criteria = value.security_criteria;
quantity.load_from(value.quantity);
price.load_from(value.price);
side = value.side.Aggregate(Side.Undefined, (flags, s) => flags | s);
}
public TradeAlertPredicate create() =>
new TradeAlertPredicate(
security_criteria,
quantity.create(),
price.create(),
EnumHelper.split_flags(side).ToArray());
}
}
@@ -0,0 +1,24 @@
using mbts.spark;
using mbts.ui.shell;
namespace mbts.ui.spark {
class TradeAlertVM : ViewModelBase, IValueFactory<TradeAlert> {
bool _is_enabled;
public TradeAlertPredicateVM predicate { get; } = new TradeAlertPredicateVM();
public SoundNotificationVM sound { get; } = new SoundNotificationVM();
public bool is_enabled { get => _is_enabled; set => Set(ref _is_enabled, value); }
public TradeAlert create() =>
new TradeAlert(
predicate.create(),
sound.create(),
is_enabled);
public void load_from(TradeAlert value) {
predicate.load_from(value.predicate);
sound.load_from(value.sound);
is_enabled = value.is_enabled;
}
}
}
@@ -0,0 +1,41 @@
using bts.api;
using bts.shell;
using mbts.spark;
using mbts.ui.shell;
using mbts.utils;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
namespace mbts.ui.spark {
class TradeAlertsVM : ViewModelBase, IValueFactory<IReadOnlyList<TradeAlert>> {
public TradeAlertsVM() {
add_alert_cmd = new DelegateCommand(() => {
var a = new TradeAlertVM();
a.load_from(_default_alert());
alerts.Add(a);
});
delete_alert_cmd = new DelegateCommand<TradeAlertVM>(a => alerts.Remove(a));
}
public ObservableCollection<TradeAlertVM> alerts { get; } = new ObservableCollection<TradeAlertVM>();
public ICommand add_alert_cmd { get; }
public ICommand delete_alert_cmd { get; }
public IReadOnlyList<TradeAlert> create() =>
alerts.Select(a => a.create()).ToList();
public void load_from(IReadOnlyList<TradeAlert> value) =>
alerts.sync_with_models(value);
static TradeAlert _default_alert() =>
new TradeAlert(
new TradeAlertPredicate(side: new[] { Side.Buy, Side.Sell }),
new SoundNotification {
system_sound = SoundNotification.SystemSound.BellCounter
},
true);
}
}
@@ -0,0 +1,24 @@
using bts.collections;
using bts.utils;
using mbts.collections;
using mbts.persist;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace mbts.spark {
class TradeSoundsVM : DisposableViewModelBase {
}
}
@@ -13,17 +13,11 @@
u:WindowStyle.AllowResize="True"
u:WindowStyle.SizeToContent="WidthAndHeight"
u:WindowStyle.TitleContent="{Binding}"
Style="{StaticResource BTSWindowTitleTemplate}"
Title="{Binding title, StringFormat='{}{0} Settings'}"
Icon="{x:Static u:Icons.Admin}"
Style="{StaticResource BTSDialog}"
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>
@@ -49,6 +43,8 @@
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
@@ -69,8 +65,10 @@
</Grid>
<TextBlock Grid.Row="2">Font Color (default)</TextBlock>
<c:ColorPickerPopupWithOpacitySlider Grid.Row="2" Grid.Column="2" HorizontalAlignment="Left" Width="{StaticResource ControlLongWidth}" DataContext="{Binding font_color}" CanUserEditOpacity="False"/>
<TextBlock Grid.Row="4">Selection Highlight</TextBlock>
<c:ColorPickerPopupWithOpacitySlider Grid.Row="4" Grid.Column="2" HorizontalAlignment="Left" Width="{StaticResource ControlLongWidth}" DataContext="{Binding selection_highlight}"/>
<TextBlock Grid.Row="4" Visibility="{Binding show_background_color, Converter={StaticResource BoolToVisibility}}">Background Color (default)</TextBlock>
<c:ColorPickerPopupWithOpacitySlider Grid.Row="4" Grid.Column="2" HorizontalAlignment="Left" Width="{StaticResource ControlLongWidth}" DataContext="{Binding background_color}" Visibility="{Binding DataContext.show_background_color, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=usp:GridSettingsView}, Converter={StaticResource BoolToVisibility}}" />
<TextBlock Grid.Row="6">Selection Highlight</TextBlock>
<c:ColorPickerPopupWithOpacitySlider Grid.Row="6" Grid.Column="2" HorizontalAlignment="Left" Width="{StaticResource ControlLongWidth}" DataContext="{Binding selection_highlight}"/>
</Grid>
</DataTemplate>
@@ -180,8 +178,7 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="1" Fill="{StaticResource HeaderBackgroundBorderBrush}"/>
<c:ActionMenu HorizontalAlignment="Right" Margin="0,2,0,0" ItemsSource="{Binding instance_vm.action_menu_items}"/>
<c:ActionMenu HorizontalAlignment="Right" Margin="0,2,0,0" DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=usp:ShellSettingsDialog}}" ItemsSource="{Binding action_menu_items}"/>
<usp:GridSettingsView Grid.Row="1" Margin="8,-8,8,8" GridSectionContent="{Binding}" GridSectionContentTemplate="{StaticResource GridSectionCoreContentTemplate}"/>
</Grid>
</DataTemplate>
@@ -213,7 +210,6 @@
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Rectangle Grid.ColumnSpan="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{StaticResource HeaderBackgroundBrush}" Stroke="{StaticResource HeaderBackgroundBorderBrush}" StrokeThickness="1"/>
<TextBlock Grid.Column="1" VerticalAlignment="Center">Profile:</TextBlock>
<ComboBox Grid.Column="2" Margin="8,4" ItemsSource="{Binding Source={StaticResource ProfilesViewSource}}" SelectedItem="{Binding selected_profile}" ItemTemplateSelector="{StaticResource ProfileItemTemplateSelector}">
<ComboBox.GroupStyle>
@@ -400,8 +396,7 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="1" Fill="{StaticResource HeaderBackgroundBorderBrush}"/>
<c:ActionMenu HorizontalAlignment="Right" Margin="0,2,0,0" ItemsSource="{Binding instance_vm.action_menu_items}"/>
<c:ActionMenu HorizontalAlignment="Right" Margin="0,2,0,0" DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=usp:ShellSettingsDialog}}" ItemsSource="{Binding action_menu_items}"/>
<usp:GridSettingsView Grid.Row="1" Margin="8,-8,8,8" DataContext="{Binding core_settings_vm}" GridSectionContent="{Binding DataContext.instance_vm, RelativeSource={RelativeSource AncestorType=usp:ShellSettingsDialog}}">
<usp:GridSettingsView.GridSectionContentTemplate>
<DataTemplate>
@@ -461,4 +456,4 @@
</c:DialogSkirt.Background>
</c:DialogSkirt>
</Grid>
</Window>
</Window>
@@ -136,8 +136,8 @@ namespace mbts.spark {
var skip_local = profiles.WrapSkip(1);
var skip_local_wrapper = skip_local.WrapSelector(
p => new SettingsProfile<T>(p.id, p.name, p.value.create(), p.security_criteria),
ns => new ProfileVM(ns.id, vm_factory(ns.value), ns.name, ns.security_criteria));
profiles.Add(_local_profile = new ProfileVM(0, vm_factory(init), "Custom", template_key: ShellSettingsDialog.LocalProfileItemTemplateKey));
ns => new ProfileVM(ns.id, vm_factory(ns.value), ns.name, ns.security_criteria, target_value: ns.value));
profiles.Add(_local_profile = new ProfileVM(0, vm_factory(init), "Custom", template_key: ShellSettingsDialog.LocalProfileItemTemplateKey, target_value: init));
_selected_profile = backing(nameof(selected_profile), _local_profile);
// dialog skirt items
_apply_item = new DialogSkirtItem("Apply", new DelegateCommand(apply));
@@ -325,26 +325,37 @@ namespace mbts.spark {
//
SecurityCriteria _security_criteria;
SettingsProfile<T> _committed, _last_result;
T _target_value;
string _name, _template_key;
bool _is_selected;
IDisposable _dispose;
public ProfileVM(uint id, TVM value, string name, SecurityCriteria security_criteria = null, string template_key = null) {
public ProfileVM(uint id, TVM value, string name, SecurityCriteria security_criteria = null, string template_key = null, T target_value = null) {
this.id = id;
this.value = value;
_name = name;
_security_criteria = security_criteria;
_template_key = template_key;
_target_value = target_value;
_committed = _last_result = create();
_is_dirty = backing(nameof(is_dirty), () => !_last_result.Equals(_committed));
_refresh_is_dirty = new DeferrableActionWrapper(_is_dirty.refresh);
var sub = value.Subscribe(v => {
_last_result = _last_result.with(value: v);
_refresh_is_dirty.execute();
_on_value_changed();
});
_dispose = value is IDisposable d ? Disposable.Create(sub, d) : sub;
}
void _on_value_changed() {
if (_target_value != null && is_dirty && _last_result.value.Equals(_target_value)) {
_target_value = null;
_committed = _last_result;
_refresh_is_dirty.execute();
}
}
public uint id { get; }
public TVM value { get; }
public string name {
@@ -380,21 +391,26 @@ namespace mbts.spark {
/// assign directly to <see cref="_last_result"/>, to capture props that aren't visible in the dialog (i.e., props that are manipulated in the main window)
_last_result = data;
}
_on_value_changed();
}
public void load_value_from(T value) {
using (_refresh_is_dirty.defer_execution())
this.value.load_from(value);
_on_value_changed();
}
public void commit(SettingsProfile<T> data) {
using (_refresh_is_dirty.defer_execution())
_load_from(_committed = data);
_on_value_changed();
}
public void commit_current() {
_committed = _last_result;
_target_value = _committed.value;
_refresh_is_dirty.execute();
_on_value_changed();
}
public void revert() {
@@ -402,6 +418,7 @@ namespace mbts.spark {
_load_from(_committed);
_last_result = _committed;
}
_on_value_changed();
}
public IObservable<bool> subscribe_is_dirty() => _is_dirty.observe_current(true);
@@ -414,6 +431,7 @@ namespace mbts.spark {
public override string ToString() => _name;
void _load_from(SettingsProfile<T> data) {
_target_value = data.value;
name = data.name;
security_criteria = data.security_criteria;
value.load_from(data.value);
@@ -1,4 +1,5 @@
using System;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -9,8 +10,6 @@ namespace mbts.ui.shell
{
public class WorkspaceViewModel : ViewModelBase
{
private const string INITIAL_VISUAL_STATE = "Normal";
WorkspacesModel _parent;
public WorkspaceViewModel(WorkspacesModel parent, string name, WorkspaceDefinition definition) {
_parent = parent;
@@ -19,45 +18,12 @@ namespace mbts.ui.shell
}
public string Name { get; private set; }
public DateTime LastSaveTime => Definition.last_save_lldatetime.lldatetime().ToLocalTime();
public bool IsShared { get { return Definition.is_shared; } }
public object Icon {
get { return Icons.Window; }
}
private string _vstate = INITIAL_VISUAL_STATE;
public string VisualState {
get {
// this unconventional getter acts as a reset after the VisualStateManager has played
// i.e. if we overwrite a workspace then save, then close the window, when we open the
// window back up, we'll see item animate b/c it has never been reset from the animate
// state. This is necessary b/c the lifetime of Workspaces is not tied to the lifetime
// of the View, rather it lives in the Model and is only instantiated on startup via MEF
var return_val = _vstate;
_vstate = INITIAL_VISUAL_STATE;
return return_val;
}
set {
_vstate = value;
RaisePropertyChanged("VisualState");
}
}
internal WorkspaceDefinition Definition { get; private set; }
public ICommand ActivateCommand {
get {
return new DelegateCommand(param => {
var unused = _parent.load_workspace(Definition, Name);
});
}
}
public ICommand DeleteCommand {
get {
return new DelegateCommand(param => {
_parent.delete_workspace(Name, Definition);
});
}
}
}
}
@@ -6,6 +6,7 @@
xmlns:local="clr-namespace:mbts.ui.shell"
mc:Ignorable="d"
xmlns:controls="clr-namespace:mbts.ui.controls"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:ui="clr-namespace:mbts.ui"
Width="250"
MaxHeight="700"
@@ -13,54 +14,108 @@
ui:WindowStyle.ShowMinimizeButton="False"
Style="{StaticResource BTSDialog}"
Title="Workspaces"
Topmost="True"
>
<Window.Resources>
<ResourceDictionary>
<Style x:Key="CommonList" TargetType="{x:Type ItemsControl}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Window.Resources>
<ResourceDictionary>
<Style x:Key="CommonList" TargetType="{x:Type ItemsControl}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="1" Margin="5">
<GroupBox Header="Existing Workspaces">
<StackPanel>
<ComboBox x:Name="workspaces_cb" Margin="4" Grid.IsSharedSizeScope="True" ItemsSource="{DynamicResource header_and_data}" SelectedValue="{Binding selected_workspace, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<!-- Inspiration for the table like appearance with headers taken from
https://stackoverflow.com/questions/5807899/multiple-item-combo-box-with-headers
-->
<ComboBox.Resources>
<CompositeCollection x:Key="header_and_data">
<ComboBoxItem IsEnabled="False">
<Grid TextElement.FontWeight="Bold">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="NameGroup"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="LastSaveTime_DateGroup" />
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="LastSaveTime_TimeGroup" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Name"/>
<TextBlock Grid.Column="2" Grid.ColumnSpan="3" Text="Last Save Time"/>
</Grid>
</ComboBoxItem>
<Separator/>
<CollectionContainer Collection="{Binding Source={x:Reference workspaces_cb}, Path=DataContext.Workspaces}"/>
</CompositeCollection>
<DataTemplate DataType="{x:Type local:WorkspaceViewModel}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border BorderThickness="0,0,0,0" BorderBrush="{ui:BTSColor UI.Button.normal_border}">
<Grid Margin="5 7 5 7">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock>Save Current Workspace As:</TextBlock>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<TextBox Margin="4" Text="{Binding SaveName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" />
<Button Grid.Column="1" HorizontalAlignment="Right" Margin="4" Command="{Binding SaveWorkspace}" IsDefault="True" MinWidth="60">Save</Button>
</Grid>
</Grid>
</Border>
<ScrollViewer Grid.Row="1" Grid.ColumnSpan="2" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Workspaces}" Style="{StaticResource CommonList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:WorkspaceItemControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="NameGroup"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="LastSaveTime_DateGroup" />
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="LastSaveTime_TimeGroup" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Name}"/>
<TextBlock Grid.Column="2" HorizontalAlignment="Right">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding LastSaveTime, StringFormat=d}" />
<Style.Triggers>
<DataTrigger Binding="{Binding LastSaveTime}" Value="{x:Static sys:DateTime.MinValue}">
<Setter Property="Text" Value="" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Grid.Column="4" HorizontalAlignment="Right">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding LastSaveTime, StringFormat=T}" />
<Style.Triggers>
<DataTrigger Binding="{Binding LastSaveTime}" Value="{x:Static sys:DateTime.MinValue}">
<Setter Property="Text" Value="" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
<!--controls:DialogSkirtOKCancel Grid.Row="1" Grid.ColumnSpan="2"/-->
</Grid>
</DataTemplate>
</ComboBox.Resources>
</ComboBox>
<UniformGrid Rows="1" Margin="4" >
<Button Grid.Column="1" MinWidth="60" Command="{Binding LoadCommand}">Load</Button>
<Button Grid.Column="2" MinWidth="60" Command="{Binding SaveCommand}">Save</Button>
<Button Grid.Column="3" MinWidth="60" Command="{Binding DeleteCommand}">Delete</Button>
</UniformGrid>
</StackPanel>
</GroupBox>
<Border BorderThickness="0,0,0,0" BorderBrush="{ui:BTSColor UI.Button.normal_border}">
<Grid Margin="5 7 5 7">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<TextBox Margin="4" Text="{Binding SaveName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" />
<Button Grid.Column="2" MinWidth="60" Command="{Binding SaveNewWorkspace}">Create New</Button>
</Grid>
</Grid>
</Border>
</StackPanel>
</Grid>
</Window>
@@ -8,12 +8,15 @@ using System.Threading.Tasks;
using mbts;
using mbts.ui;
using mbts.ui.shell;
using bts.spark;
using System.Windows;
namespace mbts.ui.shell
{
public class WorkspacesDialogViewModel : ViewModelBaseWithValidation
{
private WorkspacesModel _work_model;
IRaiseCanExecuteChangedCommand _load_command, _delete_command, _save_command;
public WorkspacesDialogViewModel(WorkspacesModel wm)
{
@@ -25,36 +28,74 @@ namespace mbts.ui.shell
public bool IsPublicWorkspace { get; set; }
public bool ShowWorkspaces { get; set; }
public ICommand SaveWorkspace
WorkspaceViewModel _selected_workspace;
public WorkspaceViewModel selected_workspace {
get => _selected_workspace;
set {
if (Set(ref _selected_workspace, value)) {
LoadCommand.TryRaiseCanExecuteChanged();
DeleteCommand.TryRaiseCanExecuteChanged();
SaveCommand.TryRaiseCanExecuteChanged();
}
}
}
private bool _command_can_execute() => selected_workspace != null;
private void _clear_error_state() => clear_error(nameof(SaveName));
public ICommand LoadCommand => _load_command ??
(_load_command = new DelegateCommand(
() => {
_work_model.load_workspace(selected_workspace.Definition, selected_workspace.Name).ignore();
_clear_error_state();
}, _command_can_execute));
public ICommand DeleteCommand => _delete_command ??
(_delete_command = new DelegateCommand(
() => {
if (BTSMessageBox.Show(
$"Delete the \"{selected_workspace.Name}\" workspace?",
"Delete Existing Workspace Confirmation",
BTSMessageBoxButtons.Yes | BTSMessageBoxButtons.No,
MessageBoxImage.Question) == BTSMessageBoxButtons.Yes) {
_work_model.delete_workspace(selected_workspace.Name, selected_workspace.Definition).ignore();
_clear_error_state();
}
}, _command_can_execute));
public ICommand SaveCommand => _save_command ??
(_save_command = new DelegateCommand(
() => {
if (BTSMessageBox.Show(
$"Overwrite \"{selected_workspace.Name}\" with current layout?",
"Save Existing Workspace Confirmation",
BTSMessageBoxButtons.Yes | BTSMessageBoxButtons.No,
MessageBoxImage.Question) == BTSMessageBoxButtons.Yes) {
_work_model.save_workspace(selected_workspace.Name, ispublic:selected_workspace.Definition.is_shared);
_clear_error_state();
}
}, _command_can_execute));
public ICommand SaveNewWorkspace
{
get
{
return new DelegateCommand(arg => {
_clear_error_state();
// Check for errors in the input
if (string.IsNullOrEmpty(SaveName))
{
set_error("SaveName", "Workspace must be named");
if (string.IsNullOrEmpty(SaveName)) {
set_error(nameof(SaveName), "Workspace must be named");
return;
}
if (SaveName.Contains(WorkspacesModel.ACTIVE_WORKSPACE_PREFIX))
{
set_error("SaveName", "Workspace name cannot contains '{0}'".fmt(WorkspacesModel.ACTIVE_WORKSPACE_PREFIX));
if (SaveName.Contains(WorkspacesModel.ACTIVE_WORKSPACE_PREFIX)) {
set_error(nameof(SaveName), "Workspace name cannot contains '{0}'".fmt(WorkspacesModel.ACTIVE_WORKSPACE_PREFIX));
return;
}
else
{
clear_error("SaveName");
if (Workspaces.Any(x => x.Name.Replace(WorkspacesModel.SHARED_MONIKER, string.Empty) == SaveName && x.IsShared == IsPublicWorkspace)) {
set_error(nameof(SaveName), $"Workspace already exists '{SaveName}'");
return;
}
var save_candidate_already_exists = Workspaces.Any(x => x.Name.Replace(WorkspacesModel.SHARED_MONIKER, string.Empty) == SaveName && x.IsShared == IsPublicWorkspace);
// Save
_work_model.save_workspace(SaveName, ispublic: IsPublicWorkspace);
// if we just overwrote something, have the vm reflect it
if (save_candidate_already_exists)
Workspaces.Where(x => x.Name.Replace(WorkspacesModel.SHARED_MONIKER, string.Empty) == SaveName && x.IsShared == IsPublicWorkspace).ForEach(x => x.VisualState = "Overridden");
});
}
}
}
}
@@ -179,6 +179,7 @@ namespace mbts.ui.shell
public void save_workspace(string name, bool persist=true, bool ispublic=false) {
WorkspaceDefinition workspace = get_workspace();
workspace.is_shared = ispublic;
workspace.last_save_lldatetime = LLDateTime.now().value;
if (ispublic) { name += SHARED_MONIKER; }
// Remove the old one if a workspace by this name exists already
@@ -282,5 +283,8 @@ namespace mbts.ui.shell
[ProtoMember(10, IsRequired = false)]
public bool is_shared { get; set; }
[ProtoMember(11, IsRequired = false)]
public ulong last_save_lldatetime { get; set; }
}
}
@@ -91,6 +91,10 @@ namespace mbts.spark {
public static double sod_pos(this PositionUnitImmutable position_unit) => position_unit.SodLongPos - position_unit.SodShortPos;
public static double day_pos(this PositionUnitImmutable position_unit) => position_unit.DayLongPos - position_unit.DayShortPos;
public static double total_pos(this PositionUnitImmutable position_unit) => position_unit.sod_pos() + position_unit.day_pos();
// raw day
public static double raw_day_long_pos(this PositionUnitImmutable position_unit) => position_unit.HasRawDay ? position_unit.RawDay.LongPos : double.NaN;
public static double raw_day_short_pos(this PositionUnitImmutable position_unit) => position_unit.HasRawDay ? position_unit.RawDay.ShortPos : double.NaN;
public static double raw_day_pos(this PositionUnitImmutable position_unit) => position_unit.HasRawDay ? position_unit.RawDay.LongPos - position_unit.RawDay.ShortPos : double.NaN;
public static void lazy_assert(bool test, Func<string> get_msg) {
if (!test)
+17 -1
View File
@@ -197,6 +197,7 @@ namespace mbts.spark {
static class TickStore {
public static readonly RegionalTickFactors default_rtf = new RegionalTickFactors(new TickFactors(1000, 1), new TickFactors(1000, 1), double.MaxValue);
static readonly ConcurrentDictionary<SpecKey, RegionalTickFactors> _exchange_ticks = new ConcurrentDictionary<SpecKey, RegionalTickFactors>();
static readonly bool _som_ticks_fix_enabled = config.Configuration.get<bts.config.toggles.SOMTicksFix>().enabled;
public static RegionalTickFactors get(ContractSpecification spec) =>
_exchange_ticks.GetOrAdd(new SpecKey(spec), _create_rtf);
@@ -249,8 +250,23 @@ namespace mbts.spark {
var tf = _try_create_tf(tick_size, tick_base);
if (tf != null && display_digits_override > 0) {
int display_digit_diff = (int)display_digits_override - tf.display_digits();
if (display_digit_diff != 0)
if (_som_ticks_fix_enabled) {
// we can always add display_digits (i.e. dislpay_digit_diff > 0), of course the ticks can be expressed precisely with extra 0s.
// but if we are going to remove display_digits (i.e display_digit_diff < 0), we must make sure the tick_unit is large enough to be expressed precisely with that many fewer digits
// - "-display_digit_diff <= Math.Ceiling(Math.Log10(tf.tick_unit))" will tell us if the digits can be removed without compromising precision
// - unfortunately, we cannot rely on the exchange's ContractSpec for this, there is 1 known case where the data they give us is incorrect
// SOM spreads ContractSpec (from the exchange):
// LowTick: 0.0025 (aka 1/400, or 2/800)
// MainFraction: 8
// SubFraction: 0
// PriceDisplayFormat: 1
// - as you can see, this cannot possibly be formatted with 1 display digit, it ticks in increments of 1/400
// - in practice, to maintain the '8' base, we format this in increments of 2/800 (e.g. 1-000, 1-002, 1-004, and so on up to 1-778, 2-000)
if (display_digit_diff > 0 || (display_digit_diff < 0 && -display_digit_diff <= Math.Ceiling(Math.Log10(tf.tick_unit))))
tf = _inflate_to_base(tf, tf.tick_base * Math.Pow(10, display_digit_diff));
} else if (display_digit_diff != 0) {
tf = _inflate_to_base(tf, tf.tick_base * Math.Pow(10, display_digit_diff));
}
}
return tf;
}
+11 -1
View File
@@ -65,6 +65,8 @@ namespace mbts.spark {
grouping.Where(y => y.fill.FillKind != Fill.Types.FillKind.Sod).Sum(y => (uint)y.sell_quantity),
grouping.Sum(y => (int)(y.buy_quantity - y.sell_quantity)),
grouping.Where(y => y.fill.FillKind == Fill.Types.FillKind.Sod).Sum(y => (int)(y.buy_quantity - y.sell_quantity)),
double.NaN,
double.NaN,
grouping.weighted_average(y => y.fill.LastPrice, y => y.buy_quantity),
grouping.weighted_average(y => y.fill.LastPrice, y => y.sell_quantity),
grouping.Select(i => i.fill),
@@ -95,6 +97,8 @@ namespace mbts.spark {
(uint)pos.DayShortPos,
(int)pos.total_pos(),
(int)pos.sod_pos(),
pos.raw_day_long_pos(),
pos.raw_day_short_pos(),
pos.DayLongAvgPrice,
pos.DayShortAvgPrice,
null,
@@ -104,7 +108,7 @@ namespace mbts.spark {
// remove rows for which there is no net pos or no day pos
summary_position_rows = summary_position_rows.Where(r =>
!(r.start_net_position == 0 && r.day_long_quantity == 0 && r.day_short_quantity == 0)
!(r.start_net_position == 0 && r.day_long_quantity == 0 && r.day_short_quantity == 0 && r.raw_day_long_position == 0 && r.raw_day_short_position == 0)
);
_pnl_data.sync_with(summary_position_rows);
_last_pnl_update_time = DateTime.Now;
@@ -124,6 +128,7 @@ namespace mbts.spark {
start_pnl_filter.load_state(fs.start_pnl);
day_long_quantity_filter.load_state(fs.day_long_pos);
day_short_quantity_filter.load_state(fs.day_short_pos);
raw_day_net_position_filter.load_state(fs.raw_day_net_pos);
day_pnl_filter.load_state(fs.day_pnl);
total_net_position_filter.load_state(fs.total_net_pos);
total_pnl_filter.load_state(fs.total_pnl);
@@ -146,6 +151,8 @@ namespace mbts.spark {
add_col(Columns.StartPNL, DefaultColumnHeaders.StartPnL, r => r.start_pnl_text, nameof(PnLSummaryRow.start_pnl), DefaultColumnWidths.PnL_Generic, start_pnl_filter, DataGridFilterKnownTemplates.DoubleMinMax, HorizontalAlignment.Right, r => r.start_pnl_background);
add_col(Columns.DayLong, DefaultColumnHeaders.DayLong, r => r.day_long_quantity.ToString(), nameof(PnLSummaryRow.day_long_quantity), DefaultColumnWidths.PnL_Generic, day_long_quantity_filter, DataGridFilterKnownTemplates.UIntMinMax, HorizontalAlignment.Right);
add_col(Columns.DayShort, DefaultColumnHeaders.DayShort, r => r.day_short_quantity.ToString(), nameof(PnLSummaryRow.day_short_quantity), DefaultColumnWidths.PnL_Generic, day_short_quantity_filter, DataGridFilterKnownTemplates.UIntMinMax, HorizontalAlignment.Right);
// convert nan to 0 so text is consistent with other position columns
add_col(Columns.RawDayPos, DefaultColumnHeaders.RawDayPos, r => SparkUtils.fmt_position(r.raw_day_net_position), nameof(PnLSummaryRow.raw_day_net_position), DefaultColumnWidths.PnL_Generic, raw_day_net_position_filter, DataGridFilterKnownTemplates.DoubleMinMax, HorizontalAlignment.Right);
add_col(Columns.DayPNL, DefaultColumnHeaders.DayPnL, r => r.day_pnl_text, nameof(PnLSummaryRow.day_pnl), 90, day_pnl_filter, DataGridFilterKnownTemplates.DoubleMinMax, HorizontalAlignment.Right, r => r.day_pnl_background);
add_col(Columns.TradedToday, "Traded Today", r => r.has_day_trades_text, nameof(PnLSummaryRow.has_day_trades), 100, has_day_trades_filter, DataGridFilterKnownTemplates.YesNo, HorizontalAlignment.Center);
add_col(Columns.TotalNetPos, DefaultColumnHeaders.TotalNetPos, r => r.total_net_position.ToString(), nameof(PnLSummaryRow.total_net_position), DefaultColumnWidths.Total_Net_Pos, total_net_position_filter, DataGridFilterKnownTemplates.DoubleMinMax, HorizontalAlignment.Right);
@@ -188,6 +195,7 @@ namespace mbts.spark {
start_pnl_filter,
day_long_quantity_filter,
day_short_quantity_filter,
raw_day_net_position_filter,
day_pnl_filter,
total_net_position_filter,
total_pnl_filter,
@@ -311,6 +319,7 @@ namespace mbts.spark {
public IDataGridColumnPersistentFilter<PnLSummaryRow, DoubleMinMaxState> start_pnl_filter { get; } = new DoubleMinMax<PnLSummaryRow>(r => r.start_pnl, nameof(PnLSummaryRow.start_pnl));
public IDataGridColumnPersistentFilter<PnLSummaryRow, UIntMinMaxState> day_long_quantity_filter { get; } = new UIntMinMax<PnLSummaryRow>(r => r.day_long_quantity, nameof(PnLSummaryRow.day_long_quantity));
public IDataGridColumnPersistentFilter<PnLSummaryRow, UIntMinMaxState> day_short_quantity_filter { get; } = new UIntMinMax<PnLSummaryRow>(r => r.day_short_quantity, nameof(PnLSummaryRow.day_short_quantity));
public IDataGridColumnPersistentFilter<PnLSummaryRow, DoubleMinMaxState> raw_day_net_position_filter { get; } = new DoubleMinMax<PnLSummaryRow>(r => r.raw_day_net_position, nameof(PnLSummaryRow.raw_day_net_position));
public IDataGridColumnPersistentFilter<PnLSummaryRow, DoubleMinMaxState> day_pnl_filter { get; } = new DoubleMinMax<PnLSummaryRow>(r => r.day_pnl, nameof(PnLSummaryRow.day_pnl));
public IDataGridColumnPersistentFilter<PnLSummaryRow, IntMinMaxState> total_net_position_filter { get; } = new IntMinMax<PnLSummaryRow>(r => r.total_net_position, nameof(PnLSummaryRow.total_net_position));
public IDataGridColumnPersistentFilter<PnLSummaryRow, DoubleMinMaxState> total_pnl_filter { get; } = new DoubleMinMax<PnLSummaryRow>(r => r.total_pnl, nameof(PnLSummaryRow.total_pnl));
@@ -329,6 +338,7 @@ namespace mbts.spark {
start_pnl = start_pnl_filter.get_state(),
day_long_pos = day_long_quantity_filter.get_state(),
day_short_pos = day_short_quantity_filter.get_state(),
raw_day_net_pos = raw_day_net_position_filter.get_state(),
day_pnl = day_pnl_filter.get_state(),
total_net_pos = total_net_position_filter.get_state(),
total_pnl = total_pnl_filter.get_state(),
@@ -3,7 +3,7 @@ using System.Windows.Controls;
namespace mbts.spark {
internal partial class PAndLView : UserControl {
public enum Columns { Account, Exchange, Symbol, Security, SecurityType, StartNetPos, StartPNL, DayLong, DayShort, DayPNL, TotalNetPos, TotalPNL, PNLPrice, TradedToday }
public enum Columns { Account, Exchange, Symbol, Security, SecurityType, StartNetPos, StartPNL, DayLong, DayShort, DayPNL, TotalNetPos, TotalPNL, PNLPrice, TradedToday, RawDayPos }
public PAndLView(PAndLVM vm) {
InitializeComponent();
@@ -24,7 +24,8 @@ namespace mbts.spark {
PAndLWindowFactory _factory;
IReadOnlyList<SparkCommand> _commands;
public PAndLWindow(OrdersModel orders_model, PAndLWindowFactory factory, IBus bus, PAndLWindowFactory.PAndLStartupData startup_data, ImageSource Icon, WindowSettingsManager<GridSettings> settings_manager) : base("P&L", bus, factory, SparkCommands.ShowPAndL.icon) {
public PAndLWindow(OrdersModel orders_model, PAndLWindowFactory factory, IBus bus, PAndLWindowFactory.PAndLStartupData startup_data, ImageSource Icon, IWindowSettingsManager<GridSettings> settings_manager)
: base("P&L", bus, factory, SparkCommands.ShowPAndL.icon) {
_orders_model = orders_model;
_factory = factory;
_startup_data = startup_data;
Loaded 100 of 325 files, more files were not shown because too many files have changed in this diff. Show more