Compare commits

...
542 Commits
Author SHA1 Message Date
Kevin Darby e0140aadaf Merge branch 'master' of bitbucket.org:bluetradesys/spark into fix2 2019-09-23 16:50:05 -04:00
Kevin Darby 276e9ad0ef Merged in fix_expired_fill_crash (pull request #358)
Fix expired fill crash
2019-09-23 17:50:02 +00:00
Pedro Pinto 5968ac10d2 The fix is right, but the code was a bit clumsy. 2019-09-23 13:48:26 -04:00
John Colvin 9bf7666c30 downgrade pnl rounding error Log assert to warning 2019-09-23 11:56:57 -05:00
Kevin Darby b9fd0e2e28 in SIM you can trade an expired security and if we leave it out of this map
we will later attempt to add this fill twice while testing SOD because the test
asks to delete the fills db
2019-09-23 12:31:03 -04:00
Pedro Pinto ec1d925dbe Added to .gitignore 2019-09-22 08:25:10 -04:00
Kevin Darby 35a69f42bd porting fix over to c++ 2019-09-20 15:17:04 -04:00
John Colvin 633ebf38a2 restore original ComboBox default padding on the left 2019-09-19 18:58:19 -05:00
John Colvin 1eb8352b6b OrdersModel blocks GlbxIceberg orders from going out to non-glbx exchange 2019-09-19 17:21:48 -05:00
John Colvin 33c201b395 remove unused SparkTag.CreateOrder 2019-09-19 17:21:17 -05:00
Kevin Darby 0c9a9fcb5e git lfs 2019-09-19 13:56:01 -04:00
Kevin Darby 6c6fb74506 Merged in minimal_api_tests (pull request #348)
RTM. Minimal api tests

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-09-19 17:26:14 +00:00
Pedro Pinto a69ed30e70 Updated release notes. 2019-09-19 13:19:52 -04:00
Eric Harding a654f302a2 Merged in SP-106-md-crashy (pull request #351)
SP-106 md crashy
2019-09-19 17:10:35 +00:00
John Colvin 9ca3285888 miniob 'CheckColumn' is visible 2019-09-19 11:56:47 -05:00
Eric Harding df4c3b9376 Merge branch 'master' of bitbucket.org:bluetradesys/spark into SP-106-md-crashy 2019-09-19 09:11:10 -04:00
Pedro Pinto f21c9f96d1 Merged from prod 2019-09-18 16:25:35 -04:00
Pedro Pinto 16a71a9f63 Updated release notes. 2019-09-18 16:24:09 -04:00
Kevin Darby a8689aee06 verified client api examples work 2019-09-18 15:13:53 -04:00
Kevin Darby 85b2d257a7 updated the example excel sheet, added some manual tests to vet api docs and excel 2019-09-18 14:57:40 -04:00
Michael Harding f49ca9f7bd Merged in system-tests (pull request #347)
System tests
2019-09-18 18:39:11 +00:00
Michael Harding 6aae7831bc typo 2019-09-18 13:44:49 -04:00
Eric Harding 297233eae6 Clientbuild probably needs this too 2019-09-18 13:28:00 -04:00
Michael Harding 69dcbdd348 Set type in system tests 2019-09-18 13:08:49 -04:00
Eric Harding 0114dd0276 SP-106 MDConsumerHandle crash both managed+native 2019-09-18 12:58:15 -04:00
Eric Harding ffd35aebac Enable btree_map.h on windows 2019-09-18 09:52:41 -04:00
Pedro Pinto b92fed4f47 Merged from prod 2019-09-17 16:59:03 -04:00
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
Pedro Pinto fd6eedda87 Fixed typo in release notes. 2019-09-17 13:10:21 -04:00
Pedro Pinto 1ef2b2f55d Updated release notes. 2019-09-17 11:44:51 -04:00
John Colvin e2cd1ec2f3 Merged in pnl_draft (pull request #344)
RTM Pnl draft
2019-09-17 15:22:30 +00:00
John Colvin 110a5f74de Merge remote-tracking branch 'origin/master' into pnl_draft 2019-09-16 22:38:35 -05:00
John Colvin 6d20c782ca add null check to FillTuple 2019-09-16 22:18:12 -05:00
John Colvin fa88ee6f68 PNL fills/order popups are filtered for visible rows (i.e., the item's account/sec pair must be visible) 2019-09-16 19:32:37 -05:00
Taha Afzal 22ef965840 Merge branch 'master' of bitbucket.org:bluetradesys/spark 2019-09-16 19:05:27 -04:00
Taha Afzal 09a19b702c Fixing SOD time's timezone issues 2019-09-16 19:05:21 -04:00
John Colvin c90915fb1c fix pnl 'selected total' orderbook popup input filter 2019-09-16 17:23:50 -05:00
John Colvin 0fc2a55378 Merged in fills-root-algo (pull request #341)
RTM Fills root algo
2019-09-16 20:09:12 +00:00
Pedro Pinto 38b466d305 Updated release notes. 2019-09-16 16:08:41 -04:00
John Colvin 0052c88804 PNL fix filter on group working orders popup 2019-09-16 15:05:51 -05:00
Pedro Pinto 6d183e0db9 Merge branch 'master' into fills-root-algo 2019-09-16 16:01:06 -04:00
John Colvin ccbf1e1323 fix MiniOrderBookView 'base()' -> 'this()' typo 2019-09-16 14:58:32 -05:00
Michael Harding c3e43beada Merged in icons (pull request #337)
RTM Make icons more consistent
2019-09-16 19:56:37 +00:00
Pedro Pinto ab44da6a96 Merge branch 'master' into icons 2019-09-16 15:48:08 -04:00
John Colvin dbad27e97d discrete values filter tableupdate impl was selecting lazy items twice 2019-09-16 14:19:49 -05:00
Pedro Pinto 10656f7071 Merge branch 'master' into icons 2019-09-16 15:15:15 -04:00
Eric Harding ed00025a01 Merged in SP-214-provide-security-state-signal (pull request #338)
RTM: SP-214 provide security state signal

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-09-16 19:13:15 +00:00
Pedro Pinto 7d076437a5 Updated release notes 2019-09-16 15:11:23 -04:00
John Colvin b7ee9aa208 Merge remote-tracking branch 'origin/master' into fills-root-algo 2019-09-16 13:58:40 -05:00
Pedro Pinto 601b989e75 Merge branch 'master' into SP-214-provide-security-state-signal 2019-09-16 14:55:33 -04:00
John Colvin 02c8714cfd change pnl defaults to use 'Mid' calc type and hide Selected/Total headers 2019-09-16 13:55:13 -05:00
John Colvin e25514b75e PNL closes filter popups on Apply/Clear click 2019-09-16 12:48:14 -05:00
John Colvin 0f05a45d0d organize PNL FilterStates members (use common pattern to break up sod/day/total/working/etc members) 2019-09-16 12:48:14 -05:00
John Colvin 8107b058b0 remove Same/Opposite pnl cacluation options 2019-09-16 12:14:24 -05:00
John Colvin 7d3efdb220 pnl supports 0 group cols (was hiding all rows in that case) 2019-09-16 12:02:05 -05:00
John Colvin e335c54f20 PNLSettingsVM - move anchor cols to the left, order wrt grouping hierarchy 2019-09-16 11:35:01 -05:00
John Colvin 5b748feb5c DIYGrid - set UseLayoutRounding=False in GridViewColumnHeader style 2019-09-16 11:09:08 -05:00
John Colvin a1b9bad321 don't use layout rounding in shell settings dialog 2019-09-16 10:58:51 -05:00
John Colvin e6ef795de7 pnl auxiliary popups open at the bottom left of the target cell 2019-09-16 10:45:29 -05:00
John Colvin aa88a0906d fix PopupAdorner initial position (when opening under target element, rather than mouse) 2019-09-16 10:44:40 -05:00
John Colvin 4b75aa6080 specify comparer for pnl working order pos cols 2019-09-16 10:23:32 -05:00
John Colvin 7b5155f599 pnl default filtering: 'sec type = fut' and 'has day trades = true' 2019-09-16 09:50:26 -05: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
John Colvin 657973c2d4 pnl toggles group 'is_expanded' on double click 2019-09-15 20:05:15 -05:00
John Colvin c509669de9 hookup pnl grid background 2019-09-15 19:47:51 -05:00
John Colvin d79378aa28 initialize _enable_background_color in GridSettings default constructor (dropped line in master -> pnl_draft merge) 2019-09-15 19:42:44 -05:00
John Colvin 5799d5ed1e style cop method grouping in PNLVM 2019-09-15 19:24:06 -05:00
John Colvin 05a7f9fa72 most pnl columns are right aligned by default 2019-09-15 19:10:09 -05:00
John Colvin fef0143ee9 increase PNLSettings working long/short avg price col default width 2019-09-15 19:10:08 -05:00
John Colvin ece8a12f4a hook up cpp filter in PNLView.xaml 2019-09-15 19:10:08 -05:00
John Colvin 6758c94adf add 'security key' column to pnl 2019-09-15 19:10:04 -05:00
John Colvin 8930f59cd6 pnl wasn't saving cpp filter state (with ~50 columns, really helps to keep col-based members tightly organized) 2019-09-15 19:07:37 -05:00
John Colvin 967f789114 add security_key prop to PNL FilterStates (recategorize has_day_trades and cpp while we're at it) 2019-09-15 19:07:37 -05:00
John Colvin d1210b06cc fix pnl group-row sort 2019-09-15 19:07:34 -05:00
John Colvin caeda6c12a better categories for a few column enum vals in pnl window 2019-09-15 18:38:49 -05:00
John Colvin b6bf02045e DIYGrid sorting was checking wrong event args' 'Handled' 2019-09-15 12:23:33 -05:00
John Colvin 156561c976 hookup pnl group row [alternating]background colors 2019-09-15 12:02:54 -05:00
John Colvin 6a433b8cef add PartialConverter 2019-09-15 11:55:31 -05:00
John Colvin 1d9f0de409 add DynamicAlternationConverter 2019-09-15 11:53:41 -05:00
John Colvin 66b06d7190 PNLSettings invalidates _grid_cell_appearance when grid prop is assigned 2019-09-14 10:20:18 -05:00
John Colvin 9c160f5e41 define pnl auxiliary popup in xaml (also fixes overlapping text in title) 2019-09-14 10:15:06 -05:00
John Colvin 19f4bfb6ab MiniOrderBookView default constructor (so it can be declared in xaml) 2019-09-13 23:27:33 -05:00
John Colvin a06dc98b98 move 'FocusOnLoaded' attached prop from TextBoxBehavior -> Behavior (it's a general behavior) 2019-09-13 22:45:22 -05:00
John Colvin 95b6382a3a hook up pnl header row background setting 2019-09-13 19:19:27 -05:00
John Colvin 419e8550c1 PNLSettings 'with' method assigns props in same order that Equals impl checks equality (easier to spot incongruence) 2019-09-13 19:19:06 -05:00
John Colvin e29c6a1a22 PNLSettings Equals impl includes header_row_animations 2019-09-13 19:19:06 -05:00
John Colvin daceee2af9 remove 'create_dp' overload that was being called inadvertantly bc compiler interpretted assignment lambda to be a Func instead of Action 2019-09-13 18:57:14 -05:00
John Colvin 4594282f2a alphabetize non-proto props in PNLSettings 2019-09-13 18:21:12 -05:00
John Colvin 58ca003c94 ColorPicker (w/ opacity) - increase opacity % text min width so it doesn't expand at 100% (bc of the extra digit) 2019-09-13 17:45:54 -05:00
John Colvin 70c0d422c8 PNLCellAppearanceVM actually loads the font_color 2019-09-13 17:41:23 -05:00
John Colvin a04871f15f PNLSettings equals impl includes 'header_columns' 2019-09-13 17:02:58 -05:00
John Colvin f9b67c7ee4 PNL group/header font color settings work now 2019-09-13 17:01:36 -05:00
John Colvin 69fdec2866 add TypefaceProto (holds font family/weight) 2019-09-13 16:55:51 -05: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
John Colvin 269b08e119 [non-functional] code style changes in TradingShellModule 2019-09-13 13:24:23 -05:00
John Colvin 7eb8f32ff7 TradingShellModule hides new/old pnl menu items if disabled in config 2019-09-13 13:20:57 -05:00
John Colvin 3bb473f62d PNL account/totals only include spread positions in raw cols 2019-09-12 13:54:50 -05:00
Pedro Pinto 4295165427 Updated release notes. 2019-09-12 09:46:26 -04:00
John Colvin 10fbf30ac4 PNL - prettier 3rd level group background color (was mustard yellow, gross) 2019-09-11 18:35:21 -05:00
John Colvin bb0aedeb0d fills - [non-root] order type column filter shows badge color 2019-09-11 17:52:07 -05:00
John Colvin e01c7f5372 SP-227 Display originating top algo in the fills window 2019-09-11 17:46:39 -05:00
John Colvin d6cb22bdf8 add 'OrdersModel.on_fill_tuples_with_root' stream 2019-09-11 16:12:15 -05:00
Taha Afzal 9a1199a058 Better comments 2019-09-11 16:07:15 -04:00
John Colvin adcb5502b0 UserSettingsHub fills migration rules impl 2019-09-11 13:04:52 -05: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
John Colvin d6ff13dd00 [non-functional] style cop changes (i am ocd) 2019-09-11 12:23:24 -05:00
John Colvin 1124829aad UserSettingsHub migration rule impl uses IImmutableProperty to reduce chance of typos 2019-09-11 12:08:01 -05:00
John Colvin f9d98787a0 added IImmutableProperty interface and anonymous factory impl 2019-09-11 12:00:15 -05:00
Pedro Pinto deca2bedb5 Made GMI sftp settings empty for prod use. 2019-09-11 09:55:24 -04:00
John Colvin 5f52d81194 white space formatting in StateManager.cs 2019-09-09 13:13:33 -05:00
John Colvin 792a4bcace added 'transition_on_loaded' delegate param to StateManager.GoToState (to avoid overwriting a more recent target state) 2019-09-09 13:13:33 -05:00
John Colvin f012f4d70b remove unusued variable in 'StateManager.GoToState' 2019-09-09 13:05:07 -05: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 af7897f9f3 pnl 'has day trades' col is visible by default 2019-09-07 19:25:16 -05:00
John Colvin 0defcce179 hookup pnl raw day pos hyperlink popups 2019-09-07 19:21:18 -05:00
John Colvin b69035acf7 PNL 'RemoveColumn/Animations' migration rules remove columns that are no longer in the columns enum (instead of removing specific, defined columns) 2019-09-07 19:21:18 -05:00
John Colvin 8e33343f9c include raw day pos filters in PNLFilterStates Equals impl 2019-09-07 19:08:18 -05:00
John Colvin 5f718750a3 hookup pnl raw day pos filters in 'build_predicate' extension 2019-09-07 19:08:06 -05:00
John Colvin 935227cfa2 PNLVM - fix some filter paths 2019-09-07 19:03:48 -05:00
John Colvin 940705a066 remove avg price delta columns from pnl window 2019-09-07 18:56:08 -05:00
John Colvin d2bfcf7603 remove avg price delta columns from new pnl 2019-09-06 20:04:03 -05:00
John Colvin ae4e6c2221 add SecurityType to pnl anchorable columns 2019-09-06 20:04:03 -05:00
John Colvin 2a85289c6a pnl settings dialog - bind anchor item checkboxes to vm (was hardcoded in xaml) 2019-09-06 20:04:03 -05:00
John Colvin 0047890f9d PNLSettings foreground animations shouldn't use partial opacity 2019-09-06 20:04:03 -05:00
John Colvin dab3b3cd80 impl pnl settings migration rule to add raw day pos columns 2019-09-06 20:03:56 -05:00
John Colvin 593533736b add IPNLSettingsMigrationRule interface, hookup in UserSettingsHub 2019-09-06 17:50:49 -05:00
John Colvin eb29368b80 impl raw day pos columns in pnl view/vm 2019-09-06 17:50:49 -05:00
John Colvin b93203e4b6 add raw day cols to PNLSettings 2019-09-06 17:50:49 -05:00
John Colvin f87c04a835 refactor add_missing_columns extension 2019-09-06 17:26:10 -05:00
Eric Harding a63657e0d5 SP-214 add doc comment for on_status 2019-09-06 16:06:01 -04:00
Eric Harding b702d8dc9e SP-214 simplify trading status for synthetic logic 2019-09-06 16:03:33 -04:00
Eric Harding b0e5a4929f SP-214 attempt to simplify calling securityTradingStatus 2019-09-06 16:02:02 -04: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
Michael Harding edf9fd268f Merged master into icons 2019-09-05 14:59:40 -04:00
Michael Harding 9f1bae7d6f Make icons more consistent 2019-09-05 14:38:00 -04:00
John Colvin 52a35a56de add null check to ISettingsMigrationRule selector-wrapper impl 2019-09-05 12:42:56 -05:00
John Colvin 59338635f1 rename file PNLNormalizedDataVM.cs -> PNLHeaderRowVM.cs 2019-09-05 12:27:58 -05:00
John Colvin 0f3c0c2f24 remove PNLNormalizedDataVM class (no longer needed) 2019-09-05 12:27:58 -05:00
John Colvin fa76cad459 PNLRowCollectionView - remove some diagnostic code that is no longer needed 2019-09-05 12:27:54 -05:00
John Colvin b98d51cc55 PNLNormalizedDataVM doesn't need to perform net pos arithmetic (data already does) 2019-09-05 12:04:40 -05:00
John Colvin 1c10149a2c fix typo in PNLNormalizedData.ToString() 2019-09-05 12:00:13 -05:00
John Colvin 2ab063d5cd remove unused method 'PNLNormalizedData.create_nanless' 2019-09-05 11:57:19 -05:00
Eric Harding 030cf8a938 SP-214 Missed initialization of EventStream 2019-09-05 12:44:56 -04:00
John Colvin 2b8815c90d fix typo in PNL WorkingOrderData (change '<' to '<=') 2019-09-05 11:40:04 -05:00
John Colvin a7ee3b28ff [non-functional] normalize white space, remove dead comments in OrdersModel.cs 2019-09-05 11:14:59 -05:00
John Colvin 06af24666b Merge remote-tracking branch 'origin/master' into pnl_draft 2019-09-05 10:58:07 -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 6bc5b35295 PNLSettings view 'header' cell background override disabled placeholder was on wrong row 2019-09-05 00:20:21 -05:00
John Colvin 562bb0423a PNLSettings view - add 'None' to header display dropdown items 2019-09-05 00:15:12 -05:00
John Colvin 122a2c6f53 PNLSettings - rename 'Selected/Total Row' -> 'Header Row' 2019-09-05 00:12:01 -05:00
John Colvin dbf6bc1bf8 PNLSettings view HeaderRowDisplay dropdown 2019-09-05 00:11:24 -05:00
John Colvin 569bcee7e7 EnumHelper.split_flags returns the default value by iteself if flag is 0 2019-09-05 00:05:23 -05:00
John Colvin 8e471af048 split PNLSettings viewmodels into separate files 2019-09-04 23:14:51 -05:00
John Colvin f7c8ceb88d SP-202 PNL feedback - get rid of checkboxes and selected row 2019-09-04 19:11:41 -05:00
John Colvin 48fca3a9cc PNLSettings HeaderRowDisplay defaults to 'Total' (hides SelectedTotal) 2019-09-04 19:10:02 -05:00
John Colvin adce1a40a8 SP-202 PNL feedback - reset to factory does not work 2019-09-04 18:59:53 -05:00
John Colvin 6f88e7585b SP-202 PNL feedback - make grouping the first tab in settings 2019-09-04 18:55:05 -05:00
John Colvin 7b0fd23b5b SP-202 PNL feedback - rename 'Waterfall' to 'Cascade' 2019-09-04 18:53:35 -05:00
John Colvin e433de5241 PNLSettings group/total headers animate foreground instead of background 2019-09-04 18:50:27 -05:00
John Colvin 8e1e96d9f8 fix merge (DIYGrid lost change that added Background/Border TemplateBindings) 2019-09-04 18:45:47 -05:00
John Colvin ff9c9e2d64 Merge branch 'master' into pnl_draft 2019-09-04 18:41:24 -05: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
Eric Harding 23d4e7f78d SP-214 Wire StatusEvent to IAlgoOrder/IAlgoServices 2019-09-04 11:49:12 -04: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
Eric Harding 8e2092c3e2 SP-214 add status to MDSource.h 2019-09-04 10:39:31 -04:00
John Colvin 775ff3e2db PAndLWindow default settings was missing 'has day trades' col 2019-09-03 16:47:12 -05:00
Eric Harding 51584e5e0f SP-214 Expose trading_status on MDConsumer. 2019-09-03 14:40:15 -04: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
Eric Harding cb46e853ee SP-214 map cme status to co2 status 2019-09-03 14:06:35 -04:00
Eric Harding 9e2ad000d4 Write exchange_status MDP3 2019-09-03 13:44:58 -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
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
Jake Norbut ea2aee5044 utilzing new argb 2019-08-14 15:04:40 -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 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
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
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
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
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
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
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
394 changed files with 14044 additions and 5476 deletions

No files matched your search

+2 -2
View File
@@ -32,5 +32,5 @@ 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/*
/.ignore
+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)
+58
View File
@@ -4,6 +4,64 @@ weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## 2.10.0
### User Visible Changes
- Fills now include information about the algo that originated the fill
- A new, more powerful PnL form is now available
### API Changes
- Added security state information (pre open, halted, normal, closed, etc)
### Bug Fixes
- Brought back the "select all" check box in the mini order book
- Fixed an issue in the market data subsystem that on rare occasions could result in a startup crash
### Internal Changes
- Fixed an issue that was stopping us from interpreting SOD times as local rather than GMT
## 2.9.3
### Internal Changes
- Fixed system test scripts
## 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
-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);
}
}
+5 -1
View File
@@ -40,6 +40,7 @@ namespace mbts.ui {
EditDocumentAltGeometryKey = nameof(EditDocumentAltGeometryKey),
EditDocumentGeometryKey = nameof(EditDocumentGeometryKey),
GearGeometryKey = nameof(GearGeometryKey),
HornGeometryKey = nameof(HornGeometryKey),
KeyGeometryKey = nameof(KeyGeometryKey),
LetterAKey = nameof(LetterAKey),
LetterDKey = nameof(LetterDKey),
@@ -50,6 +51,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),
@@ -151,7 +153,7 @@ namespace mbts.ui {
public static ImageSource CheckMark => create_icon(CheckMarkGeometryKey);
public static ImageSource Clock { get { return load_resource_and_freeze("Clock"); } }
public static ImageSource CopyWF => create_icon(CopyWFGeometryKey);
public static ImageSource DataEdit => create_icon(DataEditGeometryKey, Brushes.White, new Pen(Brushes.Black, 1).frozen());
public static ImageSource DataEdit => create_icon(DataEditGeometryKey);
public static ImageSource Defrag { get { return load_resource_and_freeze("Defrag"); } }
public static ImageSource DetachAlgos => create_icon_and_rotate(DetachAlgosKey, Brushes.Red);
public static ImageSource Disk => create_icon(DiskGeometryKey);
@@ -170,6 +172,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 Key => create_icon(KeyGeometryKey);
public static ImageSource Knight { get { return load_resource_and_freeze("Knight"); } }
@@ -181,6 +184,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"); } }
+39 -32
View File
@@ -1,7 +1,7 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mbts="clr-namespace:mbts.ui"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
@@ -18,9 +18,7 @@
Ttile: Add free icon
Credit: Icon made by Gregor Cresnar from www.flaticon.com-->
<sys:String x:Key="{x:Static mbts:Icons.AddBoxesGeometryKey}">
M393,480.3c0-5-4.1-9.1-9.1-9.1H18.2V105.5c0-5-4.1-9.1-9.1-9.1S0,100.5,0,105.5v374.8c0,5,4.1,9.1,9.1,9.1h374.8 C388.9,489.4,393,485.3,393,480.3z
M373.9,187.5H302v-71.9c0-5-4.1-9.1-9.1-9.1s-9.1,4.1-9.1,9.1v71.9h-71.9c-5,0-9.1,4.1-9.1,9.1s4.1,9.1,9.1,9.1h71.9v71.9 c0,5,4.1,9.1,9.1,9.1s9.1-4.1,9.1-9.1v-71.9h71.9c5,0,9.1-4.1,9.1-9.1C382.9,191.5,378.9,187.5,373.9,187.5z
M105.5,393h374.8c5,0,9.1-4.1,9.1-9.1V9.1c0-5-4.1-9.1-9.1-9.1H105.5c-5,0-9.1,4.1-9.1,9.1v374.8 C96.4,388.9,100.5,393,105.5,393z M114.5,18.2h356.7v356.7H114.5V18.2z
M 87.5 10 C 80.313542 10 74.400391 15.913151 74.400391 23.099609 V 397.90039 C 74.400391 405.08685 80.313541 411 87.5 411 h 374.80078 c 7.18646 0 13.09961 -5.91315 13.09961 -13.09961 V 23.099609 C 475.40039 15.913151 469.48724 10 462.30078 10 Z m 13 26.199219 H 449.19922 V 384.90039 H 100.5 Z M 21.099609 76.400391 C 13.912943 76.400391 8 82.313333 8 89.5 v 374.80078 c 0 7.18667 5.912943 13.09961 13.099609 13.09961 H 395.90039 c 7.18667 0 13.09961 -5.91294 13.09961 -13.09961 0 -7.18667 -5.91294 -13.10156 -13.09961 -13.10156 H 34.199219 V 89.5 c 0 -7.186667 -5.912943 -13.099609 -13.09961 -13.099609 z M 274.90039 116.5 c -7.18667 0 -13.09961 5.91294 -13.09961 13.09961 V 197.5 h -67.90039 c -7.18667 0 -13.09961 5.91294 -13.09961 13.09961 0 7.18667 5.91294 13.09961 13.09961 13.09961 h 67.90039 v 67.90039 c 0 7.18667 5.91294 13.09961 13.09961 13.09961 7.18667 0 13.09961 -5.91294 13.09961 -13.09961 v -67.90039 h 67.90039 c 7.18667 0 13.09961 -5.91294 13.09961 -13.09961 v -0.0391 -0.0391 C 368.85982 203.37217 363.03372 197.5 355.90039 197.5 H 288 V 129.59961 C 288 122.41294 282.08706 116.5 274.90039 116.5 Z
</sys:String>
<!--File (the icon is free to use if we include this information in our credit section)
@@ -116,7 +114,7 @@
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.DataEditGeometryKey}">
M17.997977,17.396268C18.197928,17.396268,18.297904,17.496276,18.497975,17.596284L19.497972,18.295283C19.99797,18.595292,20.697919,19.195308,21.197917,19.495299L22.196937,20.195323C22.696935,20.495315,22.596959,20.995322,22.096961,21.195338L20.997966,21.695346C20.497968,21.89434,19.597947,22.294355,19.097949,22.494355L17.997977,22.994363C17.497979,23.194362,17.097957,22.894355,17.197932,22.394347L17.297908,21.195338C17.398005,20.595323,17.497979,19.695315,17.497979,19.195308L17.597955,17.995277C17.497979,17.695283,17.69793,17.396268,17.997977,17.396268z M1.799981,13.597213L19.697923,13.597213 17.997977,15.896244C17.797906,16.096259 17.69793,16.396253 17.69793,16.69626 17.197932,16.796268 16.79791,17.296275 16.697934,17.895284L16.597959,19.195308 1.799981,19.195308C0.79998477,19.195308,0,18.395291,0,17.396268L0,15.396237C0,14.396221,0.79998477,13.597213,1.799981,13.597213z M1.799981,6.7981106L21.796914,6.7981106C22.696935,6.7981106,23.49698,7.498118,23.596955,8.3981257L20.697919,12.397197 1.799981,12.397197C0.79998477,12.397197,0,11.597181,0,10.597165L0,8.5981418C0,7.598126,0.79998477,6.7981106,1.799981,6.7981106z M27.796891,4.3990708C27.996963,4.3990708,28.096938,4.3990708,28.296889,4.5990711L31.695924,7.0981179C31.995851,7.3981105,32.095948,7.7981258,31.895875,8.0981341L23.296908,19.795308C23.096957,20.095315,22.596959,20.195323,22.196937,19.895314L18.797902,17.396268C18.497975,17.096274,18.398002,16.69626,18.597951,16.396253L27.196918,4.6990786C27.396869,4.4990784,27.59694,4.3990708,27.796891,4.3990708z M1.799981,0L21.796914,0C22.79691,4.9549271E-08,23.596955,0.8000156,23.596955,1.800031L23.596955,3.7990554C23.596955,4.7990709,22.79691,5.5990864,21.796914,5.5990864L1.799981,5.5990864C0.79998477,5.4990941,0,4.6990786,0,3.6990629L0,1.7000236C0,0.8000156,0.79998477,4.9549271E-08,1.799981,0z
M 22.589844 14.470703 H 0.82421875 C 0.36896661 14.470586 -1.1747368e-4 14.83967 0 15.294922 v 4.669922 c -1.1747368e-4 0.455252 0.36896661 0.824336 0.82421875 0.824218 H 16.940231 l 0.425003 -1.09375 0.554688 -0.554687 H 1.6484375 V 16.119141 H 20.941406 Z M 27.462891 9.5976562 V 8.0742188 A 0.8240064 0.8240064 0 0 0 26.638672 7.25 H 0.82421875 A 0.8240064 0.8240064 0 0 0 0 8.0742188 v 4.6699222 a 0.8240064 0.8240064 0 0 0 0.82421875 0.824218 H 23.492188 l 1.648437 -1.648437 H 1.6484375 V 8.8984375 H 25.814453 V 11.246094 Z M 0.82421878 2.736881e-8 A 0.8240064 0.8240064 0 0 0 2.7433281e-8 0.82421879 V 5.4941406 A 0.8240064 0.8240064 0 0 0 0.82421878 6.3183594 H 26.638672 A 0.8240064 0.8240064 0 0 0 27.46289 5.4941406 V 0.82421879 A 0.8240064 0.8240064 0 0 0 26.638672 2.736881e-8 Z M 1.6484375 1.6484375 H 25.814453 V 4.6699219 H 1.6484375 Z M 31.628051 7.2558423 19.165575 19.718635 23.70324 24.256618 36.166032 11.793823 Z m -0.0013 3.5965607 0.941422 0.94142 -8.866233 8.866232 -0.94142 -0.94142 z m -13.240122 9.708458 -1.636234 6.110706 6.109329 -1.637606 z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.DiskGeometryKey}">
M403.213,20H0v472h512V128.787L403.213,20z M131,50h180v100h10h20h10V50h30v130H131V50z M381,462H131V315
@@ -274,6 +272,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.KeyGeometryKey}">
M22.399981,2.4000006C22.099982,2.4000006 21.899982,2.5 21.599982,2.6999998 21.199983,3.1000004 21.199983,3.8000002 21.599982,4.2000003L26.199979,8.9000001C26.599978,9.3000002 27.299977,9.3000002 27.699977,8.9000001 28.099977,8.5000002 28.099977,7.8000002 27.699977,7.4000006L23.099981,2.6999998C22.899981,2.5,22.699981,2.4000006,22.399981,2.4000006z M21.499982,0C22.299982,0,23.199981,0.30000019,23.79998,0.90000057L29.299976,6.4000001C30.599975,7.7000003,30.599975,9.7000003,29.299976,11L22.599981,17.7C21.299983,19,19.299984,19,17.999985,17.7L17.299985,17 12.699989,21.6 10.399991,20.900001 10.899991,23.400001 8.4999928,22.8 8.9999924,25.5 6.2999945,25.1 6.7999945,27.6 4.2999964,27 4.7999954,29.6 2.4999971,32 0,26.6 13.499989,13.1 16.999986,16.6 12.699989,12.3C11.399991,11,11.399991,9.0000002,12.699989,7.7000003L19.399984,1C19.899983,0.30000019,20.699983,0,21.499982,0z
</sys:String>
@@ -335,6 +337,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>
@@ -344,7 +351,7 @@
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.TrashGeometryKey}">
M0,12.568994L23.662999,12.568994 20.910989,31.999999 2.7520113,31.999999z M8.0689915,1.6269979L8.0689915,4.6679968 15.594984,4.6679968 15.594984,1.6269979z M6.4420097,0L17.221997,0 17.221997,4.6679968 23.662999,4.6679968 23.662999,8.7949938 0,8.7949938 0,4.6679968 6.4420097,4.6679968z
M15.000001,8.0000272L17,8.0000272 17,27.000028 15.000001,27.000028z M11.000001,8.0000272L13.000001,8.0000272 13.000001,27.000028 11.000001,27.000028z M7.0000005,8.0000272L9.000001,8.0000272 9.000001,27.000028 7.0000005,27.000028z M3.0790101,5.0000274L4.9959994,29.922999 19.000006,30.000026 20.918949,5.0000274z M13.771989,1.9959999L10.198989,2.0000001C10.158989,2.025,9.9989892,2.241,9.9989892,2.6000001L9.9989892,3.0000258 13.998989,3.0000258 13.998989,2.6000001C13.998989,2.241,13.838988,2.025,13.771989,1.9959999z M10.198989,0L13.798988,0C15.031989,0,15.998989,1.142,15.998989,2.6000001L15.998989,3.0000258 23.07898,3.0000258 24,3.0000272 24,5.0000274 22.925121,5.0000274 20.995976,30.076991C20.999027,31.102992,20.100956,32.000026,18.999029,32.000026L4.9990512,32.000026C3.8960255,32.000026,2.9990543,31.102992,2.999054,30.000026L1.073059,5.0000274 0,5.0000274 0,3.0000272 0.91897895,3.0000272 7.9989887,3.0000258 7.9989887,2.6000001C7.9989887,1.142,8.9659892,0,10.198989,0z
</sys:String>
<!--UndoCircular (the icon is free to use if we include this information in our credit/about section)
@@ -404,10 +411,10 @@
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Geometry="
M 32.868601,3.8869994 C31.026201,3.8869996 29.534,5.3791947 29.534,7.2190399 29.534,9.0588851 31.026201,10.553 32.868601,10.553 34.7085,10.553 36.202,9.0588851 36.202,7.2190399 36.202,5.3791947 34.7085,3.8869996 32.868601,3.8869994z
M 42.868601,3.8869994 C41.026201,3.8869996 39.534,5.3791947 39.534,7.2190399 39.534,9.0588851 41.026201,10.553 42.868601,10.553 44.7085,10.553 46.202,9.0588851 46.202,7.2190399 46.202,5.3791947 44.7085,3.8869996 42.868601,3.8869994z
M 52.868601,3.8869994 C51.026201,3.8869996 49.534,5.3791947 49.534,7.2190399 49.534,9.0588851 51.026201,10.553 52.868601,10.553 54.7085,10.553 56.202,9.0588851 56.202,7.2190399 56.202,5.3791947 54.7085,3.8869996 52.868601,3.8869994z
M5.2010002,14.812L5.2010002,43.573997 56.098,43.573997 56.098,14.812z
M 32.868601,3.8869994 C31.026201,3.8869996 29.534,5.3791947 29.534,7.2190399 29.534,9.0588851 31.026201,10.553 32.868601,10.553 34.7085,10.553 36.202,9.0588851 36.202,7.2190399 36.202,5.3791947 34.7085,3.8869996 32.868601,3.8869994z
M 42.868601,3.8869994 C41.026201,3.8869996 39.534,5.3791947 39.534,7.2190399 39.534,9.0588851 41.026201,10.553 42.868601,10.553 44.7085,10.553 46.202,9.0588851 46.202,7.2190399 46.202,5.3791947 44.7085,3.8869996 42.868601,3.8869994z
M 52.868601,3.8869994 C51.026201,3.8869996 49.534,5.3791947 49.534,7.2190399 49.534,9.0588851 51.026201,10.553 52.868601,10.553 54.7085,10.553 56.202,9.0588851 56.202,7.2190399 56.202,5.3791947 54.7085,3.8869996 52.868601,3.8869994z
M5.2010002,14.812L5.2010002,43.573997 56.098,43.573997 56.098,14.812z
M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<GeometryDrawing.Pen>
<Pen Thickness="1" Brush="Black" />
@@ -423,10 +430,10 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Brush="Black" Geometry="
M 32.868601,3.8869994 C31.026201,3.8869996 29.534,5.3791947 29.534,7.2190399 29.534,9.0588851 31.026201,10.553 32.868601,10.553 34.7085,10.553 36.202,9.0588851 36.202,7.2190399 36.202,5.3791947 34.7085,3.8869996 32.868601,3.8869994z
M 42.868601,3.8869994 C41.026201,3.8869996 39.534,5.3791947 39.534,7.2190399 39.534,9.0588851 41.026201,10.553 42.868601,10.553 44.7085,10.553 46.202,9.0588851 46.202,7.2190399 46.202,5.3791947 44.7085,3.8869996 42.868601,3.8869994z
M 52.868601,3.8869994 C51.026201,3.8869996 49.534,5.3791947 49.534,7.2190399 49.534,9.0588851 51.026201,10.553 52.868601,10.553 54.7085,10.553 56.202,9.0588851 56.202,7.2190399 56.202,5.3791947 54.7085,3.8869996 52.868601,3.8869994z
M5.2010002,14.812L5.2010002,43.573997 56.098,43.573997 56.098,14.812z
M 32.868601,3.8869994 C31.026201,3.8869996 29.534,5.3791947 29.534,7.2190399 29.534,9.0588851 31.026201,10.553 32.868601,10.553 34.7085,10.553 36.202,9.0588851 36.202,7.2190399 36.202,5.3791947 34.7085,3.8869996 32.868601,3.8869994z
M 42.868601,3.8869994 C41.026201,3.8869996 39.534,5.3791947 39.534,7.2190399 39.534,9.0588851 41.026201,10.553 42.868601,10.553 44.7085,10.553 46.202,9.0588851 46.202,7.2190399 46.202,5.3791947 44.7085,3.8869996 42.868601,3.8869994z
M 52.868601,3.8869994 C51.026201,3.8869996 49.534,5.3791947 49.534,7.2190399 49.534,9.0588851 51.026201,10.553 52.868601,10.553 54.7085,10.553 56.202,9.0588851 56.202,7.2190399 56.202,5.3791947 54.7085,3.8869996 52.868601,3.8869994z
M5.2010002,14.812L5.2010002,43.573997 56.098,43.573997 56.098,14.812z
M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<GeometryDrawing.Pen>
<Pen Thickness="1" Brush="Black" />
@@ -842,7 +849,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="Black"
Geometry="M23,14C24.10498,14 25,14.89502 25,16 25,17.10498 24.10498,18 23,18 21.89502,18 21,17.10498 21,16 21,14.89502 21.89502,14 23,14z M16,14C17.105,14 18,14.895 18,16 18,17.105 17.105,18 16,18 14.895,18 14,17.105 14,16 14,14.895 14.895,14 16,14z M9,14C10.105,14 11,14.895 11,16 11,17.105 10.105,18 9,18 7.8950005,18 7,17.105 7,16 7,14.895 7.8950005,14 9,14z M16,2C8.2799997,2 2,8.2800002 2,16 2,23.72 8.2799997,30 16,30 23.72,30 30,23.72 30,16 30,8.2800002 23.72,2 16,2z M16,0C24.822,0 32,7.178 32,16 32,24.822 24.822,32 16,32 7.1780005,32 0,24.822 0,16 0,7.178 7.1780005,0 16,0z" />
</DrawingGroup.Children>
@@ -855,7 +862,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="Black"
Geometry="M299.152955025434,0L496.426178902388,0 268.890046089888,280.247772216797 198.190201729536,367.407897949219 199.87503144145,369.495361328125 271.502137154341,457.706451416016 498.999970406294,738 301.717621773481,738 101.234124153852,491.008697509766 99.5304498374462,488.936553955078 98.6409082114697,490.003173828125 0,368.488952636719 98.6409082114697,246.945526123047z" />
</DrawingGroup.Children>
@@ -867,7 +874,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="{mbts:BTSColor MarketData.bid}"
Geometry="m 24.706673 31.196264 -3.086182 3.086182 5.965481 5.963348 -5.965481 5.96548 3.086182 3.084049 5.963348 -5.963348 5.96548 5.963348 3.084049 -3.084049 -5.963347 -5.96548 5.963347 -5.963348 -3.084049 -3.086182 -5.96548 5.965481 z M 5.3507926 32.045363 H 17.816449 v 2.401501 H 5.3507926 Z m 0 -4.937233 H 24.132994 v 2.401501 H 5.3507926 Z m 0 9.874466 h 5.5381524 v 2.401501 H 5.3507926 Z M 1.3730469 0 A 1.3741374 1.3741374 0 0 0 0 1.3730469 V 45.630859 a 1.3741374 1.3741374 0 0 0 1.3730469 1.373047 H 16.853516 a 17.906029 17.906029 0 0 1 -0.05859 -1.373047 17.906029 17.906029 0 0 1 0.06836 -1.375 H 2.7480469 V 2.7480469 H 33.326172 V 27.783203 a 17.906029 17.906029 0 0 1 1.375 -0.05859 17.906029 17.906029 0 0 1 1.373047 0.06836 V 1.3730469 A 1.3741374 1.3741374 0 0 0 34.701172 0 Z M 10.662215 24.759265 V 5.1202697 h 7.148923 q 3.286862 0 5.05355 1.2051818 1.766687 1.2051838 1.766687 3.3964241 0 1.5886494 -1.081924 2.7801354 -1.068231 1.191488 -2.739053 1.657126 v 0.05479 q 2.095375 0.26021 3.341644 1.547564 1.259962 1.287354 1.259962 3.136214 0 2.697964 -1.93103 4.286613 -1.931032 1.574956 -5.272673 1.574956 z M 15.085783 8.3797408 v 4.6563852 h 1.944726 q 1.369526 0 2.150155 -0.657372 0.794325 -0.671068 0.794325 -1.835164 0 -2.1638492 -3.23208 -2.1638492 z m 0 7.9432472 v 5.176805 h 2.39667 q 1.533868 0 2.39667 -0.712152 0.876495 -0.712155 0.876495 -1.944728 0 -1.17779 -0.862802 -1.848858 -0.849105 -0.671067 -2.382974 -0.671067 z" />
</DrawingGroup.Children>
@@ -879,7 +886,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="{mbts:BTSColor MarketData.ask}"
Geometry="m 19.060265 26.797642 v -2.144583 q 0.583058 0.489233 1.266644 0.737201 0.683586 0.241265 1.380576 0.241265 0.408811 0 0.710393 -0.07372 0.308284 -0.07372 0.509339 -0.201055 0.207756 -0.134036 0.308284 -0.308283 0.100527 -0.18095 0.100527 -0.388706 0 -0.281477 -0.160844 -0.502637 -0.160844 -0.22116 -0.44232 -0.408811 -0.274775 -0.187651 -0.656779 -0.361898 -0.382004 -0.174248 -0.824324 -0.355197 -1.125906 -0.469128 -1.682158 -1.146012 -0.549549 -0.676884 -0.549549 -1.635245 0 -0.750604 0.301582 -1.28675 0.301582 -0.542847 0.817622 -0.891342 0.522743 -0.348495 1.206328 -0.509339 0.683586 -0.167545 1.447594 -0.167545 0.750604 0 1.326961 0.09383 0.583059 0.08712 1.072292 0.274775 v 2.003845 q -0.241266 -0.167545 -0.529444 -0.29488 -0.281477 -0.127335 -0.583059 -0.207757 -0.301582 -0.08712 -0.603164 -0.127334 -0.29488 -0.04021 -0.562953 -0.04021 -0.3686 0 -0.670182 0.07372 -0.301582 0.06702 -0.509339 0.194353 -0.207756 0.127334 -0.321687 0.308284 -0.113931 0.174247 -0.113931 0.395407 0 0.241266 0.127334 0.435619 0.127335 0.187651 0.361899 0.361898 0.234564 0.167546 0.569655 0.335091 0.335091 0.160844 0.757306 0.335091 0.576356 0.241266 1.03208 0.516041 0.462426 0.268073 0.790816 0.609866 0.328389 0.341793 0.502636 0.784113 0.174248 0.435618 0.174248 1.018677 0 0.804219 -0.308284 1.353768 -0.301582 0.542848 -0.824324 0.884641 -0.522743 0.335091 -1.219732 0.482531 -0.690288 0.14744 -1.460998 0.14744 -0.790815 0 -1.50791 -0.134036 -0.710393 -0.134037 -1.233135 -0.40211 z m 6.620223 3.788289 -1.556212 1.556212 3.008102 3.007026 -3.008102 3.008101 1.556212 1.555136 3.007026 -3.007026 3.008101 3.007026 1.555136 -1.555136 -3.007026 -3.008101 3.007026 -3.007026 -1.555136 -1.556212 -3.008101 3.008102 z m -9.76023 2.917766 h 2.792621 v 1.21096 h -2.792621 z m 0 -2.489606 h 6.285825 v 1.21096 h -6.285825 z m 0 -2.489607 h 9.470951 v 1.210961 H 15.920258 Z M 13.914475 14.855173 a 0.69291063 0.69291063 0 0 0 -0.692361 0.692361 v 22.317061 a 0.69291063 0.69291063 0 0 0 0.692361 0.692361 h 7.806047 a 9.0291392 9.0291392 0 0 1 -0.02954 -0.692361 9.0291392 9.0291392 0 0 1 0.03447 -0.693345 H 14.60782 V 16.24088 h 15.419061 v 12.624011 a 9.0291392 9.0291392 0 0 1 0.693346 -0.02954 9.0291392 9.0291392 0 0 1 0.692361 0.03447 V 15.547534 a 0.69291063 0.69291063 0 0 0 -0.692361 -0.692361 z" />
</DrawingGroup.Children>
@@ -892,7 +899,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="Black"
Geometry="M 5.3507926 32.045363 H 17.81645 v 2.401501 H 5.3507926 Z m 0 -4.937233 H 24.132995 v 2.401501 H 5.3507926 Z M 18.799228 22.222041 v 2.32929 h -1.567173 v -2.264886 q -2.179014 -0.01073 -4.057475 -0.987533 v -2.973336 q 0.622575 0.504501 1.856993 0.933864 1.234417 0.429362 2.200482 0.493766 v -3.907198 q -2.511771 -0.933863 -3.4349 -2.018003 -0.912395 -1.094874 -0.912395 -2.662047 0 -1.685248 1.191482 -2.8659947 1.19148 -1.1914807 3.155813 -1.3846939 V 4.918734 h 1.567173 v 1.9535992 q 2.264887 0.1073411 3.38123 0.7299162 V 10.500446 Q 20.677689 9.5880506 18.799228 9.3841034 v 4.0682086 q 2.35076 0.847992 3.370495 1.942866 1.019736 1.08414 1.019736 2.629845 0 1.781854 -1.137811 2.876728 -1.13781 1.08414 -3.25242 1.32029 z M 17.232055 12.829737 V 9.4270397 q -1.492034 0.2683516 -1.492034 1.5564393 0 1.105608 1.492034 1.846258 z m 1.567173 3.628113 v 3.25242 q 1.53497 -0.236149 1.53497 -1.53497 0 -1.051939 -1.53497 -1.71745 z m 13.258587 13.65074 h -3.954117 l 0.0014 7.641804 -7.643169 -10e-7 0.0014 3.952751 h 7.640439 l 0.0014 7.641803 h 3.951385 l 0.0014 -7.641802 7.640437 -10e-7 0.0014 -3.952751 -7.643169 10e-7 z M 5.3507926 36.982596 h 5.5381524 v 2.401501 H 5.3507926 Z M 1.3730469 0 A 1.3741374 1.3741374 0 0 0 0 1.3730469 V 45.630859 a 1.3741374 1.3741374 0 0 0 1.3730469 1.373047 H 16.853516 a 17.906029 17.906029 0 0 1 -0.05859 -1.373047 17.906029 17.906029 0 0 1 0.06836 -1.375 H 2.7480469 V 2.7480469 H 33.326172 V 27.783203 a 17.906029 17.906029 0 0 1 1.375 -0.05859 17.906029 17.906029 0 0 1 1.373047 0.06836 V 1.3730469 A 1.3741374 1.3741374 0 0 0 34.701172 0 Z" />
</DrawingGroup.Children>
@@ -904,7 +911,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="Black"
Geometry="m 26.463403 27.963318 h -2.359042 l -0.683586 -2.137882 h -3.417929 l -0.676884 2.137882 h -2.345638 l 3.498351 -9.610414 h 2.566798 z M 22.92484 24.163384 21.89276 20.933106 q -0.113931 -0.361899 -0.160844 -0.864535 h -0.05361 q -0.03351 0.422214 -0.167545 0.837727 l -1.045485 3.257086 z m 2.065112 7.055953 -1.51223 1.51223 2.923087 2.922042 -2.923087 2.923086 1.51223 1.511185 2.922042 -2.922042 2.923086 2.922042 1.511185 -1.511185 -2.922041 -2.923086 2.922041 -2.922042 -1.511185 -1.51223 -2.923086 2.923087 z m -9.484387 0.416058 h 6.108175 v 1.176737 h -6.108175 z m 0 -2.419244 h 9.203284 v 1.176735 h -9.203284 z m 0 4.83849 h 2.713696 v 1.176736 H 15.505565 Z M 13.556469 15.93316 a 0.67332766 0.67332766 0 0 0 -0.672794 0.672793 v 21.686339 a 0.67332766 0.67332766 0 0 0 0.672794 0.672793 h 7.585433 a 8.7739586 8.7739586 0 0 1 -0.02871 -0.672793 8.7739586 8.7739586 0 0 1 0.0335 -0.673751 H 14.230219 V 17.279703 h 14.983289 v 12.267233 a 8.7739586 8.7739586 0 0 1 0.67375 -0.02871 8.7739586 8.7739586 0 0 1 0.672794 0.0335 V 16.605953 A 0.67332766 0.67332766 0 0 0 29.887258 15.93316 Z" />
</DrawingGroup.Children>
@@ -916,7 +923,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="Black"
Geometry="m 24.706673 31.196264 -3.086181 3.086182 5.96548 5.963348 -5.96548 5.96548 3.086181 3.084049 5.963348 -5.963348 5.96548 5.963348 3.084049 -3.084049 -5.963347 -5.96548 5.963347 -5.963348 -3.084049 -3.086182 -5.96548 5.965481 z M 5.3507926 32.045363 H 17.81645 v 2.401501 H 5.3507926 Z m 0 -4.937233 H 24.132994 v 2.401501 H 5.3507926 Z M 26.434166 6.0031559 14.993666 17.443656 9.6399578 12.089946 6.0100982 15.719802 c 2.9622322 2.959826 6.0772448 6.092263 8.9969908 8.970151 L 30.064121 9.6329208 Z M 5.3507926 36.982596 h 5.5381524 v 2.401501 H 5.3507926 Z M 1.3730469 0 A 1.3741374 1.3741374 0 0 0 0 1.3730469 V 45.630859 a 1.3741374 1.3741374 0 0 0 1.3730469 1.373047 H 16.853516 a 17.906029 17.906029 0 0 1 -0.05859 -1.373047 17.906029 17.906029 0 0 1 0.06836 -1.375 H 2.7480469 V 2.7480469 H 33.326172 V 27.783203 a 17.906029 17.906029 0 0 1 1.375 -0.05859 17.906029 17.906029 0 0 1 1.373047 0.06836 V 1.3730469 A 1.3741374 1.3741374 0 0 0 34.701172 0 Z" />
</DrawingGroup.Children>
@@ -939,7 +946,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="{mbts:BTSColor MarketData.bid}"
Geometry="M 9.9417804 -0.02293875 1.9154138 8.0034278 17.4301 23.512569 1.9154138 39.027256 9.9417804 47.048077 25.450922 31.538936 40.965607 47.048077 48.986428 39.027256 33.47729 23.512569 48.986428 8.0034278 40.965607 -0.02293875 25.450922 15.491746 Z" />
</DrawingGroup.Children>
@@ -951,7 +958,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="{mbts:BTSColor MarketData.ask}"
Geometry="M 9.9417804 -0.02293875 1.9154138 8.0034278 17.4301 23.512569 1.9154138 39.027256 9.9417804 47.048077 25.450922 31.538936 40.965607 47.048077 48.986428 39.027256 33.47729 23.512569 48.986428 8.0034278 40.965607 -0.02293875 25.450922 15.491746 Z" />
</DrawingGroup.Children>
@@ -963,7 +970,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="Black"
Geometry="M 9.9417804 -0.02293875 1.9154138 8.0034278 17.4301 23.512569 1.9154138 39.027256 9.9417804 47.048077 25.450922 31.538936 40.965607 47.048077 48.986428 39.027256 33.47729 23.512569 48.986428 8.0034278 40.965607 -0.02293875 25.450922 15.491746 Z" />
</DrawingGroup.Children>
@@ -976,7 +983,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="Black"
Geometry="m 69.641861 87.129943 a 36.813842 36.813842 0 0 0 -36.813776 36.813767 36.813842 36.813842 0 0 0 31.044618 36.31355 l 1.208194 21.35839 1.804543 31.90967 a 41.092964 41.092964 0 0 0 -29.828154 39.50509 41.092964 41.092964 0 0 0 41.09258 41.0931 41.092964 41.092964 0 0 0 41.093104 -41.0931 41.092964 41.092964 0 0 0 -0.10284 -2.9068 41.092964 41.092964 0 0 0 -38.048841 -38.03902 l -2.962092 -52.36321 a 36.813842 36.813842 0 0 0 20.093347 -12.61939 l 33.150946 15.70705 v 5.3e-4 a 27.542416 27.542416 0 0 0 -0.12557 2.49907 27.542416 27.542416 0 0 0 27.54251 27.54249 27.542416 27.542416 0 0 0 27.5425 -27.5425 27.542416 27.542416 0 0 0 -27.5425 -27.5425 27.542416 27.542416 0 0 0 -22.44618 11.61479 l -31.44563 -14.89935 a 36.813842 36.813842 0 0 0 1.55701 -10.53786 36.813842 36.813842 0 0 0 -36.813769 -36.813767 z" />
</DrawingGroup.Children>
@@ -988,7 +995,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="Black"
Geometry="M 36.50044 86.999995 A 36.813842 36.813842 0 0 0 -0.3133356 123.81377 36.813842 36.813842 0 0 0 30.731282 160.12732 l 1.208194 21.35839 1.804543 31.90967 a 41.092964 41.092964 0 0 0 -29.8281546 39.50509 41.092964 41.092964 0 0 0 41.0925806 41.0931 41.092964 41.092964 0 0 0 41.093099 -41.0931 41.092964 41.092964 0 0 0 -0.102836 -2.9068 41.092964 41.092964 0 0 0 -38.04884 -38.03902 l -2.962092 -52.36321 a 36.813842 36.813842 0 0 0 20.093347 -12.61939 l 33.150948 15.70705 v 5.3e-4 a 27.542416 27.542416 0 0 0 -0.125574 2.49907 27.542416 27.542416 0 0 0 27.542513 27.54249 27.542416 27.542416 0 0 0 27.5425 -27.5425 27.542416 27.542416 0 0 0 -27.5425 -27.5425 27.542416 27.542416 0 0 0 -22.44618 11.61479 L 71.757201 134.35163 A 36.813842 36.813842 0 0 0 73.314213 123.81377 36.813842 36.813842 0 0 0 36.50044 86.999995 Z m 116.60981 115.628085 0.007 37.48967 h -37.49716 l 0.007 19.39257 h 37.48344 L 153.11751 297 h 19.38493 l 0.007 -37.48968 h 37.48342 L 210 240.11775 h -37.49727 l 0.007 -37.48967 z" />
</DrawingGroup.Children>
@@ -1013,7 +1020,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="Black"
Geometry="M 37.03498 86.999995 A 36.813842 36.813842 0 0 0 0.2212037 123.81377 36.813842 36.813842 0 0 0 31.265822 160.12732 l 1.208194 21.35839 1.804543 31.90967 a 41.092964 41.092964 0 0 0 -29.8281553 39.50509 41.092964 41.092964 0 0 0 41.0925813 41.0931 41.092964 41.092964 0 0 0 41.093099 -41.0931 41.092964 41.092964 0 0 0 -0.102836 -2.9068 41.092964 41.092964 0 0 0 -38.04884 -38.03902 l -2.962092 -52.36321 a 36.813842 36.813842 0 0 0 20.093347 -12.61939 l 33.150948 15.70705 v 5.3e-4 a 27.542416 27.542416 0 0 0 -0.125574 2.49907 27.542416 27.542416 0 0 0 27.542503 27.54249 27.542416 27.542416 0 0 0 27.5425 -27.5425 27.542416 27.542416 0 0 0 -27.5425 -27.5425 27.542416 27.542416 0 0 0 -22.44618 11.61479 L 72.291741 134.35163 A 36.813842 36.813842 0 0 0 73.848753 123.81377 36.813842 36.813842 0 0 0 37.03498 86.999995 Z m 145.03189 90.486735 -76.71431 76.71435 27.93311 27.93293 L 210 205.41986 Z m -0.008 22.13769 5.79522 5.79544 -54.57645 54.57644 -5.7952 -5.79522 z m -81.50016 59.76108 -10.071854 37.61451 37.606034 -10.0803 z" />
</DrawingGroup.Children>
@@ -1025,7 +1032,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="Black"
Geometry="m 139.80129 180.78831 a 20.783495 20.783495 0 0 0 -20.78345 20.78346 20.783495 20.783495 0 0 0 17.52643 20.50105 l 0.68209 12.05803 1.01877 18.01482 a 23.1993 23.1993 0 0 0 -16.83968 22.30284 23.1993 23.1993 0 0 0 23.1991 23.19938 23.1993 23.1993 0 0 0 23.19935 -23.19938 23.1993 23.1993 0 0 0 -0.058 -1.64105 23.1993 23.1993 0 0 0 -21.4807 -21.47518 l -1.67225 -29.56198 a 20.783495 20.783495 0 0 0 11.34383 -7.12437 l 18.71558 8.86752 v 3.1e-4 a 15.549251 15.549251 0 0 0 -0.0708 1.41086 15.549251 15.549251 0 0 0 15.5493 15.54927 15.549251 15.549251 0 0 0 15.54931 -15.54927 15.549251 15.549251 0 0 0 -15.54931 -15.54932 15.549251 15.549251 0 0 0 -12.67213 6.5572 l -17.75282 -8.41152 a 20.783495 20.783495 0 0 0 0.87902 -5.94921 20.783495 20.783495 0 0 0 -20.78345 -20.78346 z M 31.983713 88.265691 A 30.676234 30.676234 0 0 0 1.3075339 118.94187 30.676234 30.676234 0 0 0 27.17639 149.20122 l 1.006764 17.79752 1.503689 26.58969 A 34.241941 34.241941 0 0 0 4.8316412 226.50722 34.241941 34.241941 0 0 0 39.073263 260.74928 34.241941 34.241941 0 0 0 73.315316 226.50722 34.241941 34.241941 0 0 0 73.229625 224.08505 34.241941 34.241941 0 0 0 41.524291 192.3879 L 39.05604 148.75469 A 30.676234 30.676234 0 0 0 55.799422 138.2392 l 27.624018 13.08837 v 4.4e-4 a 22.950541 22.950541 0 0 0 -0.104638 2.08242 22.950541 22.950541 0 0 0 22.950618 22.9506 22.950541 22.950541 0 0 0 22.95062 -22.9506 22.950541 22.950541 0 0 0 -22.95062 -22.95061 22.950541 22.950541 0 0 0 -18.703946 9.67837 L 61.362463 127.72286 A 30.676234 30.676234 0 0 0 62.65989 118.94187 30.676234 30.676234 0 0 0 31.983713 88.265691 Z" />
</DrawingGroup.Children>
@@ -1149,7 +1156,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingGroup.Children>
<GeometryDrawing
Brush="Black"
Geometry="M17.740002,11.999007C17.254002,11.999007,16.859001,12.394008,16.859001,12.879008L16.859001,24.458791 16.860127,24.479386C16.861003,24.607261 16.813003,24.735511 16.716002,24.834012 16.667502,24.883012 16.611378,24.91995 16.551394,24.944762L16.521562,24.955436 16.507633,24.960536 16.506465,24.960837 16.50574,24.961097 16.49194,24.964574 16.459728,24.972863C16.42719,24.979527,16.393502,24.983027,16.359002,24.983027L16.316763,24.980893 16.315716,24.980851 16.307903,24.980445C16.223895,24.971904,16.146014,24.942538,16.07951,24.897597L16.067752,24.887895 16.047276,24.873078 16.015234,24.844558 16.005502,24.836528 16.001236,24.831357 13.737002,22.600011C13.267003,22.134011 12.502003,22.135011 12.031003,22.600011 11.805003,22.82301 11.680003,23.120011 11.680003,23.436011 11.680003,23.751012 11.805003,24.048011 12.031003,24.272012L16.842003,29.031013C19.311003,31.189013 21.546002,31.119014 22.656003,30.886014 23.939002,30.614013 25.015002,29.846013 25.464002,28.880013 25.857001,28.036012 26.301002,27.080012 26.263001,26.195012 26.262002,26.178012 26.268003,26.074012 26.269002,26.056012L26.268003,20.974011C26.268003,20.55601 25.922002,20.215011 25.496001,20.215011 25.335502,20.215011 25.239001,20.246511 25.179877,20.283135L25.144,20.309762 25.144,21.215013C25.144,21.491012 24.919999,21.715013 24.644,21.715013 24.367999,21.715013 24.144,21.491012 24.144,21.215013L24.144,19.278017C24.144,18.793018 23.749,18.399019 23.263,18.399019 22.808312,18.399019 22.432727,18.745308 22.387555,19.18822L22.383004,19.277944 22.383004,19.716007C22.383004,19.992006 22.159003,20.216004 21.883002,20.216004 21.607003,20.216004 21.383002,19.992006 21.383002,19.716007L21.383002,19.278054 21.383002,18.504013C21.383002,18.018016 20.988002,17.623017 20.502002,17.623017 20.016002,17.623017 19.621001,18.018016 19.621001,18.504013L19.621001,20.24402C19.621001,20.52102 19.397001,20.74402 19.121001,20.74402 18.844001,20.74402 18.621001,20.52102 18.621001,20.24402L18.621001,18.504035 18.621001,12.879008C18.621001,12.394008,18.226001,11.999007,17.740002,11.999007z M17.740002,10.999005C18.777001,10.999005,19.621001,11.843007,19.621001,12.879008L19.621001,16.842839 19.687315,16.808824C19.933955,16.689783 20.210345,16.623022 20.502002,16.623022 21.247346,16.623022 21.892987,17.059031 22.197202,17.689331L22.211675,17.721288 22.212884,17.720384C22.513,17.517568 22.874499,17.399021 23.263,17.399021 24.267594,17.399021 25.091062,18.190155 25.141547,19.181478L25.143222,19.247425 25.174827,19.241074C25.276639,19.223799 25.383876,19.21501 25.496001,19.21501 26.473002,19.21501 27.268003,20.00401 27.268003,20.974011L27.268003,26.087012C27.268003,26.098012 27.262002,26.194013 27.262002,26.205012 27.311002,27.281013 26.790002,28.401013 26.371001,29.302012 25.783002,30.566012 24.472001,31.523013 22.863002,31.864013 22.513001,31.938014 22.067002,31.999013 21.547002,31.999013 20.149003,31.999013 18.216002,31.561013 16.161002,29.763013L11.328003,24.983012C10.910003,24.570012 10.680003,24.020011 10.680003,23.436011 10.680003,22.85101 10.910003,22.302011 11.328003,21.88901 12.187003,21.040011 13.583003,21.041011 14.440002,21.88901L15.859002,23.287391 15.859002,12.879008C15.859002,11.843007,16.703002,10.999005,17.740002,10.999005z M6.8659983,4.0500388L19.083997,4.0500388C20.668996,4.0500388,21.957997,5.3460296,21.957997,6.9390478L21.957997,14.653042C21.957997,14.93005 21.734998,15.153042 21.457997,15.153042 21.181996,15.153042 20.957997,14.93005 20.957997,14.653042L20.957997,6.9390478C20.957997,5.8980318,20.116998,5.0500394,19.083997,5.0500394L6.8659983,5.0500394C5.8319979,5.0500394,4.9909983,5.8980318,4.9909984,6.9390478L4.9909984,26.116061C4.9909983,27.157046,5.8319979,28.00504,6.8659983,28.00504L11.696996,28.00504C11.972997,28.00504 12.196996,28.229039 12.196996,28.50504 12.196996,28.781041 11.972997,29.00504 11.696996,29.00504L6.8659983,29.00504C5.2809954,29.00504,3.9909983,27.70905,3.9909983,26.116061L3.9909983,6.9390478C3.9909983,5.3460296,5.2809954,4.0500388,6.8659983,4.0500388z M6.4360008,0L17.467001,0C17.743001,0 17.967001,0.22300029 17.967001,0.50000024 17.967001,0.77600098 17.743001,1.0000012 17.467001,1.0000012L6.4360008,1.0000012C3.4380004,1.0000012,1.0000002,3.4210042,1.0000002,6.3970079L1.0000002,20.617026C1.0000002,20.893025 0.77600026,21.117026 0.50000048,21.117026 0.22400022,21.117026 2.3841858E-07,20.893025 0,20.617026L0,6.3970079C2.3841858E-07,2.8700035,2.8870004,0,6.4360008,0z"
Geometry="M 0.814453 2e-6 C 0.365092 -7.56e-4 3.21e-4 0.36314 0 0.812502 v 23.945313 c 0.788895 1.126154 1.626953 -0.02112 1.626953 -0.02112 V 1.625002 h 16.241843 c 0 0 0.80538 -0.934678 -0.124651 -1.625002 z M 3.750875 3.43624 C 3.301514 3.435482 2.936743 3.799378 2.936422 4.24874 v 23.945313 c -7.6e-4 0.450124 0.364329 0.815213 0.814453 0.814453 h 7.439453 C 11.042745 28.716852 10.90913 28.418337 10.789937 28.113975 10.699431 27.872971 10.617994 27.628659 10.545796 27.381553 H 4.563375 V 5.06124 h 15.304687 v 7.039063 c 0.173002 -0.01023 0.346234 -0.01609 0.519531 -0.01758 0.343731 -2.6e-4 0.68726 0.01669 1.029297 0.05078 0.02606 0.0038 0.0521 0.0077 0.07813 0.01172 V 4.248738 C 21.494699 3.799376 21.129928 3.43548 20.680567 3.436238 Z m 13.332031 6.884766 c -1.226661 0 -2.230469 1.002456 -2.230469 2.228516 v 9.572265 l -0.824219 -0.8125 v 0.002 c -0.991917 -0.9815 -2.60964 -0.982302 -3.603515 0 -0.48293 0.477155 -0.751953 1.119962 -0.751953 1.796875 0 0.67607 0.269215 1.317958 0.751953 1.794922 l 4.841797 4.78711 0.0078 0.0078 c 2.115591 1.851014 4.144077 2.322266 5.615234 2.322266 0.547268 0 1.017683 -0.06414 1.388672 -0.142578 1.701281 -0.360558 3.108561 -1.372782 3.751954 -2.75586 0.419048 -0.9011 0.978455 -2.062137 0.923828 -3.261718 4.09e-4 -0.01025 0.0011 -0.0075 0.002 -0.02344 0.0015 -0.02655 0.0039 -0.01047 0.0039 -0.07813 v -5.113281 c 0 -1.161114 -0.955476 -2.109375 -2.121094 -2.109375 -0.04803 0 -0.09044 0.0088 -0.136719 0.01172 -0.207784 -1.015547 -1.021531 -1.826172 -2.095703 -1.826172 -0.343265 0 -0.63914 0.145124 -0.93164 0.285156 -0.397916 -0.616533 -1.045784 -1.0625 -1.830078 -1.0625 -0.189487 0 -0.354735 0.07615 -0.53125 0.121094 v -3.515625 c 0 -1.22606 -1.003809 -2.228516 -2.230469 -2.228516 z m 0 1.699219 c 0.148476 0 0.278642 0.05997 0.375 0.15625 0.09685 0.09678 0.15625 0.225142 0.15625 0.373047 v 5.625 1.740234 c 0 0.464846 0.384763 0.849609 0.849609 0.849609 0.464527 0 0.84961 -0.383921 0.84961 -0.849609 v -1.740234 c 0 -0.148598 0.05896 -0.275762 0.15625 -0.373047 0.09729 -0.09729 0.228277 -0.158203 0.375 -0.158203 0.146722 0 0.277709 0.06091 0.375 0.158203 0.09729 0.09729 0.15625 0.224449 0.15625 0.373047 v 0.77539 0.4375 c 0 0.465373 0.384236 0.84961 0.849609 0.84961 0.465373 0 0.849609 -0.384239 0.849609 -0.84961 v -0.425781 l 0.0059 -0.06836 c 0.0286 -0.265146 0.249414 -0.472657 0.52539 -0.472657 0.148238 0 0.278481 0.05996 0.375 0.15625 0.09649 0.09627 0.15625 0.225145 0.15625 0.373047 v 1.935547 c 0 0.465371 0.384237 0.84961 0.84961 0.84961 0.465372 0 0.849609 -0.384237 0.849609 -0.84961 v -0.65039 c 0.0016 -9e-6 2.95e-4 0 0.002 0 0.238806 0 0.421875 0.185008 0.421875 0.410156 l 0.002 5.0625 c -4.64e-4 0.0084 -0.0023 0.0401 -0.0039 0.07227 -0.0015 0.03041 -0.0059 0.02687 -0.002 0.101563 0.03253 0.757506 -0.372777 1.677807 -0.765625 2.521484 h -0.002 c -0.194342 0.418116 -0.532821 0.810028 -0.976563 1.126953 -0.443595 0.316821 -0.990203 0.55925 -1.585937 0.685547 -0.515602 0.10823 -1.298166 0.179402 -2.248047 -0.03125 -0.47546 -0.105441 -0.991926 -0.281044 -1.539063 -0.560547 -0.547306 -0.279588 -1.127683 -0.66381 -1.724609 -1.185547 L 11.620061 23.69601 c -0.162198 -0.160763 -0.248047 -0.365899 -0.248047 -0.587891 0 -0.223284 0.08648 -0.426518 0.248047 -0.585937 0.168295 -0.166151 0.386826 -0.249911 0.607422 -0.25 0.220595 -9e-5 0.437432 0.08144 0.605469 0.248047 l 2.240234 2.207031 0.01367 0.02148 0.04687 0.03516 0.03711 0.0332 0.02539 0.01758 0.01563 0.01367 0.01563 0.0098 c 0.114637 0.07747 0.248043 0.126397 0.388672 0.140625 l -0.04883 -0.0078 0.04102 0.0098 0.08594 0.0039 h 0.0078 c 0.06844 0 0.126002 -0.0085 0.169922 -0.01758 l 0.0059 -0.002 0.01953 -0.0039 h 0.02539 l 0.06641 -0.02734 0.02734 -0.0098 0.0098 -0.0039 c 0.102105 -0.04223 0.198238 -0.105586 0.28125 -0.189454 h 0.002 c 0.163006 -0.165519 0.243191 -0.385955 0.242188 -0.599609 v -0.002 -0.01953 -11.580078 c 0 -0.147904 0.0594 -0.276271 0.15625 -0.373047 0.09636 -0.09628 0.226522 -0.15625 0.375 -0.15625 z"
/>
</DrawingGroup.Children>
</DrawingGroup>
@@ -1157,10 +1164,10 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
</DrawingImage>
<!-- Pin
<!-- Pin
<Path Data="F1M3,11L3,7 0,7 0,6 1,6 1,1 1,0 6,0 6,1 6,6 7,6 7,7 4,7 4,11 3,11z M2,1L2,6 4,6 4,1 2,1z" />
Unpin
Unpin
<Path Data="F1M0,3L4,3 4,0 5,0 5,1 10,1 11,1 11,6 10,6 5,6 5,7 4,7 4,4 0,4 0,3z M10,2L5,2 5,4 10,4 10,2z" />
-->
</ResourceDictionary>
</ResourceDictionary>
@@ -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);
}
@@ -19,6 +19,8 @@ namespace mbts.spark {
this.key = key;
}
public static AlgoData empty => from_custom_def(new CustomOrderDefinitionImmutable(new CustomOrderDefinition()));
/// <summary>
/// This is the only <see cref="AlgoData"/> factory method that allows callers to explicitly specify a name.
/// It should only be used within the context of creating, or renaming, studio algos.
@@ -65,6 +67,8 @@ namespace mbts.spark {
}
struct AlgoDataVM : IEquatable<AlgoDataVM> {
public static readonly Func<AlgoDataVM, AlgoID> id_fn = vm => AlgoID.from_algo_key(vm.data.key);
public readonly AlgoData data;
public readonly AlgoDefinitionMetadata? meta;
@@ -68,13 +68,51 @@ namespace mbts.spark {
nameof(OrderBookRowVM.symbol)),
DataGridFilterKnownTemplates.DiscreteValues,
DefaultColumnWidths.Symbol);
// algo items view (common resources)
var algo_items_comp = AlgoCollectionHelper.algo_data_vm_comparer.to_non_generic();
var algo_items_group_desc = new PropertyGroupDescription(nameof(AlgoDataVM.group));
// order type
var order_type_filter = dv_filter(OrderDataHelper.all_order_types, r => r.order?.order_type() ?? OrderType.None);
var order_type_filter = DiscreteValuesFilterVM.CreateTableUpdate<FillTuple, AlgoDataVM, AlgoID, OrderType[]>(
ft => new AlgoDataVM(ft.algo ?? AlgoData.empty),
AlgoDataVM.id_fn,
ots => Array.ConvertAll(ots ?? Array.Empty<OrderType>(), ot => AlgoID.from_algo_key(AlgoKey.from_known_type(ot))),
ids => ids.empty_if_null().Select(AlgoID.to_algo_key).Where(k => k.mode == AlgoKey.Mode.Factory).Select(k => k.factory).ToArray(),
out var order_type_observer,
out var order_type_items_view);
order_type_items_view.CustomSort = algo_items_comp;
order_type_items_view.GroupDescriptions.Add(algo_items_group_desc);
order_type_observer.OnNext(TableUpdate<AlgoDataVM, AlgoID>.mk_empty(AlgoDataVM.id_fn)
.with_added(OrderDataHelper.all_order_types
.Select(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable)
.Select(AlgoData.from_custom_def)
.Select(d => new AlgoDataVM(d))));
add_col_expl(Columns.OrderType,
new OrderTypeColumn<Columns>(Columns.OrderType, new Binding(nameof(Row.badge_fill)), new Binding(nameof(Row.display_name)), order_type_filter) {
id = col_id(Columns.OrderType),
sort_member_path = nameof(Row.order_type)
}, order_type_filter);
// root algo type
var root_algo_type_filter = DiscreteValuesFilterVM.CreateTableUpdate<FillTuple, AlgoDataVM, AlgoID>(
ft => new AlgoDataVM(ft.root_algo ?? AlgoData.empty),
AlgoDataVM.id_fn,
out var root_algo_type_observer,
out var root_algo_type_items_view);
root_algo_type_items_view.CustomSort = algo_items_comp;
root_algo_type_items_view.GroupDescriptions.Add(algo_items_group_desc);
add_col_expl(Columns.RootAlgoType,
new OrderTypeColumn<Columns>(
Columns.RootAlgoType,
new Binding(nameof(Row.root_algo_badge_color)) { Converter = new ui.converters.ColorToBrushConverter() },
new Binding(nameof(Row.root_algo_display_name)),
root_algo_type_filter,
DefaultColumnHeaders.RootAlgoType) {
id = col_id(Columns.RootAlgoType),
sort_member_path = nameof(Row.root_algo_order_type)
}, root_algo_type_filter);
// side
var side_filter = dv_filter(new[] { Side.Buy, Side.Sell }, r => r.fill.Side);
add_col_expl(Columns.Side, new SideColumn<Row, Columns>(Columns.Side, r => r.side, side_filter) { id = col_id(Columns.Side) }, side_filter);
@@ -128,6 +166,7 @@ namespace mbts.spark {
};
_rows_manager.on_settings(fs, s.sorting_criterion);
}),
model.subscribe_all_algos().SelectKeyValue(AlgoDataVM.from_data, d => AlgoID.from_algo_key(d.data.key)).OnDispatcher().Subscribe(root_algo_type_observer),
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
);
// local methods
@@ -175,8 +214,8 @@ namespace mbts.spark {
static IObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, FillsSettings state) {
if (!state.orders.is_null_or_empty()) {
var orders_hash = new HashSet<uint>(state.orders);
return model.on_fill_tuples.Filter(f => orders_hash.Contains(f.fill.OrderKey));
} else return model.on_fill_tuples;
return model.on_fill_tuples_with_root.Filter(f => orders_hash.Contains(f.fill.OrderKey));
} else return model.on_fill_tuples_with_root;
}
static IObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, IObservable<FillsSettings> state) =>
@@ -191,6 +230,7 @@ namespace mbts.spark {
case Columns.FillTime: return wrap(ft => ft.fill.Timestamp.to_local_time());
case Columns.OrderKey: return wrap(ft => ft.fill.OrderKey);
case Columns.OrderType: return wrap_default(ft => ft.order?.order_type() ?? OrderType.None);
case Columns.RootAlgoType: return wrap_default(ft => ft.root_algo.HasValue ? ft.root_algo.Value.value.CustomCase : OrderType.None);
case Columns.Price: return wrap(ft => ft.fill.LastPrice);
case Columns.Security: return wrap(ft => ft.security ?? SecurityDataHelper.Defaults.SecurityImmutable);
case Columns.SecurityKey: return wrap(ft => ft.fill.SecurityKey);
@@ -224,7 +264,10 @@ namespace mbts.spark {
readonly OrdersModel _model;
readonly FillImmutable _fill;
readonly OrderRecordImmutable _order;
AlgoData? _root_algo => fill_tuple.root_algo;
AlgoDefinitionMetadata? _root_algo_meta => fill_tuple.root_algo_meta;
Brush _badge_fill;
Color? _root_algo_badge_color;
OrderMetadata _order_metadata;
public Row(FillTuple fill_tuple, OrdersModel model) :
@@ -239,6 +282,7 @@ namespace mbts.spark {
public DateTime fill_time => _fill.Timestamp.to_local_time();
public TimeInForce time_in_force => _order.time_in_force();
public OrderType order_type => _order.order_type();
//public OrderType root_algo_type => _or
public string display_name => OrderUtils.display_name(_order, fill_tuple.algo);
public OrderMetadata order_metadata => _order_metadata ?? (_order_metadata = _model.factory.get_metadata(_order.Def.CustomDef.CustomCase));
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(fill_tuple.algo_meta, order_metadata));
@@ -252,6 +296,12 @@ namespace mbts.spark {
public string fill_key => _fill.FillKey;
public string order_key => _order.Key.ToString();
// Root Algo
public OrderType root_algo_order_type => _root_algo.HasValue ? _root_algo.Value.value.CustomCase : OrderType.None;
public OrderMetadata root_algo_order_metadata => _model.factory.get_metadata(root_algo_order_type);
public Color root_algo_badge_color => _root_algo_badge_color ?? (_root_algo_badge_color = OrderUtils.badge_color(_root_algo_meta, root_algo_order_metadata)).Value;
public string root_algo_display_name => _root_algo?.name ?? root_algo_order_type.display_name();
public bool Equals(Row other) => other != null && other._fill.Equals(_fill) && other._order.Equals(_order);
public override bool Equals(object obj) => Equals(obj as Row);
public override int GetHashCode() => _fill.FillKey.GetHashCode() ^ _order.Key.GetHashCode();
@@ -16,6 +16,7 @@ namespace mbts.ui.spark {
Symbol,
TimeInForce,
OrderType,
RootAlgoType,
Side,
Price,
Size,
@@ -1,19 +1,15 @@
using bts.api;
using bts.api.orderman;
using bts.settings;
using bts.shell.fills;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel.Composition;
using System.Reactive.Linq;
using System.Threading.Tasks;
using static mbts.ui.controls.DIYGrid;
using static mbts.ui.spark.FillsView;
namespace bts.shell {
@@ -34,6 +30,7 @@ namespace bts.shell {
{ nameof(FillsVM.Row.fill_time), Columns.FillTime },
{ nameof(FillsVM.Row.order_key), Columns.OrderKey },
{ nameof(FillsVM.Row.order_type), Columns.OrderType },
{ nameof(FillsVM.Row.root_algo_order_type), Columns.RootAlgoType },
{ nameof(FillsVM.Row.price), Columns.Price },
{ nameof(FillsVM.Row.security), Columns.Security },
{ nameof(FillsVM.Row.security_key), Columns.SecurityKey },
@@ -67,5 +64,12 @@ namespace bts.shell {
set: (us, gs) => us.with(provided_fills_gridsettings: gs));
return new FillsWindow(this, _bus, _orders_model, state, settings_manager);
}
// migration rules
[Export(typeof(IFillsGridSettingsMigrationRule))]
class AddMissingColumnsMigrationRule : IFillsGridSettingsMigrationRule {
public bool apply(GridSettings settings, out GridSettings new_settings) =>
settings.add_missing_columns(FillsSettings.DefaultGridSettings.columns, out new_settings);
}
}
}
@@ -23,6 +23,10 @@ namespace bts.shell.fills {
[DIYGridColumnID(nameof(Columns.OrderType))]
public CustomOrderDefinition.CustomOneofCase[] order_type { get; set; }
[ProtoMember(16)]
[DIYGridColumnID(nameof(Columns.RootAlgoType))]
public AlgoID[] root_algo_type { get; set; }
[ProtoMember(4)]
[DIYGridColumnID(nameof(Columns.Account))]
public string[] account { get; set; }
@@ -88,6 +92,7 @@ namespace bts.shell.fills {
pb.add_discrete_values(fs.exchange, ft => ft.exchange());
pb.add_discrete_values(fs.time_in_force, ft => ft.time_in_force());
pb.add_discrete_values(fs.order_type, ft => ft.order_type());
pb.add_discrete_values(fs.root_algo_type, ft => AlgoID.from_algo_key((ft.root_algo ?? AlgoData.empty).key));
pb.add_discrete_values(fs.side, ft => ft.fill.Side);
pb.add_discrete_values(fs.fill_kind, ft => ft.fill.FillKind);
pb.add_discrete_values(fs.symbol, ft => ft.security?.Symbol ?? string.Empty);
@@ -47,6 +47,7 @@ namespace bts.shell.fills {
public static GridColumnSettings exchange => new GridColumnSettings(col_id(Columns.Exchange), DefaultColumnHeaders.Exchange, width: DefaultColumnWidths.Exchange);
public static GridColumnSettings time_in_force => new GridColumnSettings(col_id(Columns.TimeInForce), DefaultColumnHeaders.TimeInForce, width: DefaultColumnWidths.TimeInForce);
public static GridColumnSettings order_type => new GridColumnSettings(col_id(Columns.OrderType), DefaultColumnHeaders.OrderType, width: DefaultColumnWidths.OrderType);
public static GridColumnSettings root_algo_type => new GridColumnSettings(col_id(Columns.RootAlgoType), DefaultColumnHeaders.RootAlgoType, width: DefaultColumnWidths.OrderType);
public static GridColumnSettings side => new GridColumnSettings(col_id(Columns.Side), DefaultColumnHeaders.Side, width: DefaultColumnWidths.Side);
public static GridColumnSettings quantity => new GridColumnSettings(col_id(Columns.Size), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings price => new GridColumnSettings(col_id(Columns.Price), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
@@ -70,6 +71,7 @@ namespace bts.shell.fills {
yield return exchange;
yield return time_in_force;
yield return order_type;
yield return root_algo_type;
yield return side;
yield return quantity;
yield return price;
@@ -450,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)]
@@ -570,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 &&
@@ -72,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"),
@@ -137,7 +138,7 @@ namespace mbts.spark {
c.PropertyChanged -= on_prop_changed;
}
AccountTuple? account_factory(uint key, string name) => new AccountTuple(key, name);
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;
@@ -239,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;
@@ -281,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;
@@ -343,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;
@@ -415,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;
@@ -35,8 +35,8 @@ namespace mbts.spark {
_show_cancel_sells, _show_cancel_all, _show_zoom;
readonly PropertyAutoWrapper<LadderSettings, Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
readonly HashSet<AlgoKey> _sticky_algos = new HashSet<AlgoKey>();
double _position, _day_position;
bool _show_account_position, _show_day_position;
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;
@@ -109,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;
@@ -135,7 +138,7 @@ 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)));
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);
@@ -232,14 +235,19 @@ namespace mbts.spark {
}),
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));
}));
}
@@ -290,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);
@@ -316,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;
@@ -327,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); }
@@ -435,7 +451,7 @@ namespace mbts.spark {
price = temp_price;
}
}
await _model.order_gateway.send_order(order, price, size);
await _model.order_gateway.send_order(security, order, price, size);
if (auto_clear)
size_calculator.value = 0;
var fav_algo = order_properties_vm.selected_favorite_algo_definition;
@@ -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) {
@@ -15,10 +15,11 @@ namespace mbts.spark {
CVRowStyleKey = "CVRowStyle",
CVRowTemplateKey = "CVRowTemplateKey";
public MiniOrderBookView(OrderBookVM vm) {
public MiniOrderBookView() =>
InitializeComponent();
public MiniOrderBookView(OrderBookVM vm) : this() =>
DataContext = vm;
}
}
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
@@ -750,6 +750,7 @@ namespace mbts.spark {
};
header_cb.SetBinding(CheckBox.IsCheckedProperty, new Binding(nameof(OrderBookVM.all_rows_selected)));
this.Header = header_cb;
is_visible = true;
}
public override FrameworkElement view_factory() {
@@ -288,7 +288,14 @@ namespace mbts.spark {
double price = attach_vm.price;
uint? size = attach_vm.size;
uint algo_template_key = attach_vm.algo_vm.algo_root.id.is_persisted ? attach_vm.algo_vm.algo_root.id.persisted : 0u;
var order_tups = selected_rows.Select(r => r.order_tuple).ToList();
// Don't pass any order tuples that don't have a security defined.
// The actual security is passed to the OrdersModel - instead of just the security key -
// so it can prevent GlbxIceberg orders from being sent to a non-glbx exchange (such as Ice).
/// Impl details: <see cref="OrdersModel.send_order(SecurityImmutable, OrderDefinitionImmutable?, double, uint)"/>
var order_tups = selected_rows
.Select(r => r.order_tuple)
.Where(ot => ot.order.security.HasValue)
.ToList();
var mod_algo_vm = attach_vm.algo_vm.algo_root;
await Task.WhenAll(order_tups.Select(t => {
var algo = t.order.order.Def.CustomDef.actual().CloneToMutable();
@@ -302,6 +309,7 @@ namespace mbts.spark {
};
}
return _model.send_order(
t.order.security.Value,
t.order.order.Def.with(
attach_target_order_key: t.order.order.Key,
custom_def: algo
@@ -1,168 +1,179 @@
using bts.api;
using bts.api.orderman;
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.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using static mbts.spark.PAndLView;
using static mbts.ui.controls.DIYGrid;
namespace bts.shell {
internal class PAndLWindowFactory : ShellWindowFactory<PAndLWindowFactory.PAndLStartupData> {
readonly IBus _bus;
readonly OrdersModel _orders_model;
readonly UserSettingsHub _settings_hub;
public PAndLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) : base(owner, "SparkPAndL") {
_bus = bus;
_orders_model = orders_model;
_settings_hub = settings_hub;
_dispose = bus.subscribe(SparkTags.ShowPAndLTag).OnDispatcher().Subscribe(show);
}
public static string col_id(Columns col) => col.ToString();
public override Task<IShellWindow> load(PAndLStartupData state) {
return Task.FromResult(_create(state));
}
async void show(PAndLStartupData startup_data) {
var w = await load(startup_data);
_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);
}
[ProtoContract]
public class PAndLStartupData {
/// <summary>
/// Parameterless constructor for protobuf
/// </summary>
public PAndLStartupData() { }
public PAndLStartupData(List<SortingCriteria> sorting_criteria, FilterStates filters, pnl_calc_type calc_type, GridSettings grid = default) {
_sort_criteria = sorting_criteria;
this.filters = filters;
this.calc_type = calc_type;
this.grid = grid;
}
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
public IReadOnlyList<SortingCriteria> sorting_criterion => _sort_criteria ?? (_sort_criteria = DefaultColumns.default_sorting_criterion.ToList());
public static PAndLStartupData Default() =>
new PAndLStartupData(null, new FilterStates(), pnl_calc_type.midpoint);
[ProtoMember(5)]
List<SortingCriteria> _sort_criteria;
[ProtoMember(10)]
public FilterStates filters;
[ProtoMember(15)]
public pnl_calc_type calc_type;
//[ProtoMember(20)]
// ProtoMember 20 cannot be used further
[ProtoMember(25)]
public GridSettings grid;
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);
public static GridColumnSettings symbol => new GridColumnSettings(col_id(Columns.Symbol), DefaultColumnHeaders.Symbol, width: DefaultColumnWidths.Symbol);
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
public static GridColumnSettings security_type => new GridColumnSettings(col_id(Columns.SecurityType), DefaultColumnHeaders.SecurityType, width: DefaultColumnWidths.SecurityType);
public static GridColumnSettings start_net_position => new GridColumnSettings(col_id(Columns.StartNetPos), DefaultColumnHeaders.StartNetPos, width: DefaultColumnWidths.Start_Net_Pos, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
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 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 IEnumerable<SortingCriteria> default_sorting_criterion {
get {
yield return new SortingCriteria(account.id, true);
yield return new SortingCriteria(exchange.id, true);
yield return new SortingCriteria(symbol.id, true);
yield return new SortingCriteria(security.id, true);
}
}
public static IEnumerable<GridColumnSettings> all {
get {
yield return account;
yield return exchange;
yield return symbol;
yield return security;
yield return security_type;
yield return start_net_position;
yield return start_pnl;
yield return day_long_pnl;
yield return day_short_pnl;
yield return day_pnl;
yield return total_net_pos;
yield return total_pnl;
yield return pnl_price;
}
}
}
}
[ProtoContract]
public class FilterStates {
[ProtoMember(1)]
public string account_regex;
[ProtoMember(2)]
public string exchange_regex;
[ProtoMember(3)]
public string symbol_regex;
[ProtoMember(4)]
public string security_regex;
[ProtoMember(5)]
public IntMinMaxState start_net_pos;
[ProtoMember(6)]
public DoubleMinMaxState start_pnl;
[ProtoMember(7)]
public UIntMinMaxState day_long_pos;
[ProtoMember(8)]
public UIntMinMaxState day_short_pos;
[ProtoMember(9)]
public DoubleMinMaxState day_pnl;
[ProtoMember(10)]
public IntMinMaxState total_net_pos;
[ProtoMember(11)]
public DoubleMinMaxState total_pnl;
[ProtoMember(12)]
public DoubleMinMaxState pnl_price;
// [ProtoMember(13)]
// ProtoMember 13 cannot be used further
[ProtoMember(14)]
public SecurityType[] security_types;
[ProtoMember(15)]
public bool[] has_day_trades;
}
}
}
using bts.api;
using bts.settings;
using bts.spark;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
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;
using static mbts.spark.PAndLView;
using static mbts.ui.controls.DIYGrid;
namespace bts.shell {
internal class PAndLWindowFactory : ShellWindowFactory<PAndLWindowFactory.PAndLStartupData> {
readonly IBus _bus;
readonly OrdersModel _orders_model;
readonly UserSettingsHub _settings_hub;
public PAndLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) : base(owner, "SparkPAndL") {
_bus = bus;
_orders_model = orders_model;
_settings_hub = settings_hub;
_dispose = bus.subscribe(SparkTags.ShowPAndLTag).OnDispatcher().Subscribe(show);
}
public static string col_id(Columns col) => col.ToString();
public override Task<IShellWindow> load(PAndLStartupData state) {
return Task.FromResult(_create(state));
}
async void show(PAndLStartupData startup_data) {
var w = await load(startup_data);
_bus.post(SparkTags.ShowWindow, w);
}
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 {
/// <summary>
/// Parameterless constructor for protobuf
/// </summary>
public PAndLStartupData() { }
public PAndLStartupData(List<SortingCriteria> sorting_criteria, FilterStates filters, pnl_calc_type calc_type, GridSettings grid = default) {
_sort_criteria = sorting_criteria;
this.filters = filters;
this.calc_type = calc_type;
this.grid = grid;
}
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
public IReadOnlyList<SortingCriteria> sorting_criterion => _sort_criteria ?? (_sort_criteria = DefaultColumns.default_sorting_criterion.ToList());
public static PAndLStartupData Default() =>
new PAndLStartupData(null, new FilterStates(), pnl_calc_type.midpoint);
[ProtoMember(5)]
List<SortingCriteria> _sort_criteria;
[ProtoMember(10)]
public FilterStates filters;
[ProtoMember(15)]
public pnl_calc_type calc_type;
//[ProtoMember(20)]
// ProtoMember 20 cannot be used further
[ProtoMember(25)]
public GridSettings grid;
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);
public static GridColumnSettings symbol => new GridColumnSettings(col_id(Columns.Symbol), DefaultColumnHeaders.Symbol, width: DefaultColumnWidths.Symbol);
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
public static GridColumnSettings security_type => new GridColumnSettings(col_id(Columns.SecurityType), DefaultColumnHeaders.SecurityType, width: DefaultColumnWidths.SecurityType);
public static GridColumnSettings start_net_position => new GridColumnSettings(col_id(Columns.StartNetPos), DefaultColumnHeaders.StartNetPos, width: DefaultColumnWidths.Start_Net_Pos, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
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 {
yield return new SortingCriteria(account.id, true);
yield return new SortingCriteria(exchange.id, true);
yield return new SortingCriteria(symbol.id, true);
yield return new SortingCriteria(security.id, true);
}
}
public static IEnumerable<GridColumnSettings> all {
get {
yield return account;
yield return exchange;
yield return symbol;
yield return security;
yield return security_type;
yield return start_net_position;
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;
}
}
}
}
[ProtoContract]
public class FilterStates {
[ProtoMember(1)]
public string account_regex;
[ProtoMember(2)]
public string exchange_regex;
[ProtoMember(3)]
public string symbol_regex;
[ProtoMember(4)]
public string security_regex;
[ProtoMember(5)]
public IntMinMaxState start_net_pos;
[ProtoMember(6)]
public DoubleMinMaxState start_pnl;
[ProtoMember(7)]
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)]
public IntMinMaxState total_net_pos;
[ProtoMember(11)]
public DoubleMinMaxState total_pnl;
[ProtoMember(12)]
public DoubleMinMaxState pnl_price;
// [ProtoMember(13)]
// ProtoMember 13 cannot be used further
[ProtoMember(14)]
public SecurityType[] security_types;
[ProtoMember(15)]
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);
}
}
}
@@ -10,6 +10,7 @@ using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
namespace bts.shell.pnl {
@@ -19,6 +20,7 @@ namespace bts.shell.pnl {
Ask,
Opposite,
Same,
[Description("Cascade")]
Waterfall,
Last,
Mid,
@@ -142,7 +144,7 @@ namespace bts.shell.pnl {
orders.Aggregate(Empty, (seed, order) => seed.Add(order));
public WorkingOrderData Add(OrderRecordImmutable order) {
if (order.working_quantity() < 0)
if (order.working_quantity() <= 0)
return new WorkingOrderData(long_data, short_data);
switch (order.side()) {
case Side.Buy: return new WorkingOrderData(long_data.Add(order), short_data);
@@ -152,7 +154,7 @@ namespace bts.shell.pnl {
}
public WorkingOrderData Remove(OrderRecordImmutable order) {
if (order.working_quantity() < 0)
if (order.working_quantity() <= 0)
return new WorkingOrderData(long_data, short_data);
switch (order.side()) {
case Side.Buy: return new WorkingOrderData(long_data.Remove(order.Key, order.price()), short_data);
@@ -248,7 +250,7 @@ namespace bts.shell.pnl {
public readonly SecurityImmutable? security;
public readonly string account_name;
public readonly PNLCalculationType calc_type;
public readonly PositionPriceSize day, sod;
public readonly PositionPriceSize day, raw_day, sod;
public readonly WorkingOrderData working;
public readonly double
@@ -267,6 +269,7 @@ namespace bts.shell.pnl {
double settlement_price = 0,
PositionPriceSize? sod = null,
PositionPriceSize? day = null,
PositionPriceSize? raw_day = null,
WorkingOrderData? working = null,
SecurityImmutable? security = null,
string account_name = null,
@@ -280,6 +283,7 @@ namespace bts.shell.pnl {
this.settlement_price = settlement_price;
this.sod = sod ?? PositionPriceSize.empty;
this.day = day ?? PositionPriceSize.empty;
this.raw_day = raw_day ?? PositionPriceSize.empty;
this.working = working ?? WorkingOrderData.Empty;
this.security = security;
this.account_name = account_name;
@@ -392,6 +396,11 @@ namespace bts.shell.pnl {
day_long_avg_price_delta_raw, day.long_pos,
day_short_avg_price_delta_raw, day.short_pos);
/// Raw Day
public double raw_day_long_pos => raw_day.long_pos;
public double raw_day_short_pos => raw_day.short_pos;
public double raw_day_net_pos => raw_day.net_pos;
/// Total
public double total_pnl => DoubleUtil.nan_to_zero_sum(sod_pnl, day_pnl);
// long
@@ -492,6 +501,7 @@ namespace bts.shell.pnl {
account_name == other.account_name &&
sod == other.sod &&
day == other.day &&
raw_day == other.raw_day &&
working.summary == other.working.summary;
public override bool Equals(object obj) =>
@@ -516,6 +526,7 @@ namespace bts.shell.pnl {
t.value.SettlementPrice,
new PositionPriceSize(t.value.Pos.SodLongPos, t.value.Pos.SodLongAvgPrice, t.value.Pos.SodShortPos, t.value.Pos.SodShortAvgPrice),
new PositionPriceSize(t.value.Pos.DayLongPos, t.value.Pos.DayLongAvgPrice, t.value.Pos.DayShortPos, t.value.Pos.DayShortAvgPrice),
t.value.Pos.raw_day_pps(),
WorkingOrderData.Empty,
t.sec,
t.value.Pos.Account);
@@ -533,6 +544,7 @@ namespace bts.shell.pnl {
pnl_data.settlement_price,
pnl_data.sod,
pnl_data.day,
pnl_data.raw_day,
working ?? pnl_data.working,
security ?? pnl_data.security,
account_name ?? pnl_data.account_name,
@@ -4,31 +4,14 @@ using System;
namespace bts.shell.pnl {
readonly struct PNLNormalizedData : IEquatable<PNLNormalizedData> {
public static readonly PNLNormalizedData zero =
new PNLNormalizedData(0, 0, 0, 0, 0, 0);
public static PNLNormalizedData create_nanless(
decimal day_pnl,
decimal day_long_pos,
decimal day_short_pos,
decimal sod_pnl,
decimal sod_long_pos,
decimal sod_short_pos,
decimal working_long_pos,
decimal working_short_pos) =>
new PNLNormalizedData(
day_pnl,
day_long_pos,
day_short_pos,
sod_pnl,
sod_long_pos,
sod_short_pos,
working_long_pos,
working_short_pos);
new PNLNormalizedData(0, 0, 0, 0, 0, 0, 0, 0);
public PNLNormalizedData(
decimal day_pnl,
decimal day_long_pos,
decimal day_short_pos,
decimal raw_day_long_pos,
decimal raw_day_short_pos,
decimal sod_pnl,
decimal sod_long_pos,
decimal sod_short_pos,
@@ -38,6 +21,8 @@ namespace bts.shell.pnl {
this.day_pnl = day_pnl;
this.day_long_pos = day_long_pos;
this.day_short_pos = day_short_pos;
this.raw_day_long_pos = raw_day_long_pos;
this.raw_day_short_pos = raw_day_short_pos;
this.sod_pnl = sod_pnl;
this.sod_long_pos = sod_long_pos;
this.sod_short_pos = sod_short_pos;
@@ -55,6 +40,10 @@ namespace bts.shell.pnl {
public decimal day_long_pos { get; }
public decimal day_short_pos { get; }
public decimal day_net_pos => day_long_pos - day_short_pos;
// raw day
public decimal raw_day_long_pos { get; }
public decimal raw_day_short_pos { get; }
public decimal raw_day_net_pos => raw_day_long_pos - raw_day_short_pos;
// total
public decimal total_pnl => sod_pnl + day_pnl;
public decimal total_net_pos => total_long_pos - total_short_pos;
@@ -69,6 +58,8 @@ namespace bts.shell.pnl {
day_pnl == other.day_pnl &&
day_long_pos == other.day_long_pos &&
day_short_pos == other.day_short_pos &&
raw_day_long_pos == other.raw_day_long_pos &&
raw_day_short_pos == other.raw_day_short_pos &&
sod_pnl == other.sod_pnl &&
sod_long_pos == other.sod_long_pos &&
sod_short_pos == other.sod_short_pos &&
@@ -86,11 +77,13 @@ namespace bts.shell.pnl {
public override string ToString() =>
string.Join(",", new[] {
$"SOD PNL : {sod_pnl}",
$"SOD Long : {sod_pnl}",
$"SOD Short: {sod_pnl}",
$"SOD Long : {sod_long_pos}",
$"SOD Short: {sod_short_pos}",
$"Day PNL : {day_pnl}",
$"Day Long : {day_pnl}",
$"Day Short: {day_pnl}",
$"Day Long : {day_long_pos}",
$"Day Short: {day_short_pos}",
$"Raw Day Long : {raw_day_long_pos}",
$"Raw Day Short: {raw_day_short_pos}",
$"Work Long : {working_long_pos}",
$"Work Short: {working_short_pos}",
});
@@ -103,6 +96,8 @@ namespace bts.shell.pnl {
a.day_pnl + b.day_pnl,
a.day_long_pos + b.day_long_pos,
a.day_short_pos + b.day_short_pos,
a.raw_day_long_pos + b.raw_day_long_pos,
a.raw_day_short_pos + b.raw_day_short_pos,
a.sod_pnl + b.sod_pnl,
a.sod_long_pos + b.sod_long_pos,
a.sod_short_pos + b.sod_short_pos,
@@ -114,6 +109,8 @@ namespace bts.shell.pnl {
a.day_pnl - b.day_pnl,
a.day_long_pos - b.day_long_pos,
a.day_short_pos - b.day_short_pos,
a.raw_day_long_pos - b.raw_day_long_pos,
a.raw_day_short_pos - b.raw_day_short_pos,
a.sod_pnl - b.sod_pnl,
a.sod_long_pos - b.sod_long_pos,
a.sod_short_pos - b.sod_short_pos,
@@ -1,5 +1,6 @@
using bts.api;
using bts.collections;
using bts.settings;
using bts.shell.pnl;
using bts.spark;
using bts.utils;
@@ -16,6 +17,8 @@ using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.Specialized;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
@@ -47,47 +50,38 @@ namespace bts.shell {
_bus.post(SparkTags.ShowWindow, _create(settings));
IShellWindow _create(PNLSettings settings) {
if (settings != null) {
var def_grid = PNLSettings.DefaultGridSettings;
// add missing columns (i.e., if new cols were added after this workspace was saved)
if (settings.grid.columns.Count != def_grid.columns.Count) {
settings.grid.columns.difference_hard(def_grid.columns, out var added, out var removed, EqualityComparer.Create<GridColumnSettings, string>(c => c.id));
var new_cols = settings.grid.columns.ToList();
for (int i = 0; i < added.Count; i++) {
var col = added[i];
new_cols.Add(col);
}
settings.grid = settings.grid.with(columns: new_cols);
}
}
var settings_manager = new WindowSettingsManager<PNLSettings>(_settings_hub,
settings,
get: us => us.pnl,
set: (us, pnl) => us.with(pnl: pnl));
var settings_manager = _settings_hub.create_pnl_settings_manager(settings);
// Need to keep a local copy of the columns, since they are defined in xaml (not the viewmodel), and get_save_state may be called on a non-dispatcher thread.
// If that happens, we would be unable to access _grid.Columns, and thus unable to get the current column settings.
// Use an ImmutableList to avoid any threading issues when responding to CollectionChanged events on the dispatcher thread.
ImmutableList<DIYGrid.ColumnBase> cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty;
return SparkWindow<PNLSaveState>.create(_window_type, _window_title, _bus, this, Icons.Accounting, _thread_id_provider.next_id(), () => {
IEnumerable<SortingCriteria> last_sorting_criterion = null;
var vm = new PNLVM(_bus, _orders_model, settings ?? PNLSettings.Default);
var content = new PNLView {
DataContext = vm
};
vm.sorting_changed += sh => {
try {
DIYGridSortHelper.set_sort_directions(sh, content._grid.Columns);
} catch { }
};
vm.SubscribeDisposed(
DIYGridSortHelper.observe_active_comparers<PNLView.Columns, PNLRowVM>(content._grid, PNLVM.get_column_comparer)
.OnDispatcher()
.Subscribe(sh => vm.sorting_hierarchy = sh.Select(s => (s.key, s.direction)).ToList()));
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
Func<PNLSaveState> get_save_state = () => {
if (last_sorting_criterion == null)
last_sorting_criterion = PNLSettings.Default.sorting_criterion;
var cols = cached_columns.ToArray(c => c.create());
var filters = vm.get_filters_states();
var sorting_criterion = vm.sorting_hierarchy.Select(tup => new SortingCriteria(tup.key.ToString(), tup.direction == ListSortDirection.Ascending)).ToArray();
var calc_type = vm.calculation_type;
var state = settings_manager.instance_settings;
if (state == null) {
// don't use defaults if the user manipulated the window state outside of the settings dialog (e.g., resized column(s) via drag, or changed sort/filter)
var defaults = settings_manager.default_settings;
if (calc_type == defaults.calculation_type &&
defaults.sorting_criterion.SequenceEqual(last_sorting_criterion) &&
defaults.sorting_criterion.SequenceEqual(sorting_criterion) &&
defaults.grid.columns.SequenceEqual(cols) && defaults.filters.safe_equals(filters)) {
// the user's current state still matches the defaults (i.e., no outside manipulation was performed); return null so this window will use the latest default settings when restored
return new PNLSaveState { use_defaults = true, settings = null };
@@ -96,10 +90,10 @@ namespace bts.shell {
}
// assign latest values, as they may have be modified outside of the settings dialog (in which case the settings manager would be out of date)
state = state.with(
last_sorting_criterion,
sorting_criterion,
vm.anchors,
filters,
state.grid.with(columns: cols),
state.grid.with(columns: cols, background: vm.grid_background_color.rgb_color()),
calc_type,
vm.header_row_display);
return new PNLSaveState { use_defaults = false, settings = state };
@@ -160,14 +154,10 @@ namespace bts.shell {
header_appearance.font_family);
}
}
vm.rows_view.set_sort_descriptions(s.sorting_criterion, content._grid.Columns);
CollectionHelper.sync_with_never_duplicate(content._grid.Columns, s.grid.columns, cs => {
throw new InvalidOperationException();
}, c => c.id.ToLower(), cs => cs.id.ToLower());
}),
vm.rows_view
.subscribe_sorting_criterion(sm => content._grid.Columns.TryGetFirst(dc => dc.sort_member_path == sm, out var match) ? match.id : null)
.Subscribe(sc => last_sorting_criterion = sc),
content._grid.Columns.Subscribe((s, e) => {
switch (e.Action) {
case NotifyCollectionChangedAction.Add: add_new(); break;
@@ -209,5 +199,177 @@ namespace bts.shell {
show_pause_resume = options.show_pause_resume;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class AddMissingAnimationsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null && (
_apply(ref settings, settings.animations, PNLSettings.default_animations, (s, anims) => s.with(animations: anims)) |
_apply(ref settings, settings.group_header_animations, PNLSettings.default_group_header_animations, (s, anims) => s.with(group_header_animations: anims)) |
_apply(ref settings, settings.header_row_animations, PNLSettings.default_header_row_animations, (s, anims) => s.with(header_row_animations: anims)))) {
//
new_settings = settings;
return true;
}
new_settings = null;
return false;
}
static bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, Func<PNLSettings, PNLAnimationTriggerData[], PNLSettings> set_triggers) {
if (_apply(actual_triggers, default_triggers, out var result)) {
settings = set_triggers(settings, actual_triggers.Concat(result).ToArray());
return true;
}
return false;
}
static bool _apply(IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, out IEnumerable<PNLAnimationTriggerData> result) {
var default_trigger_map = default_triggers.ToDictionary(t => t.expression);
var actual_trigger_map = actual_triggers.ToImmutableDictionary(t => t.expression);
var snapshot = actual_trigger_map;
foreach (var kv in snapshot) {
if (default_trigger_map.TryGetValue(kv.Key, out var default_anims)) {
// assume 1 animation per column
var actual_col_anims = kv.Value.animations.ToDictionaryCrushed(a => a.column);
var default_col_anims = default_anims.animations.ToDictionaryCrushed(a => a.column);
actual_col_anims.Difference(default_col_anims,
out var exclusive_actual,
out var exclusive_defaults,
out var diff);
var new_col_anims = exclusive_defaults.ToList();
if (new_col_anims.Count > 0) {
// add the original col animations to the new ones (combine them)
new_col_anims.AddRange(actual_col_anims);
actual_trigger_map = actual_trigger_map.SetItem(kv.Key, kv.Value.with(animations: new_col_anims.Select(col_anim => col_anim.Value).ToArray()));
}
}
}
foreach (var kv in default_trigger_map) {
if (!actual_trigger_map.ContainsKey(kv.Key))
actual_trigger_map = actual_trigger_map.Add(kv.Key, kv.Value);
}
if (actual_trigger_map != snapshot) {
result = actual_trigger_map.Values;
return true;
}
result = null;
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class AddMissingColumnsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null) {
var defaults = PNLSettings.Default;
if (_apply(ref settings, settings.grid.columns, defaults.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
_apply(ref settings, settings.group_columns, defaults.group_columns, (s, cols) => s.with(group_columns: cols)) |
_apply(ref settings, settings.header_columns, defaults.header_columns, (s, cols) => s.with(header_columns: cols))) {
new_settings = settings;
return true;
}
}
new_settings = null;
return false;
}
static bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> actual_columns, IEnumerable<GridColumnSettings> default_columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
if (GridSettingsHelper.add_missing_columns(actual_columns, default_columns, out var new_cols)) {
settings = set_columns(settings, new_cols);
return true;
}
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class RemoveColumnsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null) {
if (_apply(ref settings, settings.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
_apply(ref settings, settings.group_columns, (s, cols) => s.with(group_columns: cols)) |
_apply(ref settings, settings.header_columns, (s, cols) => s.with(header_columns: cols))) {
new_settings = settings;
return true;
}
}
new_settings = null;
return false;
}
bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
if (_apply(columns, out var new_cols)) {
settings = set_columns(settings, new_cols);
return true;
}
return false;
}
bool _apply(IEnumerable<GridColumnSettings> columns, out IEnumerable<GridColumnSettings> result) {
var col_list = columns.ToList();
int orig_count = col_list.Count;
col_list.RemoveWhere(c => !Enum.TryParse<PNLView.Columns>(c.id, true, out var _));
if (col_list.Count < orig_count) {
result = col_list;
return true;
}
result = null;
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class RemoveColumnAnimationsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null) {
if (_apply(ref settings, settings.animations, (s, t) => s.with(animations: t)) |
_apply(ref settings, settings.group_header_animations, (s, t) => s.with(group_header_animations: t)) |
_apply(ref settings, settings.header_row_animations, (s, t) => s.with(header_row_animations: t))) {
new_settings = settings;
return true;
}
}
new_settings = null;
return false;
}
bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> triggers, Func<PNLSettings, IEnumerable<PNLAnimationTriggerData>, PNLSettings> set_triggers) {
if (_apply(triggers, out var new_triggers)) {
settings = set_triggers(settings, new_triggers);
return true;
}
return false;
}
bool _apply(IEnumerable<PNLAnimationTriggerData> triggers, out IEnumerable<PNLAnimationTriggerData> result) {
var trigger_map = triggers.ToImmutableDictionary(t => t.expression);
var snapshot = trigger_map;
foreach (var t in trigger_map.Values) {
var anims = t.animations.ToList();
int orig_count = anims.Count;
anims.RemoveWhere(a => !Enum.IsDefined(typeof(PNLView.Columns), a.column));
if (anims.Count < orig_count)
trigger_map = trigger_map.SetItem(t.expression, t.with(animations: anims.ToArray()));
}
if (trigger_map != snapshot) {
result = trigger_map.Values;
return true;
}
result = null;
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class EnableGridBackgroundColorMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null && !settings.grid.enable_background_color) {
new_settings = settings.with(grid: settings.grid.with(enable_background_color: true));
return true;
}
new_settings = null;
return false;
}
}
}
}
}
@@ -41,6 +41,12 @@ namespace bts.shell.pnl {
eval.apply : new Func<T, bool>(_ => true),
animations.ToDictionary(a => a.column, a => a.to_appearance()));
public PNLAnimationTriggerData with(string expression = null, PNLAnimation[] animations = null) =>
new PNLAnimationTriggerData {
expression = expression ?? this.expression,
animations = animations ?? this.animations
};
public bool Equals(PNLAnimationTriggerData other) =>
other != null &&
expression == other.expression &&
@@ -61,17 +61,16 @@ namespace bts.shell.pnl {
[ProtoMember(1)]
public FontParamsProto font_params { get; set; }
[ProtoMember(2)]
public uint? foreground { get; set; }
[ProtoMember(3)]
public uint? background { get; set; }
public static PNLCellAppearanceProto from_appearance(PNLCellAppearance cell_appearance) =>
new PNLCellAppearanceProto {
font_params = new FontParamsProto {
font_family = cell_appearance.font_family?.name(),
typeface = new TypefaceProto(
cell_appearance.font_family?.name(),
(cell_appearance.font_weight ?? FontWeights.Normal).ToOpenTypeWeight()),
font_color = cell_appearance.foreground?.rgb_color() ?? 0,
font_size = cell_appearance.font_size,
font_weight = (cell_appearance.font_weight ?? FontWeights.Normal).ToOpenTypeWeight()
},
background = cell_appearance.background?.rgb_color()
};
@@ -79,7 +78,7 @@ namespace bts.shell.pnl {
public PNLCellAppearance to_appearance() {
var font = font_params?.to_wpf_params();
return new PNLCellAppearance(
foreground?.rgb_color(),
font?.color,
background?.argb_color(),
font?.weight,
font?.family,
@@ -88,7 +87,6 @@ namespace bts.shell.pnl {
public bool Equals(PNLCellAppearanceProto other) =>
other != null &&
foreground == other.foreground &&
background == other.background &&
font_params.safe_equals(other.font_params);
@@ -1,275 +0,0 @@
using bts.collections;
using bts.proto;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.utility.text;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Media;
using FontFamily = System.Windows.Media.FontFamily;
namespace bts.shell.pnl {
class PNLCellAppearanceParamsVM : ViewModelBase {
FontWeight _font_weight;
FontFamily _font_family;
double _font_size = double.NaN;
public ColorPickerPopupVM background { get; } = new ColorPickerPopupVM { bind_opacity_to_alpha = true, opacity = 1 };
public ColorPickerPopupVM font_color { get; } = new ColorPickerPopupVM();
public FontWeight font_weight { get => _font_weight; set => Set(ref _font_weight, value); }
public FontFamily font_family { get => _font_family; set => Set(ref _font_family, value); }
public double font_size { get => _font_size; set => Set(ref _font_size, value); }
public PNLCellAppearance to_appearance() =>
new PNLCellAppearance(font_color.color, background.color, font_weight, font_family, font_size);
public void load(PNLCellAppearance appearance) {
if (appearance.background.HasValue)
background.color = appearance.background.Value;
if (appearance.font_family != null)
font_family = appearance.font_family;
if (appearance.font_weight.HasValue)
font_weight = appearance.font_weight.Value;
if (appearance.font_size > 0)
font_size = appearance.font_size;
}
}
class PNLColumnParamsVM : ViewModelBase {
bool
_is_background_overridden,
_is_font_color_overridden,
_is_font_weight_overridden,
_is_font_family_overridden,
_is_font_size_overridden;
FontWeight _font_weight_override;
FontFamily _font_family_override;
double _font_size_override = double.NaN;
// background
public bool is_background_overridden { get => _is_background_overridden; set => Set(ref _is_background_overridden, value); }
public ColorPickerPopupVM background_override { get; } = new ColorPickerPopupVM {
bind_opacity_to_alpha = true,
opacity = 1
};
// font color
public bool is_font_color_overridden { get => _is_font_color_overridden; set => Set(ref _is_font_color_overridden, value); }
public ColorPickerPopupVM font_color_override { get; } = new ColorPickerPopupVM();
// font weight
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); }
// font family
public bool is_font_family_overriden { get => _is_font_family_overridden; set => Set(ref _is_font_family_overridden, value); }
public FontFamily font_family_override { get => _font_family_override; set => Set(ref _font_family_override, value); }
// font size
public bool is_font_size_overriden { get => _is_font_size_overridden; set => Set(ref _is_font_size_overridden, value); }
public double font_size_override { get => _font_size_override; set => Set(ref _font_size_override, value); }
public GridColumnSettings apply_to(GridColumnSettings target) =>
target.with(
font_weight_override: new NullableFontWeightProto(is_font_weight_overridden ? font_weight_override : (FontWeight?)null),
font_color_override: new NullableColorProto(is_font_color_overridden ? font_color_override.color : (Color?)null),
background_override: new NullableColorProto(is_background_overridden ? background_override.color : (Color?)null));
public void load(GridColumnSettings settings) {
if (settings != null)
load(settings.font_weight_override, settings.font_color_override, settings.background_override);
}
public void load(FontParamsProto font_params) {
if (font_params == null)
return;
// font weight
is_font_weight_overridden = font_params.font_weight > 0;
if (is_font_weight_overridden)
font_weight_override = FontWeight.FromOpenTypeWeight(font_params.font_weight);
// font color
is_font_color_overridden = font_params.font_color > 0;
if (is_font_color_overridden)
font_color_override.color = font_params.font_color.rgb_color();
// font family
if (!string.IsNullOrEmpty(font_params.font_family)) {
font_family_override = new FontFamily(font_params.font_family);
}
if (!double.IsNaN(font_params.font_size) && font_params.font_size > 0) {
font_size_override = font_params.font_size;
} else {
font_size_override = double.NaN;
}
}
public void load(PNLAnimation animation) {
if (animation != null)
load(
animation.font_weight.HasValue ? FontWeight.FromOpenTypeWeight(animation.font_weight.Value) : (FontWeight?)null,
animation.foreground?.argb_color(),
animation.background?.argb_color());
}
public void load(FontWeight? font_weight, Color? font_color, Color? background) {
// font weight
is_font_weight_overridden = font_weight.HasValue;
if (is_font_weight_overridden)
font_weight_override = font_weight.Value;
// font color
is_font_color_overridden = font_color.HasValue;
if (is_font_color_overridden)
font_color_override.color = font_color.Value;
// background
is_background_overridden = background.HasValue;
if (background.HasValue)
background_override.color = background.Value;
}
public PNLAnimation to_animation(PNLView.Columns key) =>
new PNLAnimation {
column = key,
foreground = is_font_color_overridden ? font_color_override.color.rgb_color() : (uint?)null,
background = is_background_overridden ? background_override.color.rgb_color() : (uint?)null,
font_weight = is_font_weight_overridden ? font_weight_override.ToOpenTypeWeight() : (int?)null
};
public PNLCellAppearanceProto to_appearance_proto() =>
new PNLCellAppearanceProto {
background = is_background_overridden ? background_override.color.rgb_color() : (uint?)null,
foreground = is_font_color_overridden ? font_color_override.color.rgb_color() : (uint?)null,
font_params = to_font_params()
};
public FontParamsProto to_font_params() =>
new FontParamsProto {
font_color = is_font_color_overridden ? font_color_override.color.rgb_color() : 0,
font_weight = is_font_weight_overridden ? font_weight_override.ToOpenTypeWeight() : 0,
font_family = font_family_override?.name(),
font_size = font_size_override
};
}
class PNLColumnVM : ViewModelBase, ISimpleColumnVM {
readonly SimpleGridSettingsVM _owner;
readonly PNLView.Columns _pnl_id;
object _selected_item;
string _display_name;
bool _is_visible;
double _width;
HorizontalAlignment _horizontal_alignment;
public PNLColumnVM(SimpleGridSettingsVM owner, PNLView.Columns id) {
_owner = owner;
_pnl_id = id;
default_column_vm.PropertyChanged += echo;
void echo(object sender, PropertyChangedEventArgs e) {
switch (e.PropertyName) {
case nameof(is_font_weight_overridden):
case nameof(is_font_color_overridden):
RaisePropertyChanged(e.PropertyName);
break;
}
}
}
public PNLColumnParamsVM default_column_vm { get; } = new PNLColumnParamsVM();
public PNLColumnParamsVM group_column_vm { get; } = new PNLColumnParamsVM();
public PNLColumnParamsVM header_column_vm { get; } = new PNLColumnParamsVM();
//
public ObservableCollection<PNLRuleColumnVM> rule_columns { get; } = new ObservableCollection<PNLRuleColumnVM>();
public object selected_item {
get => _selected_item;
set {
if (value != null)
Set(ref _selected_item, value);
}
}
public string id { get => _pnl_id.ToString(); set => Log.assert(value == _pnl_id.ToString()); }
public string display_name { get => _display_name; set => Set(ref _display_name, value); }
public double width { get => _width; set => Set(ref _width, value); }
public bool is_visible { get => _is_visible; set => Set(ref _is_visible, value); }
public bool is_font_color_overridden {
get => default_column_vm.is_font_color_overridden;
set => default_column_vm.is_font_color_overridden = value;
}
public HorizontalAlignment horizontal_alignment { get => _horizontal_alignment; set => Set(ref _horizontal_alignment, value); }
public ColorPickerPopupVM font_color_override => default_column_vm.font_color_override;
public bool is_font_weight_overridden { get => default_column_vm.is_font_weight_overridden; set => default_column_vm.is_font_weight_overridden = value; }
public FontWeight font_weight_override { get => default_column_vm.font_weight_override; set => default_column_vm.font_weight_override = value; }
public void apply_flags(GridColumnSettingsFeatureFlags flags) { }
public GridColumnSettings create() {
var result = new GridColumnSettings(id: id, display_name: display_name, is_visible: is_visible, horizontal_alignment: horizontal_alignment, width: width);
result = default_column_vm.apply_to(result);
return result;
}
public void load_from(GridColumnSettings value) {
_assert_id(value.id);
default_column_vm.load(value);
display_name = value.display_name;
width = value.width;
is_visible = value.is_visible;
horizontal_alignment = value.horizontal_alignment;
}
public void load_from(GridColumnSettings value, IReadOnlyDictionary<string, PNLAnimation> triggers, GridColumnSettings group, IReadOnlyDictionary<string, PNLAnimation> group_triggers, IReadOnlyDictionary<string, PNLAnimation> header_triggers, GridColumnSettings header) {
_assert_id(value.id);
load_from(value);
if (group != null)
group_column_vm.load(group);
if (header != null)
header_column_vm.load(header);
rule_columns.SetItems(triggers.Select(kv => {
var rule_col = new PNLRuleColumnVM(kv.Key);
rule_col.load_from(kv.Value, group_triggers.GetValueOrDefault(kv.Key), header_triggers.GetValueOrDefault(kv.Key));
return rule_col;
}));
}
public IEnumerable<(string expression, PNLAnimation row, PNLAnimation group, PNLAnimation header)> get_triggers() {
foreach (var col in rule_columns)
yield return (col.rule_text, col.row_params.to_animation(_pnl_id), col.group_params.to_animation(_pnl_id), col.header_params.to_animation(_pnl_id));
}
void _assert_id(string id) =>
Log.assert(id == _pnl_id.ToString(), $"PNLColumn id string '{id}' does not match the expected {typeof(PNLView.Columns)} key '{_pnl_id}.'");
public override string ToString() => "Default";
}
class PNLRuleColumnVM : ViewModelBaseWithValidation, IEquatable<PNLRuleColumnVM> {
public PNLRuleColumnVM(string rule_text) =>
this.rule_text = rule_text;
public string rule_text { get; }
public PNLColumnParamsVM row_params { get; } = new PNLColumnParamsVM();
public PNLColumnParamsVM group_params { get; } = new PNLColumnParamsVM();
public PNLColumnParamsVM header_params { get; } = new PNLColumnParamsVM();
public void load_from(PNLAnimation row, PNLAnimation group, PNLAnimation header) {
row_params.load(row);
group_params.load(group);
header_params.load(header);
}
public bool Equals(PNLRuleColumnVM other) =>
other != null && rule_text == other.rule_text;
public override bool Equals(object obj) => Equals(obj as PNLRuleColumnVM);
public override int GetHashCode() => rule_text.GetHashCode();
public override string ToString() => rule_text;
}
}
@@ -32,177 +32,164 @@ namespace bts.shell.pnl {
get => _symbol ?? Array.Empty<string>();
set => _symbol = value;
}
// security
[ProtoMember(4)]
public string security { get; set; }
// security type
[ProtoMember(5)]
[ProtoMember(4)]
Security.ContractOneofCase[] _security_type;
public Security.ContractOneofCase[] security_type {
get => _security_type ?? Array.Empty<Security.ContractOneofCase>();
set => _security_type = value;
}
// security
[ProtoMember(5)]
public string security { get; set; }
[ProtoMember(6)]
public string security_key { get; set; }
// cpp
[ProtoMember(7)]
public DoubleMinMaxState cpp { get; set; }
/// sod
[ProtoMember(6)]
[ProtoMember(8)]
public DoubleMinMaxState sod_pnl { get; set; }
// sod long
[ProtoMember(7)]
public DoubleMinMaxState sod_long_pos { get; set; }
[ProtoMember(8)]
public DoubleMinMaxState sod_long_avg_price { get; set; }
[ProtoMember(9)]
public DoubleMinMaxState sod_long_avg_price_delta { get; set; }
// sod short
public DoubleMinMaxState sod_long_pos { get; set; }
[ProtoMember(10)]
public DoubleMinMaxState sod_short_pos { get; set; }
public DoubleMinMaxState sod_long_avg_price { get; set; }
// sod short
[ProtoMember(11)]
public DoubleMinMaxState sod_short_avg_price { get; set; }
public DoubleMinMaxState sod_short_pos { get; set; }
[ProtoMember(12)]
public DoubleMinMaxState sod_short_avg_price_delta { get; set; }
public DoubleMinMaxState sod_short_avg_price { get; set; }
// sod net
[ProtoMember(13)]
public DoubleMinMaxState sod_net_pos { get; set; }
[ProtoMember(14)]
public DoubleMinMaxState sod_net_avg_price_delta { get; set; }
/// day
[ProtoMember(15)]
[ProtoMember(14)]
public DoubleMinMaxState day_pnl { get; set; }
// day long
[ProtoMember(16)]
[ProtoMember(15)]
public DoubleMinMaxState day_long_pos { get; set; }
[ProtoMember(17)]
[ProtoMember(16)]
public DoubleMinMaxState day_long_avg_price { get; set; }
[ProtoMember(18)]
public DoubleMinMaxState day_long_avg_price_delta { get; set; }
// day short
[ProtoMember(19)]
[ProtoMember(17)]
public DoubleMinMaxState day_short_pos { get; set; }
[ProtoMember(20)]
[ProtoMember(18)]
public DoubleMinMaxState day_short_avg_price { get; set; }
[ProtoMember(21)]
public DoubleMinMaxState day_short_avg_price_delta { get; set; }
// day net
[ProtoMember(22)]
[ProtoMember(19)]
public DoubleMinMaxState day_net_pos { get; set; }
[ProtoMember(23)]
public DoubleMinMaxState day_net_avg_price_delta { get; set; }
/// total
[ProtoMember(24)]
public DoubleMinMaxState total_pnl { get; set; }
// total long
[ProtoMember(25)]
public DoubleMinMaxState total_long_pos { get; set; }
[ProtoMember(26)]
public DoubleMinMaxState total_long_avg_price { get; set; }
[ProtoMember(27)]
public DoubleMinMaxState total_long_avg_price_delta { get; set; }
// total short
[ProtoMember(28)]
public DoubleMinMaxState total_short_pos { get; set; }
[ProtoMember(29)]
public DoubleMinMaxState total_short_avg_price { get; set; }
[ProtoMember(30)]
public DoubleMinMaxState total_short_avg_price_delta { get; set; }
// total net
[ProtoMember(31)]
public DoubleMinMaxState total_net_pos { get; set; }
[ProtoMember(32)]
public DoubleMinMaxState total_net_avg_price_delta { get; set; }
/// market
[ProtoMember(33)]
public DoubleMinMaxState bid_price { get; set; }
[ProtoMember(34)]
public DoubleMinMaxState mid_price { get; set; }
[ProtoMember(35)]
public DoubleMinMaxState ask_price { get; set; }
[ProtoMember(36)]
public DoubleMinMaxState last_price { get; set; }
[ProtoMember(37)]
public DoubleMinMaxState settlement_price { get; set; }
[ProtoMember(38)]
public DoubleMinMaxState pnl_price { get; set; }
/// working (long)
[ProtoMember(39)]
public DoubleMinMaxState working_long_pos { get; set; }
[ProtoMember(40)]
public DoubleMinMaxState working_long_avg_price { get; set; }
[ProtoMember(41)]
public DoubleMinMaxState working_long_avg_price_delta { get; set; }
[ProtoMember(42)]
// working short
public DoubleMinMaxState working_short_pos { get; set; }
[ProtoMember(43)]
public DoubleMinMaxState working_short_avg_price { get; set; }
[ProtoMember(44)]
public DoubleMinMaxState working_short_avg_price_delta { get; set; }
// working net
[ProtoMember(45)]
public DoubleMinMaxState working_net_pos { get; set; }
[ProtoMember(46)]
public DoubleMinMaxState working_net_avg_price_delta { get; set; }
// has day trades
[ProtoMember(47)]
bool[] _has_day_trades { get; set; }
[ProtoMember(20)]
bool[] _has_day_trades;
public bool[] has_day_trades {
get => _has_day_trades ?? Array.Empty<bool>();
set => _has_day_trades = value;
}
// cpp
[ProtoMember(48)]
public DoubleMinMaxState cpp { get; set; }
/// raw day
[ProtoMember(21)]
public DoubleMinMaxState raw_day_long_pos { get; set; }
[ProtoMember(22)]
public DoubleMinMaxState raw_day_short_pos { get; set; }
[ProtoMember(23)]
public DoubleMinMaxState raw_day_net_pos { get; set; }
/// working (long)
[ProtoMember(24)]
public DoubleMinMaxState working_long_pos { get; set; }
[ProtoMember(25)]
public DoubleMinMaxState working_long_avg_price { get; set; }
[ProtoMember(26)]
// working short
public DoubleMinMaxState working_short_pos { get; set; }
[ProtoMember(27)]
public DoubleMinMaxState working_short_avg_price { get; set; }
// working net
[ProtoMember(28)]
public DoubleMinMaxState working_net_pos { get; set; }
/// total
[ProtoMember(29)]
public DoubleMinMaxState total_pnl { get; set; }
// total long
[ProtoMember(30)]
public DoubleMinMaxState total_long_pos { get; set; }
[ProtoMember(31)]
public DoubleMinMaxState total_long_avg_price { get; set; }
// total short
[ProtoMember(32)]
public DoubleMinMaxState total_short_pos { get; set; }
[ProtoMember(33)]
public DoubleMinMaxState total_short_avg_price { get; set; }
// total net
[ProtoMember(34)]
public DoubleMinMaxState total_net_pos { get; set; }
/// market
[ProtoMember(35)]
public DoubleMinMaxState bid_price { get; set; }
[ProtoMember(36)]
public DoubleMinMaxState mid_price { get; set; }
[ProtoMember(37)]
public DoubleMinMaxState ask_price { get; set; }
[ProtoMember(38)]
public DoubleMinMaxState last_price { get; set; }
[ProtoMember(39)]
public DoubleMinMaxState settlement_price { get; set; }
[ProtoMember(40)]
public DoubleMinMaxState pnl_price { get; set; }
public bool Equals(FilterStates other) =>
other != null &&
account.SequenceEqual(other.account) &&
symbol.SequenceEqual(other.symbol) &&
exchange.SequenceEqual(other.exchange) &&
security == other.security &&
symbol.SequenceEqual(other.symbol) &&
security_type.SequenceEqual(other.security_type) &&
security == other.security &&
security_key == other.security_key &&
cpp.safe_equals(other.cpp) &&
/// sod
sod_pnl.safe_equals(other.sod_pnl) &&
sod_long_pos.safe_equals(other.sod_long_pos) &&
sod_long_avg_price.safe_equals(other.sod_long_avg_price) &&
sod_long_avg_price_delta.safe_equals(other.sod_long_avg_price_delta) &&
sod_short_pos.safe_equals(other.sod_short_pos) &&
sod_short_avg_price.safe_equals(other.sod_short_avg_price) &&
sod_short_avg_price_delta.safe_equals(other.sod_short_avg_price_delta) &&
sod_net_pos.safe_equals(other.sod_net_pos) &&
sod_net_avg_price_delta.safe_equals(other.sod_net_avg_price_delta) &&
/// day
day_pnl.safe_equals(other.day_pnl) &&
day_long_pos.safe_equals(other.day_long_pos) &&
day_long_avg_price.safe_equals(other.day_long_avg_price) &&
day_long_avg_price_delta.safe_equals(other.day_long_avg_price_delta) &&
day_short_pos.safe_equals(other.day_short_pos) &&
day_short_avg_price.safe_equals(other.day_short_avg_price) &&
day_short_avg_price_delta.safe_equals(other.day_short_avg_price_delta) &&
day_net_pos.safe_equals(other.day_net_pos) &&
day_net_avg_price_delta.safe_equals(other.day_net_avg_price_delta) &&
has_day_trades.SequenceEqual(other.has_day_trades) &&
/// raw day
raw_day_long_pos.safe_equals(other.raw_day_long_pos) &&
raw_day_short_pos.safe_equals(other.raw_day_short_pos) &&
raw_day_net_pos.safe_equals(other.raw_day_net_pos) &&
/// working
working_long_pos.safe_equals(other.working_long_pos) &&
working_long_avg_price.safe_equals(other.working_long_avg_price) &&
working_short_pos.safe_equals(other.working_short_pos) &&
working_short_avg_price.safe_equals(other.working_short_avg_price) &&
working_net_pos.safe_equals(other.working_net_pos) &&
/// total
total_pnl.safe_equals(other.total_pnl) &&
total_long_pos.safe_equals(other.total_long_pos) &&
total_long_avg_price.safe_equals(other.total_long_avg_price) &&
total_short_pos.safe_equals(other.total_short_pos) &&
total_short_avg_price.safe_equals(other.total_short_avg_price) &&
total_net_pos.safe_equals(other.total_net_pos) &&
/// market
bid_price.safe_equals(other.bid_price) &&
ask_price.safe_equals(other.ask_price) &&
mid_price.safe_equals(other.mid_price) &&
last_price.safe_equals(other.last_price) &&
settlement_price.safe_equals(other.settlement_price) &&
pnl_price.safe_equals(other.pnl_price) &&
/// total
total_pnl.safe_equals(other.total_pnl) &&
total_long_pos.safe_equals(other.total_long_pos) &&
total_long_avg_price.safe_equals(other.total_long_avg_price) &&
total_long_avg_price_delta.safe_equals(other.total_long_avg_price_delta) &&
total_short_pos.safe_equals(other.total_short_pos) &&
total_short_avg_price.safe_equals(other.total_short_avg_price) &&
total_short_avg_price_delta.safe_equals(other.total_short_avg_price_delta) &&
total_net_pos.safe_equals(other.total_net_pos) &&
total_net_avg_price_delta.safe_equals(other.total_net_avg_price_delta) &&
has_day_trades.SequenceEqual(other.has_day_trades) &&
cpp.safe_equals(other.cpp);
pnl_price.safe_equals(other.pnl_price);
public override bool Equals(object obj) =>
Equals(obj as FilterStates);
@@ -214,50 +201,46 @@ namespace bts.shell.pnl {
static class PNLFilterStatesExtensions {
public static Func<PNLRowData, bool> build_predicate(this FilterStates fs) {
var bd = new ColumnPredicateBuilder<PNLRowData>();
// impl (add all)
bd.add_discrete_values(fs.account, d => d.key.account_id);
bd.add_discrete_values(fs.exchange, d => d.security.exchange_or_default());
bd.add_discrete_values(fs.symbol, d => d.security.symbol_or_default());
bd.add_regex(fs.security, d => d.security.ToString());
bd.add_discrete_values(fs.security_type, d => d.security.HasValue ? d.security.Value.ContractCase : Security.ContractOneofCase.None);
bd.add_regex(fs.security, d => d.security.ToString());
bd.add_regex(fs.security_key, d => d.key.security_id.ToString());
bd.add_numeric(fs.cpp, d => d.cpp);
/// sod
bd.add_numeric(fs.sod_pnl, d => d.sod_pnl);
// sod long
bd.add_numeric(fs.sod_long_pos, d => d.sod_long_pos);
bd.add_numeric(fs.sod_long_avg_price, d => d.sod_long_avg_price_raw);
bd.add_numeric(fs.sod_long_avg_price_delta, d => d.sod_long_avg_price_delta_raw);
// sod short
bd.add_numeric(fs.sod_short_pos, d => d.sod_short_pos);
bd.add_numeric(fs.sod_short_avg_price, d => d.sod_short_avg_price_raw);
bd.add_numeric(fs.sod_short_avg_price_delta, d => d.sod_short_avg_price_delta_raw);
// sod net
bd.add_numeric(fs.sod_net_pos, d => d.sod_net_pos);
bd.add_numeric(fs.sod_net_avg_price_delta, d => d.sod_net_avg_price_delta_raw);
/// day
bd.add_numeric(fs.day_pnl, d => d.day_pnl);
// day long
bd.add_numeric(fs.day_long_pos, d => d.day_long_pos);
bd.add_numeric(fs.day_long_avg_price, d => d.day_long_avg_price_raw);
bd.add_numeric(fs.day_long_avg_price_delta, d => d.day_long_avg_price_delta_raw);
// day short
bd.add_numeric(fs.day_short_pos, d => d.day_short_pos);
bd.add_numeric(fs.day_short_avg_price, d => d.day_short_avg_price_raw);
bd.add_numeric(fs.day_short_avg_price_delta, d => d.day_short_avg_price_delta_raw);
// day net
bd.add_numeric(fs.day_net_pos, d => d.day_net_pos);
bd.add_numeric(fs.day_net_avg_price_delta, d => d.day_net_avg_price_delta_raw);
bd.add_discrete_values(fs.has_day_trades, d => d.has_day_trades);
/// raw day
bd.add_numeric(fs.raw_day_long_pos, d => d.raw_day_long_pos);
bd.add_numeric(fs.raw_day_short_pos, d => d.raw_day_short_pos);
bd.add_numeric(fs.raw_day_net_pos, d => d.raw_day_net_pos);
/// working
bd.add_numeric(fs.working_long_pos, d => d.working_long_pos);
bd.add_numeric(fs.working_long_avg_price, d => d.working_long_avg_price_raw);
bd.add_numeric(fs.working_short_pos, d => d.working_short_pos);
bd.add_numeric(fs.working_short_avg_price, d => d.working_short_avg_price_raw);
bd.add_numeric(fs.working_net_pos, d => d.working_net_pos);
/// total
bd.add_numeric(fs.total_pnl, d => d.total_pnl);
// total long
bd.add_numeric(fs.total_long_pos, d => d.total_long_pos);
bd.add_numeric(fs.total_long_avg_price, d => d.total_long_avg_price_raw);
bd.add_numeric(fs.total_long_avg_price_delta, d => d.total_long_avg_price_delta_raw);
// total short
bd.add_numeric(fs.total_short_pos, d => d.total_short_pos);
bd.add_numeric(fs.total_short_avg_price, d => d.total_short_avg_price_raw);
bd.add_numeric(fs.total_short_avg_price_delta, d => d.total_short_avg_price_delta_raw);
// total net
bd.add_numeric(fs.total_net_pos, d => d.total_net_pos);
bd.add_numeric(fs.total_net_avg_price_delta, d => d.total_net_avg_price_delta_raw);
/// market
bd.add_numeric(fs.bid_price, d => d.bid_price);
bd.add_numeric(fs.ask_price, d => d.ask_price);
@@ -265,20 +248,7 @@ namespace bts.shell.pnl {
bd.add_numeric(fs.last_price, d => d.last_price);
bd.add_numeric(fs.settlement_price, d => d.settlement_price);
bd.add_numeric(fs.pnl_price, d => d.pnl_price);
/// working (long)
bd.add_numeric(fs.working_long_pos, d => d.working_long_pos);
bd.add_numeric(fs.working_long_avg_price, d => d.working_long_avg_price_raw);
bd.add_numeric(fs.working_long_avg_price_delta, d => d.working_long_avg_price_delta_raw);
// working short
bd.add_numeric(fs.working_short_pos, d => d.working_short_pos);
bd.add_numeric(fs.working_short_avg_price, d => d.working_short_avg_price_raw);
bd.add_numeric(fs.working_short_avg_price_delta, d => d.working_short_avg_price_delta_raw);
// working net
bd.add_numeric(fs.working_net_pos, d => d.working_net_pos);
bd.add_numeric(fs.working_net_avg_price_delta, d => d.working_net_avg_price_delta_raw);
// misc
bd.add_discrete_values(fs.has_day_trades, d => d.has_day_trades);
bd.add_numeric(fs.cpp, d => d.cpp);
//
return bd.to_predicate();
}
}
@@ -1,4 +1,5 @@
using bts.proto;
using bts.api;
using bts.proto;
using mbts;
using mbts.ui;
using mbts.ui.settings;
@@ -35,6 +36,7 @@ namespace bts.shell.pnl {
public static readonly IReadOnlyList<PNLHeaderRowDisplay> header_row_display_options =
new[] {
PNLHeaderRowDisplay.None,
PNLHeaderRowDisplay.Total,
PNLHeaderRowDisplay.SelectedTotal,
PNLHeaderRowDisplay.Total | PNLHeaderRowDisplay.SelectedTotal
@@ -45,6 +47,7 @@ namespace bts.shell.pnl {
Columns.Account,
Columns.Exchange,
Columns.Symbol,
Columns.SecurityType,
Columns.Security
};
@@ -67,7 +70,10 @@ namespace bts.shell.pnl {
GridColumnSettings[] _header_columns;
[ProtoMember(13)]
PNLAnimationTriggerData[] _header_row_animations;
[ProtoMember(14)]
List<uint> _group_background_colors;
GridSettings _grid_backing;
PNLCellAppearanceProto _grid_cell_appearance_backing;
public static PNLSettings Default =>
@@ -76,31 +82,44 @@ namespace bts.shell.pnl {
new SortingCriteria(nameof(Columns.Account), true),
new SortingCriteria(nameof(Columns.Security), true)
},
filters = new FilterStates {
security_type = new Security.ContractOneofCase[] {
Security.ContractOneofCase.Future
},
has_day_trades = new[] { true }
},
grid = DefaultGridSettings,
calculation_type = PNLCalculationType.Waterfall,
header_row_display = PNLHeaderRowDisplay.Total | PNLHeaderRowDisplay.SelectedTotal,
calculation_type = PNLCalculationType.Mid,
header_row_display = PNLHeaderRowDisplay.None,
_anchors = new[] { Columns.Account, Columns.Symbol },
_group_background_colors = BTSColors.PNL.group_alternating_background_colors.Select(c => c.rgb_color()).ToList(),
_group_cell_appearance = new PNLCellAppearanceProto {
foreground = Colors.Black.rgb_color(),
font_params = new FontParamsProto {
font_family = GridSettings.Default.font_family,
font_weight = FontWeights.Bold.ToOpenTypeWeight(),
typeface = new TypefaceProto(
GridSettings.Default.font_family,
FontWeights.Bold.ToOpenTypeWeight()),
font_color = Colors.Black.rgb_color(),
font_size = 11
},
background = Colors.Transparent.rgb_color()
},
_header_cell_appearance = new PNLCellAppearanceProto {
foreground = Colors.Black.rgb_color(),
font_params = new FontParamsProto {
font_family = GridSettings.Default.font_family,
font_weight = FontWeights.Bold.ToOpenTypeWeight(),
typeface = new TypefaceProto(
GridSettings.Default.font_family,
FontWeights.Bold.ToOpenTypeWeight()),
font_color = Colors.Black.rgb_color(),
font_size = 12
},
background = Colors.Transparent.rgb_color()
background = BTSColors.PNL.header_row_background_color.rgb_color()
}
};
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
public static GridSettings DefaultGridSettings =>
GridSettings.Default.with(
DefaultColumns.all,
DefaultColumns.default_sorting_criterion,
enable_background_color: true);
public static fills.FillsSettings DefaultAuxiliaryFills {
get {
@@ -127,6 +146,9 @@ namespace bts.shell.pnl {
yield return _trigger($"{nameof(PNLRowData.day_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss_color, font_weight: FontWeights.Bold, column: Columns.DayPNL));
yield return _trigger($"{nameof(PNLRowData.day_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.DayNetPos));
yield return _trigger($"{nameof(PNLRowData.day_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.DayNetPos));
// raw day
yield return _trigger($"{nameof(PNLRowData.raw_day_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.RawDayNetPos));
yield return _trigger($"{nameof(PNLRowData.raw_day_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.RawDayNetPos));
// total
yield return _trigger($"{nameof(PNLRowData.total_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit_color, font_weight: FontWeights.Bold, column: Columns.TotalPNL));
yield return _trigger($"{nameof(PNLRowData.total_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss_color, font_weight: FontWeights.Bold, column: Columns.TotalPNL));
@@ -140,45 +162,51 @@ namespace bts.shell.pnl {
public static IEnumerable<PNLAnimationTriggerData> default_group_header_animations {
get {
yield return _trigger($"{nameof(PNLNormalizedData.sod_pnl)} > 0", _animation(background: BTSColors.PNL.profit_color.with(OpacityLevels.Light), column: Columns.SODPNL));
yield return _trigger($"{nameof(PNLNormalizedData.sod_pnl)} < 0", _animation(background: BTSColors.PNL.loss_color.with(OpacityLevels.Light), column: Columns.SODPNL));
yield return _trigger($"{nameof(PNLNormalizedData.sod_net_pos)} > 0", _animation(background: BTSColors.MarketData.bid_color.with(OpacityLevels.Medium), column: Columns.SODNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.sod_net_pos)} < 0", _animation(background: BTSColors.MarketData.ask_color.with(OpacityLevels.Medium), column: Columns.SODNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.sod_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit_color, column: Columns.SODPNL));
yield return _trigger($"{nameof(PNLNormalizedData.sod_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss_color, column: Columns.SODPNL));
yield return _trigger($"{nameof(PNLNormalizedData.sod_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.SODNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.sod_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.SODNetPos));
// day
yield return _trigger($"{nameof(PNLNormalizedData.day_pnl)} > 0", _animation(background: BTSColors.PNL.profit_color.with(OpacityLevels.Light), column: Columns.DayPNL));
yield return _trigger($"{nameof(PNLNormalizedData.day_pnl)} < 0", _animation(background: BTSColors.PNL.loss_color.with(OpacityLevels.Light), column: Columns.DayPNL));
yield return _trigger($"{nameof(PNLNormalizedData.day_net_pos)} > 0", _animation(background: BTSColors.MarketData.bid_color.with(OpacityLevels.Medium), column: Columns.DayNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.day_net_pos)} < 0", _animation(background: BTSColors.MarketData.ask_color.with(OpacityLevels.Medium), column: Columns.DayNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.day_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit_color, column: Columns.DayPNL));
yield return _trigger($"{nameof(PNLNormalizedData.day_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss_color, column: Columns.DayPNL));
yield return _trigger($"{nameof(PNLNormalizedData.day_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.DayNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.day_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.DayNetPos));
// raw day
yield return _trigger($"{nameof(PNLNormalizedData.raw_day_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.RawDayNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.raw_day_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.RawDayNetPos));
// total
yield return _trigger($"{nameof(PNLNormalizedData.total_pnl)} > 0", _animation(background: BTSColors.PNL.profit_color.with(OpacityLevels.Light), column: Columns.TotalPNL));
yield return _trigger($"{nameof(PNLNormalizedData.total_pnl)} < 0", _animation(background: BTSColors.PNL.loss_color.with(OpacityLevels.Light), column: Columns.TotalPNL));
yield return _trigger($"{nameof(PNLNormalizedData.total_net_pos)} > 0", _animation(background: BTSColors.MarketData.bid_color.with(OpacityLevels.Medium), column: Columns.TotalNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.total_net_pos)} < 0", _animation(background: BTSColors.MarketData.ask_color.with(OpacityLevels.Medium), column: Columns.TotalNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.total_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit_color, column: Columns.TotalPNL));
yield return _trigger($"{nameof(PNLNormalizedData.total_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss_color, column: Columns.TotalPNL));
yield return _trigger($"{nameof(PNLNormalizedData.total_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.TotalNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.total_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.TotalNetPos));
// working
yield return _trigger($"{nameof(PNLNormalizedData.working_net_pos)} > 0", _animation(background: BTSColors.MarketData.bid_color.with(OpacityLevels.Medium), column: Columns.WorkingNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.working_net_pos)} < 0", _animation(background: BTSColors.MarketData.ask_color.with(OpacityLevels.Medium), column: Columns.WorkingNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.working_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.WorkingNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.working_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.WorkingNetPos));
}
}
public static IEnumerable<PNLAnimationTriggerData> default_header_row_animations {
get {
yield return _trigger($"{nameof(PNLNormalizedData.sod_pnl)} > 0", _animation(background: BTSColors.PNL.profit_color.with(OpacityLevels.Light), column: Columns.SODPNL));
yield return _trigger($"{nameof(PNLNormalizedData.sod_pnl)} < 0", _animation(background: BTSColors.PNL.loss_color.with(OpacityLevels.Light), column: Columns.SODPNL));
yield return _trigger($"{nameof(PNLNormalizedData.sod_net_pos)} > 0", _animation(background: BTSColors.MarketData.bid_color.with(OpacityLevels.Medium), column: Columns.SODNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.sod_net_pos)} < 0", _animation(background: BTSColors.MarketData.ask_color.with(OpacityLevels.Medium), column: Columns.SODNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.sod_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit_color, column: Columns.SODPNL));
yield return _trigger($"{nameof(PNLNormalizedData.sod_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss_color, column: Columns.SODPNL));
yield return _trigger($"{nameof(PNLNormalizedData.sod_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.SODNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.sod_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.SODNetPos));
// day
yield return _trigger($"{nameof(PNLNormalizedData.day_pnl)} > 0", _animation(background: BTSColors.PNL.profit_color.with(OpacityLevels.Light), column: Columns.DayPNL));
yield return _trigger($"{nameof(PNLNormalizedData.day_pnl)} < 0", _animation(background: BTSColors.PNL.loss_color.with(OpacityLevels.Light), column: Columns.DayPNL));
yield return _trigger($"{nameof(PNLNormalizedData.day_net_pos)} > 0", _animation(background: BTSColors.MarketData.bid_color.with(OpacityLevels.Medium), column: Columns.DayNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.day_net_pos)} < 0", _animation(background: BTSColors.MarketData.ask_color.with(OpacityLevels.Medium), column: Columns.DayNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.day_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit_color, column: Columns.DayPNL));
yield return _trigger($"{nameof(PNLNormalizedData.day_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss_color, column: Columns.DayPNL));
yield return _trigger($"{nameof(PNLNormalizedData.day_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.DayNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.day_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.DayNetPos));
// raw day
yield return _trigger($"{nameof(PNLNormalizedData.raw_day_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.RawDayNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.raw_day_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.RawDayNetPos));
// total
yield return _trigger($"{nameof(PNLNormalizedData.total_pnl)} > 0", _animation(background: BTSColors.PNL.profit_color.with(OpacityLevels.Light), column: Columns.TotalPNL));
yield return _trigger($"{nameof(PNLNormalizedData.total_pnl)} < 0", _animation(background: BTSColors.PNL.loss_color.with(OpacityLevels.Light), column: Columns.TotalPNL));
yield return _trigger($"{nameof(PNLNormalizedData.total_net_pos)} > 0", _animation(background: BTSColors.MarketData.bid_color.with(OpacityLevels.Medium), column: Columns.TotalNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.total_net_pos)} < 0", _animation(background: BTSColors.MarketData.ask_color.with(OpacityLevels.Medium), column: Columns.TotalNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.total_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit_color, column: Columns.TotalPNL));
yield return _trigger($"{nameof(PNLNormalizedData.total_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss_color, column: Columns.TotalPNL));
yield return _trigger($"{nameof(PNLNormalizedData.total_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.TotalNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.total_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.TotalNetPos));
// working
yield return _trigger($"{nameof(PNLNormalizedData.working_net_pos)} > 0", _animation(background: BTSColors.MarketData.bid_color.with(OpacityLevels.Medium), column: Columns.WorkingNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.working_net_pos)} < 0", _animation(background: BTSColors.MarketData.ask_color.with(OpacityLevels.Medium), column: Columns.WorkingNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.working_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.WorkingNetPos));
yield return _trigger($"{nameof(PNLNormalizedData.working_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.WorkingNetPos));
}
}
@@ -187,10 +215,19 @@ namespace bts.shell.pnl {
[ProtoMember(3)]
public PNLCalculationType calculation_type { get; set; }
[ProtoMember(4)]
public GridSettings grid { get; set; }
public GridSettings grid {
get => _grid_backing;
set {
_grid_backing = value;
_grid_cell_appearance_backing = null;
}
}
[ProtoMember(6)]
public PNLHeaderRowDisplay header_row_display { get; set; }
public IReadOnlyList<Columns> anchors => _anchors ?? (_anchors = Array.Empty<Columns>());
public IReadOnlyList<PNLAnimationTriggerData> animations => _animations ?? (_animations = default_animations.ToArray());
public PNLCellAppearance grid_defaults =>
new PNLCellAppearance(
font_family: new FontFamily(grid.font_family),
@@ -199,31 +236,30 @@ namespace bts.shell.pnl {
background: grid.background,
foreground: grid.font_color.rgb_color());
public PNLCellAppearanceProto group_cell_appearance {
get => _group_cell_appearance ?? _grid_cell_appearance;
set => _group_cell_appearance = value;
}
public PNLCellAppearanceProto header_cell_appearance {
get => _header_cell_appearance ?? _grid_cell_appearance;
set => _header_cell_appearance = value;
}
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criterion ?? (_sorting_criterion = DefaultColumns.default_sorting_criterion.ToArray());
public IReadOnlyList<Columns> anchors => _anchors ?? (_anchors = Array.Empty<Columns>());
public IReadOnlyList<PNLAnimationTriggerData> animations => _animations ?? (_animations = default_animations.ToArray());
public IReadOnlyList<PNLAnimationTriggerData> group_header_animations => _group_header_animations ?? (_group_header_animations = default_group_header_animations.ToArray());
public IReadOnlyList<PNLAnimationTriggerData> header_row_animations => _header_row_animations ?? (_header_row_animations = default_header_row_animations.ToArray());
// Group
public List<uint> group_background_colors => _group_background_colors ?? (_group_background_colors = new List<uint>(0));
public PNLCellAppearanceProto group_cell_appearance { get => _group_cell_appearance ?? _grid_cell_appearance; set => _group_cell_appearance = value; }
public IReadOnlyList<GridColumnSettings> group_columns => _group_columns ?? (_group_columns = DefaultColumns.group_columns.ToArray());
public PNLCellAppearance group_defaults => group_cell_appearance.to_appearance().merge(grid_defaults);
public IReadOnlyList<PNLAnimationTriggerData> group_header_animations => _group_header_animations ?? (_group_header_animations = default_group_header_animations.ToArray());
// Header
public Color header_background_color => header_cell_appearance.background?.argb_color() ?? Colors.Transparent;
public PNLCellAppearanceProto header_cell_appearance { get => _header_cell_appearance ?? _grid_cell_appearance; set => _header_cell_appearance = value; }
public IReadOnlyList<GridColumnSettings> header_columns => _header_columns ?? (_header_columns = DefaultColumns.header_columns.ToArray());
public PNLCellAppearance header_defaults => header_cell_appearance.to_appearance().merge(grid_defaults);
public IReadOnlyList<PNLAnimationTriggerData> header_row_animations => _header_row_animations ?? (_header_row_animations = default_header_row_animations.ToArray());
//
public IReadOnlyList<SortingCriteria> sorting_criterion =>
_sorting_criterion ?? (_sorting_criterion = DefaultColumns.default_sorting_criterion.ToArray());
PNLCellAppearanceProto _grid_cell_appearance => _grid_cell_appearance_backing ?? (
_grid_cell_appearance_backing = new PNLCellAppearanceProto {
background = grid.background.rgb_color(),
foreground = grid.font_color,
font_params = new FontParamsProto {
//font_color = grid.font_color,
font_family = grid.font_family,
font_weight = grid.font_weight.ToOpenTypeWeight(),
font_color = grid.font_color,
typeface = new TypefaceProto(
grid.font_family,
grid.font_weight.ToOpenTypeWeight()),
font_size = grid.font_size
}
});
@@ -241,32 +277,47 @@ namespace bts.shell.pnl {
IEnumerable<PNLAnimationTriggerData> header_row_animations = null,
IEnumerable<GridColumnSettings> header_columns = null) =>
new PNLSettings {
_sorting_criterion = (sorting_criterion ?? _sorting_criterion.empty_if_null()).ToArray(),
_anchors = (anchors ?? _anchors.empty_if_null()).ToArray(),
filters = filters ?? this.filters,
grid = grid ?? this.grid,
calculation_type = calculation_type ?? this.calculation_type,
header_row_display = header_row_display ?? this.header_row_display,
// row operators (anchors, sorting, filters)
_anchors = (anchors ?? _anchors.empty_if_null()).ToArray(),
_sorting_criterion = (sorting_criterion ?? _sorting_criterion.empty_if_null()).ToArray(),
filters = filters ?? this.filters,
// cell appearance (group/header)
_group_background_colors = _group_background_colors?.ToList(),
_group_cell_appearance = _group_cell_appearance,
_header_cell_appearance = _header_cell_appearance,
// columns (group/header)
_group_columns = group_columns?.ToArray() ?? _group_columns,
_header_columns = header_columns?.ToArray() ?? _header_columns,
// animations
_animations = animations?.ToArray() ?? _animations,
_group_header_animations = group_header_animations?.ToArray() ?? _group_header_animations,
_header_row_animations = header_row_animations?.ToArray() ?? _header_row_animations,
_group_cell_appearance = _group_cell_appearance,
_header_cell_appearance = _header_cell_appearance
// grid
grid = grid ?? this.grid
};
public bool Equals(PNLSettings other) =>
other != null &&
calculation_type == other.calculation_type &&
filters.safe_equals(other.filters) &&
header_row_display == other.header_row_display &&
header_cell_appearance.safe_equals(other.header_cell_appearance) &&
group_cell_appearance.safe_equals(other.group_cell_appearance) &&
// row operators (anchors, sorting, filters)
anchors.SequenceEqual(other.anchors) &&
sorting_criterion.SequenceEqual(other.sorting_criterion) &&
filters.safe_equals(other.filters) &&
// cell appearance (group/header)
group_background_colors.SequenceEqual(other.group_background_colors) &&
group_cell_appearance.safe_equals(other.group_cell_appearance) &&
header_cell_appearance.safe_equals(other.header_cell_appearance) &&
// columns (group/header)
group_columns.SequenceEqual(other.group_columns) &&
header_columns.SequenceEqual(other.header_columns) &&
// animations
animations.SequenceEqual(other.animations) &&
group_header_animations.SequenceEqual(other.group_header_animations) &&
header_row_animations.SequenceEqual(other.header_row_animations) &&
// grid
grid.safe_equals(other.grid);
static PNLAnimationTriggerData _trigger(string expression, params PNLAnimation[] animations) =>
@@ -291,47 +342,42 @@ namespace bts.shell.pnl {
public static GridColumnSettings account => new GridColumnSettings(nameof(Columns.Account), DefaultColumnHeaders.Account, width: Widths.Account);
public static GridColumnSettings exchange => new GridColumnSettings(nameof(Columns.Exchange), DefaultColumnHeaders.Exchange, false, width: Widths.Exchange);
public static GridColumnSettings symbol => new GridColumnSettings(nameof(Columns.Symbol), DefaultColumnHeaders.Symbol, width: Widths.Symbol);
public static GridColumnSettings security => new GridColumnSettings(nameof(Columns.Security), DefaultColumnHeaders.Security, width: Widths.Security);
public static GridColumnSettings security_type => new GridColumnSettings(nameof(Columns.SecurityType), Headers.SecurityType, width: Widths.SecurityType);
public static GridColumnSettings security => new GridColumnSettings(nameof(Columns.Security), DefaultColumnHeaders.Security, width: Widths.Security);
public static GridColumnSettings security_key => new GridColumnSettings(nameof(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, false, width: DefaultColumnWidths.SecurityKey);
public static GridColumnSettings cpp => new GridColumnSettings(nameof(Columns.CPP), "CPP", false, width: 50, horizontal_alignment: HorizontalAlignment.Right);
// sod
public static GridColumnSettings sod_pnl => new GridColumnSettings(nameof(Columns.SODPNL), Headers.SOD.PNL, false, width: Widths.PriceBig, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings sod_long_pos => new GridColumnSettings(nameof(Columns.SODLongPos), Headers.SOD.Long, false, width: Widths.Qty);
public static GridColumnSettings sod_long_pos => new GridColumnSettings(nameof(Columns.SODLongPos), Headers.SOD.Long, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings sod_long_avg_price => new GridColumnSettings(nameof(Columns.SODLongAvgPrice), Headers.SOD.LongAvg, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings sod_long_avg_price_delta => new GridColumnSettings(nameof(Columns.SODLongAvgPriceDelta), Headers.SOD.LongAvgDelta, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings sod_short_pos => new GridColumnSettings(nameof(Columns.SODShortPos), Headers.SOD.Short, false, width: Widths.Qty);
public static GridColumnSettings sod_short_pos => new GridColumnSettings(nameof(Columns.SODShortPos), Headers.SOD.Short, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings sod_short_avg_price => new GridColumnSettings(nameof(Columns.SODShortAvgPrice), Headers.SOD.ShortAvg, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings sod_short_avg_price_delta => new GridColumnSettings(nameof(Columns.SODShortAvgPriceDelta), Headers.SOD.ShortAvgDelta, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings sod_net_pos => new GridColumnSettings(nameof(Columns.SODNetPos), Headers.SOD.Net, false, width: Widths.Qty);
public static GridColumnSettings sod_net_avg_price_delta => new GridColumnSettings(nameof(Columns.SODNetAvgPriceDelta), Headers.SOD.NetAvgDelta, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings sod_net_pos => new GridColumnSettings(nameof(Columns.SODNetPos), Headers.SOD.Net, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
// day
public static GridColumnSettings day_pnl => new GridColumnSettings(nameof(Columns.DayPNL), Headers.Day.PNL, width: Widths.PriceBig, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings day_long_pos => new GridColumnSettings(nameof(Columns.DayLongPos), Headers.Day.Long, false, width: Widths.Qty);
public static GridColumnSettings day_long_pos => new GridColumnSettings(nameof(Columns.DayLongPos), Headers.Day.Long, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings day_long_avg_price => new GridColumnSettings(nameof(Columns.DayLongAvgPrice), Headers.Day.LongAvg, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings day_long_avg_price_delta => new GridColumnSettings(nameof(Columns.DayLongAvgPriceDelta), Headers.Day.LongAvgDelta, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings day_short_pos => new GridColumnSettings(nameof(Columns.DayShortPos), Headers.Day.Short, false, width: Widths.Qty);
public static GridColumnSettings day_short_pos => new GridColumnSettings(nameof(Columns.DayShortPos), Headers.Day.Short, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings day_short_avg_price => new GridColumnSettings(nameof(Columns.DayShortAvgPrice), Headers.Day.ShortAvg, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings day_short_avg_price_delta => new GridColumnSettings(nameof(Columns.DayShortAvgPriceDelta), Headers.Day.ShortAvgDelta, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings day_net_pos => new GridColumnSettings(nameof(Columns.DayNetPos), Headers.Day.Net, width: Widths.Qty);
public static GridColumnSettings day_net_avg_price_delta => new GridColumnSettings(nameof(Columns.DayNetAvgPriceDelta), Headers.Day.NetAvgDelta, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings day_net_pos => new GridColumnSettings(nameof(Columns.DayNetPos), Headers.Day.Net, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings has_day_trades => new GridColumnSettings(nameof(Columns.HasDayTrades), Headers.HasDayTrades, width: Widths.HasDayTrades, horizontal_alignment: HorizontalAlignment.Right);
// raw day
public static GridColumnSettings raw_day_long_pos => new GridColumnSettings(nameof(Columns.RawDayLongPos), Headers.RawDay.Long, false, width: Widths.RawDayQty, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings raw_day_short_pos => new GridColumnSettings(nameof(Columns.RawDayShortPos), Headers.RawDay.Short, false, width: Widths.RawDayQty, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings raw_day_net_pos => new GridColumnSettings(nameof(Columns.RawDayNetPos), Headers.RawDay.Net, false, width: Widths.RawDayQty, horizontal_alignment: HorizontalAlignment.Right);
// working
public static GridColumnSettings working_long_pos => new GridColumnSettings(nameof(Columns.WorkingLongPos), Headers.Working.Long, false, width: Widths.WorkingQty, font_color_override: BTSColors.MarketData.bid);
public static GridColumnSettings working_long_avg_price => new GridColumnSettings(nameof(Columns.WorkingLongAvgPrice), Headers.Working.LongAvg, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings working_long_avg_price_delta => new GridColumnSettings(nameof(Columns.WorkingLongAvgPriceDelta), Headers.Working.LongAvgDelta, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings working_short_pos => new GridColumnSettings(nameof(Columns.WorkingShortPos), Headers.Working.Short, false, width: Widths.WorkingQty, font_color_override: BTSColors.MarketData.ask);
public static GridColumnSettings working_short_avg_price => new GridColumnSettings(nameof(Columns.WorkingShortAvgPrice), Headers.Working.ShortAvg, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings working_short_avg_price_delta => new GridColumnSettings(nameof(Columns.WorkingShortAvgPriceDelta), Headers.Working.ShortAvgDelta, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings working_net_pos => new GridColumnSettings(nameof(Columns.WorkingNetPos), Headers.Working.Net, width: Widths.WorkingQty);
public static GridColumnSettings working_net_avg_price_delta => new GridColumnSettings(nameof(Columns.WorkingNetAvgPriceDelta), Headers.Working.NetAvgDelta, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings working_long_pos => new GridColumnSettings(nameof(Columns.WorkingLongPos), Headers.Working.Long, false, width: Widths.WorkingQty, font_color_override: BTSColors.MarketData.bid, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings working_long_avg_price => new GridColumnSettings(nameof(Columns.WorkingLongAvgPrice), Headers.Working.LongAvg, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings working_short_pos => new GridColumnSettings(nameof(Columns.WorkingShortPos), Headers.Working.Short, false, width: Widths.WorkingQty, font_color_override: BTSColors.MarketData.ask, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings working_short_avg_price => new GridColumnSettings(nameof(Columns.WorkingShortAvgPrice), Headers.Working.ShortAvg, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings working_net_pos => new GridColumnSettings(nameof(Columns.WorkingNetPos), Headers.Working.Net, width: Widths.WorkingQty, horizontal_alignment: HorizontalAlignment.Right);
// total
public static GridColumnSettings total_pnl => new GridColumnSettings(nameof(Columns.TotalPNL), Headers.Total.PNL, false, width: Widths.PriceBig, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings total_long_pos => new GridColumnSettings(nameof(Columns.TotalLongPos), Headers.Total.Long, false, width: Widths.Qty);
public static GridColumnSettings total_long_pos => new GridColumnSettings(nameof(Columns.TotalLongPos), Headers.Total.Long, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings total_long_avg_price => new GridColumnSettings(nameof(Columns.TotalLongAvgPrice), Headers.Total.LongAvg, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings total_long_avg_price_delta => new GridColumnSettings(nameof(Columns.TotalLongAvgPriceDelta), Headers.Total.LongAvgDelta, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings total_short_pos => new GridColumnSettings(nameof(Columns.TotalShortPos), Headers.Total.Short, false, width: Widths.Qty);
public static GridColumnSettings total_short_pos => new GridColumnSettings(nameof(Columns.TotalShortPos), Headers.Total.Short, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings total_short_avg_price => new GridColumnSettings(nameof(Columns.TotalShortAvgPrice), Headers.Total.ShortAvg, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings total_short_avg_price_delta => new GridColumnSettings(nameof(Columns.TotalShortAvgPriceDelta), Headers.Total.ShortAvgDelta, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings total_net_pos => new GridColumnSettings(nameof(Columns.TotalNetPos), Headers.Total.Net, false, width: Widths.Qty);
public static GridColumnSettings total_net_avg_price_delta => new GridColumnSettings(nameof(Columns.TotalNetAvgPriceDelta), Headers.Total.NetAvgDelta, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings total_net_pos => new GridColumnSettings(nameof(Columns.TotalNetPos), Headers.Total.Net, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
// market
public static GridColumnSettings bid_price => new GridColumnSettings(nameof(Columns.BidPrice), DefaultColumnHeaders.Bid, false, width: DefaultColumnWidths.Price, font_color_override: BTSColors.MarketData.bid, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings ask_price => new GridColumnSettings(nameof(Columns.AskPrice), DefaultColumnHeaders.Ask, false, width: DefaultColumnWidths.Price, font_color_override: BTSColors.MarketData.ask, horizontal_alignment: HorizontalAlignment.Right);
@@ -339,9 +385,6 @@ namespace bts.shell.pnl {
public static GridColumnSettings last_price => new GridColumnSettings(nameof(Columns.LastPrice), DefaultColumnHeaders.Last, false, width: DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings settlement_price => new GridColumnSettings(nameof(Columns.SettlementPrice), DefaultColumnHeaders.Settlement, false, width: DefaultColumnWidths.Settlement, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings pnl_price => new GridColumnSettings(nameof(Columns.PNLPrice), DefaultColumnHeaders.PnLPrice, width: DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings cpp => new GridColumnSettings(nameof(Columns.CPP), "CPP", false, width: 50, horizontal_alignment: HorizontalAlignment.Right);
// misc
public static GridColumnSettings has_day_trades => new GridColumnSettings(nameof(Columns.HasDayTrades), Headers.HasDayTrades, false, width: Widths.HasDayTrades);
public static IEnumerable<SortingCriteria> default_sorting_criterion {
get {
@@ -355,59 +398,52 @@ namespace bts.shell.pnl {
yield return account;
yield return exchange;
yield return symbol;
yield return security;
yield return security_type;
yield return security;
yield return security_key; // hidden
yield return total_pnl; // hidden
yield return sod_pnl;
yield return day_pnl;
/// total
yield return total_net_pos;
yield return total_net_avg_price_delta; // hidden
// total long
yield return total_long_pos; // hidden
yield return total_long_avg_price; // hidden
yield return total_long_avg_price_delta; // hidden
// total short
yield return total_short_pos; // hidden
yield return total_short_avg_price; // hidden
yield return total_short_avg_price_delta; // hidden
/// sod
yield return sod_net_pos;
yield return sod_net_avg_price_delta; // hidden
// sod long
yield return sod_long_pos; // hidden
yield return sod_long_avg_price; // hidden
yield return sod_long_avg_price_delta; // hidden
// sod short
yield return sod_short_pos; // hidden
yield return sod_short_avg_price; // hidden
yield return sod_short_avg_price_delta; // hidden
/// day
yield return day_net_pos;
yield return day_net_avg_price_delta; // hidden
// day long
yield return day_long_pos; // hidden
yield return day_long_avg_price; // hidden
yield return day_long_avg_price_delta; // hidden
// day short
yield return day_short_pos; // hidden
yield return day_short_avg_price; // hidden
yield return day_short_avg_price_delta; // hidden
/// raw day
yield return raw_day_long_pos; // hidden
yield return raw_day_short_pos; // hidden
yield return raw_day_net_pos; // hidden
yield return has_day_trades;
/// working
// working net
yield return working_net_pos;
yield return working_net_avg_price_delta;
// working long
yield return working_long_pos;
yield return working_long_avg_price;
yield return working_long_avg_price_delta;
// working short
yield return working_short_pos;
yield return working_short_avg_price;
yield return working_short_avg_price_delta;
/// market
yield return bid_price;
yield return ask_price;
@@ -443,10 +479,6 @@ namespace bts.shell.pnl {
static GridColumnSettings _override_color(GridColumnSettings col_settings, uint color) =>
col_settings.with(font_color_override: new NullableColorProto(color));
}
static class OpacityLevels {
public const byte Light = 0x55, Medium = 0x6A;
}
}
static class PNLSettingsExtensions {
@@ -454,4 +486,4 @@ namespace bts.shell.pnl {
public static IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> group_triggers(this PNLSettings settings) => settings.group_header_animations.Select(a => a.to_trigger<PNLNormalizedData>()).ToList();
public static IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> header_triggers(this PNLSettings settings) => settings.header_row_animations.Select(a => a.to_trigger<PNLNormalizedData>()).ToList();
}
}
}
@@ -0,0 +1,122 @@
using bts.proto;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.utility.text;
using System.Windows;
using System.Windows.Media;
using FontFamily = System.Windows.Media.FontFamily;
namespace bts.shell.pnl {
class PNLCellAppearanceOverrideVM : ViewModelBase {
bool
_is_background_overridden,
_is_font_color_overridden,
_is_font_weight_overridden,
_is_font_family_overridden,
_is_font_size_overridden;
FontWeight _font_weight_override;
FontFamily _font_family_override;
double _font_size_override = double.NaN;
// background
public bool is_background_overridden { get => _is_background_overridden; set => Set(ref _is_background_overridden, value); }
public ColorPickerPopupVM background_override { get; } = new ColorPickerPopupVM {
bind_opacity_to_alpha = true,
opacity = 1
};
// font color
public bool is_font_color_overridden { get => _is_font_color_overridden; set => Set(ref _is_font_color_overridden, value); }
public ColorPickerPopupVM font_color_override { get; } = new ColorPickerPopupVM();
// font weight
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); }
// font family
public bool is_font_family_overriden { get => _is_font_family_overridden; set => Set(ref _is_font_family_overridden, value); }
public FontFamily font_family_override { get => _font_family_override; set => Set(ref _font_family_override, value); }
// font size
public bool is_font_size_overriden { get => _is_font_size_overridden; set => Set(ref _is_font_size_overridden, value); }
public double font_size_override { get => _font_size_override; set => Set(ref _font_size_override, value); }
public GridColumnSettings apply_to(GridColumnSettings target) =>
target.with(
font_weight_override: new NullableFontWeightProto(is_font_weight_overridden ? font_weight_override : (FontWeight?)null),
font_color_override: new NullableColorProto(is_font_color_overridden ? font_color_override.color : (Color?)null),
background_color_override: new NullableColorProto(is_background_overridden ? background_override.color : (Color?)null));
public void load(GridColumnSettings settings) {
if (settings != null)
load(settings.font_weight_override, settings.font_color_override, settings.background_color_override);
}
public void load(FontParamsProto font_params) {
if (font_params == null)
return;
// font weight
is_font_weight_overridden = font_params.font_weight > 0;
if (is_font_weight_overridden)
font_weight_override = FontWeight.FromOpenTypeWeight(font_params.font_weight);
// font color
is_font_color_overridden = font_params.font_color > 0;
if (is_font_color_overridden)
font_color_override.color = font_params.font_color.rgb_color();
// font family
if (!string.IsNullOrEmpty(font_params.font_family)) {
font_family_override = new FontFamily(font_params.font_family);
}
if (!double.IsNaN(font_params.font_size) && font_params.font_size > 0) {
font_size_override = font_params.font_size;
} else {
font_size_override = double.NaN;
}
}
public void load(PNLAnimation animation) {
if (animation != null)
load(
animation.font_weight.HasValue ? FontWeight.FromOpenTypeWeight(animation.font_weight.Value) : (FontWeight?)null,
animation.foreground?.argb_color(),
animation.background?.argb_color());
}
public void load(FontWeight? font_weight, Color? font_color, Color? background) {
// font weight
is_font_weight_overridden = font_weight.HasValue;
if (is_font_weight_overridden)
font_weight_override = font_weight.Value;
// font color
is_font_color_overridden = font_color.HasValue;
if (is_font_color_overridden)
font_color_override.color = font_color.Value;
// background
is_background_overridden = background.HasValue;
if (background.HasValue)
background_override.color = background.Value;
}
public PNLAnimation to_animation(PNLView.Columns key) =>
new PNLAnimation {
column = key,
foreground = is_font_color_overridden ? font_color_override.color.rgb_color() : (uint?)null,
background = is_background_overridden ? background_override.color.rgb_color() : (uint?)null,
font_weight = is_font_weight_overridden ? font_weight_override.ToOpenTypeWeight() : (int?)null
};
public PNLCellAppearanceProto to_appearance_proto() =>
new PNLCellAppearanceProto {
background = is_background_overridden ? background_override.color.rgb_color() : (uint?)null,
font_params = to_font_params()
};
public FontParamsProto to_font_params() =>
new FontParamsProto {
typeface = new TypefaceProto(
font_family_override?.name(),
is_font_weight_overridden ? font_weight_override.ToOpenTypeWeight() : 0),
font_color = is_font_color_overridden ? font_color_override.color.rgb_color() : 0,
font_size = font_size_override
};
}
}
@@ -0,0 +1,34 @@
using mbts.ui.controls;
using mbts.ui.shell;
using System.Windows;
using FontFamily = System.Windows.Media.FontFamily;
namespace bts.shell.pnl {
class PNLCellAppearanceVM : ViewModelBase {
FontWeight _font_weight;
FontFamily _font_family;
double _font_size = double.NaN;
public ColorPickerPopupVM background { get; } = new ColorPickerPopupVM { bind_opacity_to_alpha = true, opacity = 1 };
public ColorPickerPopupVM font_color { get; } = new ColorPickerPopupVM();
public FontWeight font_weight { get => _font_weight; set => Set(ref _font_weight, value); }
public FontFamily font_family { get => _font_family; set => Set(ref _font_family, value); }
public double font_size { get => _font_size; set => Set(ref _font_size, value); }
public PNLCellAppearance to_appearance() =>
new PNLCellAppearance(font_color.color, background.color, font_weight, font_family, font_size);
public void load(PNLCellAppearance appearance) {
if (appearance.background.HasValue)
background.color = appearance.background.Value;
if (appearance.foreground.HasValue)
font_color.color = appearance.foreground.Value;
if (appearance.font_family != null)
font_family = appearance.font_family;
if (appearance.font_weight.HasValue)
font_weight = appearance.font_weight.Value;
if (appearance.font_size > 0)
font_size = appearance.font_size;
}
}
}
@@ -0,0 +1,133 @@
using bts.collections;
using mbts.spark;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
namespace bts.shell.pnl {
class PNLColumnVM : ViewModelBase, ISimpleColumnVM {
readonly SimpleGridSettingsVM _owner;
readonly PNLView.Columns _pnl_id;
object _selected_item;
string _display_name;
bool _is_visible;
double _width;
HorizontalAlignment _horizontal_alignment;
public PNLColumnVM(SimpleGridSettingsVM owner, PNLView.Columns id) {
_owner = owner;
_pnl_id = id;
default_column_vm.PropertyChanged += echo;
void echo(object sender, PropertyChangedEventArgs e) {
switch (e.PropertyName) {
case nameof(is_font_weight_overridden):
case nameof(is_font_color_overridden):
RaisePropertyChanged(e.PropertyName);
break;
}
}
}
public PNLCellAppearanceOverrideVM default_column_vm { get; } = new PNLCellAppearanceOverrideVM();
public PNLCellAppearanceOverrideVM group_column_vm { get; } = new PNLCellAppearanceOverrideVM();
public PNLCellAppearanceOverrideVM header_column_vm { get; } = new PNLCellAppearanceOverrideVM();
//
public ObservableCollection<PNLRuleColumnVM> rule_columns { get; } = new ObservableCollection<PNLRuleColumnVM>();
public object selected_item {
get => _selected_item;
set {
if (value != null)
Set(ref _selected_item, value);
}
}
public string id { get => _pnl_id.ToString(); set => Log.assert(value == _pnl_id.ToString()); }
public string display_name { get => _display_name; set => Set(ref _display_name, value); }
public double width { get => _width; set => Set(ref _width, value); }
public bool is_visible { get => _is_visible; set => Set(ref _is_visible, value); }
public bool is_font_color_overridden {
get => default_column_vm.is_font_color_overridden;
set => default_column_vm.is_font_color_overridden = value;
}
public HorizontalAlignment horizontal_alignment { get => _horizontal_alignment; set => Set(ref _horizontal_alignment, value); }
public ColorPickerPopupVM font_color_override => default_column_vm.font_color_override;
public bool is_font_weight_overridden { get => default_column_vm.is_font_weight_overridden; set => default_column_vm.is_font_weight_overridden = value; }
public FontWeight font_weight_override { get => default_column_vm.font_weight_override; set => default_column_vm.font_weight_override = value; }
public void apply_flags(GridColumnSettingsFeatureFlags flags) { }
public GridColumnSettings create() {
var result = new GridColumnSettings(id: id, display_name: display_name, is_visible: is_visible, horizontal_alignment: horizontal_alignment, width: width);
result = default_column_vm.apply_to(result);
return result;
}
public void load_from(GridColumnSettings value) {
_assert_id(value.id);
default_column_vm.load(value);
display_name = value.display_name;
width = value.width;
is_visible = value.is_visible;
horizontal_alignment = value.horizontal_alignment;
}
public void load_from(GridColumnSettings value, IReadOnlyDictionary<string, PNLAnimation> triggers, GridColumnSettings group, IReadOnlyDictionary<string, PNLAnimation> group_triggers, IReadOnlyDictionary<string, PNLAnimation> header_triggers, GridColumnSettings header) {
_assert_id(value.id);
load_from(value);
if (group != null)
group_column_vm.load(group);
if (header != null)
header_column_vm.load(header);
rule_columns.SetItems(triggers.Select(kv => {
var rule_col = new PNLRuleColumnVM(kv.Key);
rule_col.load_from(kv.Value, group_triggers.GetValueOrDefault(kv.Key), header_triggers.GetValueOrDefault(kv.Key));
return rule_col;
}));
}
public IEnumerable<(string expression, PNLAnimation row, PNLAnimation group, PNLAnimation header)> get_triggers() {
foreach (var col in rule_columns)
yield return (col.rule_text, col.row_params.to_animation(_pnl_id), col.group_params.to_animation(_pnl_id), col.header_params.to_animation(_pnl_id));
}
void _assert_id(string id) =>
Log.assert(id == _pnl_id.ToString(), $"PNLColumn id string '{id}' does not match the expected {typeof(PNLView.Columns)} key '{_pnl_id}.'");
public override string ToString() => "Default";
}
class PNLRuleColumnVM : ViewModelBase, IEquatable<PNLRuleColumnVM> {
public PNLRuleColumnVM(string rule_text) =>
this.rule_text = rule_text;
public string rule_text { get; }
public PNLCellAppearanceOverrideVM row_params { get; } = new PNLCellAppearanceOverrideVM();
public PNLCellAppearanceOverrideVM group_params { get; } = new PNLCellAppearanceOverrideVM();
public PNLCellAppearanceOverrideVM header_params { get; } = new PNLCellAppearanceOverrideVM();
public void load_from(PNLAnimation row, PNLAnimation group, PNLAnimation header) {
row_params.load(row);
group_params.load(group);
header_params.load(header);
}
public bool Equals(PNLRuleColumnVM other) =>
other != null && rule_text == other.rule_text;
public override bool Equals(object obj) => Equals(obj as PNLRuleColumnVM);
public override int GetHashCode() => rule_text.GetHashCode();
public override string ToString() => rule_text;
}
}
@@ -0,0 +1,58 @@
using bts.collections;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.shell;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using FontFamily = System.Windows.Media.FontFamily;
namespace bts.shell.pnl {
class PNLGroupRowAppearanceVM : ViewModelBase, IValueFactory<(PNLCellAppearance appearance, IReadOnlyList<Color> background_colors)> {
FontWeight _font_weight;
FontFamily _font_family;
double _font_size = double.NaN;
public PNLGroupRowAppearanceVM() {
add_background_cmd = new DelegateCommand(_add_background);
// 4 elements is enough to alternate through any reasonable number of groups,
// and clicking to add new backgrounds seems awkward
for (int i = 0; i < 4; i++)
_add_background();
}
public ObservableCollection<NamedColorPickerPopupVM> background_colors { get; } = new ObservableCollection<NamedColorPickerPopupVM>();
public ColorPickerPopupVM font_color { get; } = new ColorPickerPopupVM();
public FontWeight font_weight { get => _font_weight; set => Set(ref _font_weight, value); }
public FontFamily font_family { get => _font_family; set => Set(ref _font_family, value); }
public double font_size { get => _font_size; set => Set(ref _font_size, value); }
public ICommand add_background_cmd { get; }
public (PNLCellAppearance appearance, IReadOnlyList<Color> background_colors) create() =>
(new PNLCellAppearance(font_color.color, Colors.Transparent, font_weight, font_family, font_size),
background_colors.WrapSelectorRO(cp => cp.color));
public void load_from((PNLCellAppearance appearance, IReadOnlyList<Color> background_colors) value) {
var (app, bg_colors) = value;
// background colors
while (background_colors.Count < bg_colors.Count)
_add_background();
for (int i = 0; i < bg_colors.Count; i++)
background_colors[i].color = bg_colors[i];
//
if (app.foreground.HasValue) font_color.color = app.foreground.Value;
if (app.font_family != null) font_family = app.font_family;
if (app.font_weight.HasValue) font_weight = app.font_weight.Value;
if (app.font_size > 0) font_size = app.font_size;
}
void _add_background() =>
background_colors.Add(new NamedColorPickerPopupVM {
bind_opacity_to_alpha = true,
name = $"Background {background_colors.Count + 1}"
});
}
}
@@ -29,15 +29,7 @@ namespace bts.shell.pnl {
public PNLSettingsVM(PNLSettings settings) {
// init anchor cache, deferrable resync
IDictionary<string, object> raw_anchor_cache = new ExpandoObject();
raw_anchor_cache.AddRange(PNLSettings.anchorable_columns
.Select(k => new KeyValuePair<string, object>(k.ToString(), new AnchorItemVM(this, k))));
_anchor_cache =
MappedDictionary.create(
new ReadOnlyDictionary<string, object>(anchor_vm = raw_anchor_cache),
ConvertUtils.CastDelegate<object, AnchorItemVM>.value,
EnumHelper.parse<ColumnKeys>,
k => k.ToString());
_anchor_cache = PNLSettings.anchorable_columns.ToDictionary(k => k, k => new AnchorItemVM(this, k));
_sync_anchors = new DeferrableActionWrapper(() => {
for (int i = 0; i < anchor_items.Count; i++) {
anchor_items[i].anchor_index = i + 1;
@@ -48,22 +40,20 @@ namespace bts.shell.pnl {
var col_vm = new PNLColumnVM(core_settings_vm, EnumHelper.parse<ColumnKeys>(col_settings.id));
col_vm.load_from(col_settings);
return col_vm;
}) {
can_edit_background = true
};
});
core_settings_vm.selection_highlight.bind_opacity_to_alpha = true;
_dispose = Disposable.Create(
anchor_items.Subscribe((s, e) => _sync_anchors.execute()));
load(settings);
}
public dynamic anchor_vm { get; }
public IEnumerable<AnchorItemVM> anchor_item_headers => _anchor_cache.Values;
public ObservableCollection<AnchorItemVM> anchor_items { get; } = new ObservableCollection<AnchorItemVM>();
public SimpleGridSettingsVM core_settings_vm { get; }
public PNLCellAppearanceParamsVM group_row_defaults { get; } = new PNLCellAppearanceParamsVM();
public PNLCellAppearanceParamsVM header_row_defaults { get; } = new PNLCellAppearanceParamsVM();
public PNLGroupRowAppearanceVM group_row_defaults { get; } = new PNLGroupRowAppearanceVM();
public PNLCellAppearanceVM header_row_defaults { get; } = new PNLCellAppearanceVM();
public PNLCalculationType calculation_type { get => _calculation_type; set => Set(ref _calculation_type, value); }
public IEnumerable<PNLCalculationType> calculation_types { get; } = EnumHelper.get_values_except(PNLCalculationType.None);
public IEnumerable<PNLCalculationType> calculation_types => EnumHelper.get_values_except(PNLCalculationType.None, PNLCalculationType.Same, PNLCalculationType.Opposite);
public PNLHeaderRowDisplay header_row_display { get => _header_row_display; set => Set(ref _header_row_display, value); }
public IEnumerable<PNLHeaderRowDisplay> header_row_display_options => PNLSettings.header_row_display_options;
@@ -73,6 +63,20 @@ namespace bts.shell.pnl {
var s = _last_loaded == null ? new PNLSettings() : SparkUtils.clone_proto(_last_loaded);
s.calculation_type = _calculation_type;
s.grid = core_settings_vm.create();
// move anchor cols to the left, order wrt grouping hierarchy
var new_cols = new Lazy<List<GridColumnSettings>>(() => s.grid.columns.ToList());
for (int i = 0; i < s.grid.columns.Count; i++) {
var key = s.grid.columns[i].strong_id<ColumnKeys>();
if (_anchor_cache.TryGetValue(key, out var a) && a.anchor_index.HasValue) {
// anchor index is 1 based, subtract 1 to get 0 based
int anchor_index = a.anchor_index.Value - 1;
if (anchor_index != i && anchor_index >= 0 && anchor_index < s.grid.columns.Count) {
new_cols.Value.RemoveAt(i);
new_cols.Value.Insert(anchor_index, s.grid.columns[i]);
}
}
}
// rule-based animations
var trigger_anims = core_settings_vm.columns
.OfType<PNLColumnVM>()
.SelectMany(c => c.get_triggers())
@@ -88,6 +92,7 @@ namespace bts.shell.pnl {
.ToList();
s = s.with(anchors: anchor_items.Select(vm => vm.key),
grid: new_cols.IsValueCreated ? s.grid.with(columns: new_cols.Value) : null,
group_columns: core_settings_vm.columns.OfType<PNLColumnVM>().Select(c => {
var col_settings = c.create();
col_settings = c.group_column_vm.apply_to(col_settings);
@@ -116,14 +121,18 @@ namespace bts.shell.pnl {
}));
s.header_row_display = header_row_display;
s.header_cell_appearance = PNLCellAppearanceProto.from_appearance(header_row_defaults.to_appearance());
s.group_cell_appearance = PNLCellAppearanceProto.from_appearance(group_row_defaults.to_appearance());
// group
var (group_cell_appearance, group_row_background_colors) = group_row_defaults.create();
s.group_cell_appearance = PNLCellAppearanceProto.from_appearance(group_cell_appearance);
s.group_background_colors.SetItems(group_row_background_colors.Select(c => c.rgb_color()));
//
return s;
}
public void load(PNLSettings value) {
_last_loaded = value;
header_row_defaults.load(value.header_cell_appearance.to_appearance());
group_row_defaults.load(value.group_cell_appearance.to_appearance());
group_row_defaults.load_from((value.group_cell_appearance.to_appearance(), value.group_background_colors.WrapSelectorRO(c => c.argb_color())));
var column_map = _map_column_data(value.grid.columns, value.animations);
var group_column_map = _map_column_data(value.group_columns, value.group_header_animations);
var header_column_map = _map_column_data(value.header_columns, value.header_row_animations);
@@ -1,4 +1,5 @@
using mbts.spark;
using bts.utils;
using mbts.spark;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.spark;
@@ -15,6 +16,9 @@ namespace bts.shell.pnl {
static readonly Func<WorkingOrderSideData, string> _fmt_working_order_data =
d => OrderUtils.order_collection_summary(d.top_price, d.top_volume, d.working_total);
static readonly IComparer<WorkingOrderSideData> _working_total_comparer =
Comparer<int>.Default.wrap<int, WorkingOrderSideData>(d => d.working_total);
readonly Dictionary<PNLView.Columns, IPNLCellVM<TRow>> _cell_index = new Dictionary<PNLView.Columns, IPNLCellVM<TRow>>();
public PNLCellVM<TRow, T> add<T>(PNLView.Columns key, Func<TRow, T> get, Func<T, string> fmt = null) =>
@@ -71,7 +75,7 @@ namespace bts.shell.pnl {
var frames = top_of_book_trig.on_data(r);
return frames.TryGetFirst(out var f) ? f.animation : null;
});
var cell = add(key, get, _fmt_working_order_data);
var cell = add(key, get, _fmt_working_order_data, EqualityComparer<WorkingOrderSideData>.Default, _working_total_comparer);
animation_model.add_trigger(delta_trig);
animation_model.add_trigger(top_of_book_trig);
return cell;
@@ -89,7 +93,7 @@ namespace bts.shell.pnl {
}
public PNLCellVM<TRow, T> add<T>(PNLView.Columns key, Func<TRow, T> get, Func<T, string> fmt, IEqualityComparer<T> eq_comparer, IComparer<T> comparer) {
var cell = new PNLCellVM<TRow, T>(get, fmt, eq_comparer, comparer);
var cell = new PNLCellVM<TRow, T>(key, get, fmt, eq_comparer, comparer);
_cell_index[key] = cell;
return cell;
}
@@ -1,6 +1,8 @@
using mbts.ui;
using bts.utils;
using mbts.ui;
using mbts.ui.shell;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
@@ -8,6 +10,7 @@ using System.Windows.Media;
namespace bts.shell.pnl {
interface IPNLCellVM {
object value { get; }
IPNLCellData cell_data { get; }
FontWeight font_weight { get; set; }
Color foreground { get; set; }
Color background { get; set; }
@@ -18,38 +21,79 @@ namespace bts.shell.pnl {
void on_row(TRow row);
}
/// <see cref="IPNLCellData"/> is needed for the non-generic rows/group IComparer impl.
/// The groups are bound to IPNLCellData, so the column property can be used to retrieve the appropriate comparer for that cell.
interface IPNLCellData {
PNLView.Columns column { get; }
object value { get; }
}
readonly struct PNLCellValue<T> : IPNLCellData {
public readonly T value;
readonly string _text;
public PNLCellValue(PNLView.Columns column, T value, string text) {
this.value = value;
this.column = column;
_text = text;
}
public PNLView.Columns column { get; }
object IPNLCellData.value => value;
public bool Equals(PNLCellValue<T> other) =>
column == other.column &&
Equals(value, other.value);
public override bool Equals(object obj) =>
obj is PNLCellValue<T> other && Equals(other);
public override int GetHashCode() =>
EqualityComparer.auto_phase((int)column, value?.GetHashCode() ?? 0);
public override string ToString() => _text;
}
class PNLCellVM<TRow, TValue> : ViewModelBase, IObserver<PNLAnimation>, IPNLCellVM<TRow>, IComparable<PNLCellVM<TRow, TValue>>, IComparable {
readonly Func<TValue, string> _fmt;
readonly Func<TRow, TValue> _get;
readonly IEqualityComparer<TValue> _eq_comparer;
readonly IComparer<TValue> _comparer;
readonly PNLView.Columns _column;
IComparer _value_comparer;
Color
_foreground = Colors.Black,
_background = Colors.Transparent,
_border = Colors.Transparent;
string _text;
TValue _value;
IPNLCellData _cell_value;
public PNLCellVM(Func<TRow, TValue> get)
: this(get, null, EqualityComparer<TValue>.Default, Comparer<TValue>.Default) { }
public PNLCellVM(PNLView.Columns column, Func<TRow, TValue> get)
: this(column, get, null, EqualityComparer<TValue>.Default, Comparer<TValue>.Default) { }
public PNLCellVM(Func<TRow, TValue> get, Func<TValue, string> fmt)
: this(get, fmt, EqualityComparer<TValue>.Default, Comparer<TValue>.Default) { }
public PNLCellVM(PNLView.Columns column, Func<TRow, TValue> get, Func<TValue, string> fmt)
: this(column, get, fmt, EqualityComparer<TValue>.Default, Comparer<TValue>.Default) { }
public PNLCellVM(Func<TRow, TValue> get, Func<TValue, string> fmt, IEqualityComparer<TValue> eq_comparer, IComparer<TValue> comparer) {
public PNLCellVM(PNLView.Columns column, Func<TRow, TValue> get, Func<TValue, string> fmt, IEqualityComparer<TValue> eq_comparer, IComparer<TValue> comparer) {
_column = column;
_get = get;
_fmt = fmt;
_eq_comparer = eq_comparer;
_comparer = comparer;
}
public IComparer value_comparer => _value_comparer ?? (_value_comparer = _comparer.to_non_generic());
object IPNLCellVM.value => value;
public IPNLCellData cell_data => _cell_value ?? (_cell_value = new PNLCellValue<TValue>(_column, _value, text));
public TValue value {
get => _value;
set {
if (Set(ref _value, value)) {
_text = null;
RaisePropertyChanged(nameof(text));
_cell_value = null;
RaisePropertyChanged(nameof(cell_data), nameof(text));
}
}
}
@@ -93,6 +137,9 @@ namespace bts.shell.pnl {
public void OnError(Exception error) { }
public void OnCompleted() { }
bool Equals(PNLCellVM<TRow, TValue> other) =>
Equals(value, other.value);
public override string ToString() =>
text;
@@ -4,55 +4,11 @@ using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Windows.Media;
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
namespace bts.shell.pnl {
class PNLNormalizedDataVM : IRaiseNotifyPropertyChanged, INotifyPropertyChanged {
static readonly IPropertyChangedDetector<PNLNormalizedDataVM> _change_detector =
ObjectSnapshot.build_detector_except<PNLNormalizedDataVM>(nameof(data));
PNLNormalizedData _data;
public event PropertyChangedEventHandler PropertyChanged;
public event Action data_changed;
public PNLNormalizedData data {
get => _data;
set {
if (_data != value) {
using (_change_detector.detect_changes(this))
_data = value;
data_changed?.Invoke();
}
}
}
// total aggregate pnl
public decimal day_pnl => _data.day_pnl;
public decimal sod_pnl => _data.sod_pnl;
public decimal total_pnl => _data.day_pnl + _data.sod_pnl;
// day pos
public decimal day_long_pos => _data.day_long_pos;
public decimal day_short_pos => _data.day_short_pos;
public decimal day_net_pos => _data.day_long_pos - _data.day_short_pos;
// sod pos
public decimal sod_long_pos => _data.sod_long_pos;
public decimal sod_short_pos => _data.sod_short_pos;
public decimal sod_net_pos => _data.sod_long_pos - _data.sod_short_pos;
// total pos
public decimal total_net_pos => _data.total_net_pos;
public decimal total_long_pos => _data.day_long_pos + _data.sod_long_pos;
public decimal total_short_pos => _data.day_short_pos + _data.sod_short_pos;
// working
public decimal working_long_pos => _data.working_long_pos;
public decimal working_short_pos => _data.working_short_pos;
public decimal working_net_pos => working_long_pos - working_short_pos;
void IRaiseNotifyPropertyChanged.raise_notify_property_changed(string prop) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
class PNLHeaderRowVM : IRaiseNotifyPropertyChanged, INotifyPropertyChanged {
class PNLHeaderRowVM : ViewModelBase, IRaiseNotifyPropertyChanged, INotifyPropertyChanged {
static readonly IPropertyChangedDetector<PNLHeaderRowVM> _change_detector =
ObjectSnapshot.build_detector_except<PNLHeaderRowVM>(nameof(data));
@@ -61,8 +17,14 @@ namespace bts.shell.pnl {
ImmutableDictionary<ColumnKeys, PNLCellAppearance> _default_appearances;
IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> _triggers;
PNLNormalizedData _data = PNLNormalizedData.zero;
Color _background_color;
public PNLHeaderRowVM(ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances, IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> triggers) {
public PNLHeaderRowVM(
Color background_color,
ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances,
IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> triggers) {
//
_background_color = background_color;
_default_appearances = default_appearances;
_triggers = triggers;
_animator = new PNLCellEmbeddedAnimationModel<PNLNormalizedData>(default_appearances, triggers);
@@ -77,6 +39,10 @@ namespace bts.shell.pnl {
day_net_pos = _cell_map.add_net_pos(ColumnKeys.DayNetPos, d => d.day_net_pos);
day_long_pos = _cell_map.add_long_pos(ColumnKeys.DayLongPos, d => d.day_long_pos);
day_short_pos = _cell_map.add_short_pos(ColumnKeys.DayShortPos, d => d.day_short_pos);
// raw day
raw_day_net_pos = _cell_map.add_net_pos(ColumnKeys.RawDayNetPos, d => d.raw_day_net_pos);
raw_day_long_pos = _cell_map.add_long_pos(ColumnKeys.RawDayLongPos, d => d.raw_day_long_pos);
raw_day_short_pos = _cell_map.add_short_pos(ColumnKeys.RawDayShortPos, d => d.raw_day_short_pos);
// working
working_net_pos = _cell_map.add_net_pos(ColumnKeys.WorkingNetPos, d => d.working_net_pos);
working_long_pos = _cell_map.add_long_pos(ColumnKeys.WorkingLongPos, d => d.working_long_pos);
@@ -88,7 +54,6 @@ namespace bts.shell.pnl {
total_short_pos = _cell_map.add_short_pos(ColumnKeys.TotalShortPos, d => d.total_short_pos);
}
public event PropertyChangedEventHandler PropertyChanged;
public event Action data_changed;
public PNLNormalizedData data {
@@ -103,12 +68,20 @@ namespace bts.shell.pnl {
}
}
}
public Color background_color {
get => _background_color;
set => Set(ref _background_color, value);
}
// day
public PNLCellVM<PNLNormalizedData, decimal> day_pnl { get; }
public PNLCellVM<PNLNormalizedData, decimal> day_long_pos { get; }
public PNLCellVM<PNLNormalizedData, decimal> day_short_pos { get; }
public PNLCellVM<PNLNormalizedData, decimal> day_net_pos { get; }
// raw day
public PNLCellVM<PNLNormalizedData, decimal> raw_day_long_pos { get; }
public PNLCellVM<PNLNormalizedData, decimal> raw_day_short_pos { get; }
public PNLCellVM<PNLNormalizedData, decimal> raw_day_net_pos { get; }
// sod
public PNLCellVM<PNLNormalizedData, decimal> sod_pnl { get; }
public PNLCellVM<PNLNormalizedData, decimal> sod_long_pos { get; }
@@ -124,14 +97,16 @@ namespace bts.shell.pnl {
public PNLCellVM<PNLNormalizedData, decimal> working_short_pos { get; }
public PNLCellVM<PNLNormalizedData, decimal> working_net_pos { get; }
public void on_settings(ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances, IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> triggers) {
public void on_settings(
Color background_color,
ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances,
IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> triggers) {
//
this.background_color = background_color;
_default_appearances = default_appearances;
_triggers = triggers;
_animator.on_settings(_default_appearances, triggers);
_cell_map.set_appearances(_animator.cell_appearances);
}
void IRaiseNotifyPropertyChanged.raise_notify_property_changed(string prop) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
}
@@ -0,0 +1,101 @@
using bts.utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Windows.Data;
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
namespace bts.shell.pnl {
interface IPNLColumnComparer {
IComparer value_comparer { get; }
IComparer<PNLRowVM> row_comparer { get; }
}
class PNLColumnComparer<TInner> : IPNLColumnComparer, IComparer<PNLRowVM>, IComparer {
public readonly IComparer<TInner> inner_comparer;
IComparer _value_comparer;
readonly Func<PNLRowVM, TInner> _get;
public PNLColumnComparer(Func<PNLRowVM, TInner> get)
: this(get, Comparer<TInner>.Default) { }
public PNLColumnComparer(Func<PNLRowVM, TInner> get, IComparer<TInner> inner_comparer) {
_get = get;
this.inner_comparer = inner_comparer;
}
public IComparer value_comparer => _value_comparer ?? (_value_comparer = inner_comparer.to_non_generic());
public IComparer<PNLRowVM> row_comparer => this;
public int Compare(object x, object y) =>
Compare(x as PNLRowVM, y as PNLRowVM);
public int Compare(PNLRowVM x, PNLRowVM y) {
if (x == null) return y == null ? 0 : -1;
if (y == null) return 1;
var xval = _get(x);
var yval = _get(y);
return inner_comparer.Compare(xval, yval);
}
}
class ReversePNLColumnComparer : IPNLColumnComparer, IComparer<PNLRowVM>, IComparer {
readonly IPNLColumnComparer _comp_tuple;
public ReversePNLColumnComparer(IPNLColumnComparer comp_tuple) =>
_comp_tuple = comp_tuple;
public IComparer value_comparer => this;
public IComparer<PNLRowVM> row_comparer => this;
int IComparer.Compare(object x, object y) =>
_comp_tuple.value_comparer.Compare(y, x);
int IComparer<PNLRowVM>.Compare(PNLRowVM x, PNLRowVM y) =>
_comp_tuple.row_comparer.Compare(y, x);
}
/// <summary>
/// An <see cref="IComparer"/> that can compare 2 objects of type <see cref="PNLRowVM"/>, and 2 objects of type <see cref="CollectionViewGroup"/>
/// (assuming the latter's <see cref="CollectionViewGroup.Name"/> is of type <see cref="IPNLCellData"/>). Cross type comparisons are not supported.
/// </summary>
class PNLSuperRowComparer : IComparer, IComparer<PNLRowVM>, IComparer<CollectionViewGroup> {
readonly Func<ColumnKeys, IPNLColumnComparer> _get_column_comparer;
IComparer<PNLRowVM> _row_comp;
public PNLSuperRowComparer(IComparer<PNLRowVM> row_comp, Func<ColumnKeys, IPNLColumnComparer> get_column_comparer) {
_row_comp = row_comp;
_get_column_comparer = get_column_comparer;
}
public int Compare(object x, object y) {
var rc = _try_compare_rows(x as PNLRowVM, y as PNLRowVM);
if (rc.HasValue)
return rc.Value;
var gc = _try_compare_groups(x as CollectionViewGroup, y as CollectionViewGroup);
if (gc.HasValue)
return gc.Value;
throw new InvalidOperationException($"{nameof(PNLSuperRowComparer)} unexpected comparison '{x}' with '{y}.'");
}
int IComparer<PNLRowVM>.Compare(PNLRowVM x, PNLRowVM y) =>
_try_compare_rows(x, y) ?? throw new InvalidOperationException($"Failed to compare '{x}' with '{y}' (expected both to be of type {typeof(PNLRowVM)}).");
int IComparer<CollectionViewGroup>.Compare(CollectionViewGroup x, CollectionViewGroup y) =>
_try_compare_groups(x, y) ?? throw new InvalidOperationException($"Failed to compare '{x}' with '{y}' (expected both to be of type {typeof(CollectionViewGroup)}, with Names of type {typeof(IPNLCellVM)}).");
int? _try_compare_rows(PNLRowVM x, PNLRowVM y) {
if (x == null || y == null) return null;
return _row_comp.Compare(x, y);
}
int? _try_compare_groups(CollectionViewGroup x, CollectionViewGroup y) {
if (x == null || y == null)
return null;
var xcell = (IPNLCellData)x.Name;
var ycell = (IPNLCellData)y.Name;
var comp = _get_column_comparer(xcell.column);
return comp.value_comparer.Compare(xcell.value, ycell.value);
}
}
}
@@ -1,215 +1,209 @@
using bts.api;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reactive.Linq;
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
namespace bts.shell.pnl {
class PNLRowVM : DisposableViewModelBase, IEquatable<PNLRowVM> {
public static readonly IComparer<PNLRowVM> key_comparer = Comparer<PNLKey>.Default.wrap<PNLKey, PNLRowVM>(r => r.key);
static readonly Func<bool, string> _fmt_has_day_trades = hdt => hdt ? "Yes" : "No";
//
readonly PNLCellIndexBuilder<PNLRowData> _cell_map = new PNLCellIndexBuilder<PNLRowData>();
readonly PNLCellEmbeddedAnimationModel<PNLRowData> _animator;
bool _refresh_normalized_data = true, _is_selected;
PNLNormalizedData _normalized_data;
public PNLRowVM(PNLRowData data, ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances, IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers) {
this.data = data;
_animator = new PNLCellEmbeddedAnimationModel<PNLRowData>(default_appearances, triggers);
_animator.on_row(data);
pnl_price = _cell_map.add_price_double(ColumnKeys.PNLPrice, d => d.pnl_price);
account_name_cell = _cell_map.add(ColumnKeys.Account, d => d.account_name);
security_cell = _cell_map.add(ColumnKeys.Security, d => d.security, null, EqualityComparer<SecurityImmutable?>.Default, Comparer<SecurityImmutable>.Default.to_nullable());
security_type_cell = _cell_map.add(ColumnKeys.SecurityType, d => d.security?.ContractCase ?? Security.ContractOneofCase.None);
exchange_cell = _cell_map.add(ColumnKeys.Exchange, d => d.security?.Exchange ?? Venue.Unset);
symbol_cell = _cell_map.add(ColumnKeys.Symbol, d => d.security?.Symbol ?? string.Empty);
// sod
sod_pnl = _cell_map.add_pnl_double(ColumnKeys.SODPNL, d => d.sod_pnl);
sod_net_pos = _cell_map.add_net_pos(ColumnKeys.SODNetPos, d => d.sod_net_pos);
sod_net_avg_price_delta = _cell_map.add_net_price(ColumnKeys.SODNetAvgPriceDelta, d => d.sod_net_avg_price_delta_raw);
sod_long_pos = _cell_map.add_long_pos(ColumnKeys.SODLongPos, d => d.sod_long_pos);
sod_long_avg_price = _cell_map.add_long_price(ColumnKeys.SODLongAvgPrice, d => d.sod_long_avg_price_raw);
sod_long_avg_price_delta = _cell_map.add_long_price(ColumnKeys.SODLongAvgPriceDelta, d => d.sod_long_avg_price_delta_raw);
sod_short_pos = _cell_map.add_short_pos(ColumnKeys.SODShortPos, d => d.sod_short_pos);
sod_short_avg_price = _cell_map.add_short_price(ColumnKeys.SODShortAvgPrice, d => d.sod_short_avg_price_raw);
sod_short_avg_price_delta = _cell_map.add_short_price(ColumnKeys.SODShortAvgPriceDelta, d => d.sod_short_avg_price_delta_raw);
// day
day_pnl = _cell_map.add_pnl_double(ColumnKeys.DayPNL, d => d.day_pnl);
day_net_pos = _cell_map.add_net_pos(ColumnKeys.DayNetPos, d => d.day_net_pos);
day_net_avg_price_delta = _cell_map.add_net_price(ColumnKeys.DayNetAvgPriceDelta, d => d.day_net_avg_price_delta_raw);
day_long_pos = _cell_map.add_long_pos(ColumnKeys.DayLongPos, d => d.day_long_pos);
day_long_avg_price = _cell_map.add_long_price(ColumnKeys.DayLongAvgPrice, d => d.day_long_avg_price_raw);
day_long_avg_price_delta = _cell_map.add_long_price(ColumnKeys.DayLongAvgPriceDelta, d => d.day_long_avg_price_delta_raw);
day_short_pos = _cell_map.add_short_pos(ColumnKeys.DayShortPos, d => d.day_short_pos);
day_short_avg_price = _cell_map.add_short_price(ColumnKeys.DayShortAvgPrice, d => d.day_short_avg_price_raw);
day_short_avg_price_delta = _cell_map.add_short_price(ColumnKeys.DayShortAvgPriceDelta, d => d.day_short_avg_price_delta_raw);
// working
working_net_pos = _cell_map.add_net_pos(ColumnKeys.WorkingNetPos, d => d.working_net_pos);
working_net_avg_price_delta = _cell_map.add_net_price(ColumnKeys.WorkingNetAvgPriceDelta, d => d.working_net_avg_price_delta_raw);
working_long_pos = _cell_map.add_working_long(ColumnKeys.WorkingLongPos, d => d.working.long_data, d => d.bid_price);
working_long_avg_price = _cell_map.add_long_price(ColumnKeys.WorkingLongAvgPrice, d => d.working_long_avg_price_raw);
working_long_avg_price_delta = _cell_map.add_long_price(ColumnKeys.WorkingLongAvgPriceDelta, d => d.working_long_avg_price_delta_raw);
working_short_pos = _cell_map.add_working_short(ColumnKeys.WorkingShortPos, r => r.working.short_data, r => r.ask_price);
working_short_avg_price = _cell_map.add_short_price(ColumnKeys.WorkingShortAvgPrice, d => d.working_short_avg_price_raw);
working_short_avg_price_delta = _cell_map.add_short_price(ColumnKeys.WorkingShortAvgPriceDelta, d => d.working_short_avg_price_delta_raw);
// total
total_pnl = _cell_map.add_pnl_double(ColumnKeys.TotalPNL, d => d.total_pnl);
total_net_pos = _cell_map.add_net_pos(ColumnKeys.TotalNetPos, d => d.total_net_pos);
total_net_avg_price_delta = _cell_map.add_net_price(ColumnKeys.TotalNetAvgPriceDelta, d => d.total_net_avg_price_delta_raw);
total_long_pos = _cell_map.add_long_pos(ColumnKeys.TotalLongPos, d => d.total_long_pos);
total_long_avg_price = _cell_map.add_long_price(ColumnKeys.TotalLongAvgPrice, d => d.total_long_avg_price_raw);
total_long_avg_price_delta = _cell_map.add_long_price(ColumnKeys.TotalLongAvgPriceDelta, d => d.total_long_avg_price_delta_raw);
total_short_pos = _cell_map.add_short_pos(ColumnKeys.TotalShortPos, d => d.total_short_pos);
total_short_avg_price = _cell_map.add_short_price(ColumnKeys.TotalShortAvgPrice, d => d.total_short_avg_price_raw);
total_short_avg_price_delta = _cell_map.add_short_price(ColumnKeys.TotalShortAvgPriceDelta, d => d.total_short_avg_price_delta_raw);
// market
bid_price = _cell_map.add_price_double(ColumnKeys.BidPrice, d => d.bid_price);
ask_price = _cell_map.add_price_double(ColumnKeys.AskPrice, d => d.ask_price);
mid_price = _cell_map.add_price_double(ColumnKeys.MidPrice, d => d.mid_price);
last_price = _cell_map.add_price_double(ColumnKeys.LastPrice, d => d.last_price);
settlement_price = _cell_map.add_price_double(ColumnKeys.SettlementPrice, d => d.settlement_price);
// misc
has_day_trades = _cell_map.add(ColumnKeys.HasDayTrades, d => d.has_day_trades, _fmt_has_day_trades);
cpp = _cell_map.add(ColumnKeys.CPP, d => d.cpp);
_cell_map.on_row(data);
_cell_map.set_appearances(_animator.cell_appearances);
_dispose = _cell_map.animation_model.OnDispatcher().Subscribe(new_animations => {
animations = new_animations.Select(kv => new mbts.ui.spark.CellAnimation(kv.Key.column, kv.Key.type, kv.Value.color.to_brush())).ToList();
RaisePropertyChanged(nameof(animations));
});
}
// events
public event Action<PNLNormalizedData> updated;
public event Action<bool> is_selected_changed;
// data
public PNLKey key => data.key;
public PNLRowData data { get; private set; }
public PNLNormalizedData normalized_data {
get {
if (_refresh_normalized_data) {
_refresh_normalized_data = false;
var nd = new PNLNormalizedData(
(decimal)DoubleUtil.nan_to_zero(day_pnl.value),
(decimal)DoubleUtil.nan_to_zero(data.day.long_pos),
(decimal)DoubleUtil.nan_to_zero(data.day.short_pos),
(decimal)DoubleUtil.nan_to_zero(sod_pnl.value),
(decimal)DoubleUtil.nan_to_zero(data.sod.long_pos),
(decimal)DoubleUtil.nan_to_zero(data.sod.short_pos),
(decimal)DoubleUtil.nan_to_zero(data.working_long_pos),
(decimal)DoubleUtil.nan_to_zero(data.working_short_pos));
if (_normalized_data != nd) {
_normalized_data = nd;
updated?.Invoke(nd);
}
}
return _normalized_data;
}
}
// security
public string account_name => data.account_name;
public SecurityImmutable? security => data.security;
public Security.ContractOneofCase security_type => security?.ContractCase ?? Security.ContractOneofCase.None;
public Venue exchange => security?.Exchange ?? Venue.Unset;
public string symbol => security.symbol_or_default();
// cells
public PNLCellVM<PNLRowData, string> account_name_cell { get; }
public PNLCellVM<PNLRowData, SecurityImmutable?> security_cell { get; }
public PNLCellVM<PNLRowData, Security.ContractOneofCase> security_type_cell { get; }
public PNLCellVM<PNLRowData, Venue> exchange_cell { get; }
public PNLCellVM<PNLRowData, string> symbol_cell { get; }
// sod
public PNLCellVM<PNLRowData, double> sod_pnl { get; }
public PNLCellVM<PNLRowData, double> sod_net_pos { get; }
public PNLCellVM<PNLRowData, double> sod_net_avg_price_delta { get; }
public PNLCellVM<PNLRowData, double> sod_long_pos { get; }
public PNLCellVM<PNLRowData, double> sod_long_avg_price { get; }
public PNLCellVM<PNLRowData, double> sod_long_avg_price_delta { get; }
public PNLCellVM<PNLRowData, double> sod_short_pos { get; }
public PNLCellVM<PNLRowData, double> sod_short_avg_price { get; }
public PNLCellVM<PNLRowData, double> sod_short_avg_price_delta { get; }
// (market)
public PNLCellVM<PNLRowData, double> bid_price { get; }
public PNLCellVM<PNLRowData, double> mid_price { get; }
public PNLCellVM<PNLRowData, double> ask_price { get; }
public PNLCellVM<PNLRowData, double> last_price { get; }
public PNLCellVM<PNLRowData, double> settlement_price { get; }
// pnl price
public PNLCellVM<PNLRowData, double> pnl_price { get; }
// day
public PNLCellVM<PNLRowData, double> day_pnl { get; }
public PNLCellVM<PNLRowData, double> day_net_pos { get; }
public PNLCellVM<PNLRowData, double> day_net_avg_price_delta { get; }
public PNLCellVM<PNLRowData, double> day_long_pos { get; }
public PNLCellVM<PNLRowData, double> day_long_avg_price { get; }
public PNLCellVM<PNLRowData, double> day_long_avg_price_delta { get; }
public PNLCellVM<PNLRowData, double> day_short_pos { get; }
public PNLCellVM<PNLRowData, double> day_short_avg_price { get; }
public PNLCellVM<PNLRowData, double> day_short_avg_price_delta { get; }
// working
public PNLCellVM<PNLRowData, double> working_net_pos { get; }
public PNLCellVM<PNLRowData, double> working_net_avg_price_delta { get; }
public PNLCellVM<PNLRowData, WorkingOrderSideData> working_long_pos { get; }
public PNLCellVM<PNLRowData, double> working_long_avg_price { get; }
public PNLCellVM<PNLRowData, double> working_long_avg_price_delta { get; }
public PNLCellVM<PNLRowData, WorkingOrderSideData> working_short_pos { get; }
public PNLCellVM<PNLRowData, double> working_short_avg_price { get; }
public PNLCellVM<PNLRowData, double> working_short_avg_price_delta { get; }
// total
public PNLCellVM<PNLRowData, double> total_pnl { get; }
public PNLCellVM<PNLRowData, double> total_net_pos { get; }
public PNLCellVM<PNLRowData, double> total_net_avg_price_delta { get; }
public PNLCellVM<PNLRowData, double> total_long_pos { get; }
public PNLCellVM<PNLRowData, double> total_long_avg_price { get; }
public PNLCellVM<PNLRowData, double> total_long_avg_price_delta { get; }
public PNLCellVM<PNLRowData, double> total_short_pos { get; }
public PNLCellVM<PNLRowData, double> total_short_avg_price { get; }
public PNLCellVM<PNLRowData, double> total_short_avg_price_delta { get; }
public PNLCellVM<PNLRowData, bool> has_day_trades { get; }
public PNLCellVM<PNLRowData, double> cpp { get; }
public IList<mbts.ui.spark.CellAnimation> animations { get; private set; }
public bool is_selected {
get => _is_selected;
set {
if (Set(ref _is_selected, value))
is_selected_changed?.Invoke(value);
}
}
public void update(PNLRowData data) {
_refresh_normalized_data = true;
this.data = data;
_cell_map.on_row(data);
_animator.on_row(data);
_cell_map.set_appearances(_animator.cell_appearances);
}
public void on_settings(ImmutableDictionary<ColumnKeys, PNLCellAppearance> defaults, IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers) {
_animator.on_settings(defaults, triggers);
_cell_map.set_appearances(_animator.cell_appearances);
}
public bool Equals(PNLRowVM other) =>
other != null && key == other.key;
public override int GetHashCode() =>
key.GetHashCode();
public override string ToString() =>
$"account: {account_name} exchange: {exchange} symbol: {symbol} security: {security} buy_quantity: {day_long_pos} sell_quantity: {day_short_pos} pnl: {total_pnl}";
}
using bts.api;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reactive.Linq;
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
namespace bts.shell.pnl {
class PNLRowVM : DisposableViewModelBase, IEquatable<PNLRowVM> {
public static readonly IComparer<PNLRowVM> key_comparer = Comparer<PNLKey>.Default.wrap<PNLKey, PNLRowVM>(r => r.key);
static readonly Func<bool, string> _fmt_has_day_trades = hdt => hdt ? "Yes" : "No";
//
readonly PNLCellIndexBuilder<PNLRowData> _cell_map = new PNLCellIndexBuilder<PNLRowData>();
readonly PNLCellEmbeddedAnimationModel<PNLRowData> _animator;
bool _refresh_normalized_data = true, _is_selected;
PNLNormalizedData _normalized_data;
public PNLRowVM(PNLRowData data, ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances, IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers) {
this.data = data;
_animator = new PNLCellEmbeddedAnimationModel<PNLRowData>(default_appearances, triggers);
_animator.on_row(data);
pnl_price = _cell_map.add_price_double(ColumnKeys.PNLPrice, d => d.pnl_price);
account_name_cell = _cell_map.add(ColumnKeys.Account, d => d.account_name);
security_type_cell = _cell_map.add(ColumnKeys.SecurityType, d => d.security?.ContractCase ?? Security.ContractOneofCase.None);
exchange_cell = _cell_map.add(ColumnKeys.Exchange, d => d.security?.Exchange ?? Venue.Unset);
symbol_cell = _cell_map.add(ColumnKeys.Symbol, d => d.security?.Symbol ?? string.Empty);
security_cell = _cell_map.add(ColumnKeys.Security, d => d.security, null, EqualityComparer<SecurityImmutable?>.Default, Comparer<SecurityImmutable>.Default.to_nullable());
security_key = _cell_map.add(ColumnKeys.SecurityKey, d => d.key.security_id);
cpp = _cell_map.add(ColumnKeys.CPP, d => d.cpp);
// sod
sod_pnl = _cell_map.add_pnl_double(ColumnKeys.SODPNL, d => d.sod_pnl);
sod_net_pos = _cell_map.add_net_pos(ColumnKeys.SODNetPos, d => d.sod_net_pos);
sod_long_pos = _cell_map.add_long_pos(ColumnKeys.SODLongPos, d => d.sod_long_pos);
sod_long_avg_price = _cell_map.add_long_price(ColumnKeys.SODLongAvgPrice, d => d.sod_long_avg_price_raw);
sod_short_pos = _cell_map.add_short_pos(ColumnKeys.SODShortPos, d => d.sod_short_pos);
sod_short_avg_price = _cell_map.add_short_price(ColumnKeys.SODShortAvgPrice, d => d.sod_short_avg_price_raw);
// day
day_pnl = _cell_map.add_pnl_double(ColumnKeys.DayPNL, d => d.day_pnl);
day_net_pos = _cell_map.add_net_pos(ColumnKeys.DayNetPos, d => d.day_net_pos);
day_long_pos = _cell_map.add_long_pos(ColumnKeys.DayLongPos, d => d.day_long_pos);
day_long_avg_price = _cell_map.add_long_price(ColumnKeys.DayLongAvgPrice, d => d.day_long_avg_price_raw);
day_short_pos = _cell_map.add_short_pos(ColumnKeys.DayShortPos, d => d.day_short_pos);
day_short_avg_price = _cell_map.add_short_price(ColumnKeys.DayShortAvgPrice, d => d.day_short_avg_price_raw);
has_day_trades = _cell_map.add(ColumnKeys.HasDayTrades, d => d.has_day_trades, _fmt_has_day_trades);
// raw day
raw_day_net_pos = _cell_map.add_net_pos(ColumnKeys.RawDayNetPos, d => d.raw_day_net_pos);
raw_day_long_pos = _cell_map.add_long_pos(ColumnKeys.RawDayLongPos, d => d.raw_day_long_pos);
raw_day_short_pos = _cell_map.add_short_pos(ColumnKeys.RawDayShortPos, d => d.raw_day_short_pos);
// working
working_net_pos = _cell_map.add_net_pos(ColumnKeys.WorkingNetPos, d => d.working_net_pos);
working_long_pos = _cell_map.add_working_long(ColumnKeys.WorkingLongPos, d => d.working.long_data, d => d.bid_price);
working_long_avg_price = _cell_map.add_long_price(ColumnKeys.WorkingLongAvgPrice, d => d.working_long_avg_price_raw);
working_short_pos = _cell_map.add_working_short(ColumnKeys.WorkingShortPos, r => r.working.short_data, r => r.ask_price);
working_short_avg_price = _cell_map.add_short_price(ColumnKeys.WorkingShortAvgPrice, d => d.working_short_avg_price_raw);
// total
total_pnl = _cell_map.add_pnl_double(ColumnKeys.TotalPNL, d => d.total_pnl);
total_net_pos = _cell_map.add_net_pos(ColumnKeys.TotalNetPos, d => d.total_net_pos);
total_long_pos = _cell_map.add_long_pos(ColumnKeys.TotalLongPos, d => d.total_long_pos);
total_long_avg_price = _cell_map.add_long_price(ColumnKeys.TotalLongAvgPrice, d => d.total_long_avg_price_raw);
total_short_pos = _cell_map.add_short_pos(ColumnKeys.TotalShortPos, d => d.total_short_pos);
total_short_avg_price = _cell_map.add_short_price(ColumnKeys.TotalShortAvgPrice, d => d.total_short_avg_price_raw);
// market
bid_price = _cell_map.add_price_double(ColumnKeys.BidPrice, d => d.bid_price);
ask_price = _cell_map.add_price_double(ColumnKeys.AskPrice, d => d.ask_price);
mid_price = _cell_map.add_price_double(ColumnKeys.MidPrice, d => d.mid_price);
last_price = _cell_map.add_price_double(ColumnKeys.LastPrice, d => d.last_price);
settlement_price = _cell_map.add_price_double(ColumnKeys.SettlementPrice, d => d.settlement_price);
_cell_map.on_row(data);
_cell_map.set_appearances(_animator.cell_appearances);
_dispose = _cell_map.animation_model.OnDispatcher().Subscribe(new_animations => {
animations = new_animations.Select(kv => new mbts.ui.spark.CellAnimation(kv.Key.column, kv.Key.type, kv.Value.color.to_brush())).ToList();
RaisePropertyChanged(nameof(animations));
});
}
// events
public event Action<PNLNormalizedData> updated;
public event Action<bool> is_selected_changed;
// data
public PNLKey key => data.key;
public PNLRowData data { get; private set; }
public PNLNormalizedData normalized_data {
get {
if (_refresh_normalized_data) {
_refresh_normalized_data = false;
// only count futures for [non-raw] positions (they already take spread legs into account)
decimal day_long, day_short, sod_long, sod_short;
if (security.HasValue && security.Value.ContractCase != Security.ContractOneofCase.Future) {
day_long = day_short = sod_long = sod_short = 0;
} else {
day_long = (decimal)DoubleUtil.nan_to_zero(data.day.long_pos);
day_short = (decimal)DoubleUtil.nan_to_zero(data.day.short_pos);
sod_long = (decimal)DoubleUtil.nan_to_zero(data.sod.long_pos);
sod_short = (decimal)DoubleUtil.nan_to_zero(data.sod.short_pos);
}
var nd = new PNLNormalizedData(
(decimal)DoubleUtil.nan_to_zero(day_pnl.value),
day_long,
day_short,
(decimal)DoubleUtil.nan_to_zero(data.raw_day.long_pos),
(decimal)DoubleUtil.nan_to_zero(data.raw_day.short_pos),
(decimal)DoubleUtil.nan_to_zero(sod_pnl.value),
sod_long,
sod_short,
(decimal)DoubleUtil.nan_to_zero(data.working_long_pos),
(decimal)DoubleUtil.nan_to_zero(data.working_short_pos));
if (_normalized_data != nd) {
_normalized_data = nd;
updated?.Invoke(nd);
}
}
return _normalized_data;
}
}
// security
public string account_name => data.account_name;
public Venue exchange => security?.Exchange ?? Venue.Unset;
public string symbol => security.symbol_or_default();
public Security.ContractOneofCase security_type => security?.ContractCase ?? Security.ContractOneofCase.None;
public SecurityImmutable? security => data.security;
// cells
public PNLCellVM<PNLRowData, string> account_name_cell { get; }
public PNLCellVM<PNLRowData, Venue> exchange_cell { get; }
public PNLCellVM<PNLRowData, string> symbol_cell { get; }
public PNLCellVM<PNLRowData, Security.ContractOneofCase> security_type_cell { get; }
public PNLCellVM<PNLRowData, SecurityImmutable?> security_cell { get; }
public PNLCellVM<PNLRowData, uint> security_key { get; }
public PNLCellVM<PNLRowData, double> cpp { get; }
// sod
public PNLCellVM<PNLRowData, double> sod_pnl { get; }
public PNLCellVM<PNLRowData, double> sod_net_pos { get; }
public PNLCellVM<PNLRowData, double> sod_long_pos { get; }
public PNLCellVM<PNLRowData, double> sod_long_avg_price { get; }
public PNLCellVM<PNLRowData, double> sod_short_pos { get; }
public PNLCellVM<PNLRowData, double> sod_short_avg_price { get; }
// day
public PNLCellVM<PNLRowData, double> day_pnl { get; }
public PNLCellVM<PNLRowData, double> day_net_pos { get; }
public PNLCellVM<PNLRowData, double> day_long_pos { get; }
public PNLCellVM<PNLRowData, double> day_long_avg_price { get; }
public PNLCellVM<PNLRowData, double> day_short_pos { get; }
public PNLCellVM<PNLRowData, double> day_short_avg_price { get; }
public PNLCellVM<PNLRowData, bool> has_day_trades { get; }
// raw day
public PNLCellVM<PNLRowData, double> raw_day_net_pos { get; }
public PNLCellVM<PNLRowData, double> raw_day_long_pos { get; }
public PNLCellVM<PNLRowData, double> raw_day_short_pos { get; }
// working
public PNLCellVM<PNLRowData, double> working_net_pos { get; }
public PNLCellVM<PNLRowData, WorkingOrderSideData> working_long_pos { get; }
public PNLCellVM<PNLRowData, double> working_long_avg_price { get; }
public PNLCellVM<PNLRowData, WorkingOrderSideData> working_short_pos { get; }
public PNLCellVM<PNLRowData, double> working_short_avg_price { get; }
// total
public PNLCellVM<PNLRowData, double> total_pnl { get; }
public PNLCellVM<PNLRowData, double> total_net_pos { get; }
public PNLCellVM<PNLRowData, double> total_long_pos { get; }
public PNLCellVM<PNLRowData, double> total_long_avg_price { get; }
public PNLCellVM<PNLRowData, double> total_short_pos { get; }
public PNLCellVM<PNLRowData, double> total_short_avg_price { get; }
// (market)
public PNLCellVM<PNLRowData, double> bid_price { get; }
public PNLCellVM<PNLRowData, double> mid_price { get; }
public PNLCellVM<PNLRowData, double> ask_price { get; }
public PNLCellVM<PNLRowData, double> last_price { get; }
public PNLCellVM<PNLRowData, double> settlement_price { get; }
public PNLCellVM<PNLRowData, double> pnl_price { get; }
public IList<mbts.ui.spark.CellAnimation> animations { get; private set; }
public bool is_selected {
get => _is_selected;
set {
if (Set(ref _is_selected, value))
is_selected_changed?.Invoke(value);
}
}
public void update(PNLRowData data) {
_refresh_normalized_data = true;
this.data = data;
_cell_map.on_row(data);
_animator.on_row(data);
_cell_map.set_appearances(_animator.cell_appearances);
}
public void on_settings(ImmutableDictionary<ColumnKeys, PNLCellAppearance> defaults, IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers) {
_animator.on_settings(defaults, triggers);
_cell_map.set_appearances(_animator.cell_appearances);
}
public bool Equals(PNLRowVM other) =>
other != null && key == other.key;
public override int GetHashCode() =>
key.GetHashCode();
public override string ToString() =>
$"account: {account_name} exchange: {exchange} symbol: {symbol} security: {security} buy_quantity: {day_long_pos} sell_quantity: {day_short_pos} pnl: {total_pnl}";
}
}
+251 -161
View File
@@ -18,10 +18,12 @@ using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
@@ -32,6 +34,9 @@ namespace bts.shell.pnl {
static readonly IReadOnlyList<Func<PNLVM, IDataGridColumnFilter<PNLRowData>>> _filter_getters =
ObjectSnapshot.property_delegates_of_type<PNLVM, IDataGridColumnFilter<PNLRowData>>().ToReadOnlyList();
static readonly TimeSpan _auxiliary_grid_update_throttle = TimeSpan.FromSeconds(0.2);
// delegates
static readonly Func<OrderSecurityRootPair, PNLKey> _order_pnl_key_fn = o => new PNLKey(o.order.order.account_id(), o.order.order.security_key());
static readonly Func<FillTuple, PNLKey> _fill_pnl_key_fn = f => new PNLKey(f.account_id, f.fill.SecurityKey);
readonly IBus _bus;
readonly OrdersModel _orders_model;
@@ -43,7 +48,9 @@ namespace bts.shell.pnl {
readonly Property<PNLCalculationType> _calculation_type;
PNLHeaderRowDisplay _header_row_display = PNLHeaderRowDisplay.Total;
PNLRowCollectionView _rows_view;
IReadOnlyList<(ColumnKeys key, ListSortDirection direction)> _sorting_hierarchy;
Brush _selection_highlight;
Color _grid_background_color = Colors.Transparent;
bool _is_paused;
// default appearances
ImmutableDictionary<ColumnKeys, PNLCellAppearance>
@@ -58,6 +65,8 @@ namespace bts.shell.pnl {
_header_triggers = Array.Empty<IPNLAnimationTrigger<PNLNormalizedData>>();
// pause/resume
(TableUpdate<PNLRowData, PNLKey> filtered, TableUpdate<PNLRowData, PNLKey> raw)? _resume;
TableUpdate<PNLRowData, PNLKey> _last_table = TableUpdate<PNLRowData, PNLKey>.mk_empty(PNLRowData.key_fn);
AlternationConverter _group_background_converter = new AlternationConverter();
public PNLVM(IBus bus, OrdersModel orders_model, PNLSettings settings)
: this(bus, orders_model, settings, orders_model.security_account_pnl) { }
@@ -67,8 +76,10 @@ namespace bts.shell.pnl {
_orders_model = orders_model;
_rows_data_wrapper = rows.WrapSelector(r => r.data, d => new PNLRowVM(d, _default_appearances, _triggers));
_rows_key_wrapper = rows.WrapSelector(r => r.key, null);
_rows_view = new PNLRowCollectionView(rows, _default_group_appearances, _group_triggers);
_calculation_type = backing(nameof(calculation_type), PNLCalculationType.Waterfall);
anchors.AddRange(settings.anchors);
_group_background_converter.Values.AddRange(BTSColors.PNL.group_alternating_background_colors);
// commands
set_anchor_cmd = new DelegateCommand<ColumnKeys>(key => anchors.SetItems(new[] { key }));
append_anchor_cmd = new DelegateCommand<ColumnKeys>(key => {
@@ -86,11 +97,10 @@ namespace bts.shell.pnl {
foreach (var f in _all_filters)
f.filter_changed += _refresh_filter.execute;
// totals/selected headers
total_aggregate = new PNLHeaderRowVM(_default_header_appearances, _header_triggers);
selected_aggregate = new PNLHeaderRowVM(_default_header_appearances, _header_triggers);
total_aggregate = new PNLHeaderRowVM(settings.header_background_color, _default_header_appearances, _header_triggers);
selected_aggregate = new PNLHeaderRowVM(settings.header_background_color, _default_header_appearances, _header_triggers);
// load/subscribe to data
load_from(settings);
_rows_view = new PNLRowCollectionView(rows, _default_group_appearances, _group_triggers);
_dispose = Disposable.Create(
pnl_source.Operate(
_calculation_type.observe_current(),
@@ -102,10 +112,11 @@ namespace bts.shell.pnl {
foreach (var f in _all_filters)
f.filter_changed -= _refresh_filter.execute;
rows.DisposeAll();
_rows_view.Groups.OfType<IDisposable>().DisposeAll();
_rows_view.Groups.OfTypeSafe<IDisposable>().DisposeAll();
}));
}
public event Action<IReadOnlyList<(ColumnKeys key, ListSortDirection direction)>> sorting_changed;
event Action<SelectionChangedEventArgs> _selection_changed;
event Action<TableUpdate<PNLRowData, PNLKey>> _updated;
event Action<Func<PNLRowData, bool>> _filter_changed;
@@ -116,6 +127,27 @@ namespace bts.shell.pnl {
public int filtered_row_count => rows.Count;
public int total_row_count { get; private set; }
public IReadOnlyObservableCollection<PNLRowVM> selected_rows => _selected_rows;
// sorting
public IReadOnlyList<(ColumnKeys key, ListSortDirection direction)> sorting_hierarchy {
get => _sorting_hierarchy ?? (_sorting_hierarchy = Array.Empty<(ColumnKeys, ListSortDirection)>());
set {
_sorting_hierarchy = value;
// use 'sorting_hierarchy' instead of 'value' bc the getter ensures not null
comparer = DIYGridSortHelper.build_comparer(sorting_hierarchy, get_column_comparer, ColumnKeys.Account, ColumnKeys.Security);
var column_comp_map = sorting_hierarchy.ToDictionary(c => c.key, c => _get_column_comparer(c.key, c.direction));
var rows_view_comparer = new PNLSuperRowComparer(comparer, col => {
if (column_comp_map.TryGetValue(col, out var comp_tup))
return comp_tup;
return _get_column_comparer(col);
});
_rows_view.CustomSort = rows_view_comparer;
foreach (var g in _rows_view.GroupDescriptions)
g.CustomSort = _rows_view.CustomSort;
sorting_changed?.Invoke(sorting_hierarchy);
}
}
public IComparer<PNLRowVM> comparer { get; private set; }
// display/grouping
public PNLHeaderRowDisplay header_row_display { get => _header_row_display; set => Set(ref _header_row_display, value); }
public ObservableCollection<ColumnKeys> anchors { get; } = new ObservableCollection<ColumnKeys>();
@@ -144,64 +176,57 @@ namespace bts.shell.pnl {
}
public Brush selection_highlight { get => _selection_highlight; set => Set(ref _selection_highlight, value); }
public Color grid_background_color { get => _grid_background_color; set => Set(ref _grid_background_color, value); }
public IValueConverter group_background_converter => _group_background_converter;
// commands
public ICommand set_anchor_cmd { get; }
public ICommand append_anchor_cmd { get; }
public ICommand show_working_orders_cmd { get; }
public void set_all_expanded(bool is_expanded) =>
_rows_view.set_all_expanded(is_expanded);
#region Filters
/// security
public IDataGridColumnPersistentFilter<PNLRowData, uint[]> account_filter { get; }
public IDataGridColumnPersistentFilter<PNLRowData, Venue[]> exchange_filter { get; } = DiscreteValuesFilterVM.Common.exchange<PNLRowData>(r => r.security.exchange_or_default(), nameof(PNLRowVM.exchange));
public IDataGridColumnPersistentFilter<PNLRowData, string[]> symbol_filter { get; } = DiscreteValuesFilterVM.Common.symbol<PNLRowData>(r => r.security.symbol_or_default(), nameof(PNLRowVM.symbol));
public IDataGridColumnPersistentFilter<PNLRowData, string> security_filter { get; } = new RegexFilterVM<PNLRowData>(r => r.security?.ToString(), nameof(PNLRowData.security));
public IDataGridColumnPersistentFilter<PNLRowData, string> security_key_filter { get; } = new RegexFilterVM<PNLRowData>(r => r.key.security_id.ToString());
public IDataGridColumnPersistentFilter<PNLRowData, SecurityType[]> security_type_filter { get; } = DiscreteValuesFilterVM.Create<PNLRowData, SecurityType>(
EnumHelper.get_values_except(SecurityType.Option, SecurityType.Synthetic).ToArray(),
r => r.security?.ContractCase ?? SecurityType.None,
nameof(PNLRowData.security));
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> cpp_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.cpp);
/// sod
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_pnl_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod_pnl);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_long_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod.long_pos);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_long_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod.long_avg);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_long_avg_price_delta_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod_long_avg_price_delta);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_long_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod_long_avg_price_raw);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_short_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod.short_pos);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_short_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod.short_avg);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_short_avg_price_delta_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod_short_avg_price_delta);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_short_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod_short_avg_price_raw);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_net_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod.net_pos);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_net_avg_price_delta_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod_net_avg_price_delta);
/// day
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_pnl_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day_pnl);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_long_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day.long_pos);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_long_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day_long_pos);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_long_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day_long_avg_price_raw);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_long_avg_price_delta_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day_long_avg_price_delta);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_short_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day.short_pos);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_short_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day_short_pos);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_short_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day_short_avg_price_raw);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_short_avg_price_delta_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day_short_avg_price_delta_raw);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_net_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day.net_pos);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_net_avg_price_delta_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day_net_avg_price_delta_raw);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_net_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day_net_pos);
public IDataGridColumnPersistentFilter<PNLRowData, bool[]> has_day_trades_filter { get; } = DiscreteValuesFilterVM.Create<PNLRowData, bool>(new[] { true, false }, r => r.has_day_trades);
/// raw day
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> raw_day_long_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.raw_day_long_pos);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> raw_day_short_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.raw_day_short_pos);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> raw_day_net_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.raw_day_net_pos);
/// working (long)
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> working_long_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.working_long_pos);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> working_long_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.working_long_avg_price_raw);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> working_long_avg_price_delta_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.working_long_avg_price_delta_raw);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> working_short_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.working_short_pos);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> working_short_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.working_short_avg_price_raw);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> working_short_avg_price_delta_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.working_short_avg_price_delta_raw);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> working_net_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.working_net_pos);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> working_net_avg_price_delta_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.working_net_avg_price_delta_raw);
/// total
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_pnl_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_pnl);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_long_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_long_pos);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_long_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_long_avg_price);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_long_avg_price_delta_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_long_avg_price_delta);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_long_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_long_avg_price_raw);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_short_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_short_pos);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_short_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_short_avg_price);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_short_avg_price_delta_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_short_avg_price_delta);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_short_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_short_avg_price_raw);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_net_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_net_pos);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_net_avg_price_delta_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_net_avg_price_delta);
/// market
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.bid_price);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> ask_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.ask_price);
@@ -209,58 +234,53 @@ namespace bts.shell.pnl {
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> last_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.last_price);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> settlement_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.settlement_price);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> pnl_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.pnl_price);
/// misc
public IDataGridColumnPersistentFilter<PNLRowData, bool[]> has_day_trades_filter { get; } = DiscreteValuesFilterVM.Create<PNLRowData, bool>(new[] { true, false }, r => r.has_day_trades);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> cpp_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.cpp);
IEnumerable<IDataGridColumnFilter<PNLRowData>> _all_filters =>
_filter_getters.Select(get => get(this));
public static IComparer<PNLRowVM> get_column_comparer(ColumnKeys column) =>
_get_column_comparer(column).row_comparer;
public FilterStates get_filters_states() => new FilterStates {
account = account_filter.get_state(),
exchange = exchange_filter.get_state(),
symbol = symbol_filter.get_state(),
security = security_filter.get_state(),
security_type = security_type_filter.get_state(),
security = security_filter.get_state(),
security_key = security_key_filter.get_state(),
cpp = cpp_filter.get_state(),
/// sod
sod_pnl = sod_pnl_filter.get_state(),
sod_long_pos = sod_long_pos_filter.get_state(),
sod_long_avg_price = sod_long_avg_price_filter.get_state(),
sod_long_avg_price_delta = sod_long_avg_price_delta_filter.get_state(),
sod_short_pos = sod_short_pos_filter.get_state(),
sod_short_avg_price = sod_short_avg_price_filter.get_state(),
sod_short_avg_price_delta = sod_short_avg_price_delta_filter.get_state(),
sod_net_pos = sod_net_pos_filter.get_state(),
sod_net_avg_price_delta = sod_net_avg_price_delta_filter.get_state(),
/// day
day_pnl = day_pnl_filter.get_state(),
day_long_pos = day_long_pos_filter.get_state(),
day_long_avg_price = day_long_avg_price_filter.get_state(),
day_long_avg_price_delta = day_long_avg_price_delta_filter.get_state(),
day_short_pos = day_short_pos_filter.get_state(),
day_short_avg_price = day_short_avg_price_filter.get_state(),
day_short_avg_price_delta = day_short_avg_price_delta_filter.get_state(),
day_net_pos = day_net_pos_filter.get_state(),
day_net_avg_price_delta = day_net_avg_price_delta_filter.get_state(),
has_day_trades = has_day_trades_filter.get_state(),
/// raw day
raw_day_long_pos = raw_day_long_pos_filter.get_state(),
raw_day_short_pos = raw_day_short_pos_filter.get_state(),
raw_day_net_pos = raw_day_net_pos_filter.get_state(),
/// total
total_pnl = total_pnl_filter.get_state(),
total_long_pos = total_long_pos_filter.get_state(),
total_long_avg_price = total_long_avg_price_filter.get_state(),
total_long_avg_price_delta = total_long_avg_price_delta_filter.get_state(),
total_short_pos = total_short_pos_filter.get_state(),
total_short_avg_price = total_short_avg_price_filter.get_state(),
total_short_avg_price_delta = total_short_avg_price_delta_filter.get_state(),
total_net_pos = total_net_pos_filter.get_state(),
total_net_avg_price_delta = total_net_avg_price_delta_filter.get_state(),
/// working
working_long_pos = working_long_pos_filter.get_state(),
working_long_avg_price = working_long_avg_price_filter.get_state(),
working_long_avg_price_delta = working_long_avg_price_delta_filter.get_state(),
working_short_pos = working_short_pos_filter.get_state(),
working_short_avg_price = working_short_avg_price_filter.get_state(),
working_short_avg_price_delta = working_short_avg_price_delta_filter.get_state(),
working_net_pos = working_net_pos_filter.get_state(),
working_net_avg_price_delta = working_net_avg_price_delta_filter.get_state(),
/// market
bid_price = bid_price_filter.get_state(),
ask_price = ask_price_filter.get_state(),
@@ -268,13 +288,11 @@ namespace bts.shell.pnl {
last_price = last_price_filter.get_state(),
settlement_price = settlement_price_filter.get_state(),
pnl_price = pnl_price_filter.get_state(),
/// misc
has_day_trades = has_day_trades_filter.get_state()
};
#endregion
public void load_from(PNLSettings settings) {
selection_highlight = settings.grid.selection_highlight.to_brush();
grid_background_color = settings.grid.background;
calculation_type = settings.calculation_type;
if (settings.filters != null) {
using (_refresh_filter.defer_execution()) {
@@ -283,46 +301,42 @@ namespace bts.shell.pnl {
account_filter.load_state(f.account);
exchange_filter.load_state(f.exchange);
symbol_filter.load_state(f.symbol);
security_filter.load_state(f.security);
security_type_filter.load_state(f.security_type);
security_filter.load_state(f.security);
security_key_filter.load_state(f.security_key);
cpp_filter.load_state(f.cpp);
/// sod
sod_pnl_filter.load_state(f.sod_pnl);
sod_long_pos_filter.load_state(f.sod_long_pos);
sod_long_avg_price_filter.load_state(f.sod_long_avg_price);
sod_long_avg_price_delta_filter.load_state(f.sod_long_avg_price_delta);
sod_short_pos_filter.load_state(f.sod_short_pos);
sod_short_avg_price_filter.load_state(f.sod_short_avg_price);
sod_short_avg_price_delta_filter.load_state(f.sod_short_avg_price_delta);
sod_net_pos_filter.load_state(f.sod_net_pos);
sod_net_avg_price_delta_filter.load_state(f.sod_net_avg_price_delta);
/// day
day_pnl_filter.load_state(f.day_pnl);
day_long_pos_filter.load_state(f.day_long_pos);
day_long_avg_price_filter.load_state(f.day_long_avg_price);
day_long_avg_price_delta_filter.load_state(f.day_long_avg_price_delta);
day_short_pos_filter.load_state(f.day_short_pos);
day_short_avg_price_filter.load_state(f.day_short_avg_price);
day_short_avg_price_delta_filter.load_state(f.day_short_avg_price_delta);
day_net_pos_filter.load_state(f.day_net_pos);
day_net_avg_price_delta_filter.load_state(f.day_net_avg_price_delta);
has_day_trades_filter.load_state(f.has_day_trades);
/// raw day
raw_day_long_pos_filter.load_state(f.raw_day_long_pos);
raw_day_short_pos_filter.load_state(f.raw_day_short_pos);
raw_day_net_pos_filter.load_state(f.raw_day_net_pos);
/// total
total_pnl_filter.load_state(f.total_pnl);
total_long_pos_filter.load_state(f.total_long_pos);
total_long_avg_price_filter.load_state(f.total_long_avg_price);
total_long_avg_price_delta_filter.load_state(f.total_long_avg_price_delta);
total_short_pos_filter.load_state(f.total_short_pos);
total_short_avg_price_filter.load_state(f.total_short_avg_price);
total_short_avg_price_delta_filter.load_state(f.total_short_avg_price_delta);
total_net_pos_filter.load_state(f.total_net_pos);
/// working
working_long_pos_filter.load_state(f.working_long_pos);
working_long_avg_price_filter.load_state(f.working_long_avg_price);
working_long_avg_price_delta_filter.load_state(f.working_long_avg_price_delta);
working_short_pos_filter.load_state(f.working_short_pos);
working_short_avg_price_filter.load_state(f.working_short_avg_price);
working_short_avg_price_delta_filter.load_state(f.working_short_avg_price_delta);
working_net_pos_filter.load_state(f.working_net_pos);
working_net_avg_price_delta_filter.load_state(f.working_net_avg_price_delta);
/// market
bid_price_filter.load_state(f.bid_price);
mid_price_filter.load_state(f.mid_price);
@@ -330,158 +344,193 @@ namespace bts.shell.pnl {
last_price_filter.load_state(f.last_price);
settlement_price_filter.load_state(f.settlement_price);
pnl_price_filter.load_state(f.pnl_price);
/// misc
has_day_trades_filter.load_state(f.has_day_trades);
cpp_filter.load_state(f.cpp);
}
}
anchors.sync_with(settings.anchors);
header_row_display = settings.header_row_display;
sorting_hierarchy = DIYGridSortHelper.convert_sorting_criterion<ColumnKeys>(settings.sorting_criterion).ToList();
// group background colors
_group_background_converter = new AlternationConverter();
_group_background_converter.Values.AddRange(settings.group_background_colors.Select(c => c.argb_color()));
RaisePropertyChanged(nameof(group_background_converter));
//
map_appearances(ref _default_appearances, settings.grid.columns, settings.grid_defaults);
map_appearances(ref _default_group_appearances, settings.group_columns, settings.group_cell_appearance.to_appearance().merge(settings.grid_defaults));
map_appearances(ref _default_header_appearances, settings.header_columns, settings.header_cell_appearance.to_appearance().merge(settings.grid_defaults));
map_appearances(ref _default_group_appearances, settings.group_columns, settings.group_cell_appearance.to_appearance().merge(settings.group_defaults));
map_appearances(ref _default_header_appearances, settings.header_columns, settings.header_cell_appearance.to_appearance().merge(settings.header_defaults));
_triggers = settings.triggers();
_group_triggers = settings.group_triggers();
_header_triggers = settings.header_triggers();
foreach (var r in rows)
r.on_settings(_default_appearances, _triggers);
total_aggregate.on_settings(_default_header_appearances, _header_triggers);
selected_aggregate.on_settings(_default_header_appearances, _header_triggers);
total_aggregate.on_settings(settings.header_background_color, _default_header_appearances, _header_triggers);
selected_aggregate.on_settings(settings.header_background_color,_default_header_appearances, _header_triggers);
void map_appearances(ref ImmutableDictionary<ColumnKeys, PNLCellAppearance> from, IEnumerable<GridColumnSettings> col_settings, PNLCellAppearance fallback_appearance) {
var dabd = from.ToBuilder();
foreach (var k in EnumHelper.get_values<ColumnKeys>()) {
if (GridSettingsHelper.try_find_column(col_settings, k, out var match))
dabd[k] = new PNLCellAppearance(match.font_color_override, match.background_override, match.font_weight_override).merge(fallback_appearance);
dabd[k] = new PNLCellAppearance(match.font_color_override, match.background_color_override, match.font_weight_override).merge(fallback_appearance);
}
from = dabd.ToImmutable();
}
if (_rows_view == null)
_rows_view = new PNLRowCollectionView(rows, _default_group_appearances, _group_triggers);
else _rows_view.on_settings(_default_group_appearances, _group_triggers);
_rows_view.on_settings(_default_group_appearances, _group_triggers);
_rows_view.Refresh();
}
#region FillsVM Factory
public FillsVM mk_fills_vm(PNLRowVM row, ColumnKeys key) {
var state = PNLSettings.DefaultAuxiliaryFills;
_append_key_filters(state.filters, key);
_append_row_filters(state.filters, row);
// hide implicit cols
state.grid = _hide_cols(state.grid, anchors);
var src = _orders_model.on_fill_tuples.Filter(ft => ft.fill.SecurityKey == row.key.security_id && ft.account_id == row.key.account_id);
return new FillsVM(_bus, _orders_model, Observable.Return(state), src, new WindowSettingsManager<mbts.ui.settings.GridSettings>.DisconnectedImpl(state.grid));
return new FillsVM(
_bus, _orders_model, Observable.Return(state), new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
}
public FillsVM mk_fills_vm(PNLRowCollectionViewGroup group, ColumnKeys key) {
var state = PNLSettings.DefaultAuxiliaryFills;
_append_key_filters(state.filters, key);
_append_group_filters(state.filters, group, anchors);
// hide all columns with implicit data (i.e., the anchors)
// hide implicit cols
state.grid = _hide_cols(state.grid, anchors.WrapSubRange(0, group.level));
return new FillsVM(_bus, _orders_model, Observable.Return(state), new WindowSettingsManager<mbts.ui.settings.GridSettings>.DisconnectedImpl(state.grid));
return new FillsVM(
_bus, _orders_model, Observable.Return(state), _visible_fills_source(), new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
}
public FillsVM mk_fills_vm(PNLHeaderRowDisplay header, ColumnKeys key) {
var state = fills.FillsSettings.Default();
state.grid = orderbook.OrderBookStartupData.DefaultFillsGrid.general;
state.filters = new fills.FillsFilterStates();
state.grid = _hide_cols(state.grid, anchors);
// hide implicit cols
state.grid = _hide_cols(orderbook.OrderBookStartupData.DefaultFillsGrid.general, anchors);
_append_key_filters(state.filters, key);
var src = _orders_model.on_fill_tuples;
switch (header) {
case PNLHeaderRowDisplay.SelectedTotal: {
var filter_src = System.Reactive.Linq.ObservableExtensions.FromEventThrottle<SelectionChangedEventArgs, Func<FillTuple, bool>>(
cb => _selection_changed += cb,
cb => _selection_changed -= cb,
_ => predicate_factory(_selected_rows.Select(r => r.key)),
_auxiliary_grid_update_throttle)
.StartWith(() => predicate_factory(_selected_rows.Select(r => r.key)));
src = src.Filter(filter_src, FillTuple.key_fn);
}
case PNLHeaderRowDisplay.SelectedTotal:
// throttle selection changes (they have a potentially high frequency)
var filter_src = System.Reactive.Linq.ObservableExtensions.FromEventThrottle<SelectionChangedEventArgs, Func<FillTuple, bool>>(
cb => _selection_changed += cb,
cb => _selection_changed -= cb,
_ => _mk_selected_items_filter(_fill_pnl_key_fn),
_auxiliary_grid_update_throttle);
src = src.Filter(filter_src, FillTuple.key_fn, _mk_selected_items_filter(_fill_pnl_key_fn));
break;
case PNLHeaderRowDisplay.Total: {
var filter_src = System.Reactive.Linq.ObservableExtensions.FromEventThrottle<TableUpdate<PNLRowData, PNLKey>, Func<FillTuple, bool>>(
cb => _updated += cb,
cb => _updated -= cb,
t => predicate_factory(t.state.Keys),
_auxiliary_grid_update_throttle)
.StartWith(() => predicate_factory(rows.Select(r => r.key)));
src = src.Filter(filter_src, FillTuple.key_fn);
}
case PNLHeaderRowDisplay.Total:
src = src.Filter(_visible_items_filter_source(_fill_pnl_key_fn), FillTuple.key_fn);
break;
}
return new FillsVM(_bus, _orders_model, Observable.Return(state), src, new WindowSettingsManager<mbts.ui.settings.GridSettings>.DisconnectedImpl(state.grid));
//
Func<FillTuple, bool> predicate_factory(IEnumerable<PNLKey> keys) {
var key_set = new HashSet<PNLKey>(keys);
return new Func<FillTuple, bool>(ft => key_set.Contains(new PNLKey(ft.account_id, ft.fill.SecurityKey)));
}
return new FillsVM(
_bus, _orders_model, Observable.Return(state), src, new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
}
#endregion
#region OrderBookVM Factory
public OrderBookVM mk_orderbook_vm(PNLHeaderRowDisplay header, ColumnKeys key) {
var state = new orderbook.OrderBookStartupData(
new orderbook.FilterStates(),
orderbook.OrderBookStartupData.default_mini_grid,
use_defaults: false);
_append_key_filters(state.filters, key);
state.orderbook_grid.general = _hide_cols(state.orderbook_grid.general, anchors);
var filters = new orderbook.FilterStates();
_append_key_filters(filters, key);
var src = _orders_model.on_order_tuples_with_root_and_piq;
switch (header) {
case PNLHeaderRowDisplay.SelectedTotal: {
var filter_src = System.Reactive.Linq.ObservableExtensions.FromEventThrottle<SelectionChangedEventArgs, Func<OrderSecurityRootPair, bool>>(
cb => _selection_changed += cb,
cb => _selection_changed -= cb,
_ => predicate_factory(_selected_rows.Select(r => r.key)),
_auxiliary_grid_update_throttle);
src = src.Filter(filter_src, OrderSecurityHelper.root_pair_key_fn);
}
case PNLHeaderRowDisplay.SelectedTotal:
// throttle selection changes (they have a potentially high frequency)
var filter_src = System.Reactive.Linq.ObservableExtensions.FromEventThrottle<SelectionChangedEventArgs, Func<OrderSecurityRootPair, bool>>(
cb => _selection_changed += cb,
cb => _selection_changed -= cb,
_ => _mk_selected_items_filter(_order_pnl_key_fn),
_auxiliary_grid_update_throttle);
src = src.Filter(filter_src, OrderSecurityHelper.root_pair_key_fn, _mk_selected_items_filter(_order_pnl_key_fn));
break;
case PNLHeaderRowDisplay.Total: {
var filter_src = System.Reactive.Linq.ObservableExtensions.FromEventThrottle<TableUpdate<PNLRowData, PNLKey>, Func<OrderSecurityRootPair, bool>>(
cb => _updated += cb,
cb => _updated -= cb,
t => predicate_factory(t.state.Keys),
_auxiliary_grid_update_throttle)
.StartWith(() => predicate_factory(rows.Select(r => r.key)));
src = src.Filter(filter_src, OrderSecurityHelper.root_pair_key_fn);
}
case PNLHeaderRowDisplay.Total:
src = src.Filter(_visible_items_filter_source(_order_pnl_key_fn), OrderSecurityHelper.root_pair_key_fn);
break;
}
var settings_man = new WindowSettingsManager<orderbook.OrderBookGridSettings>.DisconnectedImpl(state.orderbook_grid);
var obvm = new OrderBookVM(_bus, null, _orders_model, src, settings_man, true);
obvm.columns.InsertRange(0, OrderBookVM.OrderBookColumnCollection.editable_columns);
return obvm;
Func<OrderSecurityRootPair, bool> predicate_factory(IEnumerable<PNLKey> keys) {
var key_set = new HashSet<PNLKey>(keys);
return new Func<OrderSecurityRootPair, bool>(o => key_set.Contains(new PNLKey(o.order.order.account_id(), o.order.order.security_key())));
}
return _mk_orderbook_vm_common(
filters, Enumerable.Empty<ColumnKeys>(), src);
}
public OrderBookVM mk_orderbook_vm(PNLRowCollectionViewGroup group, ColumnKeys key) {
var filters = new orderbook.FilterStates();
_append_key_filters(filters, key);
_append_group_filters(filters, group, anchors);
return _mk_orderbook_vm_common(filters, anchors.WrapSubRange(0, group.level));
return _mk_orderbook_vm_common(
filters, anchors.WrapSubRange(0, group.level), _visible_orders_source());
}
public OrderBookVM mk_orderbook_vm(PNLRowVM row, ColumnKeys key) {
var filters = new orderbook.FilterStates();
_append_key_filters(filters, key);
_append_row_filters(filters, row);
return _mk_orderbook_vm_common(filters, anchors.Concat(new[] { ColumnKeys.Account, ColumnKeys.Symbol, ColumnKeys.Security }).Distinct());
return _mk_orderbook_vm_common(
filters, anchors.Concat(new[] { ColumnKeys.Account, ColumnKeys.Symbol, ColumnKeys.Security }).Distinct());
}
OrderBookVM _mk_orderbook_vm_common(orderbook.FilterStates filters, IEnumerable<ColumnKeys> hide_counterparts) {
var state = new orderbook.OrderBookStartupData(filters, orderbook.OrderBookStartupData.default_mini_grid, use_defaults: false);
state.orderbook_grid.general = _hide_cols(state.orderbook_grid.general, hide_counterparts);
var settings_man = new WindowSettingsManager<orderbook.OrderBookGridSettings>.DisconnectedImpl(state.orderbook_grid);
var obvm = new OrderBookVM(_bus, null, _orders_model, settings_man, true);
foreach (var c in OrderBookVM.OrderBookColumnCollection.editable_columns)
obvm.columns.Insert(0, c);
obvm.load_from(state);
return obvm;
public void set_all_expanded(bool is_expanded) =>
_rows_view.set_all_expanded(is_expanded);
void IHandleRowSelection.row_selection_changed(SelectionChangedEventArgs e) {
RowSelectionHelper.apply_changes(_selected_rows, e);
// adding/subtracting pnl incrementally can lose precision over time, due to rounding errors.
// do the full calculation on each selection change, as long as it doesn't hurt perf (which seems unlikely).
selected_aggregate.data = _selected_rows.Aggregate(PNLNormalizedData.zero, (d, r) => d + r.normalized_data);
_selection_changed?.Invoke(e);
}
static IPNLColumnComparer _get_column_comparer(ColumnKeys column, ListSortDirection direction) {
var comp = _get_column_comparer(column);
if (direction == ListSortDirection.Descending)
comp = new ReversePNLColumnComparer(comp);
return comp;
}
static IPNLColumnComparer _get_column_comparer(ColumnKeys column) {
switch (column) {
case ColumnKeys.Account: return wrap(r => r.account_name);
case ColumnKeys.Exchange: return wrap(r => r.exchange);
case ColumnKeys.SecurityType: return wrap(r => r.security?.ContractCase ?? SecurityType.None);
case ColumnKeys.Symbol: return wrap(r => r.symbol);
case ColumnKeys.Security: return wrap(r => r.security);
case ColumnKeys.SecurityKey: return wrap(r => r.security_key);
case ColumnKeys.CPP: return wrap(r => r.cpp);
/// SOD
case ColumnKeys.SODPNL: return wrap(r => r.sod_pnl);
case ColumnKeys.SODLongPos: return wrap(r => r.sod_long_pos);
case ColumnKeys.SODLongAvgPrice: return wrap(r => r.sod_long_avg_price);
case ColumnKeys.SODShortPos: return wrap(r => r.sod_short_pos);
case ColumnKeys.SODShortAvgPrice: return wrap(r => r.sod_short_avg_price);
case ColumnKeys.SODNetPos: return wrap(r => r.sod_net_pos);
/// Day
case ColumnKeys.DayPNL: return wrap(r => r.day_pnl);
case ColumnKeys.DayLongPos: return wrap(r => r.day_long_pos);
case ColumnKeys.DayLongAvgPrice: return wrap(r => r.day_long_avg_price);
case ColumnKeys.DayShortPos: return wrap(r => r.day_short_pos);
case ColumnKeys.DayShortAvgPrice: return wrap(r => r.day_short_avg_price);
case ColumnKeys.DayNetPos: return wrap(r => r.day_net_pos);
case ColumnKeys.HasDayTrades: return wrap(r => r.has_day_trades);
/// RawDay
case ColumnKeys.RawDayLongPos: return wrap(r => r.raw_day_long_pos);
case ColumnKeys.RawDayShortPos: return wrap(r => r.raw_day_short_pos);
case ColumnKeys.RawDayNetPos: return wrap(r => r.raw_day_net_pos);
/// Total
case ColumnKeys.TotalPNL: return wrap(r => r.total_pnl);
case ColumnKeys.TotalLongPos: return wrap(r => r.total_long_pos);
case ColumnKeys.TotalLongAvgPrice: return wrap(r => r.total_long_avg_price);
case ColumnKeys.TotalShortPos: return wrap(r => r.total_short_pos);
case ColumnKeys.TotalShortAvgPrice: return wrap(r => r.total_short_avg_price);
case ColumnKeys.TotalNetPos: return wrap(r => r.total_net_pos);
/// Working
case ColumnKeys.WorkingLongPos: return wrap(r => r.working_long_pos);
case ColumnKeys.WorkingLongAvgPrice: return wrap(r => r.working_long_avg_price);
case ColumnKeys.WorkingShortPos: return wrap(r => r.working_short_pos);
case ColumnKeys.WorkingShortAvgPrice: return wrap(r => r.working_short_avg_price);
case ColumnKeys.WorkingNetPos: return wrap(r => r.working_net_pos);
/// Market
case ColumnKeys.BidPrice: return wrap(r => r.bid_price);
case ColumnKeys.MidPrice: return wrap(r => r.mid_price);
case ColumnKeys.AskPrice: return wrap(r => r.ask_price);
case ColumnKeys.LastPrice: return wrap(r => r.last_price);
case ColumnKeys.SettlementPrice: return wrap(r => r.settlement_price);
case ColumnKeys.PNLPrice: return wrap(r => r.pnl_price);
default: throw ExceptionBuilder.Argument.unexpected_case(column, nameof(column));
}
PNLColumnComparer<T> wrap<T>(Func<PNLRowVM, T> selector) =>
new PNLColumnComparer<T>(selector);
}
#endregion
static GridSettings _hide_cols(GridSettings grid, IEnumerable<ColumnKeys> columns) =>
grid.with(_hide_cols(grid.columns, columns));
@@ -506,15 +555,22 @@ namespace bts.shell.pnl {
case ColumnKeys.DayShortPos:
f.fill_kind = new[] { Fill.Types.FillKind.Standard, Fill.Types.FillKind.Leg };
break;
case ColumnKeys.RawDayNetPos:
case ColumnKeys.RawDayLongPos:
case ColumnKeys.RawDayShortPos:
f.fill_kind = new[] { Fill.Types.FillKind.Standard };
break;
}
switch (key) {
case ColumnKeys.SODLongPos:
case ColumnKeys.DayLongPos:
case ColumnKeys.RawDayLongPos:
case ColumnKeys.TotalLongPos:
f.side = new[] { Side.Buy };
break;
case ColumnKeys.SODShortPos:
case ColumnKeys.DayShortPos:
case ColumnKeys.RawDayShortPos:
case ColumnKeys.TotalShortPos:
f.side = new[] { Side.Sell };
break;
@@ -559,11 +615,13 @@ namespace bts.shell.pnl {
switch (key) {
case ColumnKeys.WorkingLongPos:
case ColumnKeys.DayLongPos:
case ColumnKeys.RawDayLongPos:
case ColumnKeys.TotalLongPos:
f.sides = new[] { Side.Buy };
break;
case ColumnKeys.WorkingShortPos:
case ColumnKeys.DayShortPos:
case ColumnKeys.RawDayShortPos:
case ColumnKeys.TotalShortPos:
f.sides = new[] { Side.Sell };
break;
@@ -612,12 +670,57 @@ namespace bts.shell.pnl {
}
#endregion
static Func<T, bool> _mk_items_filter<T>(IEnumerable<PNLKey> keys, Func<T, PNLKey> key_selector) {
var key_set = new HashSet<PNLKey>(keys);
return v => key_set.Contains(key_selector(v));
}
Func<T, bool> _mk_selected_items_filter<T>(Func<T, PNLKey> key_selector) =>
_mk_items_filter(_selected_rows.Select(r => r.key), key_selector);
OrderBookVM _mk_orderbook_vm_common(orderbook.FilterStates filters, IEnumerable<ColumnKeys> hide_counterparts, IObservable<TableUpdate<OrderSecurityRootPair, uint>> orders_source = null) {
var state = new orderbook.OrderBookStartupData(filters, orderbook.OrderBookStartupData.default_mini_grid, use_defaults: false);
state.orderbook_grid.general = _hide_cols(state.orderbook_grid.general, hide_counterparts);
var settings_man = new WindowSettingsManager<orderbook.OrderBookGridSettings>.DisconnectedImpl(state.orderbook_grid);
var obvm = orders_source == null ?
new OrderBookVM(_bus, null, _orders_model, settings_man, true) :
new OrderBookVM(_bus, null, _orders_model, orders_source, settings_man, true);
foreach (var c in OrderBookVM.OrderBookColumnCollection.editable_columns)
obvm.columns.Insert(0, c);
obvm.load_from(state);
return obvm;
}
IObservable<TableUpdate<FillTuple, string>> _visible_fills_source() =>
_orders_model.on_fill_tuples.Filter(_visible_items_filter_source(_fill_pnl_key_fn), FillTuple.key_fn);
IObservable<TableUpdate<OrderSecurityRootPair, uint>> _visible_orders_source() =>
_orders_model.on_order_tuples_with_root_and_piq.Filter(_visible_items_filter_source(_order_pnl_key_fn), OrderSecurityHelper.root_pair_key_fn);
IObservable<Func<T, bool>> _visible_items_filter_source<T>(Func<T, PNLKey> key_selector) =>
Observable.Create<Func<T, bool>>(o => {
o.OnNext(_visible_items_filter_snapshot(key_selector));
_updated += on_updated;
return Disposable.Create(() => _updated -= on_updated);
void on_updated(TableUpdate<PNLRowData, PNLKey> _) =>
o.OnNext(_visible_items_filter_snapshot(key_selector));
});
// returns a predicate that takes a snapshot of the current state, and returns true if the item's account/security pair is visible in the snapshot state.
// subsequent updates are not reflected in this predicate.
// Note: this is a necessary behavior if the predicate is used in a TableUpdate filter stream (it must always return the same value for the same item).
Func<T, bool> _visible_items_filter_snapshot<T>(Func<T, PNLKey> key_selector) {
var t = _last_table;
return o => t.state.ContainsKey(key_selector(o));
}
void _update((TableUpdate<PNLRowData, PNLKey> filtered, TableUpdate<PNLRowData, PNLKey> raw) tup) {
if (_is_paused) {
_resume = tup;
return;
}
var (filtered, raw) = tup;
_last_table = filtered;
int old_total_row_count = total_row_count;
int old_filtered_row_count = filtered_row_count;
var total_pnl = total_aggregate.data;
@@ -626,31 +729,26 @@ namespace bts.shell.pnl {
int i = CollectionHelper.binary_search(_rows_data_wrapper, v, PNLRowData.key_comparer);
Log.assert(i >= 0, $"{typeof(PNLVM)} received an update with a removed item that was not found via binary search.");
if (i >= 0) {
if (rows[i].security_type == SecurityType.Future)
total_pnl -= rows[i].normalized_data;
total_pnl -= rows[i].normalized_data;
rows.RemoveAt(i);
}
}
foreach (var v in filtered.added) {
var r = new PNLRowVM(v.with(calc_type: calculation_type), _default_appearances, _triggers);
rows.InsertSorted(r, PNLRowVM.key_comparer);
if (r.security_type == SecurityType.Future)
total_pnl += r.normalized_data;
}
foreach (var d in filtered.changed) {
int i = CollectionHelper.binary_search(_rows_data_wrapper, d, PNLRowData.key_comparer);
if (i >= 0) {
var r = rows[i];
if (r.security_type == SecurityType.Future)
total_pnl -= r.normalized_data;
total_pnl -= r.normalized_data;
r.update(d);
if (r.security_type == SecurityType.Future)
total_pnl += r.normalized_data;
total_pnl += r.normalized_data;
}
}
if (_selected_rows.Count > 0) {
selected_aggregate.data = _selected_rows
.Where(r => r.security_type == SecurityType.Future)
.Aggregate(PNLNormalizedData.zero, (d, r) => d + r.normalized_data);
}
//
@@ -662,13 +760,5 @@ namespace bts.shell.pnl {
RaisePropertyChanged(nameof(filtered_row_count));
_updated?.Invoke(filtered);
}
void IHandleRowSelection.row_selection_changed(SelectionChangedEventArgs e) {
RowSelectionHelper.apply_changes(_selected_rows, e);
// adding/subtracting pnl incrementally can lose precision over time, due to rounding errors.
// do the full calculation on each selection change, as long as it doesn't hurt perf (which seems unlikely).
selected_aggregate.data = _selected_rows.Aggregate(PNLNormalizedData.zero, (d, r) => d + r.normalized_data);
_selection_changed?.Invoke(e);
}
}
}
}
@@ -0,0 +1,69 @@
<HeaderedContentControl
x:Class="bts.shell.pnl.PNLAuxiliaryPopupView"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pnl="clr-namespace:bts.shell.pnl"
xmlns:u="clr-namespace:mbts.ui"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:sp="clr-namespace:mbts.spark"
Background="{StaticResource CommonBackground}"
BorderBrush="Black"
BorderThickness="1"
MaxHeight="300"
>
<HeaderedContentControl.Resources>
<!--Fills-->
<DataTemplate DataType="{x:Type sp:FillsVM}">
<usp:FillsView u:Behavior.FocusOnLoaded="True"/>
</DataTemplate>
<!--OrderBook-->
<DataTemplate DataType="{x:Type sp:OrderBookVM}">
<sp:MiniOrderBookView u:Behavior.FocusOnLoaded="True"/>
</DataTemplate>
</HeaderedContentControl.Resources>
<!--Header (Title Bar)-->
<HeaderedContentControl.HeaderTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Margin="4,0,0,0" FontWeight="Bold" Text="{Binding context}"/>
<TextBlock Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="4,0,0,0" FontWeight="Bold" Text="{Binding title}">
<TextBlock.Foreground>
<SolidColorBrush Color="{Binding title_foreground}"/>
</TextBlock.Foreground>
</TextBlock>
<CheckBox Grid.Column="2" VerticalAlignment="Center" Margin="4,0,0,0" Focusable="False" IsChecked="{Binding keep_open}">Keep Open</CheckBox>
<Button Grid.Column="3" HorizontalAlignment="Right" Margin="4" Command="{x:Static ApplicationCommands.Close}" CommandTarget="{Binding RelativeSource={RelativeSource AncestorType=pnl:PNLAuxiliaryPopupView}}" Style="{StaticResource RoundCloseButton}"/>
</Grid>
</DataTemplate>
</HeaderedContentControl.HeaderTemplate>
<!--Input Bindings-->
<HeaderedContentControl.InputBindings>
<KeyBinding Key="Esc" Command="{x:Static ApplicationCommands.Close}"/>
</HeaderedContentControl.InputBindings>
<!--Template-->
<HeaderedContentControl.Template>
<ControlTemplate TargetType="HeaderedContentControl">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ContentPresenter ContentSource="Header"/>
<ContentPresenter Grid.Row="1" Grid.ColumnSpan="4"/>
</Grid>
</Border>
</ControlTemplate>
</HeaderedContentControl.Template>
</HeaderedContentControl>
@@ -0,0 +1,8 @@
using System.Windows.Controls;
namespace bts.shell.pnl {
partial class PNLAuxiliaryPopupView : HeaderedContentControl {
public PNLAuxiliaryPopupView() =>
InitializeComponent();
}
}
@@ -1,458 +1,467 @@
using bts.collections;
using bts.utils;
using mbts;
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Runtime.CompilerServices;
using System.Windows.Data;
using CellAnimation = mbts.ui.spark.CellAnimation;
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
namespace bts.shell.pnl {
class PNLRowCollectionView : ListCollectionView {
ReadOnlyObservableCollection<object> _base_groups, _groups_wrapper;
ObservableAutoWrapper<object, object> _groups;
// appearance/triggers
ImmutableDictionary<ColumnKeys, PNLCellAppearance> _defaults_appearances;
IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> _triggers;
public PNLRowCollectionView(IList list, ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances, IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> triggers) : base(list) {
_defaults_appearances = default_appearances;
_triggers = triggers;
}
public override bool CanFilter => true;
public override bool CanGroup => true;
public override ReadOnlyObservableCollection<object> Groups {
get {
if (_groups_wrapper == null) {
_base_groups = base.Groups;
if (_base_groups == null)
return null;
_groups = new ObservableAutoWrapper<object, object>(_base_groups, obj => new PNLRowCollectionViewGroup((CollectionViewGroup)obj, this, _defaults_appearances, _triggers));
_groups_wrapper = new ReadOnlyObservableCollection<object>(_groups);
}
return _groups_wrapper;
}
}
public void on_settings(ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances, IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> triggers) {
_defaults_appearances = default_appearances;
_triggers = triggers;
foreach (var g in _groups_wrapper.OfTypeSafe<PNLRowCollectionViewGroup>())
g.on_settings(default_appearances, triggers);
}
public void set_all_expanded(bool is_expanded) {
foreach (var g in Groups.OfTypeSafe<PNLRowCollectionViewGroup>())
g.set_all_expanded(is_expanded);
}
}
class PNLRowCollectionViewGroup : CollectionViewGroup, IRaiseNotifyPropertyChanged, IEquatable<PNLRowCollectionViewGroup> {
readonly PNLRowCollectionView _root;
readonly CollectionViewGroup _from;
readonly Dictionary<object, InnerGroupSubscription> _inner_groups = new Dictionary<object, InnerGroupSubscription>();
readonly Dictionary<PNLKey, IDisposable> _rows = new Dictionary<PNLKey, IDisposable>();
readonly VersionContainer<bool?> _is_all_selected;
readonly DeferrableActionWrapper _refresh_is_all_selected;
readonly HashSet<PNLRowVM> _selected_rows = new HashSet<PNLRowVM>();
readonly PNLCellIndexBuilder<PNLNormalizedData> _cell_map = new PNLCellIndexBuilder<PNLNormalizedData>();
readonly PNLCellEmbeddedAnimationModel<PNLNormalizedData> _animator;
ImmutableDictionary<ColumnKeys, PNLCellAppearance> _defaults_appearances;
IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> _triggers;
IDisposable _dispose;
bool _is_expanded = true;
int _selected_item_count_backing;
public PNLRowCollectionViewGroup(CollectionViewGroup from, PNLRowCollectionView root, ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances, IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> triggers) : base(from.Name) {
_root = root;
_from = from;
_defaults_appearances = default_appearances;
_triggers = triggers;
_animator = new PNLCellEmbeddedAnimationModel<PNLNormalizedData>(default_appearances, triggers);
_animator.on_row(normalized_data.data);
normalized_data.data_changed += () => {
_cell_map.on_row(normalized_data.data);
_animator.on_row(normalized_data.data);
_cell_map.set_appearances(_animator.cell_appearances);
};
//sod_net_pos
_is_all_selected = new VersionContainer<bool?>(() => {
if (_selected_item_count == 0)
return false;
if (_selected_item_count == ProtectedItemCount)
return true;
return null;
});
bool? last_published_all_selected = _is_all_selected.value;
_refresh_is_all_selected = new DeferrableActionWrapper(() => {
if (_is_all_selected.value != last_published_all_selected) {
last_published_all_selected = is_all_selected;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(is_all_selected)));
}
});
// sod
sod_pnl = _cell_map.add_pnl(ColumnKeys.SODPNL, d => d.sod_pnl);
sod_net_pos = _cell_map.add_net_pos(ColumnKeys.SODNetPos, d => d.sod_net_pos);
sod_long_pos = _cell_map.add_long_pos(ColumnKeys.SODLongPos, d => d.sod_long_pos);
sod_short_pos = _cell_map.add_short_pos(ColumnKeys.SODShortPos, d => d.sod_short_pos);
// day
day_pnl = _cell_map.add_pnl(ColumnKeys.DayPNL, d => d.day_pnl);
day_net_pos = _cell_map.add_net_pos(ColumnKeys.DayNetPos, d => d.day_net_pos);
day_long_pos = _cell_map.add_long_pos(ColumnKeys.DayLongPos, d => d.day_long_pos);
day_short_pos = _cell_map.add_short_pos(ColumnKeys.DayShortPos, d => d.day_short_pos);
// total
total_pnl = _cell_map.add(ColumnKeys.TotalPNL, d => d.total_pnl, FormattingDelegates.Price<decimal>.Value);
total_net_pos = _cell_map.add(ColumnKeys.TotalNetPos, d => d.total_net_pos);
total_long_pos = _cell_map.add(ColumnKeys.TotalLongPos, d => d.total_long_pos);
total_short_pos = _cell_map.add(ColumnKeys.TotalShortPos, d => d.total_short_pos);
// working
working_net_pos = _cell_map.add(ColumnKeys.WorkingNetPos, d => d.working_net_pos);
working_long_pos = _cell_map.add(ColumnKeys.WorkingLongPos, d => d.working_long_pos);
working_short_pos = _cell_map.add(ColumnKeys.WorkingShortPos, d => d.working_short_pos);
_cell_map.on_row(normalized_data.data);
_cell_map.set_appearances(_animator.cell_appearances);
((INotifyCollectionChanged)_from.Items).CollectionChanged += _from_items_CollectionChanged;
_from_items_CollectionChanged(_from.Items, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, _from.Items, 0));
_dispose = _cell_map.animation_model.OnDispatcher().Subscribe(new_animations => {
//stopwatch.Start();
animations = new_animations.Select(kv => new CellAnimation(kv.Key.column, kv.Key.type, kv.Value.color.to_brush())).ToList();
OnPropertyChanged(new PropertyChangedEventArgs(nameof(animations)));
});
}
public IList<CellAnimation> animations { get; private set; }
public event Action ProtectedItemCountChanged, selected_item_count_changed;
public PNLNormalizedDataVM normalized_data { get; } = new PNLNormalizedDataVM();
// sod
public PNLCellVM<PNLNormalizedData, decimal> sod_pnl { get; }
public PNLCellVM<PNLNormalizedData, decimal> sod_net_pos { get; }
public PNLCellVM<PNLNormalizedData, decimal> sod_long_pos { get; }
public PNLCellVM<PNLNormalizedData, decimal> sod_short_pos { get; }
// day
public PNLCellVM<PNLNormalizedData, decimal> day_pnl { get; }
public PNLCellVM<PNLNormalizedData, decimal> day_net_pos { get; }
public PNLCellVM<PNLNormalizedData, decimal> day_long_pos { get; }
public PNLCellVM<PNLNormalizedData, decimal> day_short_pos { get; }
// total
public PNLCellVM<PNLNormalizedData, decimal> total_pnl { get; }
public PNLCellVM<PNLNormalizedData, decimal> total_net_pos { get; }
public PNLCellVM<PNLNormalizedData, decimal> total_long_pos { get; }
public PNLCellVM<PNLNormalizedData, decimal> total_short_pos { get; }
// working
public PNLCellVM<PNLNormalizedData, decimal> working_net_pos { get; }
public PNLCellVM<PNLNormalizedData, decimal> working_long_pos { get; }
public PNLCellVM<PNLNormalizedData, decimal> working_short_pos { get; }
//
public PNLRowCollectionViewGroup parent { get; private set; }
public int level => parent == null ? 1 : parent.level + 1;
public override bool IsBottomLevel => _from.IsBottomLevel;
public IEnumerable<PNLRowCollectionViewGroup> ancestors {
get {
if (parent != null) {
var p = parent;
do {
yield return p;
p = p.parent;
} while (p != null);
}
}
}
public IReadOnlyList<object> GroupNamePath {
get {
var path = new List<object> { Name };
foreach (var a in ancestors)
path.Add(a.Name);
return path.WrapReverseRO();
}
}
public bool is_expanded { get => _is_expanded; set => _set(ref _is_expanded, value); }
public bool? is_all_selected {
get => _is_all_selected.value;
set {
// The setter should not be called while is_all_selected deferred.
// However, it is good to be defensive, so let's add a conditional check anyways:
// if 'is_all_selected' is deferred, the value must be blindly propagated,
// bc the equality comparison is erroneous, as the current state is unknown.
if (value.HasValue && value != is_all_selected) {
using (_refresh_is_all_selected.defer_execution()) {
foreach (var i in Items) {
if (i is PNLRowCollectionViewGroup g)
g.is_all_selected = value;
else if (i is PNLRowVM r)
r.is_selected = value.Value;
}
}
}
}
}
new int ProtectedItemCount {
get => base.ProtectedItemCount;
set {
if (base.ProtectedItemCount != value) {
base.ProtectedItemCount = value;
_is_all_selected.increment_version();
ProtectedItemCountChanged?.Invoke();
_refresh_is_all_selected.execute();
}
}
}
int _selected_item_count {
get => _selected_item_count_backing;
set {
Log.assert(value >= 0, $"{nameof(_selected_item_count)} cannot be negative.");
_selected_item_count_backing = value;
_is_all_selected.increment_version();
_refresh_is_all_selected.execute();
selected_item_count_changed?.Invoke();
}
}
public void on_settings(ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances, IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> triggers) {
_defaults_appearances = default_appearances;
_triggers = triggers;
if (Name.Equals("GC")) {
}
_animator.on_settings(default_appearances, triggers);
_cell_map.set_appearances(_animator.cell_appearances);
foreach (var g in ProtectedItems.OfTypeSafe<PNLRowCollectionViewGroup>())
g.on_settings(default_appearances, triggers);
}
public void set_all_expanded(bool is_expanded) {
this.is_expanded = is_expanded;
foreach (var g in Items.OfTypeSafe<PNLRowCollectionViewGroup>())
g.set_all_expanded(is_expanded);
}
public bool Equals(PNLRowCollectionViewGroup other) =>
other != null &&
level == other.level &&
Equals(Name, other.Name);
void _select_all() {
foreach (var i in Items) {
if (i is PNLRowCollectionViewGroup cvg) {
cvg._select_all();
} else if (i is PNLRowVM row) {
}
}
}
IDisposable _detect_is_all_selected_changed() {
bool? was_all_selected = is_all_selected;
return Disposable.Create(() => {
if (is_all_selected != was_all_selected)
OnPropertyChanged(new PropertyChangedEventArgs(nameof(is_all_selected)));
});
}
void _add_item(int index, object item) {
Log.assert(index >= 0, "Index is less than 0.");
if (item is CollectionViewGroup group) {
var inner_group = new PNLRowCollectionViewGroup(group, _root, _defaults_appearances, _triggers);
_inner_groups.Add(group.Name, new InnerGroupSubscription(this, inner_group));
ProtectedItems.Insert(index, inner_group);
} else {
var row = (PNLRowVM)item;
ProtectedItems.Insert(index, item);
_rows.Add(row.key, _connect_row(row));
}
}
void _remove_item(int index, object item) {
if (item is PNLRowCollectionViewGroup inner_group) {
if (!_inner_groups.TryPopValue(inner_group.Name, out var ig_sub))
throw new InvalidOperationException($"Inner groups cache does not have an entry for the specified name '{inner_group.Name}' (entire tree structure may be compromised).");
ig_sub.Dispose();
Disposable.dispose(ref inner_group._dispose);
} else {
var row = (PNLRowVM)item;
if (!_rows.TryPopValue(row.key, out var row_sub))
throw new InvalidOperationException($"Inner rows cache does not have an entry for the specified irowstem key '{row.key}' (entire tree structure may be compromised).");
row_sub.Dispose();
}
ProtectedItems.RemoveAt(index);
}
void _from_items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
using (_refresh_is_all_selected.defer_execution()) {
switch (e.Action) {
case NotifyCollectionChangedAction.Add:
for (int i = 0; i < e.NewItems.Count; i++)
_add_item(e.NewStartingIndex + i, e.NewItems[i]);
break;
case NotifyCollectionChangedAction.Remove:
for (int i = 0; i < e.OldItems.Count; i++)
_remove_item(e.OldStartingIndex, ProtectedItems[e.OldStartingIndex]);
break;
case NotifyCollectionChangedAction.Replace:
for (int i = 0; i < e.OldItems.Count; i++)
_remove_item(e.OldStartingIndex, ProtectedItems[e.OldStartingIndex]);
for (int i = 0; i < e.NewItems.Count; i++)
_add_item(e.NewStartingIndex + i, e.NewItems[i]);
break;
case NotifyCollectionChangedAction.Reset:
while (ProtectedItems.Count > 0)
_remove_item(0, ProtectedItems[0]);
for (int i = 0; i < _from.Items.Count; i++)
_add_item(i, _from.Items[i]);
break;
}
_is_all_selected.increment_version();
_refresh_is_all_selected.execute();
}
}
IDisposable _connect_row(PNLRowVM row) {
using (_detect_is_all_selected_changed()) {
if (Name.Equals("GC")) {
}
var last_data = PNLNormalizedData.zero;
on_updated(row.normalized_data);
row.updated += on_updated;
row.is_selected_changed += on_is_selected_changed;
if (row.is_selected) {
Log.assert(_selected_rows.Add(row), $"Selected Rows out of sync ({Name}): hash set already contained selected row that was just added to group ({row}).");
_selected_item_count++;
}
ProtectedItemCount++;
return Disposable.Create(() => {
row.updated -= on_updated;
row.is_selected_changed -= on_is_selected_changed;
using (_refresh_is_all_selected.defer_execution()) {
if (row.is_selected) {
Log.assert(_selected_rows.Remove(row), $"Selected Rows out of sync ({Name}): hash set did not contain selected row that was just removed from group ({row}).");
_selected_item_count--;
}
var newval = normalized_data.data - row.normalized_data;
normalized_data.data = newval;
ProtectedItemCount--;
}
});
void on_updated(PNLNormalizedData new_data) {
if (Name.Equals("GC")) {
}
var less_last = normalized_data.data - last_data;
var plus_new = less_last + new_data;
normalized_data.data += new_data - last_data;
if (plus_new != normalized_data.data) {
}
Log.assert(plus_new == normalized_data.data);
last_data = new_data;
}
void on_is_selected_changed(bool is_selected) {
if (is_selected) {
Log.assert(_selected_rows.Add(row), $"Selected Rows out of sync ({Name}): hash set already contained row that was just selected ({row}).");
_selected_item_count++;
} else {
Log.assert(_selected_rows.Remove(row), $"Selected Rows out of sync ({Name}): hash set did not contain row that was just unselected ({row}).");
_selected_item_count--;
}
}
}
}
void _set<T>(ref T backing, T new_value, [CallerMemberName]string prop = "") =>
_set(ref backing, new_value, EqualityComparer<T>.Default, prop);
void _set<T>(ref T backing, T new_value, IEqualityComparer<T> comparer, [CallerMemberName]string prop = "") {
if (!comparer.Equals(backing, new_value)) {
backing = new_value;
OnPropertyChanged(new PropertyChangedEventArgs(prop));
}
}
void IRaiseNotifyPropertyChanged.raise_notify_property_changed(string prop) =>
OnPropertyChanged(new PropertyChangedEventArgs(prop));
class InnerGroupSubscription : IDisposable {
public readonly PNLRowCollectionViewGroup _inner_group;
readonly PNLRowCollectionViewGroup _parent;
PNLNormalizedData _last_data = PNLNormalizedData.zero;
int _last_count, _last_selected_item_count;
public InnerGroupSubscription(PNLRowCollectionViewGroup parent, PNLRowCollectionViewGroup inner_group) {
_parent = parent;
_inner_group = inner_group;
_inner_group.parent = parent;
// init item count
_on_item_count_changed();
// listen to item count changes
_inner_group.ProtectedItemCountChanged += _on_item_count_changed;
// init selected item count
_on_selected_item_count_changed();
// listen to selected item count changes
_inner_group.selected_item_count_changed += _on_selected_item_count_changed;
// init data
_on_normalized_data_changed();
// listen to data changes
_inner_group.normalized_data.data_changed += _on_normalized_data_changed;
_parent._selected_item_count += _inner_group._selected_item_count;
}
public void Dispose() {
_inner_group.ProtectedItemCountChanged -= _on_item_count_changed;
_inner_group.selected_item_count_changed -= _on_selected_item_count_changed;
_inner_group.normalized_data.data_changed -= _on_normalized_data_changed;
_inner_group.parent = null;
// make sure the state is valid (if a bad state goes by undetected here, nefarious/difficult-to-track bugs could arise)
if (_last_count != _inner_group.ProtectedItemCount) {
Log.Ui.warn("Cached inner count is not equal to the inner group's ItemCount (entire tree structure may be compromised).");
}
if (_last_data != _inner_group.normalized_data.data) {
Log.Ui.warn($"Cached normalized data is not equal to the inner group's normalized data (aggregated pnl data may be compromised).");
}
_parent.ProtectedItemCount -= _last_count;
_parent.normalized_data.data -= _last_data;
_parent._selected_item_count -= _last_selected_item_count;
}
void _on_item_count_changed() {
// add the delta
_parent.ProtectedItemCount += (_inner_group.ProtectedItemCount - _last_count);
// reset the local cache
_last_count = _inner_group.ProtectedItemCount;
}
void _on_selected_item_count_changed() {
_parent._selected_item_count += (_inner_group._selected_item_count - _last_selected_item_count);
_last_selected_item_count = _inner_group._selected_item_count;
}
void _on_normalized_data_changed() {
var new_data = _inner_group.normalized_data.data;
_parent.normalized_data.data += new_data - _last_data;
_last_data = new_data;
}
}
}
using bts.collections;
using bts.utils;
using mbts;
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Runtime.CompilerServices;
using System.Windows.Data;
using CellAnimation = mbts.ui.spark.CellAnimation;
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
namespace bts.shell.pnl {
class PNLRowCollectionView : ListCollectionView {
ReadOnlyObservableCollection<object> _base_groups, _groups_wrapper;
ObservableAutoWrapper<object, object> _groups;
// appearance/triggers
ImmutableDictionary<ColumnKeys, PNLCellAppearance> _defaults_appearances;
IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> _triggers;
public PNLRowCollectionView(IList list, ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances, IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> triggers) : base(list) {
_defaults_appearances = default_appearances;
_triggers = triggers;
}
public override bool CanFilter => true;
public override bool CanGroup => true;
// Must return null if there are no GroupDescriptions (i.e., instead of an empty collection).
// If an empty collection is returned, the view assumes grouping is active and there are no items.
public override ReadOnlyObservableCollection<object> Groups {
get {
if ((GroupDescriptions?.Count ?? 0) == 0)
return null;
if (_groups_wrapper == null) {
_base_groups = base.Groups;
if (_base_groups == null)
return null;
_groups = new ObservableAutoWrapper<object, object>(_base_groups, obj => new PNLRowCollectionViewGroup((CollectionViewGroup)obj, this, _defaults_appearances, _triggers));
_groups_wrapper = new ReadOnlyObservableCollection<object>(_groups);
}
return _groups_wrapper;
}
}
public void on_settings(ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances, IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> triggers) {
_defaults_appearances = default_appearances;
_triggers = triggers;
foreach (var g in _groups_wrapper.OfTypeSafe<PNLRowCollectionViewGroup>())
g.on_settings(default_appearances, triggers);
}
public void set_all_expanded(bool is_expanded) {
foreach (var g in Groups.OfTypeSafe<PNLRowCollectionViewGroup>())
g.set_all_expanded(is_expanded);
}
}
class PNLRowCollectionViewGroup : CollectionViewGroup, IRaiseNotifyPropertyChanged, IEquatable<PNLRowCollectionViewGroup> {
readonly PNLRowCollectionView _root;
readonly CollectionViewGroup _from;
readonly Dictionary<object, InnerGroupSubscription> _inner_groups = new Dictionary<object, InnerGroupSubscription>();
readonly Dictionary<PNLKey, IDisposable> _rows = new Dictionary<PNLKey, IDisposable>();
readonly VersionContainer<bool?> _is_all_selected;
readonly DeferrableActionWrapper _refresh_is_all_selected;
readonly HashSet<PNLRowVM> _selected_rows = new HashSet<PNLRowVM>();
readonly PNLCellIndexBuilder<PNLNormalizedData> _cell_map = new PNLCellIndexBuilder<PNLNormalizedData>();
readonly PNLCellEmbeddedAnimationModel<PNLNormalizedData> _animator;
ImmutableDictionary<ColumnKeys, PNLCellAppearance> _defaults_appearances;
IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> _triggers;
IDisposable _dispose;
bool _is_expanded = true;
int _selected_item_count_backing;
PNLNormalizedData _data;
public PNLRowCollectionViewGroup(CollectionViewGroup from, PNLRowCollectionView root, ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances, IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> triggers) : base(from.Name) {
_root = root;
_from = from;
_defaults_appearances = default_appearances;
_triggers = triggers;
_animator = new PNLCellEmbeddedAnimationModel<PNLNormalizedData>(default_appearances, triggers);
_animator.on_row(data);
data_changed += () => {
_cell_map.on_row(data);
_animator.on_row(data);
_cell_map.set_appearances(_animator.cell_appearances);
};
//sod_net_pos
_is_all_selected = new VersionContainer<bool?>(() => {
if (_selected_item_count == 0)
return false;
if (_selected_item_count == ProtectedItemCount)
return true;
return null;
});
bool? last_published_all_selected = _is_all_selected.value;
_refresh_is_all_selected = new DeferrableActionWrapper(() => {
if (_is_all_selected.value != last_published_all_selected) {
last_published_all_selected = is_all_selected;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(is_all_selected)));
}
});
// sod
sod_pnl = _cell_map.add_pnl(ColumnKeys.SODPNL, d => d.sod_pnl);
sod_net_pos = _cell_map.add_net_pos(ColumnKeys.SODNetPos, d => d.sod_net_pos);
sod_long_pos = _cell_map.add_long_pos(ColumnKeys.SODLongPos, d => d.sod_long_pos);
sod_short_pos = _cell_map.add_short_pos(ColumnKeys.SODShortPos, d => d.sod_short_pos);
// day
day_pnl = _cell_map.add_pnl(ColumnKeys.DayPNL, d => d.day_pnl);
day_net_pos = _cell_map.add_net_pos(ColumnKeys.DayNetPos, d => d.day_net_pos);
day_long_pos = _cell_map.add_long_pos(ColumnKeys.DayLongPos, d => d.day_long_pos);
day_short_pos = _cell_map.add_short_pos(ColumnKeys.DayShortPos, d => d.day_short_pos);
// raw day
raw_day_net_pos = _cell_map.add_net_pos(ColumnKeys.RawDayNetPos, d => d.raw_day_net_pos);
raw_day_long_pos = _cell_map.add_long_pos(ColumnKeys.RawDayLongPos, d => d.raw_day_long_pos);
raw_day_short_pos = _cell_map.add_short_pos(ColumnKeys.RawDayShortPos, d => d.raw_day_short_pos);
// total
total_pnl = _cell_map.add(ColumnKeys.TotalPNL, d => d.total_pnl, FormattingDelegates.Price<decimal>.Value);
total_net_pos = _cell_map.add(ColumnKeys.TotalNetPos, d => d.total_net_pos);
total_long_pos = _cell_map.add(ColumnKeys.TotalLongPos, d => d.total_long_pos);
total_short_pos = _cell_map.add(ColumnKeys.TotalShortPos, d => d.total_short_pos);
// working
working_net_pos = _cell_map.add(ColumnKeys.WorkingNetPos, d => d.working_net_pos);
working_long_pos = _cell_map.add(ColumnKeys.WorkingLongPos, d => d.working_long_pos);
working_short_pos = _cell_map.add(ColumnKeys.WorkingShortPos, d => d.working_short_pos);
_cell_map.on_row(data);
_cell_map.set_appearances(_animator.cell_appearances);
((INotifyCollectionChanged)_from.Items).CollectionChanged += _from_items_CollectionChanged;
_from_items_CollectionChanged(_from.Items, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, _from.Items, 0));
_dispose = _cell_map.animation_model.OnDispatcher().Subscribe(new_animations => {
animations = new_animations.Select(kv => new CellAnimation(kv.Key.column, kv.Key.type, kv.Value.color.to_brush())).ToList();
OnPropertyChanged(new PropertyChangedEventArgs(nameof(animations)));
});
}
public IList<CellAnimation> animations { get; private set; }
public event Action
data_changed,
ProtectedItemCountChanged,
selected_item_count_changed;
public PNLNormalizedData data {
get => _data;
set {
if (_data != value) {
_data = value;
data_changed?.Invoke();
}
}
}
// sod
public PNLCellVM<PNLNormalizedData, decimal> sod_pnl { get; }
public PNLCellVM<PNLNormalizedData, decimal> sod_net_pos { get; }
public PNLCellVM<PNLNormalizedData, decimal> sod_long_pos { get; }
public PNLCellVM<PNLNormalizedData, decimal> sod_short_pos { get; }
// day
public PNLCellVM<PNLNormalizedData, decimal> day_pnl { get; }
public PNLCellVM<PNLNormalizedData, decimal> day_net_pos { get; }
public PNLCellVM<PNLNormalizedData, decimal> day_long_pos { get; }
public PNLCellVM<PNLNormalizedData, decimal> day_short_pos { get; }
// raw day
public PNLCellVM<PNLNormalizedData, decimal> raw_day_net_pos { get; }
public PNLCellVM<PNLNormalizedData, decimal> raw_day_long_pos { get; }
public PNLCellVM<PNLNormalizedData, decimal> raw_day_short_pos { get; }
// total
public PNLCellVM<PNLNormalizedData, decimal> total_pnl { get; }
public PNLCellVM<PNLNormalizedData, decimal> total_net_pos { get; }
public PNLCellVM<PNLNormalizedData, decimal> total_long_pos { get; }
public PNLCellVM<PNLNormalizedData, decimal> total_short_pos { get; }
// working
public PNLCellVM<PNLNormalizedData, decimal> working_net_pos { get; }
public PNLCellVM<PNLNormalizedData, decimal> working_long_pos { get; }
public PNLCellVM<PNLNormalizedData, decimal> working_short_pos { get; }
//
public PNLRowCollectionViewGroup parent { get; private set; }
public int level => parent == null ? 1 : parent.level + 1;
public override bool IsBottomLevel => _from.IsBottomLevel;
public IEnumerable<PNLRowCollectionViewGroup> ancestors {
get {
if (parent != null) {
var p = parent;
do {
yield return p;
p = p.parent;
} while (p != null);
}
}
}
public IReadOnlyList<object> GroupNamePath {
get {
var path = new List<object> { Name };
foreach (var a in ancestors)
path.Add(a.Name);
return path.WrapReverseRO();
}
}
public bool is_expanded { get => _is_expanded; set => _set(ref _is_expanded, value); }
public bool? is_all_selected {
get => _is_all_selected.value;
set {
// The setter should not be called while is_all_selected deferred.
// However, it is good to be defensive, so let's add a conditional check anyways:
// if 'is_all_selected' is deferred, the value must be blindly propagated,
// bc the equality comparison is erroneous, as the current state is unknown.
if (value.HasValue && value != is_all_selected) {
using (_refresh_is_all_selected.defer_execution()) {
foreach (var i in Items) {
if (i is PNLRowCollectionViewGroup g)
g.is_all_selected = value;
else if (i is PNLRowVM r)
r.is_selected = value.Value;
}
}
}
}
}
new int ProtectedItemCount {
get => base.ProtectedItemCount;
set {
if (base.ProtectedItemCount != value) {
base.ProtectedItemCount = value;
_is_all_selected.increment_version();
ProtectedItemCountChanged?.Invoke();
_refresh_is_all_selected.execute();
}
}
}
int _selected_item_count {
get => _selected_item_count_backing;
set {
Log.assert(value >= 0, $"{nameof(_selected_item_count)} cannot be negative.");
_selected_item_count_backing = value;
_is_all_selected.increment_version();
_refresh_is_all_selected.execute();
selected_item_count_changed?.Invoke();
}
}
public void on_settings(ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances, IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> triggers) {
_defaults_appearances = default_appearances;
_triggers = triggers;
_animator.on_settings(default_appearances, triggers);
_cell_map.set_appearances(_animator.cell_appearances);
foreach (var g in ProtectedItems.OfTypeSafe<PNLRowCollectionViewGroup>())
g.on_settings(default_appearances, triggers);
}
public void set_all_expanded(bool is_expanded) {
this.is_expanded = is_expanded;
foreach (var g in Items.OfTypeSafe<PNLRowCollectionViewGroup>())
g.set_all_expanded(is_expanded);
}
public bool Equals(PNLRowCollectionViewGroup other) =>
other != null &&
level == other.level &&
Equals(Name, other.Name);
void _select_all() {
foreach (var cvg in Items.OfType<PNLRowCollectionViewGroup>())
cvg._select_all();
}
IDisposable _detect_is_all_selected_changed() {
bool? was_all_selected = is_all_selected;
return Disposable.Create(() => {
if (is_all_selected != was_all_selected)
OnPropertyChanged(new PropertyChangedEventArgs(nameof(is_all_selected)));
});
}
void _add_item(int index, object item) {
Log.assert(index >= 0, "Index is less than 0.");
if (item is CollectionViewGroup group) {
var inner_group = new PNLRowCollectionViewGroup(group, _root, _defaults_appearances, _triggers);
_inner_groups.Add(group.Name, new InnerGroupSubscription(this, inner_group));
ProtectedItems.Insert(index, inner_group);
} else {
var row = (PNLRowVM)item;
ProtectedItems.Insert(index, item);
_rows.Add(row.key, _connect_row(row));
}
}
void _remove_item(int index, object item) {
if (item is PNLRowCollectionViewGroup inner_group) {
if (!_inner_groups.TryPopValue(inner_group.Name, out var ig_sub))
throw new InvalidOperationException($"Inner groups cache does not have an entry for the specified name '{inner_group.Name}' (entire tree structure may be compromised).");
ig_sub.Dispose();
Disposable.dispose(ref inner_group._dispose);
} else {
var row = (PNLRowVM)item;
if (!_rows.TryPopValue(row.key, out var row_sub))
throw new InvalidOperationException($"Inner rows cache does not have an entry for the specified irowstem key '{row.key}' (entire tree structure may be compromised).");
row_sub.Dispose();
}
ProtectedItems.RemoveAt(index);
}
void _from_items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
using (_refresh_is_all_selected.defer_execution()) {
switch (e.Action) {
case NotifyCollectionChangedAction.Add:
for (int i = 0; i < e.NewItems.Count; i++)
_add_item(e.NewStartingIndex + i, e.NewItems[i]);
break;
case NotifyCollectionChangedAction.Remove:
for (int i = 0; i < e.OldItems.Count; i++)
_remove_item(e.OldStartingIndex, ProtectedItems[e.OldStartingIndex]);
break;
case NotifyCollectionChangedAction.Replace:
for (int i = 0; i < e.OldItems.Count; i++)
_remove_item(e.OldStartingIndex, ProtectedItems[e.OldStartingIndex]);
for (int i = 0; i < e.NewItems.Count; i++)
_add_item(e.NewStartingIndex + i, e.NewItems[i]);
break;
case NotifyCollectionChangedAction.Reset:
while (ProtectedItems.Count > 0)
_remove_item(0, ProtectedItems[0]);
for (int i = 0; i < _from.Items.Count; i++)
_add_item(i, _from.Items[i]);
break;
}
_is_all_selected.increment_version();
_refresh_is_all_selected.execute();
}
}
IDisposable _connect_row(PNLRowVM row) {
using (_detect_is_all_selected_changed()) {
var last_data = PNLNormalizedData.zero;
on_updated(row.normalized_data);
row.updated += on_updated;
row.is_selected_changed += on_is_selected_changed;
if (row.is_selected) {
Log.assert(_selected_rows.Add(row), $"Selected Rows out of sync ({Name}): hash set already contained selected row that was just added to group ({row}).");
_selected_item_count++;
}
ProtectedItemCount++;
return Disposable.Create(() => {
row.updated -= on_updated;
row.is_selected_changed -= on_is_selected_changed;
using (_refresh_is_all_selected.defer_execution()) {
if (row.is_selected) {
Log.assert(_selected_rows.Remove(row), $"Selected Rows out of sync ({Name}): hash set did not contain selected row that was just removed from group ({row}).");
_selected_item_count--;
}
var newval = data - row.normalized_data;
data = newval;
ProtectedItemCount--;
}
});
void on_updated(PNLNormalizedData new_data) {
var less_last = data - last_data;
var plus_new = less_last + new_data;
data += new_data - last_data;
if (plus_new != data) {
Log.Ui.warn($"Detected PNL arithmetic rounding error in the group sum for '{string.Join(" ", GroupNamePath)}' (this bug is under investigation).");
}
last_data = new_data;
}
void on_is_selected_changed(bool is_selected) {
if (is_selected) {
Log.assert(_selected_rows.Add(row), $"Selected Rows out of sync ({Name}): hash set already contained row that was just selected ({row}).");
_selected_item_count++;
} else {
Log.assert(_selected_rows.Remove(row), $"Selected Rows out of sync ({Name}): hash set did not contain row that was just unselected ({row}).");
_selected_item_count--;
}
}
}
}
void _set<T>(ref T backing, T new_value, [CallerMemberName]string prop = "") =>
_set(ref backing, new_value, EqualityComparer<T>.Default, prop);
void _set<T>(ref T backing, T new_value, IEqualityComparer<T> comparer, [CallerMemberName]string prop = "") {
if (!comparer.Equals(backing, new_value)) {
backing = new_value;
OnPropertyChanged(new PropertyChangedEventArgs(prop));
}
}
void IRaiseNotifyPropertyChanged.raise_notify_property_changed(string prop) =>
OnPropertyChanged(new PropertyChangedEventArgs(prop));
class InnerGroupSubscription : IDisposable {
public readonly PNLRowCollectionViewGroup _inner_group;
readonly PNLRowCollectionViewGroup _parent;
PNLNormalizedData _last_data = PNLNormalizedData.zero;
int _last_count, _last_selected_item_count;
public InnerGroupSubscription(PNLRowCollectionViewGroup parent, PNLRowCollectionViewGroup inner_group) {
_parent = parent;
_inner_group = inner_group;
_inner_group.parent = parent;
// init item count
_on_item_count_changed();
// listen to item count changes
_inner_group.ProtectedItemCountChanged += _on_item_count_changed;
// init selected item count
_on_selected_item_count_changed();
// listen to selected item count changes
_inner_group.selected_item_count_changed += _on_selected_item_count_changed;
// init data
_on_normalized_data_changed();
// listen to data changes
_inner_group.data_changed += _on_normalized_data_changed;
_parent._selected_item_count += _inner_group._selected_item_count;
}
public void Dispose() {
_inner_group.ProtectedItemCountChanged -= _on_item_count_changed;
_inner_group.selected_item_count_changed -= _on_selected_item_count_changed;
_inner_group.data_changed -= _on_normalized_data_changed;
_inner_group.parent = null;
// make sure the state is valid (if a bad state goes by undetected here, nefarious/difficult-to-track bugs could arise)
if (_last_count != _inner_group.ProtectedItemCount) {
Log.Ui.warn("Cached inner count is not equal to the inner group's ItemCount (entire tree structure may be compromised).");
}
if (_last_data != _inner_group.data) {
Log.Ui.warn($"Cached normalized data is not equal to the inner group's normalized data (aggregated pnl data may be compromised).");
}
_parent.ProtectedItemCount -= _last_count;
_parent.data -= _last_data;
_parent._selected_item_count -= _last_selected_item_count;
}
void _on_item_count_changed() {
// add the delta
_parent.ProtectedItemCount += (_inner_group.ProtectedItemCount - _last_count);
// reset the local cache
_last_count = _inner_group.ProtectedItemCount;
}
void _on_selected_item_count_changed() {
_parent._selected_item_count += (_inner_group._selected_item_count - _last_selected_item_count);
_last_selected_item_count = _inner_group._selected_item_count;
}
void _on_normalized_data_changed() {
var new_data = _inner_group.data;
_parent.data += new_data - _last_data;
_last_data = new_data;
}
}
}
}
@@ -29,11 +29,14 @@
<Color x:Key="GroupRowForegroundColor">#FF000000</Color>
<!--Converters-->
<cv:GridLengthConverter x:Key="GridLengthPixelConverter" UnitType="Pixel"/>
<AlternationConverter x:Key="GroupRowBackgroundConverter">
<SolidColorBrush>#FFE9E9E9</SolidColorBrush>
<SolidColorBrush>#FFCACACA</SolidColorBrush>
<SolidColorBrush>#FFBBAA55</SolidColorBrush>
</AlternationConverter>
<cv:MultiValueConverterPipe x:Key="GroupRowBackgroundConverter">
<cv:PartialConverter/>
<cv:ColorToBrushConverter/>
</cv:MultiValueConverterPipe>
<cv:ValueMap x:Key="SelectionModeToSelectAllVisibilityConverter" Default="{x:Static Visibility.Collapsed}">
<cv:KeyValue Key="{x:Static SelectionMode.Extended}" Value="{x:Static Visibility.Visible}"/>
<cv:KeyValue Key="{x:Static SelectionMode.Multiple}" Value="{x:Static Visibility.Visible}"/>
</cv:ValueMap>
<!--Column Header Templates-->
<DataTemplate x:Key="AnchorableColumnHeaderTemplate">
@@ -317,6 +320,7 @@
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Setter Property="Margin" Value="0,0,0,1"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="Text" Value="{Binding text}"/>
@@ -410,6 +414,7 @@
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Setter Property="Margin" Value="0,0,0,1"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="FontSize" Value="{Binding font_size}"/>
@@ -498,9 +503,6 @@
<!--DIYGrid-->
<!--Totals Row (use Margin Hack to position it just below the column headers)-->
<StackPanel Grid.Row="1" Panel.ZIndex="1" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,30,0,0">
<StackPanel.Background>
<SolidColorBrush Color="{StaticResource HeaderRowBackgroundColor}"/>
</StackPanel.Background>
<Grid
c:DIYGrid.SyncWithColumnGeometrySource="{Binding ElementName=_grid}"
c:DIYGrid.SyncWithColumnGeometry="True"
@@ -518,7 +520,10 @@
</Binding.Converter>
</Binding>
</Grid.Visibility>
<Rectangle Grid.ColumnSpan="99" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Stroke="Gray" StrokeThickness="1"/>
<Grid.Background>
<SolidColorBrush Color="{Binding total_aggregate.background_color}"/>
</Grid.Background>
<Rectangle Grid.ColumnSpan="99" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Height="1" Fill="Gray"/>
<TextBlock Style="{StaticResource HeaderRowHeaderText}">
<TextBlock.Text>
<MultiBinding Converter="{x:Static pnl:PNLView.TotalHeaderRowDisplayTextConverter}">
@@ -537,6 +542,10 @@
<c:HyperlinkCell c:DIYGrid.ColumnKey="DayNetPos" DataContext="{Binding total_aggregate.day_net_pos}" Style="{StaticResource TotalRowHyperlinkCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="DayLongPos" DataContext="{Binding total_aggregate.day_long_pos}" Style="{StaticResource TotalRowHyperlinkCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="DayShortPos" DataContext="{Binding total_aggregate.day_short_pos}" Style="{StaticResource TotalRowHyperlinkCell}"/>
<!--Raw Day-->
<c:HyperlinkCell c:DIYGrid.ColumnKey="RawDayNetPos" DataContext="{Binding total_aggregate.raw_day_net_pos}" Style="{StaticResource TotalRowHyperlinkCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="RawDayLongPos" DataContext="{Binding total_aggregate.raw_day_long_pos}" Style="{StaticResource TotalRowHyperlinkCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="RawDayShortPos" DataContext="{Binding total_aggregate.raw_day_short_pos}" Style="{StaticResource TotalRowHyperlinkCell}"/>
<!--Working-->
<c:HyperlinkCell c:DIYGrid.ColumnKey="WorkingNetPos" DataContext="{Binding total_aggregate.working_net_pos}" Style="{StaticResource TotalRowHyperlinkCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="WorkingLongPos" DataContext="{Binding total_aggregate.working_long_pos}" Style="{StaticResource TotalRowHyperlinkCell}"/>
@@ -564,7 +573,10 @@
</Binding.Converter>
</Binding>
</Grid.Visibility>
<Rectangle Grid.ColumnSpan="99" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Stroke="Gray" StrokeThickness="1"/>
<Grid.Background>
<SolidColorBrush Color="{Binding selected_aggregate.background_color}"/>
</Grid.Background>
<Rectangle Grid.ColumnSpan="99" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Height="1" Fill="Gray"/>
<TextBlock Text="{Binding selected_rows.Count, StringFormat='Selected ({0})'}" Style="{StaticResource HeaderRowHeaderText}"/>
<!--SOD-->
<c:TextCell c:DIYGrid.ColumnKey="SODPNL" DataContext="{Binding selected_aggregate.sod_pnl}" Style="{StaticResource HeaderRowTextCell}"/>
@@ -576,6 +588,10 @@
<c:HyperlinkCell c:DIYGrid.ColumnKey="DayNetPos" DataContext="{Binding selected_aggregate.day_net_pos}" Style="{StaticResource SelectedTotalRowHyperlinkCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="DayLongPos" DataContext="{Binding selected_aggregate.day_long_pos}" Style="{StaticResource SelectedTotalRowHyperlinkCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="DayShortPos" DataContext="{Binding selected_aggregate.day_short_pos}" Style="{StaticResource SelectedTotalRowHyperlinkCell}"/>
<!--Raw Day-->
<c:HyperlinkCell c:DIYGrid.ColumnKey="RawDayNetPos" DataContext="{Binding selected_aggregate.raw_day_net_pos}" Style="{StaticResource SelectedTotalRowHyperlinkCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="RawDayLongPos" DataContext="{Binding selected_aggregate.raw_day_long_pos}" Style="{StaticResource SelectedTotalRowHyperlinkCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="RawDayShortPos" DataContext="{Binding selected_aggregate.raw_day_short_pos}" Style="{StaticResource SelectedTotalRowHyperlinkCell}"/>
<!--Working-->
<c:HyperlinkCell c:DIYGrid.ColumnKey="WorkingNetPos" DataContext="{Binding selected_aggregate.working_net_pos}" Style="{StaticResource SelectedTotalRowHyperlinkCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="WorkingLongPos" DataContext="{Binding selected_aggregate.working_long_pos}" Style="{StaticResource SelectedTotalRowHyperlinkCell}"/>
@@ -599,6 +615,18 @@
SupportGroupItems="True"
Loaded="_grid_loaded"
>
<c:DIYGrid.SelectionMode>
<Binding Path="header_row_display">
<Binding.Converter>
<cv:ValueConverterPipe>
<cv:EnumHasFlagsConverter>
<pnl:PNLHeaderRowDisplay>SelectedTotal</pnl:PNLHeaderRowDisplay>
</cv:EnumHasFlagsConverter>
<cv:BooleanToObjectConverter ValueForFalse="{x:Static SelectionMode.Single}" ValueForTrue="{x:Static SelectionMode.Extended}"/>
</cv:ValueConverterPipe>
</Binding.Converter>
</Binding>
</c:DIYGrid.SelectionMode>
<c:DIYGrid.CachedColumnParams>
<x:Array Type="{x:Type sys:String}">
<sys:String>font_weight</sys:String>
@@ -624,8 +652,9 @@
c:DataGridFilterProperties.Filter="{Binding DataContext.account_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DiscreteValues}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.account_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.account_filter.open, Source={u:DirectReference _grid}}"
key="Account"
data_member_path="account_name"
data_member_path="account_name_cell.cell_data"
sort_member_path="account_name"
HeaderTemplate="{StaticResource AnchorableColumnHeaderTemplate}"
/>
@@ -634,8 +663,9 @@
c:DataGridFilterProperties.Filter="{Binding DataContext.exchange_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.EnumValues}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.exchange_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.exchange_filter.open, Source={u:DirectReference _grid}}"
key="Exchange"
data_member_path="exchange"
data_member_path="exchange_cell.cell_data"
sort_member_path="exchange"
HeaderTemplate="{StaticResource AnchorableColumnHeaderTemplate}"
/>
@@ -644,37 +674,61 @@
c:DataGridFilterProperties.Filter="{Binding DataContext.symbol_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DiscreteValues}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.symbol_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.symbol_filter.open, Source={u:DirectReference _grid}}"
key="Symbol"
data_member_path="symbol"
data_member_path="symbol_cell.cell_data"
sort_member_path="symbol"
HeaderTemplate="{StaticResource AnchorableColumnHeaderTemplate}"
/>
<!--Security-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.security_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.Regex}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.security_filter.active, Source={u:DirectReference _grid}}"
key="Security"
sort_member_path="security"
data_member_path="security"
HeaderTemplate="{StaticResource AnchorableColumnHeaderTemplate}"
/>
<!--Security Type-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.security_type_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DiscreteValues}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.security_type_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.security_type_filter.open, Source={u:DirectReference _grid}}"
key="SecurityType"
data_member_path="security_type"
data_member_path="security_type_cell.cell_data"
sort_member_path="security_type"
HeaderTemplate="{StaticResource AnchorableColumnHeaderTemplate}"
/>
<!--Security-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.security_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.Regex}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.security_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.security_filter.open, Source={u:DirectReference _grid}}"
key="Security"
sort_member_path="security_cell.cell_data"
data_member_path="security"
HeaderTemplate="{StaticResource AnchorableColumnHeaderTemplate}"
/>
<!--Security Key-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.security_key_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.Regex}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.security_key_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.security_key_filter.open, Source={u:DirectReference _grid}}"
key="SecurityKey"
sort_member_path="security_key.cell_data"
data_member_path="security_key"
/>
<!--CPP-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.cpp_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.cpp_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.cpp_filter.open, Source={u:DirectReference _grid}}"
key="CPP"
data_member_path="cpp"
sort_member_path="cpp"
/>
<!--SOD-->
<!--SOD PNL-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.sod_pnl_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.sod_pnl_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.sod_pnl_filter.open, Source={u:DirectReference _grid}}"
key="SODPNL"
data_member_path="sod_pnl"
Header="SOD PnL"
@@ -684,23 +738,17 @@
c:DataGridFilterProperties.Filter="{Binding DataContext.sod_net_pos_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.sod_net_pos_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.sod_net_pos_filter.open, Source={u:DirectReference _grid}}"
key="SODNetPos"
data_member_path="sod_net_pos"
sort_member_path="sod_net_pos"
/>
<!--SOD Net Avg Price Delta-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.sod_net_avg_price_delta_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.sod_net_avg_price_delta_filter.active, Source={u:DirectReference _grid}}"
key="SODNetAvgPriceDelta"
data_member_path="sod_net_avg_price_delta"
/>
<!--SOD Long Pos-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.sod_long_pos_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.sod_long_pos_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.sod_long_pos_filter.open, Source={u:DirectReference _grid}}"
key="SODLongPos"
data_member_path="sod_long_pos"
/>
@@ -709,22 +757,16 @@
c:DataGridFilterProperties.Filter="{Binding DataContext.sod_long_avg_price_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.sod_long_avg_price_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.sod_long_avg_price_filter.open, Source={u:DirectReference _grid}}"
key="SODLongAvgPrice"
data_member_path="sod_long_avg_price"
/>
<!--SOD Long Avg Price Delta-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.sod_long_avg_price_delta_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.sod_long_avg_price_delta_filter.active, Source={u:DirectReference _grid}}"
key="SODLongAvgPriceDelta"
data_member_path="sod_long_avg_price_delta"
/>
<!--SOD Short Pos-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.sod_short_pos_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.sod_short_pos_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.sod_short_pos_filter.open, Source={u:DirectReference _grid}}"
key="SODShortPos"
data_member_path="sod_short_pos"
/>
@@ -733,23 +775,17 @@
c:DataGridFilterProperties.Filter="{Binding DataContext.sod_short_avg_price_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.sod_short_avg_price_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.sod_short_avg_price_filter.open, Source={u:DirectReference _grid}}"
key="SODShortAvgPrice"
data_member_path="sod_short_avg_price"
/>
<!--SOD Short Avg Price Delta-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.sod_short_avg_price_delta_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.sod_short_avg_price_delta_filter.active, Source={u:DirectReference _grid}}"
key="SODShortAvgPriceDelta"
data_member_path="sod_short_avg_price_delta"
/>
<!--Day-->
<!--Day PNL-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.day_pnl_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.day_pnl_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.day_pnl_filter.open, Source={u:DirectReference _grid}}"
key="DayPNL"
data_member_path="day_pnl"
sort_member_path="day_pnl"
@@ -759,24 +795,17 @@
c:DataGridFilterProperties.Filter="{Binding DataContext.day_net_pos_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.day_net_pos_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.day_net_pos_filter.open, Source={u:DirectReference _grid}}"
key="DayNetPos"
data_member_path="day_net_pos"
sort_member_path="day_net_pos"
/>
<!--Day Net Avg Price Delta-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.day_net_avg_price_delta_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.day_net_avg_price_delta_filter.active, Source={u:DirectReference _grid}}"
key="DayNetAvgPriceDelta"
data_member_path="day_net_avg_price_delta"
sort_member_path="day_net_avg_price_delta"
/>
<!--Day Long Pos-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.day_long_pos_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.day_long_pos_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.day_long_pos_filter.open, Source={u:DirectReference _grid}}"
key="DayLongPos"
data_member_path="day_long_pos"
sort_member_path="day_long_pos"
@@ -786,24 +815,47 @@
c:DataGridFilterProperties.Filter="{Binding DataContext.day_long_avg_price_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.day_long_avg_price_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.day_long_avg_price_filter.open, Source={u:DirectReference _grid}}"
key="DayLongAvgPrice"
data_member_path="day_long_avg_price"
sort_member_path="day_long_avg_price"
/>
<!--Day Long Avg Price Delta-->
<!--Raw Day Net Pos-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.day_long_avg_price_delta_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.Filter="{Binding DataContext.raw_day_net_pos_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.day_long_avg_price_delta_filter.active, Source={u:DirectReference _grid}}"
key="DayLongAvgPriceDelta"
data_member_path="day_long_avg_price_delta"
sort_member_path="day_long_avg_price_delta"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.raw_day_net_pos_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.raw_day_net_pos_filter.open, Source={u:DirectReference _grid}}"
key="RawDayNetPos"
data_member_path="raw_day_net_pos"
sort_member_path="raw_day_net_pos"
/>
<!--Raw Day Long Pos-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.raw_day_long_pos_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.raw_day_long_pos_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.raw_day_long_pos_filter.open, Source={u:DirectReference _grid}}"
key="RawDayLongPos"
data_member_path="raw_day_long_pos"
sort_member_path="raw_day_long_pos"
/>
<!--Raw Day Short Pos-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.raw_day_short_pos_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.raw_day_short_pos_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.raw_day_short_pos_filter.open, Source={u:DirectReference _grid}}"
key="RawDayShortPos"
data_member_path="raw_day_short_pos"
sort_member_path="raw_day_short_pos"
/>
<!--Day PNL Bid Price-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.bid_price_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.bid_price_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.bid_price_filter.open, Source={u:DirectReference _grid}}"
key="BidPrice"
data_member_path="bid_price"
sort_member_path="bid_price"
@@ -813,6 +865,7 @@
c:DataGridFilterProperties.Filter="{Binding DataContext.ask_price_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.ask_price_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.ask_price_filter.open, Source={u:DirectReference _grid}}"
key="AskPrice"
data_member_path="day_pnl_ask_price"
sort_member_path="day_pnl_ask_price"
@@ -822,6 +875,7 @@
c:DataGridFilterProperties.Filter="{Binding DataContext.mid_price_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.mid_price_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.mid_price_filter.open, Source={u:DirectReference _grid}}"
key="MidPrice"
data_member_path="mid_price"
sort_member_path="mid_price"
@@ -831,6 +885,7 @@
c:DataGridFilterProperties.Filter="{Binding DataContext.last_price_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.last_price_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.last_price_filter.open, Source={u:DirectReference _grid}}"
key="LastPrice"
data_member_path="last_price"
sort_member_path="last_price"
@@ -840,6 +895,7 @@
c:DataGridFilterProperties.Filter="{Binding DataContext.settlement_price_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.settlement_price_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.settlement_price_filter.open, Source={u:DirectReference _grid}}"
key="SettlementPrice"
data_member_path="settlement_price"
sort_member_path="settlement_price"
@@ -849,24 +905,17 @@
c:DataGridFilterProperties.Filter="{Binding DataContext.pnl_price_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.pnl_price_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.pnl_price_filter.open, Source={u:DirectReference _grid}}"
key="PNLPrice"
data_member_path="pnl_price"
sort_member_path="pnl_price"
/>
<!--Day Short Avg Price Delta-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.day_short_avg_price_delta_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.day_short_avg_price_delta_filter.active, Source={u:DirectReference _grid}}"
key="DayShortAvgPriceDelta"
data_member_path="day_short_avg_price_delta"
sort_member_path="day_short_avg_price_delta"
/>
<!--Day Avg Short Price-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.day_short_avg_price_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.day_short_avg_price_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.day_short_avg_price_filter.open, Source={u:DirectReference _grid}}"
key="DayShortAvgPrice"
data_member_path="day_short_avg_price"
sort_member_path="day_short_avg_price"
@@ -876,15 +925,27 @@
c:DataGridFilterProperties.Filter="{Binding DataContext.day_short_pos_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.day_short_pos_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.day_short_pos_filter.open, Source={u:DirectReference _grid}}"
key="DayShortPos"
data_member_path="day_short_pos"
sort_member_path="day_short_pos"
/>
<!--Has Day Trades-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.has_day_trades_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.YesNo}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.has_day_trades_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.has_day_trades_filter.open, Source={u:DirectReference _grid}}"
key="HasDayTrades"
data_member_path="has_day_trades"
sort_member_path="has_day_trades"
/>
<!--Working Long-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.working_long_pos_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.IntMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.working_long_pos_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.working_long_pos_filter.open, Source={u:DirectReference _grid}}"
key="WorkingLongPos"
data_member_path="working_long_pos"
sort_member_path="working_long_pos"
@@ -894,25 +955,17 @@
c:DataGridFilterProperties.Filter="{Binding DataContext.working_long_avg_price_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.working_long_avg_price_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.working_long_avg_price_filter.open, Source={u:DirectReference _grid}}"
key="WorkingLongAvgPrice"
data_member_path="working_long_avg_price"
sort_member_path="working_long_avg_price"
/>
<!--Working Long Avg Price Delta-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.working_long_avg_price_delta_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.working_long_avg_price_delta_filter.active, Source={u:DirectReference _grid}}"
key="WorkingLongAvgPriceDelta"
data_member_path="working_long_avg_price_delta"
sort_member_path="working_long_avg_price"
Header="D/Price"
/>
<!--Working Short-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.working_short_pos_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.IntMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.working_short_pos_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.working_short_pos_filter.open, Source={u:DirectReference _grid}}"
key="WorkingShortPos"
data_member_path="working_short_pos"
sort_member_path="working_short_pos"
@@ -922,52 +975,26 @@
c:DataGridFilterProperties.Filter="{Binding DataContext.working_short_avg_price_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.working_short_avg_price_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.working_short_avg_price_filter.open, Source={u:DirectReference _grid}}"
key="WorkingShortAvgPrice"
data_member_path="working_short_avg_price"
/>
<!--Working Short Avg Price Delta-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.working_short_avg_price_delta_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.working_short_avg_price_delta_filter.active, Source={u:DirectReference _grid}}"
key="WorkingShortAvgPriceDelta"
data_member_path="working_short_avg_price_delta"
sort_member_path="working_short_avg_price"
Header="D/Price"
/>
<!--Working Net-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.working_net_pos_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.IntMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.working_net_pos_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.working_net_pos_filter.open, Source={u:DirectReference _grid}}"
key="WorkingNetPos"
data_member_path="working_net_pos"
sort_member_path="working_net_pos"
/>
<!--Working Net Avg Price Delta-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.working_net_avg_price_delta_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.working_net_avg_price_delta_filter.active, Source={u:DirectReference _grid}}"
key="WorkingNetAvgPriceDelta"
data_member_path="working_net_avg_price_delta"
sort_member_path="working_net_avg_price"
Header="D/Price"
/>
<!--Has Day Trades-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.has_day_trades_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.YesNo}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.has_day_trades_filter.active, Source={u:DirectReference _grid}}"
key="HasDayTrades"
data_member_path="has_day_trades"
sort_member_path="has_day_trades"
/>
<!--Total PNL-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.total_pnl_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.total_pnl_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.total_pnl_filter.open, Source={u:DirectReference _grid}}"
key="TotalPNL"
data_member_path="total_pnl"
/>
@@ -976,24 +1003,17 @@
c:DataGridFilterProperties.Filter="{Binding DataContext.total_net_pos_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.total_net_pos_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.total_net_pos_filter.open, Source={u:DirectReference _grid}}"
key="TotalNetPos"
data_member_path="total_net_pos"
sort_member_path="total_net_pos"
/>
<!--Total Net Avg Price Delta-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.total_net_avg_price_delta_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.total_net_avg_price_delta_filter.active, Source={u:DirectReference _grid}}"
key="TotalNetAvgPriceDelta"
data_member_path="total_net_avg_price_delta"
sort_member_path="total_net_avg_price_delta"
/>
<!--Total Long Pos-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.total_long_pos_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.total_long_pos_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.total_long_pos_filter.open, Source={u:DirectReference _grid}}"
key="TotalLongPos"
data_member_path="total_long_pos"
sort_member_path="total_long_pos"
@@ -1003,24 +1023,17 @@
c:DataGridFilterProperties.Filter="{Binding DataContext.total_long_avg_price_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.total_long_avg_price_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.total_long_avg_price_filter.open, Source={u:DirectReference _grid}}"
key="TotalLongAvgPrice"
data_member_path="total_long_avg_price"
sort_member_path="total_long_avg_price"
/>
<!--Total Long Avg Price Delta-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.total_long_avg_price_delta_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.total_long_avg_price_delta_filter.active, Source={u:DirectReference _grid}}"
key="TotalLongAvgPriceDelta"
data_member_path="total_long_avg_price_delta"
sort_member_path="total_long_avg_price_delta"
/>
<!--Total Short Pos-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.total_short_pos_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.total_short_pos_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.total_short_pos_filter.open, Source={u:DirectReference _grid}}"
key="TotalShortPos"
data_member_path="total_short_pos"
sort_member_path="total_short_pos"
@@ -1030,57 +1043,46 @@
c:DataGridFilterProperties.Filter="{Binding DataContext.total_short_avg_price_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.total_short_avg_price_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.total_short_avg_price_filter.open, Source={u:DirectReference _grid}}"
key="TotalShortAvgPrice"
data_member_path="total_short_avg_price"
sort_member_path="total_short_avg_price"
/>
<!--Total Short Avg Price Delta-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.total_short_avg_price_delta_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.total_short_avg_price_delta_filter.active, Source={u:DirectReference _grid}}"
key="TotalShortAvgPriceDelta"
data_member_path="total_short_avg_price_delta"
sort_member_path="total_short_avg_price_delta"
/>
<!--CPP-->
<pnl:PNLColumn
key="CPP"
data_member_path="cpp"
sort_member_path="cpp"
/>
</c:DIYGridColumnCollection>
</c:DIYGrid.Columns>
<!--Row Template-->
<c:DIYGrid.RowTemplate>
<DataTemplate>
<Grid c:DIYGrid.SyncWithColumnGeometry="True" usp:GridAnimation.RowAnimation="{Binding animations}">
<Grid.Background>
<SolidColorBrush Color="{Binding DataContext.grid_background_color, RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"/>
</Grid.Background>
<c:TextCell c:DIYGrid.ColumnKey="Account" DataContext="{Binding account_name_cell}" Style="{StaticResource RowTextCell}" Visibility="{Binding AnchorIndex.anchors.Account, RelativeSource={RelativeSource AncestorType=pnl:PNLView}, Converter={StaticResource FalseVisibilityConverter}}"/>
<c:TextCell c:DIYGrid.ColumnKey="Exchange" DataContext="{Binding exchange_cell}" Style="{StaticResource RowTextCell}" Visibility="{Binding AnchorIndex.anchors.Exchange, RelativeSource={RelativeSource AncestorType=pnl:PNLView}, Converter={StaticResource FalseVisibilityConverter}}"/>
<c:TextCell c:DIYGrid.ColumnKey="Symbol" DataContext="{Binding symbol_cell}" Style="{StaticResource RowTextCell}" Visibility="{Binding AnchorIndex.anchors.Symbol, RelativeSource={RelativeSource AncestorType=pnl:PNLView}, Converter={StaticResource FalseVisibilityConverter}}"/>
<c:TextCell c:DIYGrid.ColumnKey="Security" DataContext="{Binding security_cell}" Style="{StaticResource RowTextCell}" Visibility="{Binding AnchorIndex.anchors.Security, RelativeSource={RelativeSource AncestorType=pnl:PNLView}, Converter={StaticResource FalseVisibilityConverter}}"/>
<c:TextCell c:DIYGrid.ColumnKey="SecurityType" DataContext="{Binding security_type_cell}" Style="{StaticResource RowTextCell}" Visibility="{Binding AnchorIndex.anchors.SecurityType, RelativeSource={RelativeSource AncestorType=pnl:PNLView}, Converter={StaticResource FalseVisibilityConverter}}"/>
<c:TextCell c:DIYGrid.ColumnKey="Security" DataContext="{Binding security_cell}" Style="{StaticResource RowTextCell}" Visibility="{Binding AnchorIndex.anchors.Security, RelativeSource={RelativeSource AncestorType=pnl:PNLView}, Converter={StaticResource FalseVisibilityConverter}}"/>
<c:TextCell c:DIYGrid.ColumnKey="SecurityKey" DataContext="{Binding security_key}" Style="{StaticResource RowTextCell}"/>
<c:TextCell c:DIYGrid.ColumnKey="CPP" DataContext="{Binding cpp}" Style="{StaticResource RowTextCell}"/>
<!--SOD-->
<c:TextCell c:DIYGrid.ColumnKey="SODPNL" DataContext="{Binding sod_pnl}" Style="{StaticResource RowTextCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="SODNetPos" DataContext="{Binding sod_net_pos}" Style="{StaticResource RowHyperlinkCell}"/>
<c:TextCell c:DIYGrid.ColumnKey="SODNetAvgPriceDelta" DataContext="{Binding sod_net_avg_price_delta}" Style="{StaticResource RowTextCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="SODLongPos" DataContext="{Binding sod_long_pos}" Style="{StaticResource RowHyperlinkCell}"/>
<c:TextCell c:DIYGrid.ColumnKey="SODLongAvgPrice" DataContext="{Binding sod_long_avg_price}" Style="{StaticResource RowTextCell}"/>
<c:TextCell c:DIYGrid.ColumnKey="SODLongAvgPriceDelta" DataContext="{Binding sod_long_avg_price_delta}" Style="{StaticResource RowTextCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="SODShortPos" DataContext="{Binding sod_short_pos}" Style="{StaticResource RowHyperlinkCell}"/>
<c:TextCell c:DIYGrid.ColumnKey="SODShortAvgPrice" DataContext="{Binding sod_short_avg_price}" Style="{StaticResource RowTextCell}"/>
<c:TextCell c:DIYGrid.ColumnKey="SODShortAvgPriceDelta" DataContext="{Binding sod_short_avg_price_delta}" Style="{StaticResource RowTextCell}"/>
<!--Day-->
<c:TextCell c:DIYGrid.ColumnKey="DayPNL" DataContext="{Binding day_pnl}" Style="{StaticResource RowTextCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="DayNetPos" DataContext="{Binding day_net_pos}" Style="{StaticResource RowHyperlinkCell}"/>
<c:TextCell c:DIYGrid.ColumnKey="DayNetAvgPriceDelta" DataContext="{Binding day_net_avg_price_delta}" Style="{StaticResource RowTextCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="DayLongPos" DataContext="{Binding day_long_pos}" Style="{StaticResource RowHyperlinkCell}"/>
<c:TextCell c:DIYGrid.ColumnKey="DayLongAvgPrice" DataContext="{Binding day_long_avg_price}" Style="{StaticResource RowTextCell}"/>
<c:TextCell c:DIYGrid.ColumnKey="DayLongAvgPriceDelta" DataContext="{Binding day_long_avg_price_delta}" Style="{StaticResource RowTextCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="DayShortPos" DataContext="{Binding day_short_pos}" Style="{StaticResource RowHyperlinkCell}"/>
<c:TextCell c:DIYGrid.ColumnKey="DayShortAvgPrice" DataContext="{Binding day_short_avg_price}" Style="{StaticResource RowTextCell}"/>
<c:TextCell c:DIYGrid.ColumnKey="DayShortAvgPriceDelta" DataContext="{Binding day_short_avg_price_delta}" Style="{StaticResource RowTextCell}"/>
<c:TextCell c:DIYGrid.ColumnKey="HasDayTrades" DataContext="{Binding has_day_trades}" Style="{StaticResource RowTextCell}"/>
<!--Raw Day-->
<c:HyperlinkCell c:DIYGrid.ColumnKey="RawDayNetPos" DataContext="{Binding raw_day_net_pos}" Style="{StaticResource RowHyperlinkCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="RawDayLongPos" DataContext="{Binding raw_day_long_pos}" Style="{StaticResource RowHyperlinkCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="RawDayShortPos" DataContext="{Binding raw_day_short_pos}" Style="{StaticResource RowHyperlinkCell}"/>
<!--Market (Day)-->
<c:TextCell c:DIYGrid.ColumnKey="BidPrice" DataContext="{Binding bid_price}" Style="{StaticResource RowTextCell}"/>
<c:TextCell c:DIYGrid.ColumnKey="MidPrice" DataContext="{Binding mid_price}" Style="{StaticResource RowTextCell}"/>
@@ -1090,24 +1092,17 @@
<c:TextCell c:DIYGrid.ColumnKey="PNLPrice" DataContext="{Binding pnl_price}" Style="{StaticResource RowTextCell}"/>
<!--Working-->
<c:HyperlinkCell c:DIYGrid.ColumnKey="WorkingNetPos" DataContext="{Binding working_net_pos}" Style="{StaticResource RowHyperlinkCell}"/>
<c:TextCell c:DIYGrid.ColumnKey="WorkingNetAvgPriceDelta" DataContext="{Binding working_net_avg_price_delta}" Style="{StaticResource RowTextCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="WorkingLongPos" DataContext="{Binding working_long_pos}" Style="{StaticResource RowHyperlinkCell}"/>
<c:TextCell c:DIYGrid.ColumnKey="WorkingLongAvgPrice" DataContext="{Binding working_long_avg_price}" Style="{StaticResource RowTextCell}"/>
<c:TextCell c:DIYGrid.ColumnKey="WorkingLongAvgPriceDelta" DataContext="{Binding working_long_avg_price_delta}" Style="{StaticResource RowTextCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="WorkingShortPos" DataContext="{Binding working_short_pos}" Style="{StaticResource RowHyperlinkCell}"/>
<c:TextCell c:DIYGrid.ColumnKey="WorkingShortAvgPrice" DataContext="{Binding working_short_avg_price}" Style="{StaticResource RowTextCell}"/>
<c:TextCell c:DIYGrid.ColumnKey="WorkingShortAvgPriceDelta" DataContext="{Binding working_short_avg_price_delta}" Style="{StaticResource RowTextCell}"/>
<TextBlock c:DIYGrid.ColumnKey="CPP" Text="{Binding cpp}"/>
<!--Total-->
<c:TextCell c:DIYGrid.ColumnKey="TotalPNL" DataContext="{Binding total_pnl}" Style="{StaticResource RowTextCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="TotalNetPos" DataContext="{Binding total_net_pos}" Style="{StaticResource RowHyperlinkCell}"/>
<c:TextCell c:DIYGrid.ColumnKey="TotalNetAvgPriceDelta" DataContext="{Binding total_net_avg_price_delta}" Style="{StaticResource RowTextCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="TotalLongPos" DataContext="{Binding total_long_pos}" Style="{StaticResource RowHyperlinkCell}"/>
<c:TextCell c:DIYGrid.ColumnKey="TotalLongAvgPrice" DataContext="{Binding total_long_avg_price}" Style="{StaticResource RowTextCell}"/>
<c:TextCell c:DIYGrid.ColumnKey="TotalLongAvgPriceDelta" DataContext="{Binding total_long_avg_price_delta}" Style="{StaticResource RowTextCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="TotalShortPos" DataContext="{Binding total_short_pos}" Style="{StaticResource RowHyperlinkCell}"/>
<c:TextCell c:DIYGrid.ColumnKey="TotalShortAvgPrice" DataContext="{Binding total_short_avg_price}" Style="{StaticResource RowTextCell}"/>
<c:TextCell c:DIYGrid.ColumnKey="TotalShortAvgPriceDelta" DataContext="{Binding total_short_avg_price_delta}" Style="{StaticResource RowTextCell}"/>
</Grid>
</DataTemplate>
</c:DIYGrid.RowTemplate>
@@ -1120,7 +1115,14 @@
<Setter Property="VirtualizingPanel.VirtualizationMode" Value="Recycling"/>
<Setter Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="True"/>
<Setter Property="ScrollViewer.CanContentScroll" Value="True"/>
<Setter Property="Background" Value="{Binding Path='(pnl:PNLView.GroupLevel)', RelativeSource={RelativeSource Self}, Converter={StaticResource GroupRowBackgroundConverter}}"/>
<Setter Property="Background">
<Setter.Value>
<MultiBinding Converter="{StaticResource GroupRowBackgroundConverter}">
<Binding Path="DataContext.group_background_converter" RelativeSource="{RelativeSource AncestorType=pnl:PNLView}"/>
<Binding Path="(pnl:PNLView.GroupLevel)" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="HorizontalAlignment" Value="Left"/>
<EventSetter Event="MouseDown" Handler="_on_group_item_mouse_down"/>
<Setter Property="Template">
@@ -1155,7 +1157,12 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<CheckBox VerticalAlignment="Center" Margin="2,0,4,0" IsChecked="{Binding is_all_selected}"/>
<CheckBox
VerticalAlignment="Center"
Margin="2,0,4,0"
IsChecked="{Binding is_all_selected}"
Visibility="{Binding SelectionMode, RelativeSource={RelativeSource AncestorType=c:DIYGrid}, Converter={StaticResource SelectionModeToSelectAllVisibilityConverter}}"
/>
<c:ExpanderToggleButton
x:Name="expander"
Grid.Column="1"
@@ -1178,6 +1185,10 @@
<c:HyperlinkCell c:DIYGrid.ColumnKey="DayNetPos" DataContext="{Binding day_net_pos}" Style="{StaticResource GroupRowHyperlinkCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="DayLongPos" DataContext="{Binding day_long_pos}" Style="{StaticResource GroupRowHyperlinkCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="DayShortPos" DataContext="{Binding day_short_pos}" Style="{StaticResource GroupRowHyperlinkCell}"/>
<!--Raw Day-->
<c:HyperlinkCell c:DIYGrid.ColumnKey="RawDayNetPos" DataContext="{Binding raw_day_net_pos}" Style="{StaticResource GroupRowHyperlinkCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="RawDayLongPos" DataContext="{Binding raw_day_long_pos}" Style="{StaticResource GroupRowHyperlinkCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="RawDayShortPos" DataContext="{Binding raw_day_short_pos}" Style="{StaticResource GroupRowHyperlinkCell}"/>
<!--Working-->
<c:HyperlinkCell c:DIYGrid.ColumnKey="WorkingNetPos" DataContext="{Binding working_net_pos}" Style="{StaticResource GroupRowHyperlinkCell}"/>
<c:HyperlinkCell c:DIYGrid.ColumnKey="WorkingLongPos" DataContext="{Binding working_long_pos}" Style="{StaticResource GroupRowHyperlinkCell}"/>
@@ -1,130 +1,123 @@
using bts.collections;
using mbts;
using mbts.collections;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.controls.adorners;
using mbts.ui.converters;
using mbts.ui.extensions;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
namespace bts.shell.pnl {
enum PNLHeaderRowDisplay {
None,
Total,
SelectedTotal
}
partial class PNLView : UserControl {
public enum Columns {
Account = 1,
Exchange,
Symbol,
Security,
SecurityType,
// SOD
SODPNL,
SODLongPos,
SODLongAvgPrice,
SODLongAvgPriceDelta,
SODShortPos,
SODShortAvgPrice,
SODShortAvgPriceDelta,
SODNetPos,
SODNetAvgPriceDelta,
// Day
DayPNL,
DayLongPos,
DayLongAvgPrice,
DayLongAvgPriceDelta,
DayShortAvgPriceDelta,
DayShortAvgPrice,
DayShortPos,
DayCalcType,
DayNetPos,
DayNetAvgPriceDelta,
// Working
WorkingLongPos,
WorkingLongAvgPrice,
WorkingLongAvgPriceDelta,
WorkingShortPos,
WorkingShortAvgPrice,
WorkingShortAvgPriceDelta,
WorkingNetPos,
WorkingNetAvgPriceDelta,
// Total
TotalPNL,
TotalLongPos,
TotalLongAvgPrice,
TotalLongAvgPriceDelta,
TotalShortPos,
TotalShortAvgPrice,
TotalShortAvgPriceDelta,
TotalNetPos,
TotalNetAvgPriceDelta,
// Market
BidPrice,
MidPrice,
AskPrice,
LastPrice,
SettlementPrice,
PNLPrice,
// Misc
HasDayTrades,
CPP
}
public static readonly IValueConverter
IncrementalGroupLevelConverter = ValueConverter.create<int, int>(level => level + 1),
GroupLevelMarginConverter = ValueConverter.create<int, Thickness>(level => new Thickness(24 * level, 0, 0, 0)),
ExpandedVisualStateConverter = ValueConverter.create<bool, string>(is_expanded => is_expanded ? "Expanded" : "Collapsed");
public static readonly IMultiValueConverter
TotalHeaderRowDisplayTextConverter = ValueConverter.create<int, int, string>((filtered, total) => $"Total ({(filtered == total ? total.ToString() : $"{filtered}/{total}")})"),
CellParamsConverter = ValueConverter.create<object, Columns, PNLCell>((item, col) => new PNLCell(item, col));
public static readonly RoutedCommand CellClickCommand = new RoutedCommand();
const double _auxiliary_popup_height = 300;
static readonly string _node_path_separator = $" {SpecialCharacters.Dot} ";
IDisposable _anchors_hook, _animations_hook;
int _anchor_version;
public PNLView() {
SetCurrentValue(AnchorIndexProperty, new AnchorIndex());
InitializeComponent();
SetBinding(AnchorsProperty, new Binding(nameof(PNLVM.anchors)));
_grid.ConnectOnLoaded(() => {
if (_grid.try_find_descendent_of_type(out DIYGridRowsPresenter presenter)) {
presenter.GroupItemSelectionPivot += on_group_item_selection_pivot;
return Disposable.Create(() => presenter.GroupItemSelectionPivot -= on_group_item_selection_pivot);
}
return null;
});
void on_group_item_selection_pivot(object sender, GroupItemSelectionPivotEventArgs e) =>
e.Handled = e.group_item is PNLRowCollectionViewGroup pnl_group && pnl_group.is_expanded;
}
public new PNLVM DataContext {
get => base.DataContext as PNLVM;
set => base.DataContext = value;
}
using bts.collections;
using mbts;
using mbts.collections;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.controls.adorners;
using mbts.ui.converters;
using mbts.ui.extensions;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Dynamic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
namespace bts.shell.pnl {
enum PNLHeaderRowDisplay {
None,
Total,
SelectedTotal
}
partial class PNLView : UserControl {
public enum Columns {
Account = 1,
// Security
Exchange,
Symbol,
SecurityType,
Security,
SecurityKey,
CPP,
// SOD
SODPNL,
SODLongPos,
SODLongAvgPrice,
SODShortPos,
SODShortAvgPrice,
SODNetPos,
// Day
DayPNL,
DayLongPos,
DayLongAvgPrice,
DayShortAvgPrice,
DayShortPos,
DayCalcType,
DayNetPos,
HasDayTrades,
// Raw Day
RawDayLongPos,
RawDayShortPos,
RawDayNetPos,
// Working
WorkingLongPos,
WorkingLongAvgPrice,
WorkingShortPos,
WorkingShortAvgPrice,
WorkingNetPos,
// Total
TotalPNL,
TotalLongPos,
TotalLongAvgPrice,
TotalShortPos,
TotalShortAvgPrice,
TotalNetPos,
// Market
BidPrice,
MidPrice,
AskPrice,
LastPrice,
SettlementPrice,
PNLPrice
}
public static readonly IValueConverter
IncrementalGroupLevelConverter = ValueConverter.create<int, int>(level => level + 1),
GroupLevelMarginConverter = ValueConverter.create<int, Thickness>(level => new Thickness(24 * level, 0, 0, 0)),
ExpandedVisualStateConverter = ValueConverter.create<bool, string>(is_expanded => is_expanded ? "Expanded" : "Collapsed");
public static readonly IMultiValueConverter
TotalHeaderRowDisplayTextConverter = ValueConverter.create<int, int, string>((filtered, total) => $"Total ({(filtered == total ? total.ToString() : $"{filtered}/{total}")})"),
CellParamsConverter = ValueConverter.create<object, Columns, PNLCell>((item, col) => new PNLCell(item, col));
public static readonly RoutedCommand CellClickCommand = new RoutedCommand();
static readonly string _node_path_separator = $" {SpecialCharacters.Dot} ";
IDisposable _anchors_hook, _animations_hook;
int _anchor_version;
public PNLView() {
SetCurrentValue(AnchorIndexProperty, new AnchorIndex());
InitializeComponent();
SetBinding(AnchorsProperty, new Binding(nameof(PNLVM.anchors)));
_grid.ConnectOnLoaded(() => {
if (_grid.try_find_descendent_of_type(out DIYGridRowsPresenter presenter)) {
presenter.GroupItemSelectionPivot += on_group_item_selection_pivot;
return Disposable.Create(() => presenter.GroupItemSelectionPivot -= on_group_item_selection_pivot);
}
return null;
});
void on_group_item_selection_pivot(object sender, GroupItemSelectionPivotEventArgs e) =>
e.Handled = e.group_item is PNLRowCollectionViewGroup pnl_group && pnl_group.is_expanded;
}
public new PNLVM DataContext {
get => base.DataContext as PNLVM;
set => base.DataContext = value;
}
public bool EnableAnimations {
get => _animations_hook != null;
set {
@@ -139,387 +132,282 @@ namespace bts.shell.pnl {
}
}
}
}
#region AnchorIndex
public static readonly DependencyProperty AnchorIndexProperty =
WPFHelper.create_dp<AnchorIndex, PNLView>(
nameof(AnchorIndex),
(o, v) => v?.reset(o?.Anchors));
public AnchorIndex AnchorIndex {
get => GetValue(AnchorIndexProperty) as AnchorIndex;
set => SetValue(AnchorIndexProperty, value);
}
#endregion
#region GroupLevel
public static readonly DependencyProperty GroupLevelProperty =
DependencyProperty.RegisterAttached(
"GroupLevel",
typeof(int),
typeof(PNLView),
new PropertyMetadata(0, (d, e) => {
if (d is GroupItem group && e.NewValue is int level)
group.SetBinding(DIYGrid.ColumnKeyProperty, new Binding($"{nameof(Anchors)}[{level}]") { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(PNLView), 1) });
}));
public static int GetGroupLevel(DependencyObject d) => (int)d.GetValue(GroupLevelProperty);
public static void SetGroupLevel(DependencyObject d, int value) => d.SetValue(GroupLevelProperty, value);
void _grid_loaded(object sender, RoutedEventArgs e) {
// this is a hack to make room for the 'totals row' between the column headers and the regular rows.
if (_grid.try_find_descendent_of_type(out DIYGridRowsPresenter rp)) {
rp.SetBinding(MarginProperty, new Binding(nameof(PNLVM.header_row_display)) {
Converter = ValueConverter.create<PNLHeaderRowDisplay, Thickness>(hrd => {
double top = 0;
if ((hrd & PNLHeaderRowDisplay.Total) != 0)
top += 24;
if ((hrd & PNLHeaderRowDisplay.SelectedTotal) != 0)
top += 24;
return new Thickness(0, top, 0, 0);
}),
Source = DataContext
});
}
}
#endregion
#region Anchors
public static readonly DependencyProperty AnchorsProperty =
WPFHelper.create_dp<IEnumerable<Columns>, PNLView>(
nameof(Anchors),
(o, v) => {
var cols = o?._grid?.Columns;
if (cols == null)
return;
if (!(o._grid.Rows is ICollectionView rows_view))
return;
if (!(v is INotifyCollectionChanged anchor_notifier && v is IReadOnlyList<Columns> anchor_keys))
return;
int version = ++o._anchor_version;
bool busy = false;
var pnl_cols = cols.CastObservable<DIYGrid.ColumnBase, PNLColumn>();
Disposable.replace(ref o._anchors_hook, Disposable.Create(
anchor_notifier.Subscribe((_, __) => {
if (busy)
return;
// invoke async to avoid noisy consecutive resets due to batch changes
busy = true;
o.Dispatcher.InvokeAsync(() => {
sync_anchor_cols();
busy = false;
});
})));
o.AnchorIndex?.reset(anchor_keys);
sync_anchor_cols();
void sync_anchor_cols() {
// since reset is invoked asynchronously, need to check we are still the latest version.
if (o._anchor_version != version)
return;
o.AnchorIndex?.reset(anchor_keys);
// move all the anchor columns to the left, ordered by anchor position (grouping hierarchy).
for (int i = 0; i < anchor_keys.Count; i++) {
if (pnl_cols.TryGetFirst(c => c.key == anchor_keys[i], out var match, out int curr_pos)) {
match.is_visible = true;
if (curr_pos != i) {
cols.Move(curr_pos, i);
}
match.anchor_index = i + 1;
}
}
for (int i = anchor_keys.Count; i < pnl_cols.Count; i++) {
pnl_cols[i].anchor_index = null;
}
using (rows_view.DeferRefresh()) {
rows_view.GroupDescriptions.Clear();
var anchor_cols = from key in anchor_keys
join col in pnl_cols on key equals col.key
select new PNLGroupDescription(col);
rows_view.GroupDescriptions.AddRange(anchor_cols);
}
}
});
public IEnumerable<Columns> Anchors {
get => GetValue(AnchorsProperty) as IEnumerable<Columns>;
set => SetValue(AnchorsProperty, value);
}
#endregion
void _cell_click(PNLCell cell) {
Log.Ui.info($"PNLCell Click: {cell}.");
try {
switch (cell.column) {
case Columns.SODNetPos:
case Columns.SODLongPos:
case Columns.SODShortPos:
case Columns.DayNetPos:
case Columns.DayLongPos:
case Columns.DayShortPos:
case Columns.TotalNetPos:
case Columns.TotalLongPos:
case Columns.TotalShortPos: {
var oneof = cell.oneof();
if (oneof == null)
return;
var (vm, context, title, fg) = cell.oneof().Match(_row_fills, _group_fills, _header_fills);
_show_fills_grid(vm, context, title, fg);
}
break;
case Columns.WorkingNetPos:
case Columns.WorkingLongPos:
case Columns.WorkingShortPos: {
var (vm, context, title, fg) = cell.oneof().Match(_orderbook_params, _orderbook_params, _orderbook_params);
_show_orderbook(vm, context, title, fg);
}
break;
}
} catch (Exception ex) {
Log.Ui.warn($"{nameof(PNLView)} error on cell click: {ex}.");
}
}
#region Fills
(FillsVM vm, string context, string title, Color fg) _row_fills(PNLRowCell cell) {
var (title_desc, fg) = _fills_title(cell.column);
return (DataContext.mk_fills_vm(cell.row, cell.column), $"{cell.row.account_name} {cell.row.security}", title_desc, fg);
}
(FillsVM vm, string context, string title, Color fg) _group_fills(PNLGroupCell cell) {
var (title_desc, fg) = _fills_title(cell.column);
return (DataContext.mk_fills_vm(cell.group, cell.column), string.Join(_node_path_separator, cell.group.GroupNamePath), title_desc, fg);
}
(FillsVM vm, string context, string title, Color fg) _header_fills(PNLHeaderRowCell cell) {
var (title_desc, fg) = _fills_title(cell.column);
return (DataContext.mk_fills_vm(cell.header_row, cell.column), EnumHelper.get_display_name(cell.header_row), title_desc, fg);
}
static (string desc, Color fg) _fills_title(Columns key) {
const uint net_fg = 0x333333u;
switch (key) {
case Columns.SODLongPos: return ("Open Buy Fills", BTSColors.MarketData.bid);
case Columns.SODShortPos: return ("Open Sell Fills", BTSColors.MarketData.ask);
case Columns.SODNetPos: return ("Open Fills", net_fg.rgb_color());
case Columns.DayLongPos: return ("Day Buy Fills", BTSColors.MarketData.bid);
case Columns.DayShortPos: return ("Day Sell Fills", BTSColors.MarketData.ask);
case Columns.DayNetPos: return ("Day Fills", net_fg.rgb_color());
case Columns.TotalLongPos: return ("All Buy Fills", BTSColors.MarketData.bid);
case Columns.TotalShortPos: return ("All Sell Fills", BTSColors.MarketData.ask);
case Columns.TotalNetPos: return ("All Fills", net_fg.rgb_color());
default: throw ExceptionBuilder.Argument.unexpected_case(key, nameof(key));
}
}
async void _show_fills_grid(FillsVM fills_vm, string context_desc, string title_desc, Color fg) {
var fills_view = new FillsView { DataContext = fills_vm, MaxHeight = _auxiliary_popup_height };
await Task.Delay(100); // Wait for obvm to load rows collection before showing UI to avoid a flash
fills_view.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
// banner
var group_header = new TextBlock {
Margin = new Thickness(4, 0, 0, 0),
VerticalAlignment = VerticalAlignment.Center,
FontWeight = FontWeights.Bold,
Text = context_desc
};
var title = new TextBlock {
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
FontWeight = FontWeights.Bold,
Foreground = fg.to_brush(),
Text = title_desc
};
var keep_open_checkbox = new CheckBox {
VerticalAlignment = VerticalAlignment.Center,
Content = "Keep Open",
Focusable = false
};
var close_bn = new Button {
HorizontalAlignment = HorizontalAlignment.Right,
Margin = new Thickness(8, 4, 4, 4),
Command = ApplicationCommands.Close,
Style = TryFindResource("RoundCloseButton") as Style
};
var banner_panel_rt = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Right };
banner_panel_rt.Children.Add(keep_open_checkbox);
banner_panel_rt.Children.Add(close_bn);
// grid panel
var panel = new Grid();
panel.Children.Add(group_header);
panel.Children.Add(title);
panel.Children.Add(banner_panel_rt);
panel.Children.Add(fills_view);
panel.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
panel.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
Grid.SetRow(fills_view, 1);
// popup adorner
var popup_adorner = new PopupAdorner(
_grid, _grid,
new Border {
Child = panel,
Background = BTSColors.common_background_brush,
BorderBrush = Brushes.Black,
BorderThickness = new Thickness(1),
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top
},
HorizontalPosition.Middle,
VerticalPosition.Bottom,
0,
0) {
OpenRelativeToMouse = true,
EnableDragMove = true,
ClipToBounds = true
};
close_bn.CommandTarget = popup_adorner.PopupElement;
popup_adorner.SetBinding(PopupAdorner.StaysOpenProperty, new Binding(nameof(CheckBox.IsChecked)) { Source = keep_open_checkbox });
popup_adorner.PopupElement.InputBindings.Add(new KeyBinding(ApplicationCommands.Close, Key.Escape, ModifierKeys.None));
popup_adorner.PopupElement.CommandBindings.Add(new CommandBinding(ApplicationCommands.Close, (s, e) => {
Log.assert(popup_adorner != null);
popup_adorner.detach();
popup_adorner = null;
}));
//
popup_adorner.attach();
popup_adorner.IsOpen = true;
FocusManager.SetIsFocusScope(popup_adorner.PopupElement, true);
fills_view.Focus();
popup_adorner.AdornedElement.Focus();
}
#endregion
#region OrderBook
(OrderBookVM vm, string context, string title, Color fg) _orderbook_params(PNLRowCell cell) {
var (title_desc, fg) = _orderbook_title(cell.column);
return (DataContext.mk_orderbook_vm(cell.row, cell.column), $"{cell.row.account_name} {cell.row.security}", title_desc, fg);
}
(OrderBookVM vm, string context, string title, Color fg) _orderbook_params(PNLGroupCell cell) {
var (title_desc, fg) = _orderbook_title(cell.column);
return (DataContext.mk_orderbook_vm(cell.group, cell.column), string.Join(_node_path_separator, cell.group.GroupNamePath), title_desc, fg);
}
(OrderBookVM vm, string context, string title, Color fg) _orderbook_params(PNLHeaderRowCell cell) {
var (title_desc, fg) = _orderbook_title(cell.column);
return (DataContext.mk_orderbook_vm(cell.header_row, cell.column), EnumHelper.get_display_name(cell.header_row), title_desc, fg);
}
static (string desc, Color fg) _orderbook_title(Columns key) {
switch (key) {
case Columns.WorkingLongPos: return ("Working Buy Orders", BTSColors.MarketData.bid);
case Columns.WorkingShortPos: return ("Working Sell Orders", BTSColors.MarketData.ask);
case Columns.WorkingNetPos: return ("Working Orders", 0x333333u.rgb_color());
default: throw ExceptionBuilder.Argument.unexpected_case(key, nameof(key));
}
}
async void _show_orderbook(OrderBookVM obvm, string context_desc, string title_desc, Color fg) {
var mini_ob = new MiniOrderBookView(obvm) { MaxHeight = _auxiliary_popup_height };
await Task.Delay(100); // Wait for obvm to load rows collection before showing UI to avoid a flash
mini_ob.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
// banner
var group_header = new TextBlock {
Margin = new Thickness(4, 0, 0, 0),
VerticalAlignment = VerticalAlignment.Center,
FontWeight = FontWeights.Bold,
Text = context_desc
};
var title = new TextBlock {
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
FontWeight = FontWeights.Bold,
Foreground = fg.to_brush(),
Text = title_desc
};
var keep_open_checkbox = new CheckBox {
VerticalAlignment = VerticalAlignment.Center,
Content = "Keep Open",
Focusable = false
};
var close_bn = new Button {
HorizontalAlignment = HorizontalAlignment.Right,
Margin = new Thickness(8, 4, 4, 4),
Command = ApplicationCommands.Close,
//CommandTarget = mini_ob,
Style = TryFindResource("RoundCloseButton") as Style
};
var banner_panel_rt = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Right };
banner_panel_rt.Children.Add(keep_open_checkbox);
banner_panel_rt.Children.Add(close_bn);
// grid panel
var panel = new Grid();
panel.Children.Add(group_header);
panel.Children.Add(title);
panel.Children.Add(banner_panel_rt);
panel.Children.Add(mini_ob);
panel.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
panel.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
Grid.SetRow(mini_ob, 1);
// popup adorner
var popup_adorner = new PopupAdorner(
_grid, _grid,
new Border {
Child = panel,
Background = BTSColors.common_background_brush,
BorderBrush = Brushes.Black,
BorderThickness = new Thickness(1),
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top
},
HorizontalPosition.Middle,
VerticalPosition.Bottom,
0,
0) {
OpenRelativeToMouse = true,
EnableDragMove = true,
ClipToBounds = true
};
close_bn.CommandTarget = popup_adorner.PopupElement;
popup_adorner.SetBinding(PopupAdorner.StaysOpenProperty, new Binding(nameof(CheckBox.IsChecked)) { Source = keep_open_checkbox });
popup_adorner.PopupElement.InputBindings.Add(new KeyBinding(ApplicationCommands.Close, Key.Escape, ModifierKeys.None));
popup_adorner.PopupElement.CommandBindings.Add(new CommandBinding(ApplicationCommands.Close, (s, e) => {
Log.assert(popup_adorner != null);
popup_adorner.detach();
popup_adorner = null;
}));
//
popup_adorner.attach();
popup_adorner.IsOpen = true;
FocusManager.SetIsFocusScope(popup_adorner.PopupElement, true);
mini_ob.Focus();
popup_adorner.AdornedElement.Focus();
}
#endregion
void _cell_click_executed(object sender, ExecutedRoutedEventArgs e) {
if (e.Parameter is PNLCell cell) {
_cell_click(cell);
} else Log.Ui.warn($"{nameof(PNLView)} '{nameof(_cell_click_executed)}' parameter should be a {typeof(PNLCell)} (actually '{e.Parameter}').");
}
void _on_group_item_mouse_down(object sender, MouseButtonEventArgs e) {
if (!e.Handled && sender is FrameworkElement el && el.DataContext is PNLRowCollectionViewGroup group) {
group.is_all_selected = !group.is_all_selected.GetValueOrDefault();
e.Handled = true;
}
}
class PNLGroupDescription : PropertyGroupDescription, IEquatable<PNLGroupDescription> {
public PNLGroupDescription(PNLColumn column) : base(column.data_member_path.Path) =>
key = column.key;
public Columns key { get; }
public bool Equals(PNLGroupDescription other) =>
other != null && key == other.key;
public override int GetHashCode() =>
key.GetHashCode();
public override bool Equals(object obj) =>
Equals(obj as PNLGroupDescription);
}
}
}
}
#region AnchorIndex
public static readonly DependencyProperty AnchorIndexProperty =
WPFHelper.create_dp<AnchorIndex, PNLView>(
nameof(AnchorIndex),
(o, v) => v?.reset(o?.Anchors));
public AnchorIndex AnchorIndex {
get => GetValue(AnchorIndexProperty) as AnchorIndex;
set => SetValue(AnchorIndexProperty, value);
}
#endregion
#region GroupLevel
public static readonly DependencyProperty GroupLevelProperty =
DependencyProperty.RegisterAttached(
"GroupLevel",
typeof(int),
typeof(PNLView),
new PropertyMetadata(0, (d, e) => {
if (d is GroupItem group && e.NewValue is int level)
group.SetBinding(DIYGrid.ColumnKeyProperty, new Binding($"{nameof(Anchors)}[{level}]") { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(PNLView), 1) });
}));
public static int GetGroupLevel(DependencyObject d) => (int)d.GetValue(GroupLevelProperty);
public static void SetGroupLevel(DependencyObject d, int value) => d.SetValue(GroupLevelProperty, value);
void _grid_loaded(object sender, RoutedEventArgs e) {
// this is a hack to make room for the 'totals row' between the column headers and the regular rows.
if (_grid.try_find_descendent_of_type(out DIYGridRowsPresenter rp)) {
rp.SetBinding(MarginProperty, new Binding(nameof(PNLVM.header_row_display)) {
Converter = ValueConverter.create<PNLHeaderRowDisplay, Thickness>(hrd => {
double top = 0;
if ((hrd & PNLHeaderRowDisplay.Total) != 0)
top += 24;
if ((hrd & PNLHeaderRowDisplay.SelectedTotal) != 0)
top += 24;
return new Thickness(0, top, 0, 0);
}),
Source = DataContext
});
}
}
#endregion
#region Anchors
public static readonly DependencyProperty AnchorsProperty =
WPFHelper.create_dp<IEnumerable<Columns>, PNLView>(
nameof(Anchors),
(o, v) => {
var cols = o?._grid?.Columns;
if (cols == null)
return;
if (!(o._grid.Rows is ICollectionView rows_view))
return;
if (!(v is INotifyCollectionChanged anchor_notifier && v is IReadOnlyList<Columns> anchor_keys))
return;
int version = ++o._anchor_version;
bool busy = false;
var pnl_cols = cols.CastObservable<DIYGrid.ColumnBase, PNLColumn>();
Disposable.replace(ref o._anchors_hook, Disposable.Create(
anchor_notifier.Subscribe((_, __) => {
if (busy)
return;
// invoke async to avoid noisy consecutive resets due to batch changes
busy = true;
o.Dispatcher.InvokeAsync(() => {
sync_anchor_cols();
busy = false;
});
})));
o.AnchorIndex?.reset(anchor_keys);
sync_anchor_cols();
void sync_anchor_cols() {
// since reset is invoked asynchronously, need to check we are still the latest version.
if (o._anchor_version != version)
return;
o.AnchorIndex?.reset(anchor_keys);
// move all the anchor columns to the left, ordered by anchor position (grouping hierarchy).
for (int i = 0; i < anchor_keys.Count; i++) {
if (pnl_cols.TryGetFirst(c => c.key == anchor_keys[i], out var match, out int curr_pos)) {
match.is_visible = true;
if (curr_pos != i) {
cols.Move(curr_pos, i);
}
match.anchor_index = i + 1;
}
}
for (int i = anchor_keys.Count; i < pnl_cols.Count; i++) {
pnl_cols[i].anchor_index = null;
}
using (rows_view.DeferRefresh()) {
rows_view.GroupDescriptions.Clear();
var anchor_cols = from key in anchor_keys
join col in pnl_cols on key equals col.key
select new PNLGroupDescription(col);
rows_view.GroupDescriptions.AddRange(anchor_cols);
}
}
});
public IEnumerable<Columns> Anchors {
get => GetValue(AnchorsProperty) as IEnumerable<Columns>;
set => SetValue(AnchorsProperty, value);
}
#endregion
void _cell_click(PNLCell cell) {
Log.Ui.info($"PNLCell Click: {cell}.");
try {
switch (cell.column) {
case Columns.SODNetPos:
case Columns.SODLongPos:
case Columns.SODShortPos:
case Columns.DayNetPos:
case Columns.DayLongPos:
case Columns.DayShortPos:
case Columns.RawDayNetPos:
case Columns.RawDayLongPos:
case Columns.RawDayShortPos:
case Columns.TotalNetPos:
case Columns.TotalLongPos:
case Columns.TotalShortPos: {
var oneof = cell.oneof();
if (oneof == null)
return;
var (vm, context, title, fg) = cell.oneof().Match(_row_fills, _group_fills, _header_fills);
_show_auxiliary_popup(vm, context, title, fg);
}
break;
case Columns.WorkingNetPos:
case Columns.WorkingLongPos:
case Columns.WorkingShortPos: {
var (vm, context, title, fg) = cell.oneof().Match(_orderbook_params, _orderbook_params, _orderbook_params);
_show_auxiliary_popup(vm, context, title, fg);
}
break;
}
} catch (Exception ex) {
Log.Ui.warn($"{nameof(PNLView)} error on cell click: {ex}.");
}
}
#region Fills
(FillsVM vm, string context, string title, Color fg) _row_fills(PNLRowCell cell) {
var (title_desc, fg) = _fills_title(cell.column);
return (DataContext.mk_fills_vm(cell.row, cell.column), $"{cell.row.account_name} {cell.row.security}", title_desc, fg);
}
(FillsVM vm, string context, string title, Color fg) _group_fills(PNLGroupCell cell) {
var (title_desc, fg) = _fills_title(cell.column);
return (DataContext.mk_fills_vm(cell.group, cell.column), string.Join(_node_path_separator, cell.group.GroupNamePath), title_desc, fg);
}
(FillsVM vm, string context, string title, Color fg) _header_fills(PNLHeaderRowCell cell) {
var (title_desc, fg) = _fills_title(cell.column);
return (DataContext.mk_fills_vm(cell.header_row, cell.column), EnumHelper.get_display_name(cell.header_row), title_desc, fg);
}
static (string desc, Color fg) _fills_title(Columns key) {
const uint net_fg = 0x333333u;
switch (key) {
case Columns.SODLongPos: return ("Open Buy Fills", BTSColors.MarketData.bid);
case Columns.SODShortPos: return ("Open Sell Fills", BTSColors.MarketData.ask);
case Columns.SODNetPos: return ("Open Fills", net_fg.rgb_color());
case Columns.DayLongPos: return ("Day Buy Fills", BTSColors.MarketData.bid);
case Columns.DayShortPos: return ("Day Sell Fills", BTSColors.MarketData.ask);
case Columns.DayNetPos: return ("Day Fills", net_fg.rgb_color());
case Columns.RawDayLongPos: return ("Raw Day Buy Fills", BTSColors.MarketData.bid);
case Columns.RawDayShortPos: return ("Raw Day Sell Fills", BTSColors.MarketData.ask);
case Columns.RawDayNetPos: return ("Raw Day Fills", net_fg.rgb_color());
case Columns.TotalLongPos: return ("All Buy Fills", BTSColors.MarketData.bid);
case Columns.TotalShortPos: return ("All Sell Fills", BTSColors.MarketData.ask);
case Columns.TotalNetPos: return ("All Fills", net_fg.rgb_color());
default: throw ExceptionBuilder.Argument.unexpected_case(key, nameof(key));
}
}
#endregion
#region OrderBook
(OrderBookVM vm, string context, string title, Color fg) _orderbook_params(PNLRowCell cell) {
var (title_desc, fg) = _orderbook_title(cell.column);
return (DataContext.mk_orderbook_vm(cell.row, cell.column), $"{cell.row.account_name} {cell.row.security}", title_desc, fg);
}
(OrderBookVM vm, string context, string title, Color fg) _orderbook_params(PNLGroupCell cell) {
var (title_desc, fg) = _orderbook_title(cell.column);
return (DataContext.mk_orderbook_vm(cell.group, cell.column), string.Join(_node_path_separator, cell.group.GroupNamePath), title_desc, fg);
}
(OrderBookVM vm, string context, string title, Color fg) _orderbook_params(PNLHeaderRowCell cell) {
var (title_desc, fg) = _orderbook_title(cell.column);
return (DataContext.mk_orderbook_vm(cell.header_row, cell.column), EnumHelper.get_display_name(cell.header_row), title_desc, fg);
}
static (string desc, Color fg) _orderbook_title(Columns key) {
switch (key) {
case Columns.WorkingLongPos: return ("Working Buy Orders", BTSColors.MarketData.bid);
case Columns.WorkingShortPos: return ("Working Sell Orders", BTSColors.MarketData.ask);
case Columns.WorkingNetPos: return ("Working Orders", 0x333333u.rgb_color());
default: throw ExceptionBuilder.Argument.unexpected_case(key, nameof(key));
}
}
#endregion
void _cell_click_executed(object sender, ExecutedRoutedEventArgs e) {
if (e.Parameter is PNLCell cell) {
_cell_click(cell);
} else Log.Ui.warn($"{nameof(PNLView)} '{nameof(_cell_click_executed)}' parameter should be a {typeof(PNLCell)} (actually '{e.Parameter}').");
}
void _on_group_item_mouse_down(object sender, MouseButtonEventArgs e) {
if (!e.Handled && sender is FrameworkElement el && el.DataContext is PNLRowCollectionViewGroup group) {
if (e.ClickCount == 2) {
group.is_expanded = !group.is_expanded;
} else if (el.try_find_ancestor_of_type(out DIYGrid grid) && grid.SelectionMode != SelectionMode.Single) {
group.is_all_selected = !group.is_all_selected.GetValueOrDefault();
} else {
return;
}
e.Handled = true;
}
}
async void _show_auxiliary_popup<T>(T content, string context_desc, string title_desc, Color fg) {
// vm
dynamic header = new ExpandoObject();
header.context = context_desc;
header.title = title_desc;
header.title_foreground = fg;
header.keep_open = false;
// view
var view = new PNLAuxiliaryPopupView {
Content = content,
Header = header,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top
};
await Task.Delay(100); // Wait for obvm to load rows collection before showing UI to avoid a flash
view.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
// popup adorner
// tho unlikely, better not assume target cell is non-null
var target_cell = _grid.ChildAtPoint<HyperlinkCell>(Mouse.GetPosition(_grid));
var popup_adorner = new PopupAdorner(_grid, target_cell ?? (FrameworkElement)_grid, view, HorizontalPosition.Middle, VerticalPosition.Bottom, 0, 0) {
OpenRelativeToMouse = target_cell == null,
EnableDragMove = true,
ClipToBounds = true
};
popup_adorner.SetBinding(PopupAdorner.StaysOpenProperty, new Binding("keep_open") { Source = header });
popup_adorner.PopupElement.InputBindings.Add(new KeyBinding(ApplicationCommands.Close, Key.Escape, ModifierKeys.None));
popup_adorner.PopupElement.CommandBindings.Add(new CommandBinding(ApplicationCommands.Close, (s, e) => {
Log.assert(popup_adorner != null);
popup_adorner.detach();
popup_adorner = null;
}));
popup_adorner.attach();
popup_adorner.IsOpen = true;
FocusManager.SetIsFocusScope(popup_adorner.PopupElement, true);
popup_adorner.AdornedElement.Focus();
}
class PNLGroupDescription : PropertyGroupDescription, IEquatable<PNLGroupDescription> {
public PNLGroupDescription(PNLColumn column) : base(column.data_member_path.Path) =>
key = column.key;
public Columns key { get; }
public bool Equals(PNLGroupDescription other) =>
other != null && key == other.key;
public override int GetHashCode() =>
key.GetHashCode();
public override bool Equals(object obj) =>
Equals(obj as PNLGroupDescription);
}
}
}
@@ -5,6 +5,7 @@ using bts.spark;
using mbts.persist;
using mbts.spark;
using mbts.ui;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
@@ -16,13 +17,17 @@ using static mbts.ui.shell.ProtoBufSerializationHelper;
namespace bts.shell {
[Export(typeof(IShellModule))]
class TradingShellModule : IShellModule {
LadderWindowFactory _lwf;
OrderBookWindowFactory _obwf;
FillsWindowFactory _fwf;
PAndLWindowFactory _old_plwf;
PNLWindowFactory _plwf;
AlgoStudioWindowFactory _aswf;
ManualFillsWindowFactory _mfwf;
readonly PNLConfig _pnl_conf;
// window factories
readonly LadderWindowFactory _lwf;
readonly OrderBookWindowFactory _obwf;
readonly FillsWindowFactory _fwf;
readonly PAndLWindowFactory _old_plwf;
readonly PNLWindowFactory _plwf;
readonly AlgoStudioWindowFactory _aswf;
readonly ManualFillsWindowFactory _mfwf;
//
List<SparkCommand> _actions;
[ImportingConstructor]
public TradingShellModule(
@@ -34,13 +39,16 @@ namespace bts.shell {
OrdersModel orders_model,
md.MBOService mbo,
ErrorReporter error_reporter) {
/// Unlike our <see cref="SparkCommand"/> members, <see cref="IShellWindowFactory"/> members must be instantiated immediately,
/// as they are responsible for launching windows in response to a SparkTag being published to the bus.
/// The tags may be published by anything (i.e., not just the spark commands in <see cref="actions"/>).
_lwf = new LadderWindowFactory(this, bus, wm, settings_hub, orders_model, mbo);
_obwf = new OrderBookWindowFactory(this, bus, notification_service, notification_service_data_cache, orders_model, settings_hub);
_fwf = new FillsWindowFactory(this, bus, settings_hub, orders_model);
var pnl_conf = mbts.config.Configuration.get<bts.config.PNLOptions>();
if (pnl_conf.show_new_pnl)
_pnl_conf = new PNLConfig(mbts.config.Configuration.get<bts.config.PNLOptions>());
if (_pnl_conf.show_new_pnl)
_plwf = new PNLWindowFactory(this, bus, settings_hub, orders_model);
if (pnl_conf.show_old_pnl)
if (_pnl_conf.show_old_pnl)
_old_plwf = new PAndLWindowFactory(this, bus, settings_hub, orders_model);
_aswf = new AlgoStudioWindowFactory(this, bus, orders_model, error_reporter);
_mfwf = new ManualFillsWindowFactory(this, bus, orders_model);
@@ -52,35 +60,62 @@ namespace bts.shell {
public int priority => 20;
public IEnumerable<SparkCommand> actions => new [] {
SparkCommands.ShowOrders.complete(() => bts.shell.orderbook.OrderBookStartupData.Default),
SparkCommands.ShowFills(null).complete(() => new FillsSettings()),
SparkCommands.ShowPNL.complete(() => null),
SparkCommands.ShowPAndL.complete(() => new PAndLWindowFactory.PAndLStartupData()),
SparkCommands.PickLadder,
SparkCommands.PickOrderTicket,
SparkCommands.ShowAlgoStudio.complete(() => new AlgoStudioWindowFactory.StartupData()),
SparkCommands.ShowManualFills().complete(() => ManualFillsSettings.Default)
};
public IEnumerable<SparkCommand> actions {
get {
if (_actions == null) {
_actions = new List<SparkCommand> {
SparkCommands.ShowOrders.complete(() => bts.shell.orderbook.OrderBookStartupData.Default),
SparkCommands.ShowFills(null).complete(() => new FillsSettings()),
};
if (_pnl_conf.show_new_pnl)
_actions.Add(SparkCommands.ShowPNL.complete(() => null));
if (_pnl_conf.show_old_pnl)
_actions.Add(SparkCommands.ShowPAndL.complete(() => new PAndLWindowFactory.PAndLStartupData()));
_actions.AddRange(new[] {
SparkCommands.PickLadder,
SparkCommands.PickOrderTicket,
SparkCommands.ShowAlgoStudio.complete(() => new AlgoStudioWindowFactory.StartupData()),
SparkCommands.ShowManualFills().complete(() => ManualFillsSettings.Default)
});
}
return _actions;
}
}
IEnumerable<IShellWindowFactory> window_factories() {
yield return _lwf;
yield return _obwf;
yield return _fwf;
if (_plwf != null)
yield return _plwf;
if (_old_plwf != null)
yield return _old_plwf;
yield return _aswf;
yield return _mfwf;
IEnumerable<IShellWindowFactory> _window_factories {
get {
yield return _lwf;
yield return _obwf;
yield return _fwf;
if (_plwf != null)
yield return _plwf;
if (_old_plwf != null)
yield return _old_plwf;
yield return _aswf;
yield return _mfwf;
}
}
public Task<IShellWindow> create(Stream load_stream) =>
dispatch_to_factory(load_stream, window_factories().ToArray());
dispatch_to_factory(load_stream, _window_factories.ToArray());
public void Dispose() {
foreach (var i in window_factories())
if (i != null) i.Dispose();
if (_actions != null) {
_actions.OfType<IDisposable>().DisposeAll();
_actions = null;
}
_window_factories.DisposeAll();
}
readonly struct PNLConfig {
public readonly bool
show_new_pnl,
show_old_pnl;
public PNLConfig(bts.config.PNLOptions options) {
show_new_pnl = options.show_new_pnl;
show_old_pnl = options.show_old_pnl;
}
}
}
}
@@ -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();
+32 -18
View File
@@ -2,28 +2,40 @@
using bts.api.orderman;
using bts.utils;
using System;
using static bts.api.orderman.Fill.Types;
namespace mbts.spark {
class FillTuple : IEquatable<FillTuple> {
public static readonly Func<FillTuple, string> key_fn = ft => ft.fill.FillKey;
public readonly FillImmutable fill;
public readonly OrderRecordImmutable? order;
public readonly SecurityImmutable? security;
public readonly AlgoData? algo;
public readonly AlgoDefinitionMetadata? algo_meta;
public FillTuple((SecurityImmutable? sec, FillImmutable val) fill, OrderSecurity? order_tuple) {
this.fill = fill.val;
this.security = fill.sec;
if (order_tuple is OrderSecurity osam) {
order = osam.order;
algo = osam.algo;
algo_meta = osam.meta;
}
OrderSecurityRootPair? _order_tuple;
public FillTuple(FillImmutable fill, SecurityImmutable? security, OrderSecurityRootPair? order_tuple) {
this.fill = fill;
this.security = security;
_order_tuple = order_tuple;
}
public FillTuple((SecurityImmutable? sec, FillImmutable val) fill, OrderSecurity? order_tuple)
: this(fill.val, fill.sec, order_tuple.HasValue ? OrderSecurityRootPair.from_order_security(order_tuple.Value) : (OrderSecurityRootPair?)null) { }
public AlgoData? algo =>
_order_tuple.HasValue ? _order_tuple.Value.order.algo : (AlgoData?)null;
public AlgoDefinitionMetadata? algo_meta =>
_order_tuple.HasValue ? _order_tuple.Value.order.meta : null;
public OrderRecordImmutable? order =>
_order_tuple.HasValue ? _order_tuple.Value.order.order : (OrderRecordImmutable?)null;
public AlgoData? root_algo =>
_order_tuple.HasValue ? _order_tuple.Value.root.algo : (AlgoData?)null;
public AlgoDefinitionMetadata? root_algo_meta =>
_order_tuple.HasValue ? _order_tuple.Value.root.meta : null;
public string account {
get {
var account_string = string.Empty;
@@ -38,12 +50,14 @@ namespace mbts.spark {
order.Value.account_id() :
fill.InternalAccountId;
public bool Equals(FillTuple other) => other != null
&& other.order.HasValue == order.HasValue
&& other.security.HasValue == security.HasValue
&& other.fill.Equals(fill);
public bool Equals(FillTuple other) =>
other != null &&
_order_tuple.Equals(other._order_tuple) &&
security.HasValue == other.security.HasValue &&
fill.Equals(other.fill);
public override bool Equals(object obj) => Equals(obj as FillTuple);
public override bool Equals(object obj) =>
Equals(obj as FillTuple);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
@@ -71,4 +85,4 @@ namespace mbts.spark {
public static string user(this FillTuple fill_tuple) =>
fill_tuple.order.HasValue ? fill_tuple.order.Value.user() : string.Empty;
}
}
}
+1 -1
View File
@@ -53,7 +53,7 @@ namespace mbts.spark {
send_order = new DelegateCommand<Side>(side => {
bool price_valid = _validate_price(), size_valid = size_calculator.validate();
if (price_valid && size_valid && !HasErrors && !properties_vm.HasErrors) {
model.send_order(properties_vm.create(side), price, size_calculator.value.Value).ignore();
model.send_order(security, properties_vm.create(side), price, size_calculator.value.Value).ignore();
RequestClose?.Invoke(this, new EventArgs());
}
});
+16
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;
@@ -199,6 +200,13 @@ namespace mbts.spark {
public static string short_description(this OrderRecordImmutable order, ContractSpecificationImmutable? spec) =>
$"{(order.HasDef ? order.Def.Side.ToString() : "(unknown-side)")} {(order.HasState ? ($@"{spec.format(order.State.Price)} @ {order.State.Quantity}") : "(unknown-price) @ (unknown-quantity)")}";
public static string short_description(SecurityImmutable security, Side side, double price, uint quantity) =>
new StringBuilderEx(
security.ToString(), " ",
side.ToString(), " ",
price_quantity_description(price, quantity, security.spec_or_default())
).ToString();
public static string short_description(this OrderRecordImmutable order, SecurityImmutable? security) {
var spec = security.spec_or_default();
return new StringBuilderEx(
@@ -339,5 +347,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;
}
}
}
}
+54 -41
View File
@@ -173,8 +173,8 @@ namespace mbts.spark {
PNLRowData.key_fn)
.Where(t => t.has_changes())
.Cache();
// fills
var on_fills =
// fill tuples
var on_fills =
_service.subscribei_fills()
.Naggle(interval, scheduler)
.PermissionFilter(admin_model.on_admin_data)
@@ -189,6 +189,16 @@ namespace mbts.spark {
(f, fk, ok) => new FillTuple(f, null),
f => f.fill.FillKey);
on_fill_tuples = fill_order_tuples_join.Cache();
// fill tuples with root (algo)
var fill_order_tuples_with_root_join = on_fills.LeftJoin(
on_order_tuples_with_root,
f => f.val.FillKey,
OrderSecurityHelper.root_pair_key_fn,
f => f.val.OrderKey,
(f, fk, o, ok) => new FillTuple(f.val, f.sec, o),
(f, fk, ok) => new FillTuple(f.val, f.sec, null),
FillTuple.key_fn);
on_fill_tuples_with_root = fill_order_tuples_with_root_join.Cache();
// manual fills
on_manual_fills_securities = _position_service.subscribei_manual_fills()
.Naggle(interval, scheduler)
@@ -231,7 +241,6 @@ namespace mbts.spark {
.Filter(os => os.order.is_active() && os.order.State.DisplayStatus == OrderState.Types.DisplayStatus.Visible, key => key.order.Key)
.Subscribe(_security_working_orders_with_piq_demuxer),
on_positions.Subscribe(_security_positions_demuxer),
_bus.subscribe(SparkTags.CreateOrder).Subscribe(a => send_order(a.definition, a.price, a.size).ignore()),
_bus.subscribe(SparkTags.RecreateOrder).Subscribe(a => a.ForEach(x => send_order(x.definition, x.price, x.size).ignore())),
_bus.subscribe(SparkTags.CancelOrders).Subscribe(x => cancel_orders(x).ignore()),
_bus.subscribe(SparkTags.DetachOrders).Subscribe(x => detach_orders(x).ignore()),
@@ -239,7 +248,7 @@ namespace mbts.spark {
);
}
#region Output
#region Output
public OrderFactory factory => _factory;
public SpreaderModel spreader => _spreader;
public DynamicOrderHelper dynamic_helper => _dynamic_helper;
@@ -252,7 +261,8 @@ namespace mbts.spark {
public IObservable<TableUpdate<KeyValuePair<uint, bool>, uint>> on_orders_can_attach { get; }
public IObservable<IIncrementalUpdate<SecurityImmutable, uint>> on_order_securities { get; }
public IObservable<TableUpdate<FillTuple, string>> on_fill_tuples { get; }
public IObservable<IReadOnlyDictionary<uint,OrderSecurity>> working_orders { get; }
public IObservable<TableUpdate<FillTuple, string>> on_fill_tuples_with_root { get; }
public IObservable<IReadOnlyDictionary<uint, OrderSecurity>> working_orders { get; }
public IObservable<TableUpdate<(SecurityImmutable?, PNLUnitImmutable), int>> on_pnl { get; }
public IObservable<TableUpdate<PNLRowData, PNLKey>> security_account_pnl { get; }
public IObservable<TableUpdate<AccountPNLImmutable, uint>> on_account_pnl { get; }
@@ -273,16 +283,36 @@ namespace mbts.spark {
public IObservable<TableUpdate<(SecurityImmutable?, PositionUnitImmutable), int>> on_security_positions(uint security) =>
_security_positions_demuxer.subscribe(security);
#endregion
#endregion
#region Input
public async Task<uint?> send_order(OrderDefinitionImmutable? order, double price, uint size) {
#region Input
// the security arg is needed for a temporary hack to prevent GlbxIceberg orders from going out in non-glbx exchanges (like Ice)
// eventually, we'll need an algo type model to handle this stuff.
// This hack leverages the fact that every place that calls 'send_order' already has the security handy (not just the sec id).
public Task<uint?> send_order(SecurityImmutable security, OrderDefinitionImmutable? order, double price, uint size) {
if (order.HasValue) {
var result = (await send_order_get_error(order.Value, price, size));
_activity.on_send_order(order.Value, price, size, result.key, result.error);
return result.key;
#if DEBUG
if (security.Exchange != Venue.Glbx && security.Exchange != Venue.Sim) {
#else
if (security.Exchange != Venue.Glbx) {
#endif
var algo_structure = OrderDataHelper.enumerate_algo_structure(order.Value.CustomDef);
if (algo_structure.Any(t => t.type == CustomOrderDefinition.CustomOneofCase.GlbxIceberg)) {
Log.Ui.info($"OrdersModel prevented a GlbxIceberg order from going out to a non-glbx exchange ({OrderUtils.short_description(security, order.Value.Side, price, size)}).");
return Task.FromResult<uint?>(null);
}
}
return send_order(order.Value, price, size);
}
return 0;
return Task.FromResult<uint?>(null);
}
/// Note: this method is private to force outside callers to use this overload <see cref="send_order(SecurityImmutable, OrderDefinitionImmutable?, double, uint)"/>.
/// This allows us to control which orders can be sent to which security/exchange (e.g., GlbxIceberg orders are only permitted for securities on the Glbx exchange).
async Task<uint?> send_order(OrderDefinitionImmutable order, double price, uint size) {
var result = (await send_order_get_error(order, price, size));
_activity.on_send_order(order, price, size, result.key, result.error);
return result.key;
}
async Task<(uint? key, string error)> send_order_get_error(OrderDefinitionImmutable order, double price, uint size) {
@@ -294,7 +324,7 @@ namespace mbts.spark {
return _handle_error(result, "Error sending order: {0}");
}
Task<(OrderRecordImmutable,bool)[]> cancel_orders(IEnumerable<uint> orders) =>
Task<(OrderRecordImmutable, bool)[]> cancel_orders(IEnumerable<uint> orders) =>
this.cancel_orders_where(o => orders.Contains(o.Key));
public async Task<bool> cancel_order(OrderRecordImmutable order) {
@@ -342,7 +372,7 @@ namespace mbts.spark {
(uint? key, string error) _handle_error(Result<Response> result, string template) =>
_error.handle_error(result, template, r => r.ErrorCode, e => e == OrderError.Ok, r => new uint?(r.OrderKey));
#region Manual Fillls
#region Manual Fillls
public async Task<bool> create_manual_fills(IEnumerable<ManualFill> fills) {
return await create_manual_fills(_create_fill_batch(fills));
}
@@ -378,9 +408,9 @@ namespace mbts.spark {
_error.report_error(result.error_message);
return result.value.Response.All(r => r.Ok);
}
#endregion // Manual Fills
#endregion
#endregion
#endregion
public void Dispose() =>
Disposable.dispose(ref _dispose);
@@ -432,13 +462,10 @@ namespace mbts.spark {
}
}
// usinig new OrdersModel extensions to reduce the amount of code change needed to switch uis over to the new OrdersModel,
// without changing any code in the actual new OrdersModel class.
static class OrdersModelExtensions {
#region Cancel Orders
#region Cancel Orders
public static async Task<bool> cancel_order(this OrdersModel orders_model, uint order_id) {
var tu = await orders_model.on_orders.FirstAsync();
if (tu.state.TryGetValue(order_id, out var o)) {
@@ -452,7 +479,7 @@ namespace mbts.spark {
var orders =
tu.state.Values
.Where(o => o.order.is_non_terminal() && pred(o.order))
.Select(async o => { return (o.order, await model.cancel_order(o.order)); } );
.Select(async o => { return (o.order, await model.cancel_order(o.order)); });
return await Task.WhenAll(orders);
}
@@ -464,8 +491,7 @@ namespace mbts.spark {
public static Task<(OrderRecordImmutable, bool)[]> cancel_security_sell_orders(this OrdersModel model, uint security) =>
model.cancel_orders_where(o => o.Def.SecurityKey == security && o.Def.Side == Side.Sell);
#endregion
#endregion
public static IObservable<bool> security_has_working_orders(this OrdersModel orders_model, uint security_key) =>
orders_model
@@ -475,26 +501,15 @@ namespace mbts.spark {
public static IObservable<double> security_total_position(this OrdersModel orders_model, uint security_key, string account) =>
orders_model.on_security_positions(security_key)
.Select((t,k) => {
.Select((t, k) => {
foreach (var kv in t.state) {
if (kv.Value.Item2.Account == account)
return kv.Value.Item2.total_pos();
return kv.Value.Item2.total_pos();
}
return double.NaN;
}).DistinctUntilChanged()
.StartWith(double.NaN);
// DO NOT REMOVE (BEGIN)
//public static IObservable<double> security_total_position(this OrdersModel orders_model, uint security_key, string account) =>
// // PositionUnit.Account is actually the clearing account; this code assumes that clearing account and account are equivalent.
// // However, it still must match account to PositionUnit.Account.Trimmed() because the exchange likes to slap spaces on the end.
// orders_model.on_security_positions(security_key)
// .filter(p => p.Account == account)
// // the state should always have 1 element, since the PositionUnit-identity is security + account
// .Scan(0.0, (pos, table) => pos + (table.added.Sum(p => p.total_pos()) - table.removed.Sum(p => p.total_pos())))
// .StartWith(double.NaN);
// DO NOT REMOVE (END)
public static IObservable<ITableUpdateTransform<OrderRecordImmutable, uint>> on_security_root_orders(this OrdersModel orders_model, uint security_key) =>
orders_model.on_security_orders(security_key).AddRemoveOnly().Scan(new RootOrderData(), (rod, table) => {
// ** using HashSet/List pairs, rather than a Dictionary, because TableUpdateTransform calls for IReadOnlyLists, while Dictionary.Values only implements IReadOnlyCollection.
@@ -562,14 +577,12 @@ namespace mbts.spark {
orders_model.studio_algos_with_meta.MergeTable(Observable.Return(
new TableUpdate<(AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>(
ImmutableDictionary<AlgoKey, (AlgoData algo, AlgoDefinitionMetadata? meta)>.Empty.AddRange(orders_model.factory.default_algos(OrderDataHelper.all_order_types).Select(kv => new KeyValuePair<AlgoKey, (AlgoData algo, AlgoDefinitionMetadata? meta)>(kv.Key, (kv.Value, null)))),
Array.Empty<(AlgoData,AlgoDefinitionMetadata?)>(),
Array.Empty<Change<(AlgoData,AlgoDefinitionMetadata?)>>(),
Array.Empty<(AlgoData,AlgoDefinitionMetadata?)>(),
Array.Empty<(AlgoData, AlgoDefinitionMetadata?)>(),
Array.Empty<Change<(AlgoData, AlgoDefinitionMetadata?)>>(),
Array.Empty<(AlgoData, AlgoDefinitionMetadata?)>(),
d => d.algo.key
).as_snapshot()));
//public static IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_all_algos_with_meta(this OrdersModel orders_model) =>
public static async Task<OrderRecordImmutable?> order_from_key(this OrdersModel orders_model, uint order_key) {
(var tu, var success) = await orders_model.on_orders.FirstAsyncTask(t => t.state.ContainsKey(order_key), TimeSpan.FromSeconds(3));
if (success && tu.state.TryGetValue(order_key, out var rec)) {
@@ -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
@@ -339,7 +339,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="6,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));
}
}
}
}
}
@@ -110,6 +110,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 -1
View File
@@ -17,7 +17,6 @@ namespace mbts.spark {
#endregion
#region Order
public static ITag<(OrderDefinitionImmutable definition, double price, uint size)> CreateOrder { get; } = new SparkTag<(OrderDefinitionImmutable, double, uint)>();
public static ITag<IEnumerable<(OrderDefinitionImmutable definition, double price, uint size)>> RecreateOrder { get; } = new SparkTag<IEnumerable<(OrderDefinitionImmutable, double, uint)>>();
public static ITag<IEnumerable<uint>> CancelOrders { get; } = new SparkTag<IEnumerable<uint>>();
@@ -79,6 +78,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> {
@@ -200,17 +200,20 @@ namespace mbts.collections {
public ObservableCollectionEx<T> selected_items { get; } = new ObservableCollectionEx<T>();
public IEnumerable<TKey> selected_keys => selected_items.Select(_key_selector);
public bool try_select(TKey key) {
public bool try_select(TKey key) =>
try_select(key, true);
public bool try_select(TKey key, bool reattempt_on_collection_change) {
if (items.TryGetFirst(i => _key_selector(i).Equals(key), out T result)) {
selected_items.Add(result);
return true;
} else {
} else if (reattempt_on_collection_change) {
if (_desired_keys.Count == 0) {
items.CollectionChanged += _on_items_CollectionChanged;
}
_desired_keys.Add(key);
return false;
}
return false;
}
void _on_items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
@@ -1,4 +1,5 @@
using System.Windows.Media;
using System.Collections.Generic;
using System.Windows.Media;
namespace mbts.ui {
// Usage
@@ -148,6 +149,21 @@ namespace mbts.ui {
public static Color anchor_color => Colors.Gold;
public static Color profit_color => Colors.Green;
public static Color loss_color => BTSColors.red;
public static Color header_row_background_color => 0xFFAAAAAA.argb_color();
// group background colors
public static Color group_row_background_color1 => 0xFFE9E9E9.argb_color();
public static Color group_row_background_color2 => 0xFFCACACA.argb_color();
public static Color group_row_background_color3 => 0xFF9DB0C1.argb_color();
public static Color group_row_background_color4 => group_row_background_color1;
public static IEnumerable<Color> group_alternating_background_colors {
get {
yield return group_row_background_color1;
yield return group_row_background_color2;
yield return group_row_background_color3;
yield return group_row_background_color4;
}
}
}
public static class Orders {
@@ -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,91 @@
using bts.shell.pnl;
using bts.utils;
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 IFillsGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
interface IMarketViewGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
interface IPAndLGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
interface IPNLSettingsMigrationRule : ISettingsMigrationRule<PNLSettings> { }
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, IImmutableProperty<TOuter, TInner> propi) =>
new SelectorWrapper<TInner, TOuter>(rule, propi.get, propi.set);
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) {
if (settings != null) {
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;
}
}
}
}
@@ -59,6 +59,8 @@ namespace mbts.spark {
bts.shell.orderbook.OrderBookGridSettings _order_book_grid;
[ProtoMember(13)]
PNLSettings _pnl;
[ProtoMember(14)]
GlobalAlertSettings _alerts;
public UserSettings() { }
@@ -77,6 +79,8 @@ namespace mbts.spark {
public PNLSettings pnl => _pnl ?? (_pnl = PNLSettings.Default);
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,
@@ -86,7 +90,8 @@ namespace mbts.spark {
GridSettings provided_synthetic_management_gridsettings = null,
GridSettings provided_pnl_gridsettings = null,
PNLSettings pnl = 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,
@@ -97,6 +102,7 @@ namespace mbts.spark {
_pnl_grid = provided_pnl_gridsettings ?? _pnl_grid,
_pnl = pnl ?? _pnl,
_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;
@@ -181,4 +187,4 @@ namespace mbts.spark {
public TimeSpan? duration => _mode == Mode.Flash ? _duration?.ToTimeSpan() ?? TimeSpan.FromSeconds(5) : new TimeSpan?();
public double max_height => _max_height;
}
}
}
@@ -1,6 +1,9 @@
using bts;
using bts.settings;
using bts.shell.pnl;
using bts.utils;
using mbts.spark;
using mbts.ui.settings;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
@@ -18,20 +21,51 @@ namespace mbts.persist {
class UserSettingsHub {
// do NOT CHANGE these consts, they represent a file path on the server
const string NAME_SPACE = "settings", DEFAULT_KEY = "default";
IUserPersist _persist;
UserSettings _default_settings;
readonly BehaviorSubject<UserSettings> _default_settings_subject = new BehaviorSubject<UserSettings>(UserSettings.factory_defaults);
static readonly IImmutableProperty<UserSettings, GridSettings>
_fills_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.fills_grid, (us, gs) => us.with(provided_fills_gridsettings: gs)),
_mview_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.market_view_grid, (us, gs) => us.with(provided_market_view_gridsettings: gs)),
_pandl_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.pnl_grid, (us, gs) => us.with(provided_pnl_gridsettings: gs));
static readonly IImmutableProperty<UserSettings, PNLSettings>
_pnl_propi = ImmutableProperty.create<UserSettings, PNLSettings>(us => us.pnl, (us, pnl) => us.with(pnl: pnl));
readonly object _default_settings_lock = new object();
readonly BehaviorSubject<UserSettings> _default_settings_subject = new BehaviorSubject<UserSettings>(UserSettings.factory_defaults);
readonly IEnumerable<IFillsGridSettingsMigrationRule> _fills_migration_rules;
readonly IEnumerable<IMarketViewGridSettingsMigrationRule> _mview_migration_rules;
readonly IEnumerable<IPAndLGridSettingsMigrationRule> _pandl_migration_rules;
readonly IEnumerable<IPNLSettingsMigrationRule> _pnl_migration_rules;
readonly IUserPersist _persist;
UserSettings _default_settings;
[ImportingConstructor]
public UserSettingsHub(IUserPersist persist) {
public UserSettingsHub(
IUserPersist persist,
[ImportMany]IEnumerable<IFillsGridSettingsMigrationRule> fills_migration_rules,
[ImportMany]IEnumerable<IMarketViewGridSettingsMigrationRule> mview_migration_rules,
[ImportMany]IEnumerable<IPAndLGridSettingsMigrationRule> pandl_migration_rules,
[ImportMany]IEnumerable<IPNLSettingsMigrationRule> pnl_migration_rules) {
//
_persist = persist;
_fills_migration_rules = fills_migration_rules ?? Enumerable.Empty<IFillsGridSettingsMigrationRule>();
_mview_migration_rules = mview_migration_rules ?? Enumerable.Empty<IMarketViewGridSettingsMigrationRule>();
_pandl_migration_rules = pandl_migration_rules ?? Enumerable.Empty<IPAndLGridSettingsMigrationRule>();
_pnl_migration_rules = pnl_migration_rules ?? Enumerable.Empty<IPNLSettingsMigrationRule>();
init();
async void init() {
UserSettings s;
try {
s = await _persist.get<UserSettings>(NAME_SPACE, DEFAULT_KEY);
if (s != null) {
var wrapped_rules = new[] {
_fills_migration_rules.merge().wrap(_fills_propi),
_mview_migration_rules.merge().wrap(_mview_propi),
_pandl_migration_rules.merge().wrap(_pandl_propi)
};
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>();
@@ -52,7 +86,7 @@ namespace mbts.persist {
if (!sorted_lps.SequenceEqual(s.ladder_profiles))
await _persist.set(NAME_SPACE, DEFAULT_KEY, s = s.with(ladder_profiles: sorted_lps));
}
} catch(Exception ex) {
} catch (Exception ex) {
Log.Persist.warn($"Error loading {typeof(UserSettings)} from namespace '{NAME_SPACE}', will restore factory defaults and continue as usual: {ex}");
await save_default(s = UserSettings.factory_defaults);
}
@@ -67,10 +101,6 @@ namespace mbts.persist {
// indicates whether or not the settings have been loaded from the server (this happens once, on startup - see the constructor for details)
public bool initialized { get; private set; }
/// Subscribing via _persist.subscribe may be cleaner in theory, however using a local event allows observers to be notified immediately.
/// Assuming there aren't any rogue agents saving UserSettings by themselves, there should never be any discrepancies.
public IObservable<UserSettings> subscribe_default_settings() => _default_settings_subject.AsObservable();
internal UserSettings current {
get {
lock (_default_settings_lock)
@@ -78,6 +108,34 @@ namespace mbts.persist {
}
}
/// Subscribing via _persist.subscribe may be cleaner in theory, however using a local event allows observers to be notified immediately.
/// Assuming there aren't any rogue agents saving UserSettings by themselves, there should never be any discrepancies.
public IObservable<UserSettings> subscribe_default_settings() => _default_settings_subject.AsObservable();
public IWindowSettingsManager<GridSettings> create_fills_settings_manager(GridSettings instance_settings) =>
_create_settings_manager(
instance_settings,
_fills_migration_rules,
_fills_propi);
public IWindowSettingsManager<GridSettings> create_mview_settings_manager(GridSettings instance_settings) =>
_create_settings_manager(
instance_settings,
_mview_migration_rules,
_mview_propi);
public IWindowSettingsManager<GridSettings> create_pandl_settings_manager(GridSettings instance_settings) =>
_create_settings_manager(
instance_settings,
_pandl_migration_rules,
_pandl_propi);
public IWindowSettingsManager<PNLSettings> create_pnl_settings_manager(PNLSettings instance_settings) =>
_create_settings_manager(
instance_settings,
_pnl_migration_rules,
_pnl_propi);
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,11 +144,17 @@ namespace mbts.persist {
await _persist_async();
}
IWindowSettingsManager<T> _create_settings_manager<T>(T instance_settings, IEnumerable<ISettingsMigrationRule<T>> migration_rules, IImmutableProperty<UserSettings, T> propi) 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, propi.get, propi.set);
}
async Task _persist_async() {
try {
await _persist.set(NAME_SPACE, DEFAULT_KEY, _default_settings);
} catch(Exception ex) {
Log.Persist.warn($"Error persisting {typeof(UserSettings)} in namespace '{NAME_SPACE}': {ex}");
} catch (Exception ex) {
Log.Persist.warn($"Error persisting {typeof(UserSettings)} in namespace '{NAME_SPACE}': {ex}");
}
}
}
@@ -0,0 +1,31 @@
using System;
namespace bts.utils {
/// <summary>
/// A property-like interface (with get/set accessors) in the immutable pattern. The 'set' method returns a new instance of <typeparamref name="TSource"/>, rather than mutating the original.
/// </summary>
/// <typeparam name="TSource">The type of source, from which the value is read, and to which it is written.</typeparam>
/// <typeparam name="T">The type of value read from, and written to, the source.</typeparam>
interface IImmutableProperty<TSource, T> {
T get(TSource source);
TSource set(TSource source, T value);
}
static class ImmutableProperty {
public static IImmutableProperty<TSource, T> create<TSource, T>(Func<TSource, T> get, Func<TSource, T, TSource> set) =>
new Impl<TSource, T>(get, set);
class Impl<TSource, T> : IImmutableProperty<TSource, T> {
readonly Func<TSource, T> _get;
readonly Func<TSource, T, TSource> _set;
public Impl(Func<TSource, T> get, Func<TSource, T, TSource> set) {
_get = get;
_set = set;
}
public T get(TSource source) => _get(source);
public TSource set(TSource source, T value) => _set(source, value);
}
}
}
@@ -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) =>
@@ -1,4 +1,5 @@
using mbts.ui;
using bts.utils;
using mbts.ui;
using mbts.ui.utility.text;
using ProtoBuf;
using System;
@@ -9,49 +10,56 @@ using FontFamily = System.Windows.Media.FontFamily;
namespace bts.proto {
[ProtoContract]
class FontParamsProto : IEquatable<FontParamsProto> {
static readonly int _type_hash = typeof(FontParamsProto).GetHashCode();
[ProtoMember(1)]
public string font_family { get; set; }
public TypefaceProto typeface { get; set; }
[ProtoMember(2)]
public int font_weight { get; set; }
public double font_size { get; set; }
[ProtoMember(3)]
public uint font_color { get; set; }
[ProtoMember(4)]
public double font_size { get; set; }
public (FontFamily family, FontWeight? weight, Color? color, double size) to_wpf_params() =>
(string.IsNullOrEmpty(font_family) ? null : new FontFamily(font_family),
font_weight == 0 ? (FontWeight?)null : FontWeight.FromOpenTypeWeight(font_weight),
font_color == 0 ? (Color?)null : font_color.argb_color(),
// zero is an invalid font size, convert to nan to follow convention
font_size == 0 ? double.NaN : font_size);
public (FontFamily family, FontWeight weight, Color color, double size) to_wpf_params_safe(
FontFamily fallback_family = null,
FontWeight? fallback_weight = null,
Color? fallback_color = null,
double fallback_size = double.NaN) {
//
var (family, weight, color, size) = to_wpf_params();
return (
family ?? fallback_family ?? TextUtils.StandardFontFamily,
weight ?? fallback_weight ?? FontWeights.Normal,
color ?? fallback_color ?? Colors.Black,
double.IsNaN(font_size) ? double.IsNaN(fallback_size) ? FontSizes.Small : fallback_size : font_size);
}
public string font_family => typeface.font_family;
public int font_weight => typeface.font_weight;
public bool Equals(FontParamsProto other) =>
other != null &&
font_family == other.font_family &&
font_weight == other.font_weight &&
font_color == other.font_color &&
font_size.Equals(other.font_size);
typeface == other.typeface &&
font_size == other.font_size &&
font_color == other.font_color;
public override bool Equals(object obj) =>
Equals(obj as FontParamsProto);
public override int GetHashCode() =>
_type_hash;
typeface.GetHashCode();
}
static class FontParamsProtoExtensions {
public static (FontFamily family, FontWeight? weight, Color? color, double size) to_wpf_params(this FontParamsProto proto) =>
proto.typeface.to_typeface_parts().with(
_to_nullable_color(proto.font_color),
// zero/negative are invalid font sizes, convert to nan to follow convention
proto.font_size <= 0 ? double.NaN : proto.font_size);
public static (FontFamily family, FontWeight? weight, Color? color, double size) to_wpf_params_safe(this FontParamsProto proto,
FontFamily fallback_family = null,
FontWeight? fallback_weight = null,
Color? fallback_color = null,
double fallback_size = double.NaN) =>
//
proto.typeface.to_typeface_parts_safe(fallback_family, fallback_weight).with(
_to_color_safe(proto.font_color, fallback_color ?? Colors.Black),
_to_size_safe(proto.font_size));
static Color? _to_nullable_color(uint proto) =>
proto == 0 ? (Color?)null : proto.argb_color();
static Color _to_color_safe(uint proto, Color fallback_color) =>
_to_nullable_color(proto) ?? fallback_color;
static double _to_size_safe(double size, double fallback_size = double.NaN) =>
double.IsNaN(size) ?
(double.IsNaN(fallback_size) ? FontSizes.Small : fallback_size) :
size;
}
}
@@ -31,4 +31,4 @@ namespace bts.proto {
public static bool operator ==(NullableColorProto a, NullableColorProto b) => a.Equals(b);
public static bool operator !=(NullableColorProto a, NullableColorProto b) => !a.Equals(b);
}
}
}
@@ -0,0 +1,61 @@
using bts.utils;
using mbts.ui.utility.text;
using ProtoBuf;
using System;
using System.Windows;
using FontFamily = System.Windows.Media.FontFamily;
namespace bts.proto {
[ProtoContract]
readonly struct TypefaceProto : IEquatable<TypefaceProto> {
static readonly int _type_hash = typeof(TypefaceProto).GetHashCode();
[ProtoMember(1)]
public readonly string font_family;
[ProtoMember(2)]
public readonly int font_weight;
// TODO (maybe): add property for FontStyle (italic, underline, etc.)
public TypefaceProto(string font_family, int font_weight) {
this.font_family = font_family;
this.font_weight = font_weight;
}
public TypefaceProto with(string font_family = null, int? font_weight = null, double font_size = double.NaN) =>
new TypefaceProto(
font_family ?? this.font_family,
font_weight ?? this.font_weight);
public bool Equals(TypefaceProto other) =>
font_family == other.font_family &&
font_weight == other.font_weight;
public override bool Equals(object obj) =>
obj is TypefaceProto tfp && Equals(tfp);
public override int GetHashCode() =>
EqualityComparer.auto_phase(
font_family?.GetHashCode() ?? 0,
font_weight);
public static bool operator ==(TypefaceProto x, TypefaceProto y) => x.Equals(y);
public static bool operator !=(TypefaceProto x, TypefaceProto y) => !x.Equals(y);
}
static class TypefaceProtoExtensions {
public static (FontFamily family, FontWeight? weight) to_typeface_parts(this TypefaceProto proto) =>
(string.IsNullOrEmpty(proto.font_family) ? null : new FontFamily(proto.font_family),
proto.font_weight == 0 ? (FontWeight?)null : FontWeight.FromOpenTypeWeight(proto.font_weight));
public static (FontFamily family, FontWeight weight) to_typeface_parts_safe(
this TypefaceProto proto,
FontFamily fallback_family = null,
FontWeight? fallback_weight = null) {
//
var (family, weight) = proto.to_typeface_parts();
return (
family ?? fallback_family ?? TextUtils.StandardFontFamily,
weight ?? fallback_weight ?? FontWeights.Normal);
}
}
}
+3
View File
@@ -113,6 +113,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>
@@ -173,14 +173,14 @@ namespace mbts.ui.controls.adorners {
double rltv_x, rltv_y;
switch (_horizontalPosition) {
case HorizontalPosition.Left: rltv_x = -el_width; break;
case HorizontalPosition.Middle: rltv_x = 0.5 * (_popupTarget.ActualWidth - el_width); break;
case HorizontalPosition.Right: rltv_x = _popupTarget.ActualWidth + el_width; break;
case HorizontalPosition.Middle: rltv_x = 0.5 * _popupTarget.ActualWidth; break;
case HorizontalPosition.Right: rltv_x = _popupTarget.ActualWidth; break;
default: throw new NotImplementedException();
}
switch (_verticalPosition) {
case VerticalPosition.Top: rltv_y = -el_height; break;
case VerticalPosition.Middle: rltv_y = 0.5 * (_popupTarget.ActualHeight - el_height); break;
case VerticalPosition.Bottom: rltv_y = _popupTarget.ActualHeight + el_height; break;
case VerticalPosition.Middle: rltv_y = 0.5 * _popupTarget.ActualHeight; break;
case VerticalPosition.Bottom: rltv_y = _popupTarget.ActualHeight; break;
default: throw new NotImplementedException();
}
tl = new Point(tl.X + rltv_x + _horizontalOffset, tl.Y + rltv_y + _verticalOffset);
@@ -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();
}
}
}
@@ -146,7 +146,7 @@
<cp:NamedColorCellEditingTemplateSelector>
<cp:NamedColorCellEditingTemplateSelector.EditableTemplate>
<DataTemplate>
<TextBox Text="{Binding name}" u:TextBoxBehavior.CommitOnEnter="True" u:TextBoxBehavior.SelectAllOnFocus="True" u:TextBoxBehavior.FocusOnLoaded="True"/>
<TextBox Text="{Binding name}" u:TextBoxBehavior.CommitOnEnter="True" u:TextBoxBehavior.SelectAllOnFocus="True" u:Behavior.FocusOnLoaded="True"/>
</DataTemplate>
</cp:NamedColorCellEditingTemplateSelector.EditableTemplate>
<cp:NamedColorCellEditingTemplateSelector.ReadOnlyTemplate>
@@ -84,4 +84,16 @@ namespace mbts.ui.controls {
}
public ColorOpacityProto to_proto() => new ColorOpacityProto(color.rgb_color(), opacity);
}
class NamedColorPickerPopupVM : ColorPickerPopupVM {
object _name;
public object name {
get => _name;
set => Set(ref _name, value);
}
public override string ToString() =>
_name == null ? color.ToHexString() : $"{_name}:{color.ToHexString()}";
}
}
@@ -80,7 +80,7 @@ namespace mbts.ui.controls {
new[] {
new ColumnDefinition { Width = new GridLength(visual_split.HorizontalSplit.Left, GridUnitType.Star) },
new ColumnDefinition { Width = new GridLength(visual_split.HorizontalSplit.Right, GridUnitType.Star) },
new ColumnDefinition { MinWidth = 30, Width = GridLength.Auto }
new ColumnDefinition { MinWidth = 34, Width = GridLength.Auto }
});
break;
default:
@@ -361,11 +361,6 @@ namespace mbts.ui.controls {
}
drawingContext.Pop();
}
if (bg != null) {
var bgo = DIYGrid.GetBackgroundOverlay(this);
if (bgo != null)
drawingContext.DrawRectangle(bgo, null, rect);
}
}
static Thickness _underline_padding(Thickness padding) =>
@@ -393,4 +388,4 @@ namespace mbts.ui.controls {
InvalidateVisual();
}
}
}
}
@@ -70,6 +70,8 @@ namespace mbts.ui.controls {
Symbol = 72,
Security = 87,
Qty = 42,
// deafult RawDayQty headers are too big for [normal]Qty width
RawDayQty = 51,
Price = 64,
PriceBig = 92,
SecurityType = 64,
@@ -130,6 +132,7 @@ namespace mbts.ui.controls {
StartPnL = "Open PnL",
DayLong = "Day Long",
DayShort = "Day Short",
RawDayPos = "Raw Day Pos",
DayPnL = "Day PnL",
TotalNetPos = "Total Net Pos",
TotalPnL = "Total PnL",
@@ -154,13 +157,13 @@ namespace mbts.ui.controls {
Short = "Short",
Net = "Pos",
AvgPrice = "Avg",
AvgPriceDelta = "Avg Delta",
HasDayTrades = "Traded Today",
SecurityType = "Type";
public static readonly Part
SOD = new Part("Open"),
Day = new Part("Day"),
RawDay = new Part("Raw Day"),
Total = new Part("Total"),
Working = new Part("Working");
@@ -172,13 +175,10 @@ namespace mbts.ui.controls {
public string PNL => $"{prefix} PNL";
public string Net => $"{prefix} {PNLHeaders.Net}";
public string NetAvgDelta => $"{prefix} Net {AvgPriceDelta}";
public string Long => $"{prefix} {PNLHeaders.Long}";
public string LongAvg => $"{Long} {AvgPrice}";
public string LongAvgDelta => $"{Long} {AvgPriceDelta}";
public string Short => $"{prefix} {PNLHeaders.Short}";
public string ShortAvg => $"{Short} {AvgPrice}";
public string ShortAvgDelta => $"{Short} {AvgPriceDelta}";
}
}
}
@@ -217,7 +217,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
@@ -226,10 +226,12 @@ namespace mbts.ui.controls {
GridColumnSettings _settings;
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;
bool _is_visible;
@@ -237,6 +239,7 @@ namespace mbts.ui.controls {
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;
@@ -279,6 +282,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();
public GridColumnSettings settings => _settings ?? (_settings = create());
// 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.
@@ -291,7 +297,7 @@ namespace mbts.ui.controls {
/// important: don't access any dependency props in here.
/// this may be called from a thread other than that of this dispatcher (i.e., when <see cref="IShellWindow.GetState(System.IO.Stream)"/> is called).
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;
@@ -303,7 +309,7 @@ namespace mbts.ui.controls {
return;
_settings = settings;
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))
@@ -314,21 +320,22 @@ 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;
Width = original_width;
}
//
raise_prop_changed(nameof(ColumnBase.settings));
}
// '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();
@@ -375,4 +382,4 @@ namespace mbts.ui.controls {
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;
}
}
@@ -16,7 +16,7 @@ namespace mbts.ui.controls {
}
public IDataGridColumnFilter filter { get; }
public virtual string filter_template => DataGridFilterKnownTemplates.DiscreteValues;
public virtual string filter_template => DataGridFilterKnownTemplates.AlgoType;
public BindingBase font_weight_binding { get; set; }
public override FrameworkElement view_factory() {
@@ -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; }
+107 -26
View File
@@ -9,6 +9,7 @@ using mbts.utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
@@ -50,6 +51,61 @@ namespace mbts.ui.controls {
delegate void GroupItemSelectionPivotEventHandler(object sender, GroupItemSelectionPivotEventArgs e);
static class DIYGridSortHelper {
public static IComparer<TRow> build_comparer<TColumnKey, TRow>(IEnumerable<(TColumnKey key, ListSortDirection direction)> sorting_hierarchy, Func<TColumnKey, IComparer<TRow>> get_column_comparer, params TColumnKey[] primary_key) {
// ensure primary key is included for deterministic sort (combination of account/security)
for (int i = 0; i < primary_key.Length; i++) {
var key = primary_key[i];
if (!sorting_hierarchy.Any(tup => tup.key.Equals(key)))
sorting_hierarchy = sorting_hierarchy.Append((key, ListSortDirection.Ascending));
}
return new ComparerBuilder<TRow>(
sorting_hierarchy
.Select(tup => {
var col_comp = get_column_comparer(tup.key);
return tup.direction == ListSortDirection.Ascending ? col_comp : col_comp.reverse();
}))
.to_comparer();
}
public static IEnumerable<(TKey key, ListSortDirection direction)> convert_sorting_criterion<TKey>(IEnumerable<SortingCriteria> sorting_criterion) where TKey : struct {
foreach (var sc in sorting_criterion) {
if (Enum.TryParse(sc.col_name, false, out TKey key))
yield return (key, sc.direction());
}
}
public static IObservable<ImmutableList<(TKey key, IComparer<TRow> comparer, ListSortDirection direction)>> observe_active_comparers<TKey, TRow>(
DIYGrid grid, Func<TKey, IComparer<TRow>> get_comparer, bool mark_handled = true) =>
//
Observable.Create<ImmutableList<(TKey key, IComparer<TRow> comparer, ListSortDirection direction)>>(o => {
var active_comparers = ImmutableList<(TKey key, IComparer<TRow> comp, ListSortDirection dir)>.Empty;
grid.Sorting += on_sorting;
return Disposable.Create(() => grid.Sorting -= on_sorting);
void on_sorting(object sender, DIYGridSortingEventArgs e) {
if (mark_handled)
e.Handled = true;
if (e.Column is DIYGrid.IKeyedColumn<TKey> kc) {
var comp = get_comparer(kc.key);
if (comp == null)
return;
var dir = e.Column.SortDirection;
var next = dir.HasValue && dir.Value == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending;
if (!KeyboardExtensions.IsShiftDown()) {
e.Column.SortDirection = next;
active_comparers = active_comparers.Clear();
active_comparers = active_comparers.Add((kc.key, comp, next));
} else {
if (active_comparers.TryGetFirst(tup => tup.key.Equals(kc.key), out var match, out int i)) {
active_comparers = active_comparers.SetItem(i, (kc.key, comp, next));
} else {
active_comparers = active_comparers.Add((kc.key, comp, next));
}
}
}
o.OnNext(active_comparers);
}
});
/// Important: All columns MUST IMPLEMENT <see cref="DIYGrid.IKeyedColumn{TColKey}"/>.
public static IReadOnlyList<(TColKey col, ListSortDirection direction)> get_sorting_data<TColKey>(List<(DIYGrid.ColumnBase col, TColKey key, ListSortDirection direction)> active_comparers, TColKey primary_key) {
var rv = new List<(TColKey col, ListSortDirection direction)>();
@@ -76,6 +132,14 @@ namespace mbts.ui.controls {
public static IComparer<T> reverse_if_descending<T>(IComparer<T> comp, ListSortDirection direction) =>
direction == ListSortDirection.Descending ? comp.reverse() : comp;
public static void set_sort_directions<TColumnKey>(IEnumerable<(TColumnKey key, ListSortDirection direction)> sorting_hierarchy, IEnumerable<DIYGrid.ColumnBase> columns) {
foreach (var col in columns) {
col.SortDirection = col is DIYGrid.IKeyedColumn<TColumnKey> kc && sorting_hierarchy.TryGetFirst(tup => tup.key.Equals(kc.key), out var match) ?
match.direction :
(ListSortDirection?)null;
}
}
/// Important: All columns MUST IMPLEMENT <see cref="DIYGrid.IKeyedColumn{TColKey}"/>.
static IReadOnlyList<(TColKey col, ListSortDirection direction)> _on_sorting<TColKey>(DIYGridSortingEventArgs e, ref List<(DIYGrid.ColumnBase col, TColKey key, ListSortDirection direction)> active_comparers, TColKey primary_key, bool clear_current) {
if (e.Column is DIYGrid.IKeyedColumn<TColKey> col) {
@@ -143,6 +207,7 @@ namespace mbts.ui.controls {
ScrollContentPresenter _content_presenter_backing;
DIYGrid _grid;
DragRectAdorner _drag_rect_adorner;
RowSelectionAdorner _rsa;
ScrollViewer _scroll_viewer;
Point? _drag_start;
double _drag_start_vertical_drift;
@@ -169,12 +234,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;
void on_support_group_items_changed(bool v) => _support_group_items = v;
@@ -355,6 +426,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);
@@ -392,33 +469,37 @@ namespace mbts.ui.controls {
CommandBindings.Add(new CommandBinding(ApplicationCommands.Copy, OnCopy, OnCopyCanExecute));
CommandBindings.Add(new CommandBinding(SortCommand, (s, e) => {
if (e.Parameter is GridViewColumnHeader col_header && col_header.Column is ColumnBase cb) {
Sorting?.Invoke(this, new DIYGridSortingEventArgs(cb));
if (!e.Handled) {
if (Rows is ICollectionView cv_rows) {
using (((INotifyCollectionChanged)cv_rows.SortDescriptions).SuspendHandler(_sort_descriptions_collection_changed_cb))
using (cv_rows.DeferRefresh()) {
if (cb.sort_member_path is string path) {
ListSortDirection? curr = cb.SortDirection;
var next = curr.HasValue && curr.Value == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending;
if (!KeyboardExtensions.IsShiftDown()) {
foreach (var sd in cv_rows.SortDescriptions)
if (sort_map.TryGetValue(sd.PropertyName, out ColumnBase match)) {
match.SortDirection = null;
sort_map.Remove(sd.PropertyName);
}
cv_rows.SortDescriptions.Clear();
cb.SortDirection = next;
cv_rows.SortDescriptions.Add(new SortDescription(path, next));
} else {
var sort_desc = new SortDescription(path, next);
int i = cv_rows.SortDescriptions.IndexOf(sort_desc, EqualityComparer.Create<SortDescription, string>(x => x.PropertyName));
cb.SortDirection = next;
if (i >= 0) {
cv_rows.SortDescriptions[i] = sort_desc;
} else cv_rows.SortDescriptions.Add(new SortDescription(path, next));
}
sort_map[path] = cb;
var sorting_event = Sorting;
if (sorting_event != null) {
var sorting_args = new DIYGridSortingEventArgs(cb);
sorting_event(this, sorting_args);
if (sorting_args.Handled)
return;
}
if (Rows is ICollectionView cv_rows) {
using (((INotifyCollectionChanged)cv_rows.SortDescriptions).SuspendHandler(_sort_descriptions_collection_changed_cb))
using (cv_rows.DeferRefresh()) {
if (cb.sort_member_path is string path) {
ListSortDirection? curr = cb.SortDirection;
var next = curr.HasValue && curr.Value == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending;
if (!KeyboardExtensions.IsShiftDown()) {
foreach (var sd in cv_rows.SortDescriptions)
if (sort_map.TryGetValue(sd.PropertyName, out ColumnBase match)) {
match.SortDirection = null;
sort_map.Remove(sd.PropertyName);
}
cv_rows.SortDescriptions.Clear();
cb.SortDirection = next;
cv_rows.SortDescriptions.Add(new SortDescription(path, next));
} else {
var sort_desc = new SortDescription(path, next);
int i = cv_rows.SortDescriptions.IndexOf(sort_desc, EqualityComparer.Create<SortDescription, string>(x => x.PropertyName));
cb.SortDirection = next;
if (i >= 0) {
cv_rows.SortDescriptions[i] = sort_desc;
} else cv_rows.SortDescriptions.Add(new SortDescription(path, next));
}
sort_map[path] = cb;
}
}
}
@@ -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 c: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=c:DIYGrid}}"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
@@ -66,6 +50,9 @@
</Grid>
</DataTemplate>
<Style TargetType="c: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">
@@ -82,6 +69,7 @@
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="Height" Value="{x:Static c:DIYGrid.DefaultColumnHeaderHeight}"/>
<Setter Property="UseLayoutRounding" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GridViewColumnHeader}">
@@ -310,95 +298,101 @@
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type c:DIYGrid}">
<Grid>
<u:StateManager.VisualState>
<Binding Path="FrozenColumnCount" RelativeSource="{RelativeSource TemplatedParent}">
<Binding.Converter>
<cv:ValueMap Default="Active">
<cv:KeyValue Key="{u:Int 0}" Value="Inactive"/>
</cv:ValueMap>
</Binding.Converter>
</Binding>
</u:StateManager.VisualState>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="FrozenColumnHeaderStates">
<VisualState Name="Active"/>
<VisualState Name="Inactive">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="_header_frozen">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="_super_header_frozen">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<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 x:Name="_super_header_frozen" Fill="{u: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}"/>
<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="{u: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>
<c:DIYGridRowsPresenter
x:Name="_row_presenter"
Grid.Row="2"
Grid.ColumnSpan="2"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingPanel.VirtualizationMode="Recycling"
ScrollViewer.CanContentScroll="True"
VirtualizingPanel.ScrollUnit="{TemplateBinding VirtualizingPanel.ScrollUnit}"
AlternationCount="2"
Background="{TemplateBinding Background}"
BorderThickness="0"
ItemsSource="{TemplateBinding Rows}"
ItemTemplate="{TemplateBinding RowTemplate}"
SelectedItem="{Binding CurrentRow, RelativeSource={RelativeSource TemplatedParent}}"
SelectionMode="{TemplateBinding SelectionMode}"
>
<c:DIYGridRowsPresenter.Resources>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource DIYGridRowStyle}"/>
</c:DIYGridRowsPresenter.Resources>
<c:DIYGridRowsPresenter.Template>
<ControlTemplate TargetType="{x:Type c:DIYGridRowsPresenter}">
<ScrollViewer Focusable="False">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</ScrollViewer>
</ControlTemplate>
</c:DIYGridRowsPresenter.Template>
</c: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>
<u:StateManager.VisualState>
<Binding Path="FrozenColumnCount" RelativeSource="{RelativeSource TemplatedParent}">
<Binding.Converter>
<cv:ValueMap Default="Active">
<cv:KeyValue Key="{u:Int 0}" Value="Inactive"/>
</cv:ValueMap>
</Binding.Converter>
</Binding>
</u:StateManager.VisualState>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="FrozenColumnHeaderStates">
<VisualState Name="Active"/>
<VisualState Name="Inactive">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="_header_frozen">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="_super_header_frozen">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<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 x:Name="_super_header_frozen" Fill="{u: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}"/>
<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="{u: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>
<c:DIYGridRowsPresenter
x:Name="_row_presenter"
Grid.Row="2"
Grid.ColumnSpan="2"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingPanel.VirtualizationMode="Recycling"
ScrollViewer.CanContentScroll="True"
VirtualizingPanel.ScrollUnit="{TemplateBinding VirtualizingPanel.ScrollUnit}"
AlternationCount="2"
Background="{TemplateBinding Background}"
BorderThickness="0"
ItemsSource="{TemplateBinding Rows}"
ItemTemplate="{TemplateBinding RowTemplate}"
SelectedItem="{Binding CurrentRow, RelativeSource={RelativeSource TemplatedParent}}"
SelectionMode="{TemplateBinding SelectionMode}"
>
<c:DIYGridRowsPresenter.Resources>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource DIYGridRowStyle}"/>
</c:DIYGridRowsPresenter.Resources>
<c:DIYGridRowsPresenter.Template>
<ControlTemplate TargetType="{x:Type c:DIYGridRowsPresenter}">
<ScrollViewer Focusable="False">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</ScrollViewer>
</ControlTemplate>
</c:DIYGridRowsPresenter.Template>
</c: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>
</Style>
</ResourceDictionary>
</ResourceDictionary>
@@ -61,7 +61,7 @@ namespace mbts.ui.settings {
readonly double _horizontal_offset;
[ProtoMember(10)]
readonly NullableColorProto _background_override;
readonly NullableColorProto _background_color_override;
public GridColumnSettings() {
_is_visible = _default_is_visible;
@@ -72,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, Color? background_override = null) {
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;
@@ -82,7 +82,7 @@ namespace mbts.ui.settings {
_width = width;
_horizontal_alignment = horizontal_alignment;
_horizontal_offset = horizontal_offset;
_background_override = new NullableColorProto(background_override);
_background_color_override = new NullableColorProto(background_color_override);
}
public string id => _id;
@@ -90,7 +90,7 @@ namespace mbts.ui.settings {
public bool is_collapsed => _is_collapsed;
public FontWeight? font_weight_override => _font_weight_override.value;
public Color? font_color_override => _font_color_override.value;
public Color? background_override => _background_override.argb;
public Color? background_color_override => _background_color_override.argb;
public double width => _width;
public HorizontalAlignment horizontal_alignment => _horizontal_alignment;
public double horizontal_offset => _horizontal_offset;
@@ -98,7 +98,7 @@ namespace mbts.ui.settings {
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, NullableColorProto? background_override = 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,
@@ -109,7 +109,7 @@ namespace mbts.ui.settings {
width ?? this.width,
horizontal_alignment ?? this.horizontal_alignment,
horizontal_offset ?? this.horizontal_offset,
(background_override ?? _background_override).argb);
(background_color_override ?? _background_color_override).argb);
public bool Equals(GridColumnSettings other) =>
other != null &&
@@ -119,6 +119,7 @@ namespace mbts.ui.settings {
is_collapsed == other.is_collapsed &&
font_weight_override.Equals(other.font_weight_override) &&
font_color_override.Equals(other.font_color_override) &&
background_color_override.Equals(other.background_color_override) &&
width == other.width &&
horizontal_alignment == other.horizontal_alignment &&
horizontal_offset == other.horizontal_offset &&
@@ -135,6 +136,7 @@ namespace mbts.ui.settings {
is_collapsed.GetHashCode(),
font_weight_override.GetHashCode(),
font_color_override.GetHashCode(),
background_color_override.GetHashCode(),
width.GetHashCode(),
horizontal_alignment.GetHashCode(),
horizontal_offset.GetHashCode(),
@@ -145,4 +147,4 @@ namespace mbts.ui.settings {
return $"id: {id} display_name: {display_name}";
}
}
}
}
@@ -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) : new NullableFontWeightProto(null),
is_font_color_overridden ? new NullableColorProto(font_color_override.color) : new NullableColorProto(null),
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})";
@@ -17,11 +17,16 @@ namespace mbts.ui.settings {
const double _default_font_size = 11;
const uint
_default_font_color = 0,
_default_background = 0;
_default_background = 0x00FFFFFF;
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: _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)
@@ -41,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;
}
public static IValueConverter selection_highlight_converter => ValueConverter.create<GridSettings, SolidColorBrush>(gs => gs.selection_highlight.to_brush().frozen());
@@ -77,6 +83,9 @@ namespace mbts.ui.settings {
[ProtoMember(8)]
[DefaultValue(_default_background)]
uint _background;
[ProtoMember(9)]
[DefaultValue(_default_enable_background_color)]
readonly bool _enable_background_color;
public GridSettings() {
_font_family = _default_font_family;
@@ -84,6 +93,7 @@ namespace mbts.ui.settings {
_font_weight = _default_font_weight;
_font_color = _default_font_color;
_background = _default_background;
_enable_background_color = _default_enable_background_color;
}
public GridSettings(
@@ -94,7 +104,8 @@ namespace mbts.ui.settings {
ColorOpacityProto? selection_highlight = null,
IEnumerable<GridColumnSettings> columns = null,
IEnumerable<SortingCriteria> sorting_criterion = null,
uint background = _default_background) {
uint background = _default_background,
bool enable_background_color = _default_enable_background_color) {
//
_font_family = font_family;
_font_size = font_size;
@@ -104,6 +115,7 @@ namespace mbts.ui.settings {
_columns = new List<GridColumnSettings>(columns.empty_if_null());
_sorting_criterion = new List<SortingCriteria>(sorting_criterion.empty_if_null());
_background = background;
_enable_background_color = enable_background_color;
}
public string font_family => _font_family;
@@ -112,6 +124,8 @@ namespace mbts.ui.settings {
public FontWeight font_weight => FontWeight.FromOpenTypeWeight(_font_weight);
public Color background => _background.argb_color();
public ColorOpacityProto selection_highlight => _selection_highlight;
public uint background_color => _background;
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));
@@ -124,8 +138,8 @@ namespace mbts.ui.settings {
IEnumerable<SortingCriteria> sorting_criterion = null,
string font_family = null,
double font_size = double.NaN,
uint? background = null) =>
//
uint? background = null,
bool? enable_background_color = null) =>
new GridSettings(
font_family ?? _font_family,
DoubleHelper.IsNaNOrInfinityOr(font_size, 0) ? _font_size : font_size,
@@ -134,7 +148,8 @@ namespace mbts.ui.settings {
_selection_highlight,
columns ?? _columns,
sorting_criterion ?? _sorting_criterion,
background ?? _background);
background ?? _background,
enable_background_color ?? _enable_background_color);
public GridSettings clone() => with();
@@ -146,6 +161,7 @@ namespace mbts.ui.settings {
font_color == other.font_color &&
selection_highlight == other.selection_highlight &&
_background == other._background &&
_enable_background_color == other._enable_background_color &&
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);
@@ -162,6 +178,7 @@ namespace mbts.ui.settings {
font_color.GetHashCode(),
selection_highlight.GetHashCode(),
_background.GetHashCode(),
_enable_background_color.GetHashCode(),
EqualityComparer.safe_aggregate_hash(columns),
#pragma warning disable CS0612 // Type or member is obsolete
EqualityComparer.safe_aggregate_hash(sorting_criterion));
Loaded 100 of 394 files, more files were not shown because too many files have changed in this diff. Show more