Compare commits

...
Author SHA1 Message Date
pfisher 742cc022f3 changing .cmd to .bat 2019-05-22 08:47:31 -04:00
pfisher 9ef557b808 first try 2019-05-22 08:38:46 -04:00
Eric Harding 07fa55d317 Fix settingsmanager crash 2019-05-21 10:32:34 -04:00
Michael Harding 83650f1e1b System tests: fix timeout, minor improvements 2019-05-20 11:13:36 -04:00
Michael Harding 523002ac56 Fix System tests 2019-05-17 16:57:24 -04:00
Pedro Pinto 9c25a8751e Updated release notes. 2019-05-17 15:38:42 -04:00
Pedro Pinto 89fc99b59a Updated docker subrepo ptr 2019-05-17 15:37:52 -04:00
Eric Harding cd413d10ab Merged in SPARK-2363 (pull request #231)
RTM: SPARK-2363 - PIQ popup

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2019-05-17 19:35:39 +00:00
Eric Harding 1c5cc9c1bb Merged in SPARK-2455-crash-on-md-startup-server-si (pull request #232)
SPARK-2455 crash on sim startup

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-05-17 19:33:53 +00:00
Eric Harding 7965743a80 SPARK-2455 crash on sim startup 2019-05-17 15:20:53 -04:00
Michael Harding c9db5d13a9 System tests wait for system ready 2019-05-17 13:36:21 -04:00
Eric Harding 13e911760d Middle click off popup shoudl close popup 2019-05-17 11:46:06 -04:00
Eric Harding a12c3128a3 Don't allow mbo popup index number to flicker width 2019-05-17 11:45:22 -04:00
Eric Harding 6bb84b65a8 Merge branch 'master' of bitbucket.org:bluetradesys/spark into SPARK-2363 2019-05-17 10:34:11 -04:00
Eric Harding 6fb1140a97 test review feedback 2019-05-17 10:29:12 -04:00
Eric Harding 0e449844ca Clarify this point 2019-05-17 10:25:28 -04:00
Eric Harding 476206edb0 add piq/mbo manual test 2019-05-17 10:21:12 -04:00
Eric Harding 3483ac0712 Code review feedback 2019-05-17 09:03:49 -04:00
Eric Harding d6e586c4ce SPARK2452 - Update release notes for MBO 2019-05-16 16:44:18 -04:00
Eric Harding 0f1735fba2 SPARK-2453 Do not end order on bid/ask middle click 2019-05-16 16:39:50 -04:00
Eric Harding e5254dcf7f btree_set benchmarks are veerry interesting 2019-05-16 15:40:17 -04:00
Eric Harding 7bd183c8a4 btree_set benchmarks are very interesting 2019-05-16 15:29:59 -04:00
Michael Harding aaf09b197b Fix sdk docs 2019-05-16 15:16:49 -04:00
Eric Harding 6081128b92 move sparkvm launch to the top of the debug menu 2019-05-16 15:11:26 -04:00
Eric Harding 6d6ecb33ba SPARK-2450 Simulator MBO realism ™ 2019-05-16 15:08:51 -04:00
Michael Harding 6e85612adc deleting the right files this time 2019-05-16 14:01:35 -04:00
Eric Harding f33a13bfc8 Wire mbo popup to ladder in order book 2019-05-16 13:36:19 -04:00
Jake Norbut c4eeac8d0a fixing ServiceTail time formatting option 2019-05-16 12:07:49 -05:00
Michael Harding 432f39a1fa Don't delete everything 2019-05-16 12:54:39 -04:00
Eric Harding 6084da64a2 merge master 2019-05-16 12:18:45 -04:00
Jake Norbut 5ae484e6c1 adding comment to method 2019-05-16 10:07:59 -05:00
Eric Harding a2d4eb7d10 SPARK-2444 MBO Polish 2 - the Scrollening 2019-05-16 10:43:23 -04:00
Michael Harding 8082ab12dd Delete everything 2019-05-16 10:37:00 -04:00
Jake Norbut b1def915d6 Merged in SPARK-2449 (pull request #229)
SPARK-2449 - if the badge only represents 1 order, that order's piq has to be the min piq
2019-05-15 23:04:56 +00:00
Jake Norbut 70bb22c966 SPARK-2449 - if the badge only represents 1 order, that order's piq has to be the min piq 2019-05-15 17:39:47 -05:00
Eric Harding 06520791fa SPARK-2444 MBO UI polish 2019-05-15 17:19:46 -04:00
Jake Norbut 1fa5e3c9af fixing test_join_qty_threshold() of JoinAlgoTests 2019-05-15 11:29:23 -05:00
Michael Harding 7fede58044 Make system tests fail 2019-05-15 11:35:17 -04:00
Pedro Pinto 090a7985c3 Fixed spelling mistakes in release notes. 2019-05-15 10:38:35 -04:00
Michael Harding f5fa2ff23a Adding Pete's fixes to system test scripts 2019-05-15 09:42:23 -04:00
Taha Afzal 6a1b154061 Fixing the bug causing the broken unit test 2019-05-14 22:43:42 -04:00
Michael Harding 73a4e474f1 System Test scripts: jenkins improvements 2019-05-14 17:26:59 -04:00
Eric Harding 6f630a0e62 MBOService should indicate bid/ask 2019-05-14 17:03:17 -04:00
Michael Harding 4e30591837 Hopefully fixing line endings this time 2019-05-14 14:38:36 -04:00
Eric Harding 3144032ed6 Fix dictionary key with ShellLatency (add user) 2019-05-14 13:46:03 -04:00
Michael Harding d9221e1053 fix system test scripts 2019-05-14 12:54:01 -04:00
Eric Harding bee1df45ad SPARK-2430 prioritize draining incremental queue over publishing shapshots 2019-05-14 11:52:51 -04:00
Pedro Pinto 316be12b71 Return order key in API create. This broke in 1944. 2019-05-14 11:46:27 -04:00
Pedro Pinto 6c51ff83d5 Release notes again. 2019-05-14 10:01:39 -04:00
Pedro Pinto 1cad5b6b39 Updated release notes. 2019-05-14 09:56:47 -04:00
Michael Harding 171396ca57 Merged in system_tests (pull request #228)
System Tests
2019-05-14 13:51:33 +00:00
Pedro Pinto 193eb4e445 Merged in SPARK-1944-attach/detach/update (pull request #141)
SPARK-1944 attach/detach/update
2019-05-14 13:48:17 +00:00
Pedro Pinto 69b3831271 Merged with master 2019-05-14 08:58:30 -04:00
Pedro Pinto 39efacace4 Updated release notes 2019-05-14 08:57:32 -04:00
Eric Harding e70dfc8832 Merged in SPARK-2151 (pull request #225)
RTM: SPARK-2151 Dump shared memory on bad handle for forensic analysis
2019-05-14 12:55:17 +00:00
Jake Norbut 60bf851567 Merged in SPARK-2364 (pull request #222)
RTM - settingsmanager - print descriptive workspace info after selecting a user's workspace to copy

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2019-05-14 12:54:03 +00:00
Jake Norbut 053038df8d Merged in piq_ladder_dirty (pull request #226)
RTM - don't draw piq on every tick; might save some cycles

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2019-05-14 12:53:22 +00:00
Eric Harding f0afc74e5f Merged in slightly_fast_webui (pull request #220)
RTM: Apply lazyView to the limits tables

Approved-by: Michael Harding <michael.harding@bluetradesys.com>
Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2019-05-14 12:51:38 +00:00
Eric Harding 9d3c8aaf5c Merged in SPARK-2303-assert-failure-trade.key-inva (pull request #224)
RTM: Safe workaround for SPARK-2303.
2019-05-14 12:49:34 +00:00
Taha Afzal 8575d92373 once the lean's child has crossed, changin lean's price has no effect on the child 2019-05-13 17:53:19 -04:00
Pedro Pinto 21ee7c6af5 2.0.0 Release notes. 2019-05-13 17:30:49 -04:00
Michael Harding f6cf0f28ea Move test scripts in spark_test.csproj 2019-05-13 13:31:02 -04:00
Michael Harding 2b51bda3e7 Update system test scripts 2019-05-13 11:27:12 -04:00
Pedro Pinto 1be2e81920 Merge branch 'SPARK-1944-attach/detach/update' of bitbucket.org:bluetradesys/spark into SPARK-1944-attach/detach/update 2019-05-12 14:33:40 -04:00
John Colvin ea45c5c694 OrderBook - added config for status color opacity when row is selected 2019-05-12 12:24:32 -05:00
John Colvin 768567dfc7 HighPerformanceRow - added BackgroundProperty 2019-05-12 12:14:38 -05:00
John Colvin 870e7a9ef9 DIYGrid - added mutable InactiveSelectionOpacity prop (was just hardcoded at 0.2) 2019-05-12 12:11:39 -05:00
Pedro Pinto a9e572a1bf Merge branch 'master' into SPARK-1944-attach/detach/update 2019-05-11 19:41:18 -04:00
John Colvin c7899a1dae SPARK-2434 OrderBook - security column filter popup is empty if shell is started before server has published all securities 2019-05-11 12:47:07 -05:00
John Colvin 7bb077a74d remove ToLArray extension (it is not more efficient than Enumerable.ToArray()) 2019-05-11 12:27:42 -05:00
John Colvin a29eaa2618 EnumCaseStringConverter - cleanup code style/syntax 2019-05-11 12:25:38 -05:00
John Colvin 762369ad2b SPARK-2433 child order font weight in OB should be dropped or adjusted 2019-05-10 21:22:42 -05:00
John Colvin a8610beafd SPARK-2432 Crash when detaching order window 2019-05-10 20:38:33 -05:00
John Colvin 5e1a91f484 add period to the end of 'Make current parameters the local defaults' tooltip 2019-05-10 16:01:07 -05:00
John Colvin 04fdb9626d SPARK-2409 Changing a composite algo name does not reflect in the attach window dropdown 2019-05-10 15:57:31 -05:00
Michael Harding 6c5429b353 Minor updates to position limits test 2019-05-10 16:36:13 -04:00
Michael Harding 2bedf1f355 Merge branch 'master' into system_tests 2019-05-10 16:22:18 -04:00
Michael Harding 02752d9151 SPARK-2064: simple sanity simplified 2019-05-10 16:14:06 -04:00
John Colvin 689bfa1c76 OrderBook 'attach popup' price/qty vis depends on the selected attach-algo (not the selected orders) 2019-05-10 13:51:39 -05:00
Pedro Pinto 70af046131 Updated docker subrepo ptr 2019-05-10 13:52:44 -04:00
Pedro Pinto 7a45013782 Changed cover to stick around until all hedges are completed. 2019-05-10 13:52:44 -04:00
Michael Harding 6f7851b7ba SPARK-2064: Simple sanity test 2019-05-10 13:41:05 -04:00
Taha Afzal 85f42d1e23 Join won't modify its child's price once it has joined 2019-05-10 11:12:52 -04:00
John Colvin f8ddaab884 OrderBook - (Root)OrderType col filters show green beaker when filter active on startup/load workspace 2019-05-09 18:00:12 -05:00
John Colvin 78373c61ce miniob - hide price/qty param rows in attach popup 2019-05-09 16:10:18 -05:00
John Colvin 13e5c017e2 miniob - set orderbookvm.is_attaching_orders to false when attach popup closes 2019-05-09 15:53:12 -05:00
Eric Harding 3789d73593 Do not allow minTick to be 0 (infinite loop) 2019-05-09 16:25:25 -04:00
John Colvin 38f42fe747 SPARK-2417 Attach window price, qty 2019-05-09 14:28:52 -05:00
John Colvin 3a6f9a608d OrderBookVM.send_attached_orders_async uses CustomDef.actual() to unwrap composite algos 2019-05-09 14:28:52 -05:00
Michael Harding e14a5b2575 update SDK readme 2019-05-09 18:47:09 +00:00
John Colvin 04a68a4e96 OrderBookRowVM - fix typeo in badge_color prop (was using 'root') 2019-05-09 13:43:47 -05:00
Eric Harding dd049374d7 Fix equality comparison between mbo price levels 2019-05-09 14:26:14 -04:00
John Colvin 526ec306ae (Root)OrderType col filters ItemTemplate uses 'OrderTypeItem' (displays badge and name) 2019-05-09 12:41:35 -05:00
John Colvin f16ed40c82 OrderUtils.badge_fill does not need the OrderRecordImmutable arg 2019-05-09 12:35:17 -05:00
Eric Harding 96572539a7 SPARK-2381 View MBO in ladder 2019-05-09 13:05:47 -04:00
John Colvin 793eb14ed7 AlgoDefinitionMetadata - null check _favorite_templates in GetHashCode 2019-05-09 11:39:55 -05:00
John Colvin 1f94454d63 SPARK-2421 Default ladder to new fancy param grid 2019-05-09 11:20:10 -05:00
John Colvin c7e3bdcd6c OrderBookRowVM - normalize spacing 2019-05-09 11:18:27 -05:00
John Colvin 004a7b1395 OrderBookRowVM - don't hook up to invalidated event until '_modify_order_quantity_andor_price' cmd is created 2019-05-09 11:17:03 -05:00
John Colvin b1498f3df1 OrderBookRowVM - forgot to raise property changed when root badge color changes 2019-05-09 11:03:08 -05:00
John Colvin b0759eba62 OrderBookRowVM - fix root badge color 2019-05-09 10:58:40 -05:00
Jake Norbut ff69cafe7f ob alignments - left aligning nearly everything, price and qty cols stay right aligned 2019-05-09 09:59:40 -05:00
Jake Norbut f1e90394a0 restoring Root Algo column header after merge 2019-05-09 08:24:44 -05:00
Jake Norbut b24f855321 misc refactoring after root algo branch merge; OrderBookVM parameter passing no longer necessary 2019-05-09 08:20:36 -05:00
John Colvin 6e56b6cbaf sort algo type items in (Root)OrderType col filters 2019-05-08 23:12:21 -05:00
John Colvin 16ff473fd6 ViewModelBase - added 'DelegateProperty' backing method that doesn't invoke get_value during creation 2019-05-08 23:07:55 -05:00
John Colvin 4565fada08 change KeyedMultiSelector.selected_items to ObservableCollectionEx (supported by ListBoxEx, which is used by DiscreteValuesFilters) 2019-05-08 23:05:21 -05:00
John Colvin 648f808583 SPARK-2409 / SPARK-2422 fix duplicate algo items in (Root)OrderType col filters 2019-05-08 20:53:54 -05:00
John Colvin 7a6212932e Merge branch 'root_algo_col' into SPARK-1944-attach/detach/update 2019-05-08 19:20:38 -05:00
John Colvin aa933696ec OrderBook - include root_algo member in order_tuples join (for new 'Root Algo' col) 2019-05-08 19:16:10 -05:00
John Colvin 39d59dbbb0 IObservable<IReadOnlyDictionary>.Table() extension applies value-changes to state 2019-05-08 18:43:03 -05:00
Taha Afzal a511ea3a4b Lean cancels itself when detach on OnToward 2019-05-08 17:23:10 -04:00
John Colvin e489e809ab Merge remote-tracking branch 'origin/SPARK-1944-attach/detach/update' into root_algo_col 2019-05-08 16:22:30 -05:00
Jake Norbut e56d41d26b SPARK-2419 - algo studio childs display correct name and badge color now when they are not roots; verified w/ john his great comment no longer applied 2019-05-08 16:05:21 -05:00
John Colvin b1d45ad593 fix benign typeos in UpdateExtensions.ParentDependent 2019-05-08 15:45:17 -05:00
Pedro Pinto 32038f8948 Fixed algo API bug that could lead to lost order updates. 2019-05-08 16:36:11 -04:00
Jake Norbut 2a90b521da SPARK-2418 - miniob to make sure of new algo_tree_display_mode setting for RootAndLeafNodes 2019-05-08 14:19:35 -05:00
Jake Norbut 2098a60af1 eliminating root_algo_badge_fill property so that the fill color is reevaluated on all updates, for now ... 2019-05-08 12:01:18 -05:00
Jake Norbut 67c0178b9a adding GridColumnSettings for root algo column 2019-05-08 12:00:28 -05:00
Jake Norbut 5da07c42bb Adding RootAlgoType to ColumnBase 2019-05-08 11:59:31 -05:00
Jake Norbut f82035be98 refactoring OrderBookOrderTypeColumn to eliminate the newly created RootAlgo column type 2019-05-08 11:58:33 -05:00
Jake Norbut a652268eb8 get filtering working correctly 2019-05-08 11:02:33 -05:00
Jake Norbut 13d054a54c just keep checking the OB cache rather than against our local one 2019-05-08 10:04:01 -05:00
Jake Norbut 84cf2a3ad1 refactoring; kind of get filtering working, but not really 2019-05-08 09:42:50 -05:00
Jake Norbut 32e0d60dcd get filering working by passing in an 'owner' reference to the OrderBookColumnCollection 2019-05-08 08:53:04 -05:00
Jake Norbut 0a6755992a removing new non-working rootalgocvorderbookcolumn interface; using OB cache instead of tree; filtering still a mystery 2019-05-08 08:26:47 -05:00
Pedro Pinto 3cc0f32617 Protected iceberg against negative show quantities. 2019-05-08 06:46:40 -04:00
John Colvin 29ae9c6927 SPARK-2404 Make ladder param grid behave more like a popup 2019-05-07 21:44:47 -05:00
John Colvin 892abe098c SPARK-2404 / SPARK-2410 replace dialog skirt buttons with toolbar menu items 2019-05-07 21:27:56 -05:00
John Colvin 9cf683f64f AlgoTemplateToolBar - RevertDefinition's visibility is not contingent upon a studio definition being selected 2019-05-07 21:27:56 -05:00
John Colvin f2bb8de4e1 AlgoTemplateToolBar - execute RevertDefinition visual state transitions through InvokeAsync 2019-05-07 21:27:51 -05:00
John Colvin b6a28af99e SPARK-2406 OrderBook - update param rows misbehave with non-uniform values 2019-05-07 18:35:06 -05:00
John Colvin 13a9dd3696 SPARK-2406 / SPARK-2408 QuantityMeasureControl should assume 'Absolute' mode when QuantityMeasure is null, or QuantityCase is 'None' 2019-05-07 18:33:38 -05:00
John Colvin 60bbd12386 code style change in ReflectionHelper.GetGenericTypeArgs method 2019-05-07 17:57:56 -05:00
John Colvin 3e09274cb5 SPARK-2403 Top is still showing this unknown parameter 2019-05-07 17:17:59 -05:00
John Colvin e9d1da4b51 SPARK-2401 mview - mass selection degrades performance 2019-05-07 17:13:06 -05:00
John Colvin 68e3fcb1cd made extension methods for Ice-dependent security props 2019-05-07 17:13:06 -05:00
John Colvin 745be0909a remove unused implementations of SecurityGrid 2019-05-07 17:13:06 -05:00
Eric Harding 74dd36bde1 SPARK-2380 add MBO popup 2019-05-07 18:02:59 -04:00
Taha Afzal 0bee4168b4 Join test has attach/detach/updates now 2019-05-07 16:19:24 -04:00
Jake Norbut d87c6027c9 display of column works, filtering not so much 2019-05-07 14:27:42 -05:00
John Colvin 859faadecd SPARK-2376 When collapsed the OB scrollbar overlaps with the collapsing widget 2019-05-07 12:22:40 -05:00
Taha Afzal e8741f9151 Top order manual test 2019-05-07 13:19:38 -04:00
Jake Norbut ef8b0fc258 enforce 2 digit algo abbreviation on badge 2019-05-07 11:43:59 -05:00
John Colvin 3b27fedcd3 SPARK-2402 piq merge broke the orderbook mini-ladder order filter 2019-05-07 11:18:27 -05:00
Michael Harding 0991fbfffd Update SDK README 2019-05-07 11:31:56 -04:00
Jake Norbut 499072c94f PR edit - we don't need an array here 2019-05-07 09:33:25 -05:00
Jake Norbut fa8598adbd PR suggestion - explicitly set default value 2019-05-07 09:22:44 -05:00
Jake Norbut 089c4ac35e part of SPARK-2376 - OB footer now spans the entire window width, regardless of the new right panel state 2019-05-07 08:44:48 -05:00
Jake Norbut af8b559fa6 Merge branch 'master' of bitbucket.org:bluetradesys/spark into piq_ladder_dirty 2019-05-07 08:26:15 -05:00
Taha Afzal 62ba85d383 Lean deals with gapped markets better 2019-05-06 19:22:06 -04:00
Taha Afzal f4b2f84e68 Lean's corner cases 2019-05-06 18:14:33 -04:00
Taha Afzal 474e7880d6 LeanAlgo's spurious price/qty controlled 2019-05-06 18:14:33 -04:00
John Colvin 89f74b7c81 SPARK-2399 add commit/revert buttons to ladder algo params grid popup 2019-05-06 17:05:52 -05:00
Jake Norbut 0abcb8bf23 SPARK-2391 - attach/detach icons 2019-05-06 16:33:25 -05:00
Pedro Pinto 47887b67d2 Fixed SIM handling of updates to size of partially filled orders. 2019-05-06 15:57:28 -04:00
John Colvin 41a96b990d SPARK-2382 Windows that take a long time to render do not give any user feedback until they are visible 2019-05-06 14:18:21 -05:00
Jake Norbut ce8bc5c631 SPARK-2394 - rename Fonts and Colors to Column Settings 2019-05-06 12:23:21 -05:00
John Colvin eebb214574 make FixNestedScrolling diygrid popup conditional check more specific 2019-05-06 11:28:53 -05:00
Jake Norbut 47c827caf6 badge - refactor (forgot a ToUpper for 1 of the return paths 2019-05-06 11:22:42 -05:00
Michael Harding a82a22b133 Update test text 2019-05-06 12:17:07 -04:00
Brian Muhr 1fd444c91c adding new tests 2019-05-06 11:12:58 -05:00
Jake Norbut f28ae29990 changing A and D geometry to Segoe UI font 2019-05-06 11:07:56 -05:00
Jake Norbut aedabcdf00 SPARK-2392 - ob attach/detach icons to match mini ob 2019-05-06 11:07:56 -05:00
Jake Norbut 3b37f8e48b adding A and D icons; created from Inkscape 2019-05-06 11:07:56 -05:00
John Colvin beb328ec09 SPARK-2397 grid column popups don't scroll on mouse wheel 2019-05-06 10:58:41 -05:00
Jake Norbut f9b9bdab47 SPARK-2391 - generate badge abbreviation for algo studio orders lacking one 2019-05-06 09:22:44 -05:00
Eric Harding 3beb82c959 Add comment about why dump_to_shm does'nt need to hold mutex 2019-05-06 09:01:45 -04:00
Jake Norbut 9e6776c6fb updating test text 2019-05-06 07:49:31 -05:00
Pedro Pinto e807f6a702 Fixed composite handling of detach. 2019-05-05 11:56:35 -04:00
John Colvin 07212aa5d6 IAlgoVM.load impls unwrap CompositeOrderDefinitions to get the underlying algo 2019-05-04 15:57:50 -05:00
Pedro Pinto a0f31d6443 Fixed composite order some more. 2019-05-04 15:14:20 -04:00
Pedro Pinto df6a5096c2 Composite order was not reflecting child price/size 2019-05-04 14:33:16 -04:00
John Colvin 43336d04d4 SPARK-2375 Composite algo order types do not show as options in the OB filter 2019-05-04 13:26:23 -05:00
John Colvin ca73b20fe5 made 'MergeTable' extension method for TableUpdate 2019-05-04 13:16:45 -05:00
John Colvin 09f6ad4031 OrderBook - fix crash when selecting composite-order row 2019-05-04 13:04:56 -05:00
John Colvin 97b52f2e64 OrdersModel.update_order should ignore size change if size == quantity (not working_quantity) 2019-05-04 13:04:34 -05:00
Pedro Pinto 0adc77a5cf Fixed composite order. 2019-05-04 11:53:12 -04:00
Pedro Pinto e469e8fc70 Tweaked cover test. 2019-05-04 11:53:12 -04:00
Pedro Pinto 10da3c517c Changed hedge ticks to be a uint, from int 2019-05-04 11:53:12 -04:00
Pedro Pinto 5f7c4b5c37 Improved test. 2019-05-04 11:53:12 -04:00
Pedro Pinto c34d05218a Fixed clang build. 2019-05-04 11:53:12 -04:00
Pedro Pinto 3abf99da91 Cleaned up tests. 2019-05-04 11:53:12 -04:00
Pedro Pinto 8d94238e91 Updated cover test 2019-05-04 11:53:12 -04:00
John Colvin dc169fc4fd SPARK-2388 studio-template orders should use new CompositeOrderDefinition (not CustomOrderDefinition) 2019-05-04 11:53:12 -04:00
John Colvin abe8361b18 when updating orders without a 'new size' arg, use the order's actual quantity, not working_quantity 2019-05-03 20:16:18 -05:00
Eric Harding 9f406e83aa SPARK-2151 Dump shared memory on bad handle for forensic analysis 2019-05-03 17:39:47 -04:00
John Colvin 2ee163bf8a SPARK-2387 crash when opening orderbook from other windows 2019-05-03 15:44:22 -05:00
Jake Norbut 775a720e0a SPARK-2027 - it's depressing how many hours of your life 1 line of code can steal 2019-05-03 15:01:27 -05:00
Eric Harding 84aefabfea Safe workaround for SPARK-2303. 2019-05-03 13:19:35 -04:00
Eric Harding 102fefbd92 1.6.2 release notes 2019-05-03 13:13:59 -04:00
Eric Harding fc7a4b0800 Merged in PIQachu (pull request #223)
RTm: spark-2316 mbo support for simulator

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-05-03 15:30:13 +00:00
Michael Harding 6d9c083c0f Verify quoting order 2019-05-02 10:58:45 -04:00
Michael Harding 3e822bcca1 Merge branch 'master' into spreader_test 2019-05-01 10:58:30 -04:00
Jake Norbut 8dde9bf3c5 print descriptive workspace info after selecting a user's workspace to copy 2019-05-01 08:39:37 -05:00
Jake Norbut 0a683dae15 Merge branch 'master' of bitbucket.org:bluetradesys/spark into piq_ladder_dirty 2019-04-30 16:58:37 -05:00
Eric Harding 77f5723936 Apply lazyView to the limits tables 2019-04-30 12:23:18 -04:00
Jake Norbut bd643aa525 don't draw piq on every tick; might save some cycles 2019-04-26 14:50:16 -05:00
Michael Harding 8a53863218 Merge branch 'master' into spreader_test 2019-04-23 16:16:19 -04:00
Michael Harding 6ad12e922c Submit and confirm working spreader order 2019-04-16 13:03:36 -04:00
Michael Harding 3171ae0afb Create a Synthetic Security 2019-04-11 16:06:59 -04:00
Michael Harding c1fe0b3191 SPARK-2066: Create book and common setup 2019-04-04 11:56:05 -04:00
Michael Harding a071b974c8 Merge branch 'master' into system_tests 2019-04-04 10:23:50 -04:00
Michael Harding dd7db5661b Utility for tests to verify the system is ready 2019-03-25 12:21:10 -04:00
Michael Harding 4b8d725528 Test scripts start sparkd. spreader sets config 2019-03-25 12:21:10 -04:00
Jake Norbut db1b2a63af the start of a lean test 2019-03-21 08:05:33 -05:00
Jake Norbut 6006a328fe Merge branch 'master' of bitbucket.org:bluetradesys/spark into system_tests 2019-03-20 08:45:18 -05:00
Jake Norbut 5cb48fb517 Adding qty threshold test 2019-03-15 13:28:30 -05:00
Jake Norbut f2f8c258f9 run all tests within a test class 2019-03-15 13:22:37 -05:00
Jake Norbut cb8dac05d5 if linux no Dispose for now b/c seg fault 2019-03-14 16:10:58 -05:00
Jake Norbut f6eb1e4745 QuickBusClientChanel diag logging for sub / unsub 2019-03-14 15:53:12 -05:00
Jake Norbut 16e8a6e26d adding .NET Core Remote Lauch and Attach debugging options 2019-03-14 15:51:19 -05:00
Jake Norbut 9744f53d65 shell script that will perform setup work; will probably need to chmod this 2019-03-14 11:07:57 -05:00
Jake Norbut b231a66fc7 need to make this test public 2019-03-14 11:06:59 -05:00
Jake Norbut 65b3d4fffa refactoring OrderDefinition utils 2019-03-13 16:58:00 -05:00
Jake Norbut ba1b667ae8 basic join test 2019-03-13 16:47:23 -05:00
Jake Norbut e31b05042f refactoring common services into a base class; other misc refactoring 2019-03-13 16:46:58 -05:00
Jake Norbut 84e443ee4b copying *.sh scripts for testing on linux 2019-03-13 11:40:16 -05:00
Jake Norbut 3821dfb8ff Merge branch 'master' of bitbucket.org:bluetradesys/spark into system_tests 2019-03-11 14:45:10 -05:00
Jake Norbut 01a8502b20 refactoring to resuse user and account between different tests 2019-03-11 14:04:40 -05:00
Jake Norbut edfa079326 ib avg filled test 2019-03-11 13:46:00 -05:00
Jake Norbut 316a617c65 more refactoring 2019-03-11 13:08:16 -05:00
Jake Norbut 0e92a12e65 refactoring/reorg 2019-03-11 10:08:13 -05:00
Jake Norbut 5f4eb58db9 refactoring to promote eventual code reuse 2019-03-08 16:26:00 -06:00
Jake Norbut f7cdb1f00b refactor/cleanup of some common functions 2019-03-08 10:36:51 -06:00
Jake Norbut 0caa6b6ee0 IB test 1 2019-03-08 08:53:00 -06:00
Jake Norbut 6fb8fdc9f6 updating test timeout 2019-02-27 14:28:45 -06:00
Jake Norbut b7fcd17ef0 first test; create retrieve update delete of algo definitions 2019-02-27 11:05:23 -06:00
Michael Harding 482ca6b484 SPARK-2065: Move order functions to OrderUtilities 2019-02-26 14:02:09 -05:00
Michael Harding 7ed3a977ab SPARK-2065: Move AdminUtilities 2019-02-26 11:18:26 -05:00
Michael Harding 59cfe3dc8d SPARK-2067: Setup bash scripts for running system tests 2019-02-26 11:07:47 -05:00
176 changed files with 5266 additions and 1405 deletions

No files matched your search

+177 -91
View File
@@ -1,83 +1,14 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Launch",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceRoot}/build/ninja_debug/server/sparkd/sparkd",
"args": ["run"],
"stopAtEntry": false,
"cwd": "${workspaceRoot}/build/ninja_debug/src/server/sparkd",
"environment": [],
"externalConsole": true,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
},
{
"name": "Client Launch",
"type": "clr",
"request": "launch",
"program": "${workspaceRoot}/build/Debug/bin/net471/spark.exe",
"args": [],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "SDK Launch",
"type": "clr",
"request": "launch",
"program": "${workspaceRoot}/build/Debug/bin/net471/spark_sdk.exe",
"args": ["md","GLBX","Future","CL", "CLZ9", "--user=bts", "--pass="],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "Excel Launch",
"type": "clr",
"request": "launch",
"program": "C:/Program Files/Microsoft Office/Office14/EXCEL.EXE",
"args": ["${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64-packed.xll"],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "Client Tests",
"type": "clr",
"request": "launch",
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
"args": ["test","test_filter"],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "Client Attach",
"type": "clr",
"request": "attach",
"processId":"${command:pickProcess}"
},
{
"name": "SparkVM Launch",
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
"args": ["run"],
"args": [
"run"
],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/server/",
"environment": [],
@@ -102,24 +33,113 @@
"description": "LD LIBRARY",
"text": "set env LD_LIBRARY_PATH=.",
"ignoreFailures": false
}
}
],
"pipeTransport": {
"pipeCwd": "${workspaceRoot}",
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": ["bts@sparkvm"],
"pipeArgs": [
"bts@sparkvm"
],
"debuggerPath": "/usr/bin/gdb"
},
"sourceFileMap": {
"/home/bts/spark/": "${workspaceRoot}"
}
},
{
"name": "(gdb) Launch",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceRoot}/build/ninja_debug/server/sparkd/sparkd",
"args": [
"run"
],
"stopAtEntry": false,
"cwd": "${workspaceRoot}/build/ninja_debug/src/server/sparkd",
"environment": [],
"externalConsole": true,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
},
{
"name": "Client Launch",
"type": "clr",
"request": "launch",
"program": "${workspaceRoot}/build/Debug/bin/net471/spark.exe",
"args": [],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "SDK Launch",
"type": "clr",
"request": "launch",
"program": "${workspaceRoot}/build/Debug/bin/net471/spark_sdk.exe",
"args": [
"md",
"GLBX",
"Future",
"CL",
"CLZ9",
"--user=bts",
"--pass="
],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "Excel Launch",
"type": "clr",
"request": "launch",
"program": "C:/Program Files/Microsoft Office/Office14/EXCEL.EXE",
"args": [
"${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64-packed.xll"
],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "Client Tests",
"type": "clr",
"request": "launch",
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
"args": [
"test",
"test_filter"
],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "Client Attach",
"type": "clr",
"request": "attach",
"processId": "${command:pickProcess}"
},
{
"name": "Spreader Tests",
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
"args": ["test", "[spreader]"],
"args": [
"test",
"[spreader]"
],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
"environment": [],
@@ -139,7 +159,9 @@
"pipeTransport": {
"pipeCwd": "${workspaceRoot}",
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": ["bts@sparkvm"],
"pipeArgs": [
"bts@sparkvm"
],
"debuggerPath": "/usr/bin/gdb"
},
"sourceFileMap": {
@@ -151,7 +173,10 @@
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
"args": ["test", "[admin_service]"],
"args": [
"test",
"[admin_service]"
],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
"environment": [],
@@ -171,7 +196,9 @@
"pipeTransport": {
"pipeCwd": "${workspaceRoot}",
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": ["bts@sparkvm"],
"pipeArgs": [
"bts@sparkvm"
],
"debuggerPath": "/usr/bin/gdb"
},
"sourceFileMap": {
@@ -183,7 +210,10 @@
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
"args": ["test", "[limiter]"],
"args": [
"test",
"[limiter]"
],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
"environment": [],
@@ -203,7 +233,9 @@
"pipeTransport": {
"pipeCwd": "${workspaceRoot}",
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": ["bts@sparkvm"],
"pipeArgs": [
"bts@sparkvm"
],
"debuggerPath": "/usr/bin/gdb"
},
"sourceFileMap": {
@@ -215,7 +247,10 @@
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
"args": ["test", "[mbo]"],
"args": [
"test",
"[mbo]"
],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/server/",
"environment": [],
@@ -240,14 +275,16 @@
"pipeTransport": {
"pipeCwd": "${workspaceRoot}",
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": ["bts@sparkvm"],
"pipeArgs": [
"bts@sparkvm"
],
"debuggerPath": "/usr/bin/gdb"
},
"sourceFileMap": {
"/home/bts/spark/": "${workspaceRoot}"
}
},
{
{
"name": "SparkVM attach",
"type": "cppdbg",
"request": "attach",
@@ -255,16 +292,18 @@
"processId": "${command:pickRemoteProcess}",
"MIMode": "gdb",
"pipeTransport": {
"pipeCwd": "${workspaceRoot}",
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": ["bts@sparkvm"],
"debuggerPath": "/usr/bin/gdb"
"pipeCwd": "${workspaceRoot}",
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": [
"bts@sparkvm"
],
"debuggerPath": "/usr/bin/gdb"
},
"sourceFileMap": {
"/home/bts/spark/": "${workspaceRoot}"
"/home/bts/spark/": "${workspaceRoot}"
}
},
{
},
{
"name": "Python",
"type": "python",
"request": "launch",
@@ -274,12 +313,59 @@
"cwd": "${workspaceRoot}",
"env": {},
"envFile": "${workspaceRoot}/.env",
"args": [ "DEBUG" ],
"args": [
"DEBUG"
],
"debugOptions": [
"WaitOnAbnormalExit",
"WaitOnNormalExit",
"RedirectOutput"
]
},
{
"name": ".NET Core Remote Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickRemoteProcess}",
"pipeTransport": {
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": [
"bts@sparkvm"
],
"debuggerPath": "/home/bts/vsdbg/vsdbg",
"pipeCwd": "${workspaceRoot}",
"quoteArgs": true
},
"sourceFileMap": {
"/home/bts/spark/": "${workspaceRoot}"
},
"justMyCode": true
},
{
"name": ".NET Core Remote Launch",
"type": "coreclr",
"request": "launch",
"program": "/usr/bin/dotnet",
"args": [
"sparkcmd.dll",
"test",
"test_join_create_and_takeout_market"
],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/server",
"pipeTransport": {
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": [
"bts@sparkvm"
],
"debuggerPath": "/home/bts/vsdbg/vsdbg",
"pipeCwd": "${workspaceRoot}",
"quoteArgs": true
},
"sourceFileMap": {
"/home/bts/spark/": "${workspaceRoot}"
},
"justMyCode": false
},
]
}
+1 -1
View File
@@ -1 +1 @@
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Algos/_index.md;content/bts_spark/Glossary/index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Help/index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Launcher/index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Logging-In-to-the-System/index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/index.md;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/My-Windows/index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Settings/index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Spreader/index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/index.md;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
set(sources /home/pfisher/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Algos/_index.md;content/bts_spark/Glossary/index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Help/index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Launcher/index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Logging-In-to-the-System/index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/index.md;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/My-Windows/index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Settings/index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Spreader/index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/index.md;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
+37
View File
@@ -4,6 +4,43 @@ weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## 2.0.2
### User Visible Changes
- Middle click the ladder price column to show the orders for that price level
- Middle click the bid or ask column in the ladder no longer sends an order
### Internal changes
- Improved market by order performance under heavy load
- Fixed a concurrency bug in SIM
## 2.0.0
### User Visible Changes
- Support for attaching and detaching algos to existing orders
- Support for display and update of all parameters in running algos
- Streamlining of order badge decorations in ladder
- New order search/filtering algorithm for order book
- Simpler representation of complex algo chains in order book and mini order book
- Visual and usability improvements to mini order book
- Streamlining of algo parameter dialog in ladder
- New spreader queue holder behavior
- Visual improvements to the order book
- New server side API
- Improved admin UI performance
- Optimized PIQ rendering
### Internal changes
- Exchange simulator no longer requires daily sequence number reset
- Improved MD resilience to corrupted shared memory
- Native orders can now buffer updates/cancels when disconnected or pending first acknowledgment
- Improved settings manager tool output
- Added diagnostics in the event MD memory corruption is detected
- Added systems tests for join,lean, iceberg and spreader orders
- Made exchange order recovery more resilient in the presence of database loss
## 1.6.2
### User Visible Changes
### Exchange simulator
- Exchange simulator now now produce position in queue data
## 1.6.0
### User Visible Changes
#### New Features
+21 -4
View File
@@ -22,9 +22,9 @@ namespace mbts.ui {
AddBoxesGeometryKey = nameof(AddBoxesGeometryKey),
AddFileGeometryKey = nameof(AddFileGeometryKey),
AddFileThinGeometryKey = nameof(AddFileThinGeometryKey),
AttachAlgosGeometryKey = nameof(AttachAlgosGeometryKey),
AttachAlgosKey = nameof(AttachAlgosKey),
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
DetachAlgosGeometryKey = nameof(DetachAlgosGeometryKey),
DetachAlgosKey = nameof(DetachAlgosKey),
DiskGeometryKey = nameof(DiskGeometryKey),
DiskFileGeometryKey = nameof(DiskFileGeometryKey),
DocumentGeometryKey = nameof(DocumentGeometryKey),
@@ -37,11 +37,14 @@ namespace mbts.ui {
EditDocumentAltGeometryKey = nameof(EditDocumentAltGeometryKey),
EditDocumentGeometryKey = nameof(EditDocumentGeometryKey),
GearGeometryKey = nameof(GearGeometryKey),
LetterAKey = nameof(LetterAKey),
LetterDKey = nameof(LetterDKey),
OrdersGeometryKey = nameof(OrdersGeometryKey),
PaymentGeometryKey = nameof(PaymentGeometryKey),
PencilGeometryKey = nameof(PencilGeometryKey),
PlayGeometryKey = nameof(PlayGeometryKey),
ProhibitedGeometryKey = nameof(ProhibitedGeometryKey),
RubberStampGeometryKey = nameof(RubberStampGeometryKey),
StarGeometryKey = nameof(StarGeometryKey),
ToolsGeometryKey = nameof(ToolsGeometryKey),
TrashGeometryKey = nameof(TrashGeometryKey),
@@ -118,7 +121,7 @@ namespace mbts.ui {
public static ImageSource AddBoxes => create_icon(AddBoxesGeometryKey);
public static ImageSource AddEncircled { get { return load_resource_and_freeze("AddEncircled"); } }
public static ImageSource AddFile => create_icon(Smashicons.AddFileGeometryKey);
public static ImageSource AttachAlgos => create_icon(AttachAlgosGeometryKey);
public static ImageSource AttachAlgos => create_icon(AttachAlgosKey);
public static ImageSource Beaker { get { return load_resource_and_freeze("Beaker"); } }
public static ImageSource Book { get { return load_resource_and_freeze("Book"); } }
@@ -143,7 +146,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 Defrag { get { return load_resource_and_freeze("Defrag"); } }
public static ImageSource DetachAlgos => create_icon(DetachAlgosGeometryKey);
public static ImageSource DetachAlgos => create_icon_and_rotate(DetachAlgosKey, Brushes.Red);
public static ImageSource Disk => create_icon(DiskGeometryKey);
public static ImageSource DiskFile => create_icon(DiskFileGeometryKey);
public static ImageSource Display { get { return load_resource_and_freeze("Display"); } }
@@ -164,6 +167,7 @@ namespace mbts.ui {
public static ImageSource Payment => create_icon(PaymentGeometryKey);
public static ImageSource Pencil => create_icon(PencilGeometryKey);
public static ImageSource Prohibited => create_icon(ProhibitedGeometryKey);
public static ImageSource RubberStamp => create_icon(RubberStampGeometryKey);
public static ImageSource RedoCircular => create_icon(UndoCircularGeometryKey, Brushes.Black, null, new ScaleTransform(-1, 1));
public static ImageSource Spawn { get { return load_resource_and_freeze("Spawn"); } }
public static ImageSource Star => create_icon(StarGeometryKey);
@@ -192,6 +196,19 @@ namespace mbts.ui {
return rv;
}
public static ImageSource create_icon_and_rotate(string geometric_path_key, Brush brush = null, Pen pen = null, Transform transform = null, double degrees = 90) {
if (brush == null) {
brush = Brushes.Black;
}
var drawing = create_drawing(geometric_path_key, brush, pen, transform);
var dg = new DrawingGroup();
dg.Children.Add(drawing);
dg.Transform = new RotateTransform(degrees);
var rv = new DrawingImage(dg);
rv.Freeze();
return rv;
}
public static Drawing create_drawing(string geometric_path_key) =>
create_drawing(geometric_path_key, Brushes.Black);
+12 -52
View File
@@ -5,6 +5,14 @@
xmlns:mbts="clr-namespace:mbts.ui"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<sys:String x:Key="{x:Static mbts:Icons.AttachAlgosKey}">
M10.519881,14.945064C11.300896,14.955074,12.079896,15.102078,12.819896,15.386075L10.878893,17.326085C9.6458764,17.201085,8.3688531,17.591071,7.4258485,18.533091L3.5977991,22.360102C1.9327704,24.026093 1.9317786,26.736124 3.5977991,28.402117 5.2638197,30.068108 7.9748359,30.068108 9.6398644,28.402117L13.467914,24.575106C14.410919,23.632109,14.800938,22.355097,14.675937,21.121083L16.615963,19.180096C17.523962,21.546103,17.034943,24.325104,15.126931,26.234108L11.298882,30.061119C8.7138591,32.64612 4.5238051,32.64612 1.9387672,30.062127 -0.64625573,27.477126 -0.64625573,23.287106 1.9387672,20.701098L5.7668166,16.875065C7.077837,15.564055,8.8018727,14.923061,10.519881,14.945064z M19.825993,10.418039L21.580012,12.17305 12.089907,21.665091 10.333873,19.911087z M25.383069,0C27.077088,3.0538786E-08 28.771109,0.64599859 30.063117,1.9390028 32.647133,4.5230274 32.647133,8.7120385 30.062109,11.298047L26.235068,15.125058C24.327055,17.033085,21.547022,17.52207,19.181973,16.614077L21.121998,14.674067C22.356022,14.799068,23.633046,14.409051,24.57605,13.466054L28.403094,9.6400513C30.068121,7.9740295 30.069129,5.2630205 28.403094,3.5980362 26.738064,1.9320143 24.027033,1.9320143 22.362034,3.5980362L18.533986,7.4240398C17.591958,8.3670363,17.201968,9.643042,17.32697,10.877056L15.385937,12.817066C14.478913,10.452035,14.968939,7.6730337,16.875945,5.7660436L20.703019,1.9390028C21.995026,0.64599859,23.689047,3.0538786E-08,25.383069,0z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.DetachAlgosKey}">
M19.522852,25.255011L20.920847,25.256994 20.920847,29.729012 19.522852,29.729012z M22.826874,21.952002L25.989905,25.113989 25.001887,26.101996 21.838856,22.941015z M25.269894,19.506992L29.742937,19.509007 29.742937,20.906012 25.269894,20.907995z M10.519729,14.945011C11.300744,14.954991,12.079745,15.103001,12.818766,15.386997L10.878742,17.327C9.6457264,17.200016,8.368704,17.592014,7.4257004,18.532993L3.597655,22.359015C1.9326275,24.025 1.9316508,26.734991 3.597655,28.40299 5.2636887,30.068001 7.9747178,30.066017 9.6397144,28.40299L13.468767,24.575018C14.410794,23.633001,14.800783,22.355993,14.674774,21.122015L16.615807,19.181004C17.523806,21.547003,17.034817,24.325995,15.126776,26.233008L11.298761,30.061012C8.713709,32.644997 4.5236599,32.644997 1.9396465,30.061012 -0.64638097,27.475012 -0.64638097,23.286017 1.9386395,20.702002L5.7666852,16.875004C7.0777046,15.562992,8.8017227,14.922001,10.519729,14.945011z M2.1536381,10.959994L6.6257035,10.959994 6.6266801,12.359988 2.1536381,12.359988z M6.7686803,5.894014L9.9307338,9.0549947 8.9427158,10.044008 5.7806932,6.8819901z M10.976735,2.1390082L12.374761,2.1390082 12.375768,6.6130081 10.975728,6.6130081z M25.382902,0C27.076922,-1.8170738E-08 28.770941,0.6470033 30.063955,1.9389956 32.646992,4.5220043 32.646992,8.7130147 30.061941,11.297L26.234902,15.126011C24.326891,17.032993,21.546862,17.521,19.181845,16.614994L21.121868,14.674015C22.35586,14.798008,23.63389,14.407992,24.575886,13.467013L28.403933,9.6390096C30.067953,7.972994 30.069967,5.2619945 28.403933,3.5960094 26.738905,1.9310002 24.0269,1.9320073 22.361872,3.5960094L18.533827,7.4249893C17.591829,8.3659993,17.201811,9.6440145,17.32782,10.87601L15.385781,12.817996C14.477781,10.453005,14.968784,7.6730062,16.875818,5.7659928L20.702858,1.9379885C21.994864,0.64599622,23.688883,-1.8170738E-08,25.382902,0z
</sys:String>
<!--AddBoxes (the icon is free to use if we include this information in our credit section)
Source: https://www.flaticon.com/authors/gregor-cresnar
Ttile: Add free icon
@@ -40,58 +48,6 @@
</sys:String>
</x:Array>
<!--File (the icon is free to use if we include this information in our credit section)
Source: https://www.flaticon.com/authors/freepik
Ttile: Unlink free icon
Credit: Icon made by Chanut from www.flaticon.com-->
<x:Array x:Key="{x:Static mbts:Icons.AttachAlgosGeometryKey}" Type="sys:String">
<sys:String>
M51.173,3.801c-5.068-5.068-13.315-5.066-18.384,0l-9.192,9.192c-0.781,0.781-0.781,2.047,0,2.828
c0.781,0.781,2.047,0.781,2.828,0l9.192-9.192c1.691-1.69,3.951-2.622,6.363-2.622c2.413,0,4.673,0.932,6.364,2.623
s2.623,3.951,2.623,6.364c0,2.412-0.932,4.672-2.623,6.363L36.325,31.379c-3.51,3.508-9.219,3.508-12.729,0
c-0.781-0.781-2.047-0.781-2.828,0s-0.781,2.048,0,2.828c2.534,2.534,5.863,3.801,9.192,3.801s6.658-1.267,9.192-3.801
l12.021-12.021c2.447-2.446,3.795-5.711,3.795-9.192C54.968,9.512,53.62,6.248,51.173,3.801z
</sys:String>
<sys:String>
M27.132,40.57l-7.778,7.778c-1.691,1.691-3.951,2.623-6.364,2.623c-2.412,0-4.673-0.932-6.364-2.623
c-3.509-3.509-3.509-9.219,0-12.728L17.94,24.306c1.691-1.69,3.951-2.622,6.364-2.622c2.412,0,4.672,0.932,6.363,2.622
c0.781,0.781,2.047,0.781,2.828,0s0.781-2.047,0-2.828c-5.067-5.067-13.314-5.068-18.384,0L3.797,32.793
c-2.446,2.446-3.794,5.711-3.794,9.192c0,3.48,1.348,6.745,3.795,9.191c2.446,2.447,5.711,3.795,9.191,3.795
c3.481,0,6.746-1.348,9.192-3.795l7.778-7.778c0.781-0.781,0.781-2.047,0-2.828S27.913,39.789,27.132,40.57z
</sys:String>
</x:Array>
<!--File (the icon is free to use if we include this information in our credit section)
Source: https://www.flaticon.com/authors/freepik
Ttile: Disconnected chains free icon
Credit: Icon made by Chanut from www.flaticon.com-->
<x:Array x:Key="{x:Static mbts:Icons.DetachAlgosGeometryKey}" Type="sys:String">
<sys:String>
M462.557,77.627c-14.323-25.556-41.488-41.423-70.889-41.423c-13.835,0-27.54,3.585-39.602,10.364
l-152.557,85.635l17.55,31.279l152.565-85.635c6.747-3.78,14.371-5.779,22.069-5.779c16.428,0,31.596,8.852,39.594,23.11
c5.909,10.535,7.356,22.752,4.072,34.408c-3.268,11.656-10.876,21.346-21.411,27.255l-138.291,77.604
c-2.634-16.851-10.494-32.392-22.793-44.699c-21.687-21.663-56.063-29.328-84.935-18.964l-12.485,4.495l12.128,33.734
l12.534-4.471c16.127-5.869,35.319-1.496,47.406,10.567c6.974,6.982,11.388,15.826,12.786,25.41
c-0.585-0.138-1.171-0.285-1.731-0.447c-11.681-3.284-21.354-10.9-27.271-21.476l-6.519-11.518l-31.303,17.59l6.56,11.518
c10.6,18.915,27.93,32.571,48.836,38.44c0.723,0.187,1.455,0.39,2.187,0.561c-1.105,1.398-2.284,2.731-3.536,3.983
l-114.32,114.328c-8.551,8.543-19.931,13.241-32.035,13.241c-12.112,0-23.492-4.698-32.027-13.241
c-8.535-8.543-13.233-19.907-13.233-32.027c0-12.112,4.698-23.483,13.233-32.018l99.291-99.282l-25.369-25.369l-99.282,99.282
C8.446,319.427,0,339.806,0,361.468c0,21.679,8.446,42.049,23.776,57.396c15.339,15.339,35.709,23.768,57.388,23.768
s42.049-8.429,57.388-23.768l114.345-114.32c8.372-8.405,14.81-18.566,18.842-29.71c6.625-1.764,12.924-4.3,18.777-7.6
l140.99-79.115c18.875-10.608,32.514-27.946,38.375-48.828C475.749,118.4,473.148,96.493,462.557,77.627z
</sys:String>
<sys:String>
M173.171,141.769c4.503-0.821,7.511-5.145,6.69-9.657l-17.103-94.942
c-0.821-4.519-5.162-7.527-9.665-6.722c-4.511,0.821-7.527,5.145-6.706,9.673l17.111,94.925
C164.311,139.575,168.644,142.574,173.171,141.769z
</sys:String>
<sys:String>
M43.561,191.776l94.934-17.119c4.519-0.821,7.527-5.145,6.706-9.657
c-0.821-4.528-5.145-7.543-9.649-6.722L40.61,175.405c-4.503,0.797-7.519,5.145-6.698,9.657
C34.717,189.582,39.058,192.589,43.561,191.776z
</sys:String>
</x:Array>
<!--File (the icon is free to use if we include this information in our credit section)
Source: https://www.flaticon.com/authors/chanut
Ttile: Cancel free icon
@@ -334,6 +290,10 @@
M0,0L25.194,16 0,32z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.RubberStampGeometryKey}">
M1.8950181,29.607025L24.423994,29.607025 24.423994,31.103027C24.423994,31.601013,24.025009,32,23.526993,32L2.7920208,32C2.2929976,32,1.8950181,31.601013,1.8950181,31.103027z M0,19.838013L26.218001,19.838013 26.218001,27.813019 0,27.813019z M12.960014,0C16.549,0 19.538988,2.9909973 19.538988,6.57901 19.538988,8.0750122 19.041002,9.3710022 18.243,10.46701L18.043995,10.766022C17.345997,11.863007 16.647999,12.76001 16.647999,12.76001 15.052999,15.651001 15.253011,17.745026 15.253011,17.745026L10.667015,17.745026C10.667015,17.745026 10.866997,15.651001 9.2719967,12.76001 9.2719967,12.76001 8.5739996,11.863007 7.8760016,10.766022L7.6759898,10.46701 7.5769908,10.368011C6.7790176,9.3710022 6.3810074,7.9750061 6.3810074,6.57901 6.3810074,2.9909973 9.2719967,0 12.960014,0z
</sys:String>
<!--File (the icon is free to use if we include this information in our credit section)
Source: https://www.flaticon.com/authors/smashicons
Ttile: File free icon
@@ -204,13 +204,15 @@ namespace mbts.spark {
}
}
void _show_order_book(Side side) =>
_bus.post(SparkTags.ShowOrderBookTag, new bts.shell.orderbook.OrderBookStartupData(
new bts.shell.orderbook.FilterStates {
statuses = new[] { OrderStatus.Working },
sides = new[] { side },
security_criteria = new SecurityCriteria(security)
}));
void _show_order_book(Side side) {
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
ob_settings.filters = new bts.shell.orderbook.FilterStates {
statuses = new[] { OrderStatus.Working },
sides = new[] { side },
security_criteria = new SecurityCriteria(security)
};
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
}
void _show_order_ticket(MarketViewView.Columns col) {
var order = _orders_model.factory.order(security);
@@ -1,5 +1,6 @@
using bts.api;
using bts.collections;
using bts.utils;
using mbts.persist;
using mbts.spark.filters;
using mbts.ui;
@@ -22,7 +23,6 @@ namespace mbts.spark {
readonly OrdersModel _orders_model;
IWindowSettingsManager<GridSettings> _settings_manager;
public MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IWindowSettingsManager<GridSettings> settings_manager )
: this(bus, orders_model, state, state.SelectManyInSeries(s => s.security_criteria.subscribe_md(bus)), settings_manager) { }
@@ -58,12 +58,12 @@ namespace mbts.spark {
add_col(Columns.SessionHigh, "High", r => r.format_price(r.session_high), nameof(Row.session_high), DefaultColumnWidths.Price, session_high_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.SecurityKey, "Security Key", r => r.security_key.ToString(), nameof(Row.security_key), DefaultColumnWidths.SecurityKey, security_key_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.MarketDescription, "Market Description", r => r.md.security.HasIce ? r.md.security.Ice.MarketDescription : string.Empty, nameof(Row.market_description), DefaultColumnWidths.SecurityKey, market_description_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.ClearedAlias, "Cleared Alias", r => r.md.security.HasIce ? r.md.security.Ice.ClearedAlias : string.Empty, nameof(Row.cleared_alias), DefaultColumnWidths.SecurityKey, cleared_alias_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.ProductDescription, "Product Description", r => r.md.security.HasIce ? r.md.security.Ice.ProductName : string.Empty, nameof(Row.product_description), DefaultColumnWidths.SecurityKey, product_description_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.HubAlias, "Hub Alias", r => r.md.security.HasIce ? r.md.security.Ice.HubAlias : string.Empty, nameof(Row.hub_alias), DefaultColumnWidths.SecurityKey, hub_alias_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.StripName, "Strip Name", r => r.md.security.HasIce ? r.md.security.Ice.StripName : string.Empty, nameof(Row.strip_name), DefaultColumnWidths.SecurityKey, strip_name_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.MicCode, "Mic Code", r => r.md.security.HasIce ? r.md.security.Ice.MicCode : string.Empty, nameof(Row.mic_code), DefaultColumnWidths.SecurityKey, mic_code_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.MarketDescription, "Market Description", r => r.security.market_description(), nameof(Row.market_description), DefaultColumnWidths.SecurityKey, market_description_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.ClearedAlias, "Cleared Alias", r => r.security.cleared_alias(), nameof(Row.cleared_alias), DefaultColumnWidths.SecurityKey, cleared_alias_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.ProductDescription, "Product Description", r => r.security.product_name(), nameof(Row.product_description), DefaultColumnWidths.SecurityKey, product_description_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.HubAlias, "Hub Alias", r => r.security.hub_alias(), nameof(Row.hub_alias), DefaultColumnWidths.SecurityKey, hub_alias_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.StripName, "Strip Name", r => r.security.strip_name(), nameof(Row.strip_name), DefaultColumnWidths.SecurityKey, strip_name_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.MicCode, "Mic Code", r => r.security.mic_code(), nameof(Row.mic_code), DefaultColumnWidths.SecurityKey, mic_code_filter, DataGridFilterKnownTemplates.Regex);
//
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, double width, IDataGridColumnFilter filter, string filter_template, bool is_animatable = false, Color? font_color = null, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
@@ -180,6 +180,40 @@ namespace mbts.spark {
return grid_settings_manager.create();
}
public static IComparer<Row> get_column_comparer(Columns column) {
switch (column) {
case Columns.Security: return wrap(r => r.security);
case Columns.SecurityKey: return wrap(r => r.security.Id);
case Columns.Volume: return wrap(r => r.volume);
case Columns.Last: return wrap(r => r.last_price);
case Columns.LastSize: return wrap(r => r.last_size);
case Columns.Position: return wrap(r => r.position);
case Columns.WorkingBids: return wrap(r => r.working_bid_volume);
case Columns.BidImpliedSize: return wrap(r => r.bid_implied_size);
case Columns.BidDirectSize: return wrap(r => r.bid_direct_size);
case Columns.BidMergedSize: return wrap(r => r.bid_merged_size);
case Columns.Bid: return wrap(r => r.bid_price);
case Columns.Ask: return wrap(r => r.ask_price);
case Columns.AskMergedSize: return wrap(r => r.ask_merged_size);
case Columns.AskDirectSize: return wrap(r => r.ask_direct_size);
case Columns.AskImpliedSize: return wrap(r => r.ask_implied_size);
case Columns.WorkingAsks: return wrap(r => r.working_ask_volume);
case Columns.Settlement: return wrap(r => r.settlement);
case Columns.DayChange: return wrap(r => r.day_change);
case Columns.SessionLow: return wrap(r => r.session_low);
case Columns.SessionHigh: return wrap(r => r.session_high);
case Columns.MarketDescription: return wrap(r => r.market_description);
case Columns.ClearedAlias: return wrap(r => r.security.cleared_alias());
case Columns.ProductDescription: return wrap(r => r.security.product_name());
case Columns.HubAlias: return wrap(r => r.security.hub_alias());
case Columns.StripName: return wrap(r => r.security.strip_name());
case Columns.MicCode: return wrap(r => r.security.mic_code());
default: return null;
}
IComparer<Row> wrap<T>(Func<Row, T> selector) =>
Comparer<T>.Default.wrap(selector);
}
#region Filters
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<Row>(r => r.bid_price, nameof(Row.bid_price));
@@ -2,6 +2,7 @@
using bts.api;
using bts.shell;
using bts.spark;
using bts.utils;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
@@ -11,11 +12,12 @@ using mbts.utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Windows;
namespace mbts.spark {
class MarketViewWindow : SparkWindow<MarketViewWindowFactory.StartupData>, ISharedWindow {
class MarketViewWindow : SparkWindow<MarketViewWindowFactory.StartupData>, ISharedWindow, ISlowMeasureContentSize {
const string _window_type = "MarketView", _window_type_display_name = "Market View";
readonly OrdersModel _orders_model;
readonly AllowOnce _init_guard = new AllowOnce("MarketView");
@@ -25,6 +27,9 @@ namespace mbts.spark {
MarketViewWindowFactory.StartupData _state;
IReadOnlyList<SparkCommand> _commands;
IDisposable _subscriptions;
SecurityCriteria
_selected_security_criteria_backing,
_sorted_selected_security_criteria_backing;
public MarketViewWindow(MarketViewWindowFactory factory, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state, WindowSettingsManager<GridSettings> settings_manager)
: base("Market View", bus, factory, SparkCommands.ShowMarketView.icon) {
@@ -50,23 +55,91 @@ namespace mbts.spark {
public string thread_id { get; }
SecurityCriteria _selected_security_criteria {
get {
if (_selected_security_criteria_backing == null) {
if (_sorted_selected_security_criteria_backing != null) {
_selected_security_criteria_backing = _sorted_selected_security_criteria_backing;
} else if (_vm != null)
_selected_security_criteria_backing = new SecurityCriteria(_vm.selected_rows.Select(r => r.security));
}
return _selected_security_criteria_backing;
}
}
SecurityCriteria _sorted_selected_security_criteria {
get {
if (_sorted_selected_security_criteria_backing == null) {
if (_vm != null) {
var sorting_criteria = _vm.rows_view.get_sorting_criteria(_vm.columns);
var comp_builder = new ComparerBuilder<MarketViewVM.Row>();
foreach (var sc in sorting_criteria) {
if (_vm.columns.TryGetFirst(col => string.Equals(col.id, sc.col_name, StringComparison.CurrentCultureIgnoreCase), out var match)) {
if (match is ui.controls.DIYGrid.IKeyedColumn<MarketViewView.Columns> kcol) {
var col_comp = MarketViewVM.get_column_comparer(kcol.key);
if (col_comp != null) {
comp_builder.add(sc.is_ascending ? col_comp : col_comp.reverse());
} else {
Log.Ui.warn($"{_window_type} is unable to find the row comparer for the '{sc.col_name}' column.");
}
} else {
Log.Ui.warn($"{_window_type} is unable find a column with id '{sc.col_name}.'");
}
}
}
var comp = comp_builder.to_comparer();
_sorted_selected_security_criteria_backing = new SecurityCriteria(_vm.selected_rows.Ordered(comp).Select(r => r.security));
}
}
return _sorted_selected_security_criteria_backing;
}
}
bool _has_selected_rows() => _vm != null && _vm.selected_rows.Count > 0;
public void thread_init() {
_init_guard.check_access();
Log.notnull(_state);
var state_src = Observable.FromEvent<Action<MarketViewWindowFactory.StartupData>, MarketViewWindowFactory.StartupData>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
_vm = new MarketViewVM(_bus, _orders_model, state_src, _settings_manager);
_view = new MarketViewView { DataContext = _vm };
_view._grid.Sorting += on_grid_sorting;
/// The 'CanExecute' plumbing for the cmds below is designed to avoid potentially costly .ToList() operations on the selected_rows/securities.
/// Some of the cmds post ITags with data that is, or has, a SecurityCriteria member; the SecurityCriteria constructor creates a new List of the specified securities.
/// For these commands, the objective is to evaluate 'CanExecute' without instantiating the SecurityCriteria (or more importantly, without creating a new List).
// show time and sales
var show_tas_cmd = new DelegateCommand(() => {
var sc = _selected_security_criteria;
if (sc.is_defined())
_bus.post(SparkTags.ShowTimeAndSalesTag, sc);
}, _has_selected_rows);
var show_tas_actual_cmd = SparkCommands.ShowTimeAndSales;
var show_tas_cmd_wrapper = new SparkCommandCommand(show_tas_actual_cmd.title, show_tas_actual_cmd.icon, show_tas_cmd);
// show fills
var show_fills_cmd = new DelegateCommand(() => {
var sc = _selected_security_criteria;
_bus.post(SparkTags.ShowFillsTag, new FillsWindowFactory.StartupData(
new FillsWindowFactory.FilterStates {
security = sc.is_defined() ? sc : _state.security_criteria
}));
});
var show_fills_actual_cmd = SparkCommands.ShowFills();
var show_fills_cmd_wrapper = new SparkCommandCommand(show_fills_actual_cmd.title, show_fills_actual_cmd.icon, show_fills_cmd, SparkCommands.KeyGestures.ShowFills);
// show synthetic create
var show_synth_create_cmd = new DelegateCommand(() => {
var sc = _sorted_selected_security_criteria;
if (sc.is_defined())
_bus.post(SparkTags.ShowSyntheticCreateTag, sc);
}, () => _vm.selected_rows.Count > 0 && _vm.selected_rows.Count <= 24);
var show_synth_create_actual_cmd = SparkCommands.ShowSyntheticCreate();
var show_synth_create_cmd_wrapper = new SparkCommandCommand(show_synth_create_actual_cmd.title, show_synth_create_actual_cmd.icon, show_synth_create_cmd, SparkCommands.KeyGestures.ShowSyntheticCreate);
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
_commands = new List<SparkCommand> {
SparkCommands.ShowLadders(SparkCommands.KeyGestures.ShowLadder).complete(_vm.selected_security_mds_observable(), smds => smds.Count > 0),
SparkCommands.ShowOrderTicketFromSecurity(SparkCommands.KeyGestures.ShowOrderTicket).complete(_vm.current_security_md, md => md.HasValue, ConvertUtils.ValueOrDefault),
SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(_vm.selected_securities_observable(), _ => true,
secs => new FillsWindowFactory.StartupData(new FillsWindowFactory.FilterStates {
security = secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria
})
),
SparkCommands.ShowTimeAndSales.complete(_vm.selected_securities_observable(), _ => true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria),
SparkCommands.ShowSyntheticCreate(SparkCommands.KeyGestures.ShowSyntheticCreate).complete(_vm.sorted_selected_securities_observable(), secs => secs.Count > 0 && secs.Count <= 24, secs => new SecurityCriteria(secs)),
show_fills_cmd_wrapper,
show_tas_cmd_wrapper,
show_synth_create_cmd_wrapper,
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
@@ -87,8 +160,27 @@ namespace mbts.spark {
return dialog;
}))
};
_subscriptions = _bus.subscribe(update_securities_cmd.tag).OnDispatcher().Subscribe(c =>
_state_changed?.Invoke(_state = new MarketViewWindowFactory.StartupData(c, _vm.get_filter_states(), _vm.grid_settings_manager.create())));
//
_vm.selection_changed += on_selection_changed;
_subscriptions = new CompositeDisposable(_bus.subscribe(update_securities_cmd.tag).OnDispatcher().Subscribe(c =>
_state_changed?.Invoke(_state = new MarketViewWindowFactory.StartupData(c, _vm.get_filter_states(), _vm.grid_settings_manager.create())))) {
() => {
_vm.selection_changed -= on_selection_changed;
_view._grid.Sorting -= on_grid_sorting;
}
};
void on_selection_changed((IEnumerable<MarketViewVM.Row> removed, IEnumerable<MarketViewVM.Row> added) change) {
// invalidate selected security criteria
_selected_security_criteria_backing = null;
_sorted_selected_security_criteria_backing = null;
show_tas_cmd.RaiseCanExecuteChanged();
show_fills_cmd.RaiseCanExecuteChanged();
show_synth_create_cmd.RaiseCanExecuteChanged();
}
void on_grid_sorting(object sender, ui.controls.DIYGridSortingEventArgs e) =>
_sorted_selected_security_criteria_backing = null;
}
public override void Dispose() {
@@ -12,6 +12,9 @@
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Modules/Spreader/ExecutionStrategyEditor.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style TargetType="ScrollViewer">
<Setter Property="u:ScrollViewerCorrector.FixNestedScrolling" Value="False"/>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid>
@@ -68,12 +68,13 @@ namespace mbts.spark {
rs => rs.HasValue, select_row_strategy_tuple
), SparkCommands.ShowLadder(SparkCommands.KeyGestures.ShowLadder).complete(
vm_connectable_builder.create(vm => vm.current_security_md), md => md.HasValue, ConvertUtils.ValueOrDefault
), SparkCommands.ShowOrders.complete(() => new bts.shell.orderbook.OrderBookStartupData {
filters = new bts.shell.orderbook.FilterStates {
security_criteria = new SecurityCriteria(_vm.value.row_vms.Select(r => r.security))
}
}
), SparkCommands.window_settings_command(new DialogCommand(() => {
), SparkCommands.ShowOrders.complete(() => {
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
ob_settings.filters = new bts.shell.orderbook.FilterStates {
security_criteria = new SecurityCriteria(_vm.value.row_vms.Select(r => r.security))
};
return ob_settings;
}), SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
_settings_manager,
us => us.synthetic_management_grid,
@@ -73,7 +73,7 @@ namespace mbts.spark {
_has_working_orders = backing(nameof(has_working_orders), false);
contract_picker_model = new ContractPickerModel(bus, false, null, new SecurityCriteria(new[] {
new SecurityCriteriaClause() {
exchanges = EnumHelper.get_values_except(Venue.Bts).ToLArray()
exchanges = EnumHelper.get_values_except(Venue.Bts).ToArray()
}
}));
contract_picker_model.cancel_action = _close_new_leg_popup;
@@ -168,12 +168,13 @@ namespace mbts.spark {
void _show_ladder() =>
_bus.post(SparkTags.ShowLadderTag, md);
void _show_order_book() =>
_bus.post(SparkTags.ShowOrderBookTag, new bts.shell.orderbook.OrderBookStartupData {
filters = new bts.shell.orderbook.FilterStates {
security_criteria = new SecurityCriteria(security)
}
});
void _show_order_book() {
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
ob_settings.filters = new bts.shell.orderbook.FilterStates {
security_criteria = new SecurityCriteria(security)
};
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
}
}
}
}
@@ -2,6 +2,7 @@
using bts.utils;
using mbts.spark.utilities;
using System;
using System.Windows.Media;
namespace mbts.spark {
struct AlgoData : IEquatable<AlgoData> {
@@ -36,4 +37,31 @@ namespace mbts.spark {
public static bool operator ==(AlgoData a, AlgoData b) => a.Equals(b);
public static bool operator !=(AlgoData a, AlgoData b) => !a.Equals(b);
}
struct AlgoDataVM : IEquatable<AlgoDataVM> {
public readonly AlgoData data;
public readonly AlgoDefinitionMetadata? meta;
public AlgoDataVM(AlgoData data, AlgoDefinitionMetadata? meta = null) {
this.data = data;
this.meta = meta;
}
public string name => data.name;
public Brush badge_fill => OrderUtils.badge_fill(meta, OrderDataHelper.get_metadata(data.value.CustomCase));
public AlgoDisplayGroup group => data.key.display_group();
public override string ToString() =>
data.name;
public static AlgoDataVM from_data(AlgoData data, AlgoDefinitionMetadata? meta) =>
new AlgoDataVM(data, meta);
public static AlgoDataVM from_data((AlgoData algo, AlgoDefinitionMetadata? meta) tup) =>
new AlgoDataVM(tup.algo, tup.meta);
public bool Equals(AlgoDataVM other) => data == other.data && meta == other.meta;
public override bool Equals(object obj) => obj is AlgoDataVM other && Equals(other);
public override int GetHashCode() => EqualityComparer.aggregate_hash(data.GetHashCode(), meta.GetHashCode());
}
}
@@ -1,9 +1,76 @@
using bts.api.orderman;
using bts.persist.algo;
using bts.utils;
using ProtoBuf;
using System;
namespace mbts.spark {
[ProtoContract]
struct AlgoID : IEquatable<AlgoID> {
[ProtoMember(1)]
readonly CustomOrderDefinition.CustomOneofCase? _factory_key;
[ProtoMember(2)]
readonly string _dynamic_key;
[ProtoMember(3)]
readonly uint? _persisted_key;
public AlgoID(CustomOrderDefinition.CustomOneofCase factory_key) {
_factory_key = factory_key;
_dynamic_key = null;
_persisted_key = null;
}
public AlgoID(string dynamic_key) {
_factory_key = null;
_dynamic_key = dynamic_key;
_persisted_key = null;
}
public AlgoID(uint persisted_key) {
_factory_key = null;
_dynamic_key = null;
_persisted_key = persisted_key;
}
public static AlgoID from_order_def(OrderDefinitionImmutable order_def) {
switch (order_def.CustomDef.CustomCase) {
case CustomOrderDefinition.CustomOneofCase.Composite: return new AlgoID(order_def.CustomDef.Composite.AlgoTemplateKey);
case CustomOrderDefinition.CustomOneofCase.Dynamic: return new AlgoID(order_def.CustomDef.Dynamic.DynamicOrderType);
default: return new AlgoID(order_def.CustomDef.CustomCase);
}
}
public static AlgoID from_algo_key(AlgoKey key) {
switch (key.mode) {
case AlgoKey.Mode.Factory:
if (key.factory.structure.structure.Count > 0)
return new AlgoID((CustomOrderDefinition.CustomOneofCase)key.factory.structure.structure[0]);
break;
case AlgoKey.Mode.Persisted:
return new AlgoID(key.persisted.id_uint);
}
return new AlgoID(CustomOrderDefinition.CustomOneofCase.None);
}
public bool Equals(AlgoID other) =>
_factory_key == other._factory_key &&
_dynamic_key == other._dynamic_key &&
_persisted_key == other._persisted_key;
public override bool Equals(object obj) =>
obj is AlgoID && Equals(obj);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
_factory_key?.GetHashCode() ?? 0,
_dynamic_key?.GetHashCode() ?? 0,
_persisted_key?.GetHashCode() ?? 0);
public static bool operator ==(AlgoID a, AlgoID b) => a.Equals(b);
public static bool operator !=(AlgoID a, AlgoID b) => !a.Equals(b);
}
/// TODO: Remove <see cref="AlgoKey"/>, and redirect references to <see cref="AlgoID"/>.
struct AlgoKey : IEquatable<AlgoKey> {
readonly AlgoPersistedKey? _persisted_key;
readonly AlgoNameStructureKey? _factory_key;
@@ -83,4 +150,4 @@ namespace mbts.spark {
InvalidOperationException _unexpected_mode_ex() => new InvalidOperationException($"Unexpected Mode '{mode}.'");
}
}
}
@@ -70,6 +70,9 @@ namespace mbts.spark {
public static CustomOrderDefinitionImmutable build_algo(this AlgoStructureKey structure_key, DynamicOrderHelper dynamic_helper) =>
parsei(structure_key.structure, dynamic_helper);
public static CustomOrderDefinition.CustomOneofCase order_type(this AlgoStructureKey structure_key) =>
structure_key.structure.Count > 0 ? (CustomOrderDefinition.CustomOneofCase)structure_key.structure[0] : CustomOrderDefinition.CustomOneofCase.None;
public static AlgoStructureKey structure_key(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
new AlgoStructureKey(to_array(algo, dynamic_type_index));
@@ -70,7 +70,7 @@ namespace mbts.spark {
is_hidden.GetHashCode(),
abbreviation == null ? 0 : abbreviation.GetHashCode(),
badge_color?.GetHashCode() ?? 0,
EqualityComparer.aggregate_hash_limited(_favorite_templates));
_favorite_templates == null ? 0 : EqualityComparer.aggregate_hash_limited(_favorite_templates));
public static bool operator ==(AlgoDefinitionMetadata a, AlgoDefinitionMetadata b) => a.Equals(b);
public static bool operator !=(AlgoDefinitionMetadata a, AlgoDefinitionMetadata b) => !a.Equals(b);
@@ -1,23 +1,36 @@
using bts.api.core;
using bts.api.orderman;
using bts.utils;
using mbts.collections;
using mbts.ui.utilities;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Linq;
namespace mbts.spark {
static class AlgoCollectionHelper {
public static readonly IComparer<AlgoDefinitionVM> algo_definition_vm_comparer =
new ComparerBuilder<AlgoDefinitionVM>(
new [] {
new[] {
Comparer<AlgoDefinitionVM>.Create(_CompareUtils.compare_groups),
Comparer<AlgoDefinitionVM>.Create(_CompareUtils.compare_names)
}).to_comparer().to_safe();
public static readonly IComparer<AlgoData> algo_data_comparer =
new ComparerBuilder<AlgoData>(
new[] {
Comparer<AlgoData>.Create(_CompareUtils.compare_groups),
Comparer<AlgoData>.Create(_CompareUtils.compare_names)
}
).to_comparer();
public static readonly IComparer<AlgoDataVM> algo_data_vm_comparer =
algo_data_comparer.wrap<AlgoData, AlgoDataVM>(vm => vm.data);
public static readonly IComparer<IAlgoVM> algo_vm_comparer =
new ComparerBuilder<IAlgoVM>(
new [] {
new[] {
Comparer<IAlgoVM>.Create(_CompareUtils.compare_groups),
Comparer<IAlgoVM>.Create(_CompareUtils.compare_names)
}).to_comparer().to_safe();
@@ -34,18 +47,50 @@ namespace mbts.spark {
}
}
public static void update_persisted_definition_wrappers(ObservableKeyedCollection<IAlgoVM, AlgoKey> target, TableUpdate<AlgoData, AlgoKey> update, Func<AlgoData, AlgoDefinitionVM> factory) {
public static void update_persisted_definition_wrappers(IDictionary<AlgoKey, IAlgoVM> target, TableUpdate<AlgoData, AlgoKey> update, Func<AlgoData, AlgoDefinitionVM> factory) {
foreach (var d in update.removed)
target.Remove(d.key);
foreach (var d in update.changed)
target[d.key].load_from(d.value);
foreach (var d in update.changed) {
var vm = target[d.key];
if (vm is AlgoVMWrapper wrapper) {
wrapper.value.load(d);
} else vm.load_from(d.value);
}
foreach (var d in update.added) {
if (target.TryGetValue(d.key, out IAlgoVM def)) {
def.load_from(d.value);
} else target.Add(new AlgoVMWrapper(factory(d)));
} else target.Add(d.key, new AlgoVMWrapper(factory(d)));
}
}
public static void commit_all(this IEnumerable<AlgoParamRowVM> algo_param_rows) =>
algo_param_rows.Select(r => r.param).commit_all();
public static void commit_all(this IEnumerable<IAlgoParamVM> algo_params) {
foreach (var p in algo_params)
p.mark_committed();
}
public static void revert_all(this IEnumerable<AlgoParamRowVM> algo_param_rows) =>
algo_param_rows.Select(r => r.param).revert_all();
public static void revert_all(this IEnumerable<IAlgoParamVM> algo_params) {
foreach (var p in algo_params)
p.revert();
}
public static AlgoDisplayGroup display_group(CustomOrderDefinition.CustomOneofCase order_type) => order_type.is_bts_algo_type() ? AlgoDisplayGroup.BTS : AlgoDisplayGroup.Exchange;
public static AlgoDisplayGroup display_group(this AlgoKey key) {
switch (key.mode) {
case AlgoKey.Mode.Factory: return display_group(key.factory.structure.order_type());
case AlgoKey.Mode.Unpersisted:
case AlgoKey.Mode.Persisted:
return AlgoDisplayGroup.Studio;
}
return AlgoDisplayGroup.BTS;
}
static class _CompareUtils {
static AlgoDisplayGroup[] _sorted_groups = new[] {
AlgoDisplayGroup.Exchange,
@@ -57,14 +102,20 @@ namespace mbts.spark {
public static int compare_groups(AlgoDefinitionVM a, AlgoDefinitionVM b) =>
Array.IndexOf(_sorted_groups, a.group).CompareTo(Array.IndexOf(_sorted_groups, b.group));
public static int compare_groups(AlgoData a, AlgoData b) =>
Array.IndexOf(_sorted_groups, a.key.display_group()).CompareTo(Array.IndexOf(_sorted_groups, b.key.display_group()));
public static int compare_groups(IAlgoVM a, IAlgoVM b) =>
Array.IndexOf(_sorted_groups, a.group).CompareTo(Array.IndexOf(_sorted_groups, b.group));
public static int compare_names(AlgoDefinitionVM a, AlgoDefinitionVM b) =>
DisplayNameHelper.compare_alpha_numeric(a.name, b.name);
public static int compare_names(AlgoData a, AlgoData b) =>
DisplayNameHelper.compare_alpha_numeric(a.name, b.name);
public static int compare_names(IAlgoVM a, IAlgoVM b) =>
DisplayNameHelper.compare_alpha_numeric(a.display_name, b.display_name);
}
}
}
}
@@ -202,6 +202,7 @@ namespace mbts.spark {
public string name { get => _name.Value; set => _name.set(value); }
public string abbreviation { get => _abbreviation.Value; set => _abbreviation.set(value); }
public ObservableCollection<IAlgoVM> algo_roots => _algo_roots;
public IDictionary<AlgoKey, IAlgoVM> algo_roots_index => _algo_roots;
public IReadOnlyObservableCollection<IAlgoVM> algos_view { get; }
public IAlgoVM algo_root { get => _algo_root.Value; set => _algo_root.set(value); }
public IAlgoVM visible_algo_root => _visible_algo_root.Value;
@@ -372,7 +373,12 @@ namespace mbts.spark {
}
public void load_many(IEnumerable<CustomOrderDefinitionImmutable> defs) {
var def_list = defs as IList<CustomOrderDefinitionImmutable> ?? defs.ToList();
/// don't load composite algos; we don't support recursive composites (<see cref="OrderUtils.actual(CustomOrderDefinitionImmutable)"/>)
if (defs is IList<CustomOrderDefinitionImmutable> def_list) {
def_list = def_list.WrapSelector(d => d.actual(), null);
} else {
def_list = defs.Select(d => d.actual()).ToList();
}
switch (def_list.Count) {
case 0: algo_root = null; break;
case 1: load_def(def_list[0]); break;
@@ -18,12 +18,16 @@ namespace mbts.spark {
this.algo = algo;
this.parent = parent;
_template_key = template_key;
param.permissions_changed += on_permissions_changed;
_dispose = Disposable.Create(() => param.permissions_changed -= on_permissions_changed);
void on_permissions_changed(AlgoParamPermissions oldval, AlgoParamPermissions newval) => RaisePropertyChanged(nameof(permissions));
}
public event Action invalidated;
public IAlgoVM algo { get; set; }
public IAlgoParamVM param { get; }
public AlgoParamPermissions permissions => param.permissions;
public AlgoParamRowVM parent { get; }
public int level => parent == null ? 0 : parent.level + 1;
public string path_disambiguation {
@@ -29,13 +29,12 @@ namespace mbts.spark {
nameof(editing_permissions),
nameof(definition),
nameof(display_name)
},
_value_echo_props = new HashSet<string> {
nameof(AlgoDefinitionVM.name),
nameof(AlgoDefinitionVM.abbreviation),
nameof(AlgoDefinitionVM.badge_fill),
nameof(AlgoDefinitionVM.can_undo),
nameof(AlgoDefinitionVM.can_redo)
};
static readonly IReadOnlyDictionary<string, string>
_value_echo_props = new Dictionary<string, string> {
{ nameof(AlgoDefinitionVM.name), nameof(display_name) },
{ nameof(AlgoDefinitionVM.abbreviation), nameof(abbreviation) },
{ nameof(AlgoDefinitionVM.badge_fill), nameof(meta) }
};
readonly ObservableAggregateCollection<IAlgoSelector> _child_algo_selectors = new ObservableAggregateCollection<IAlgoSelector>();
readonly ObservableAggregateCollection<IAlgoParamVM> _algo_params = new ObservableAggregateCollection<IAlgoParamVM>();
@@ -143,8 +142,8 @@ namespace mbts.spark {
RaisePropertyChanged(e.PropertyName);
}
void on_value_prop_changed(object sender, PropertyChangedEventArgs e) {
if (_value_echo_props.Contains(e.PropertyName))
RaisePropertyChanged(e.PropertyName);
if (_value_echo_props.TryGetValue(e.PropertyName, out string wrapper_prop))
RaisePropertyChanged(wrapper_prop);
}
}
@@ -377,7 +377,7 @@
<DataTemplate>
<ComboBox
HorizontalAlignment="Stretch"
ItemsSource="{Binding param.value, Converter={StaticResource EnumItemsSourceConverter}, Mode=OneTime}"
ItemsSource="{Binding Converter={x:Static usp:AlgoEditorGridView.NullableEnumParamRowItemsSourceConverter}}"
ItemTemplate="{StaticResource NullableEnumDescriptionTemplate}"
SelectedItem="{Binding param.value, UpdateSourceTrigger=PropertyChanged}"
/>
@@ -481,10 +481,14 @@
<usp:AlgoEditorGridRowStyleSelector.DefaultStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding param.is_dirty}" Value="{u:True}">
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding param.is_dirty}" Value="{u:True}"/>
<Condition Binding="{Binding AnimateDirty, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}" Value="{u:True}"/>
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="#8098FB98"/>
<Setter Property="ToolTip" Value="Value has been modified."/>
</DataTrigger>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</usp:AlgoEditorGridRowStyleSelector.DefaultStyle>
@@ -5,8 +5,8 @@ using mbts.ui.controls;
using mbts.ui.converters;
using mbts.ui.utility;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
@@ -19,7 +19,19 @@ namespace mbts.ui.spark {
public static readonly IValueConverter
GeneralVisualStateConverter = ValueConverter.create<bool, string>(is_read_only => is_read_only ? VisualStateNames.General.ReadOnly : VisualStateNames.General.Normal),
AlgoRootVisualStateConverter = ValueConverter.create<bool, string>(show_bbo => show_bbo ? VisualStateNames.AlgoRoot.Active : VisualStateNames.AlgoRoot.Inactive),
AlgoRootComboBoxMarginConverter = ValueConverter.create<double, Thickness>(name_col_width => new Thickness(name_col_width + 8, 0, 0, 0));
AlgoRootComboBoxMarginConverter = ValueConverter.create<double, Thickness>(name_col_width => new Thickness(name_col_width + 8, 0, 0, 0)),
NullableEnumParamRowItemsSourceConverter = ValueConverter.create<AlgoParamRowVM, IEnumerable>(param_row => {
if (param_row?.param != null) {
var tenum = param_row.param.GetType().GetInterfaceGenericArgument(AlgoParamTemplateSelector.generic_algo_param_itype);
if (tenum.IsNullable()) {
// .First() should be safe, since we know this is a Nullable<T>.
tenum = tenum.GetGenericArguments().First();
}
if (tenum.IsEnum)
return Enum.GetValues(tenum);
}
return null;
});
public static readonly MouseButtonEventHandler PayupTicksMouseButtonHandler = (s, e) => {
if ((s as Hyperlink)?.Command is ICommand cmd) {
@@ -40,6 +52,8 @@ namespace mbts.ui.spark {
// AlgoParamRows
AlgoParamRowsViewSource = new CollectionViewSource<ListCollectionView<AlgoParamRowVM>>();
BindingOperations.SetBinding(AlgoParamRowsViewSource, CollectionViewSource.SourceProperty, new Binding(nameof(AlgoParamRows)) { Source = this });
AlgoParamRowsViewSource.IsLiveFilteringRequested = true;
AlgoParamRowsViewSource.LiveFilteringProperties.Add(nameof(AlgoParamRowVM.permissions));
AlgoParamRowsViewSource.Filter += param_rows_filter;
DataGridEditHelper.set_hooks(this);
void param_rows_filter(object sender, FilterEventArgs e) =>
@@ -64,6 +78,16 @@ namespace mbts.ui.spark {
}
}
#region AnimateDirtyParams
public static readonly DependencyProperty AnimateDirtyProperty =
WPFHelper.create_dp<bool, AlgoEditorGridView>(nameof(AnimateDirty), true);
public bool AnimateDirty {
get => (bool)GetValue(AnimateDirtyProperty);
set => SetValue(AnimateDirtyProperty, value);
}
#endregion
#region IsReadOnly
public static readonly DependencyProperty IsReadOnlyProperty =
WPFHelper.create_dp<bool, AlgoEditorGridView>(nameof(IsReadOnly));
@@ -19,6 +19,8 @@ namespace mbts.ui.spark {
/// TODO: remove this class and just use a <see cref="KeyedTemplateSelector"/>.
class AlgoParamTemplateSelector : DataTemplateSelector {
public static readonly Type generic_algo_param_itype = typeof(IAlgoParamVM<>);
KeyedCollection<TemplateSelectorKeyValue, string> _keyed_templates = new KeyedCollection<TemplateSelectorKeyValue, string>(tsk => tsk.Key);
public DataTemplate BoolTemplate { get; set; }
@@ -58,7 +60,7 @@ namespace mbts.ui.spark {
if (pvm is IAlgoParamVM<QuantityMeasureImmutable?>) return QuantityMeasureTemplate;
if (pvm is IAlgoParamVM<ExecutionStrategy>) return ExecutionStrategyTemplate;
if (pvm is IAlgoParamVM<Google.Protobuf.WellKnownTypes.Timestamp>) return TimestampTemplate;
var targ = ReflectionHelper.GetGenericTypeArgs(pvm.GetType(), typeof(IAlgoParamVM<>)).FirstOrDefault();
var targ = ReflectionHelper.GetGenericTypeArgs(pvm.GetType(), generic_algo_param_itype).FirstOrDefault();
if (targ != null) {
if (targ.IsEnum)
return EnumTemplate;
@@ -233,7 +233,7 @@ namespace mbts.spark {
public OrderType order_type => _order.order_type();
public string display_name => OrderUtils.display_name(_order, _fill_tuple.algo);
public OrderMetadata order_metadata => _order_metadata ?? (_order_metadata = _model.factory.get_metadata(_order.Def.CustomDef.CustomCase));
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(_order, _fill_tuple.algo_meta, order_metadata));
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(_fill_tuple.algo_meta, order_metadata));
public string account { get; private set; }
public string user => _order.user();
public Venue exchange => security.Exchange;
@@ -2,23 +2,23 @@
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.collections;
using bts.md;
using mbts.co2;
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using System.Threading.Tasks;
namespace mbts.spark {
class LadderModel {
readonly OrdersModel _order_gateway;
public LadderModel(SecurityMD source, OrdersModel ordergw, Func<OrderSecurity, bool> order_filter = null) {
public LadderModel(SecurityMD source, OrdersModel ordergw, MBOService mbo, Func<OrderSecurity, bool> order_filter = null) {
security_md = source;
_order_gateway = ordergw;
orders = ordergw.on_security_working_orders(source.security.Id);
if (order_filter != null)
orders = orders.Filter(o => order_filter(new OrderSecurity(source.security, o)), o => o.Key);
orders = ordergw.on_security_working_orders_with_piq(source.security.Id);
this.mbo_service = mbo;
if (order_filter != null) {
orders = orders.Filter(order_filter, o => o.order.Key);
}
}
public enum CancelType { All, Bids, Asks }
@@ -27,7 +27,9 @@ namespace mbts.spark {
public SecurityImmutable security => security_md.security;
public MDConsumerHandle md => security_md.md;
public OrdersModel order_gateway => _order_gateway;
public IObservable<TableUpdate<OrderRecordImmutable, uint>> orders { get; }
public IObservable<TableUpdate<OrderSecurity, uint>> orders { get; }
public MBOService mbo_service { get; }
public Task cancel_orders(CancelType ct) {
switch (ct) {
@@ -1,4 +1,5 @@
using bts.api;
using bts.md;
using mbts;
using mbts.config;
using mbts.persist;
@@ -21,17 +22,26 @@ namespace bts.shell {
readonly LadderConfig _ladder_conf;
readonly IBus _bus;
readonly IWindowManager _wm;
readonly MBOService _mbo;
readonly UserSettingsHub _settings_hub;
readonly OrdersModel _orders_model;
IReadOnlyDictionary<uint, SecurityMD> _securities = new Dictionary<uint, SecurityMD>();
public LadderWindowFactory(IShellModule owner, IBus bus, IWindowManager wm, UserSettingsHub settings_hub, OrdersModel orders_model)
public LadderWindowFactory(
IShellModule owner,
IBus bus,
IWindowManager wm,
UserSettingsHub settings_hub,
OrdersModel orders_model,
MBOService mbo)
: base(owner, "SparkLadder") {
_bus = bus;
_wm = wm;
_settings_hub = settings_hub;
_orders_model = orders_model;
_ladder_conf = new LadderConfig(Configuration.get<bts.config.LadderKnobs>(), Configuration.get<bts.config.MultiThreadedWindows>().ladder);
_mbo = mbo;
_dispose = Disposable.Create(
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
bus.subscribe(SparkTags.ShowLadderTag).OnDispatcher().Subscribe(s => show_windows(new[] { s })),
@@ -54,7 +64,7 @@ namespace bts.shell {
IShellWindow create_ladder_window(SecurityMD md, LadderSettings state_settings) {
var id = Interlocked.Increment(ref _ladder_count) % Environment.ProcessorCount;
var view = new Lazy<UIElement>(() => {
var model = new LadderModel(md, _orders_model);
var model = new LadderModel(md, _orders_model, _mbo);
var settings_manager = new WindowSettingsManager<LadderSettings>(
_settings_hub, state_settings == null || state_settings.use_defaults ? null : state_settings,
us => sanitize_ladder_settings(us.ladder),
@@ -44,11 +44,14 @@ namespace mbts.spark {
double _real_price = double.NaN, _phantom_price = double.NaN;
uint filled, quantity, num_root_orders;
int working_quantity;
uint? _min_piq = UInt32.MaxValue;
public event Action piq_changed;
LadderModel model => _owner.model;
Dictionary<uint, (OrderRecordImmutable order, uint? p, AlgoDefinitionMetadata? meta)> _orders = new Dictionary<uint, (OrderRecordImmutable order, uint? p, AlgoDefinitionMetadata? meta)>();
Dictionary<uint, (OrderRecordImmutable order, uint? p, AlgoData? algo, AlgoDefinitionMetadata? meta)> _orders = new Dictionary<uint, (OrderRecordImmutable order, uint? p, AlgoData? algo, AlgoDefinitionMetadata? meta)>();
public LadderOrderVM((OrderRecordImmutable, uint?, AlgoDefinitionMetadata?) op, double real_price, LadderVM owner, IBus bus, IObservable<LadderSettings.BadgeStyle> bs) {
public LadderOrderVM((OrderRecordImmutable, uint?, AlgoData?, AlgoDefinitionMetadata?) op, double real_price, LadderVM owner, IBus bus, IObservable<LadderSettings.BadgeStyle> bs) {
_owner = owner;
_bus = bus;
_bs = bs;
@@ -114,7 +117,7 @@ namespace mbts.spark {
var single_order_metadata = _owner.model.order_gateway.factory.get_metadata(single_order);
border = single_order_metadata.Color.rgb_brush();
if (_orders.TryGetValue(single_order.Key, out var opm)) {
algo_studio_abbreviation = opm.meta?.abbreviation ?? string.Empty;
algo_studio_abbreviation = _sanitize_abbreviation(string.IsNullOrEmpty(opm.meta?.abbreviation) ? _generate_abbreviation_for_algo_studio_algo(opm.algo) : opm.meta?.abbreviation);
border = opm.meta?.badge_color?.rgb_color().rgb_brush() ?? border;
}
if (single_order.order_type().is_bts_algo_type()) {
@@ -131,6 +134,34 @@ namespace mbts.spark {
_set_badge_text();
string _generate_abbreviation_for_algo_studio_algo(AlgoData? a) {
char[] delim = { ' ' };
string candidate = string.Empty;
if (a.HasValue) {
var algo_name = a.Value.name;
var words = algo_name.Split(delim);
if (words.Length == 1) {
var single_word = words[0];
if (single_word.Length > 1) {
candidate = words[0];
} else {
candidate = single_word;
}
} else {
candidate = words.Take(2).Select(w => w.Substring(0, 1)).list_string(string.Empty);
}
}
return candidate.ToUpper();
}
string _sanitize_abbreviation(string ab) {
string candidate = ab;
if (ab.Length > 2) {
candidate = candidate.Substring(0, 2);
}
return candidate.ToUpper();
}
}
private void _set_badge_text() {
@@ -138,12 +169,32 @@ namespace mbts.spark {
RaisePropertyChanged(nameof(text_lines));
}
public void add_order((OrderRecordImmutable order, uint? p, AlgoDefinitionMetadata? meta) new_op) {
/// <summary>
/// Add Order performs an add the first time it's called via the constructor. When it's called, directly
/// it really serves as an update/replace. Also note that since the min_piq is a separate piece of state representing
/// the lowest piq of the orders the badge represents, we must reevaluate if that has changed on said update.
/// </summary>
public void add_order((OrderRecordImmutable order, uint? p, AlgoData? algo, AlgoDefinitionMetadata? meta) new_op) {
_orders[new_op.order.Key] = new_op;
// reevaluate min piq if this order's piq is less
if (new_op.p.HasValue) {
// if this lovm only represents 1 order and the piq is now different, regardless of what it is that's our piq
if (_orders.Count == 1 && new_op.p.Value != min_piq) {
min_piq = new_op.p.Value;
} else if (new_op.p.Value < min_piq) {
min_piq = new_op.p.Value;
}
}
}
public void remove_order(uint order_key) {
_orders.Remove(order_key);
// on a remove, potentially the order w/ the lowest piq has been removed, so
// if the min() is now > than the current min_piq, update the min_piq
var prospective_piq = _orders.Select(op => op.Value.p).Where(piq => piq.HasValue).Min();
if (prospective_piq > min_piq) {
min_piq = prospective_piq;
}
}
//public void update(OrderRecordImmutable neworder) {
@@ -155,7 +206,16 @@ namespace mbts.spark {
public uint security_key => _owner.security.Id;
public double price { get => double.IsNaN(_phantom_price) ? _real_price : _phantom_price; }
public uint? min_piq => _orders.Select(op => op.Value.p).Where(piq => piq.HasValue).Min();
public uint? min_piq {
get {
return _min_piq;
}
set {
if (Set(ref _min_piq, value)) {
piq_changed?.Invoke();
}
}
}
public double real_price => _real_price;
public Side side {
get {
@@ -116,7 +116,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(position)));
send_order_command = new DelegateCommand<(double price, Side? side, bool size_from_alternate)>(send_order);
send_order_command = new DelegateCommand<(double price, Side? side, MouseButton button)>(send_order);
cancel_command = new AsyncCommand<LadderModel.CancelType>(_model.cancel_orders, true);
show_book_command = new DelegateCommand(_show_book);
show_ticket_command = new DelegateCommand(_show_ticket);
@@ -146,24 +146,10 @@ namespace mbts.spark {
size_calculator = new CalculatorLogic(0, _settings_manager.subscribe_current().Select(s => s.calculator_buttons)),
bus.subscribe(SparkTags.LogActivity).OnDispatcher().Subscribe(status_update),
// we need to hook this up to model.orders which is filtered for just the security
model.order_gateway.working_orders_with_piq.OnDispatcher().Subscribe(working => {
model.orders.OnDispatcher().Subscribe(working => {
// Here we manage N orders to 1 price slot in the ladder
Dictionary<double, HashSet<uint>> orders_used_in_this_update = new Dictionary<double, HashSet<uint>>(_equality);
// In master we originally had something similar to this snippet to restrict the foreach to just the orders
// for this ladder. In 1944, at one point we made use of model.orders, which made use of the on_security_working_orders demuxer.
// When we merged PIQ into 1944, we moved from that demuxer to working_orders_with_piq, so we reintroduce this little snippet here
// to once again restrict the processing to those working orders that match the criteria. Check with John to see if we can
// make a demuxer from working_orders_with_piq, which is an IReadOnlyDictionary; that probably won't work, but I have no doubt
// there is a better way.
var ladders_orders =
working.Values
.Where(o =>
o.order.Def.SecurityKey == security.Id &&
(o.order.HasState && o.order.State.DisplayStatus == OrderState.Types.DisplayStatus.Visible))
.ToReadOnlyDictionary(os => os.order.Key, os => os);
_cache = ladders_orders;
_cache = working.state;
foreach (var os in _cache.Values) {
var wo = os.order;
@@ -180,9 +166,9 @@ namespace mbts.spark {
}
if (_slot_price_vs_orders.TryGetValue(slot_price_target, out LadderOrderVM slot_vm)) {
slot_vm.add_order((wo, os.piq, os.meta));
slot_vm.add_order((wo, os.piq, os.algo, os.meta));
} else {
slot_vm = new LadderOrderVM((wo, os.piq, os.meta), slot_price_target, this, _bus, _settings_manager.subscribe_current().Select(s => s.badge_style));
slot_vm = new LadderOrderVM((wo, os.piq, os.algo, os.meta), slot_price_target, this, _bus, _settings_manager.subscribe_current().Select(s => s.badge_style));
_slot_price_vs_orders[slot_price_target] = slot_vm;
}
@@ -246,7 +232,7 @@ namespace mbts.spark {
// we return only entries who have piqs and whose piqs are not uint.Max
// uint.Max happens when the server is restarted but the client is still up
// maybe long term we don't show this data while the exchange connection is down
public IEnumerable<(double, uint)> price_vs_piq => _slot_price_vs_orders.Values.Where(result => result.min_piq.HasValue && result.min_piq.Value != uint.MaxValue).Select(o => (o.price, o.min_piq.Value));
public IEnumerable<(double, uint)> price_vs_piq => _slot_price_vs_orders.Values.Where(result => result.min_piq.HasValue && result.min_piq.Value != uint.MaxValue).Select(o => (o.price, o.min_piq.Value));
public bool is_synthetic => _model.security.ContractCase == Security.ContractOneofCase.Synthetic;
Property<string> _status;
@@ -326,7 +312,7 @@ namespace mbts.spark {
public uint volume_column_width { get => _volume_column_width.Value; set => _volume_column_width.set(value); }
internal LadderModel model => _model;
internal IObservable<LadderSettings> settings => _settings_manager.subscribe_current();
internal OrderRecordImmutable get_root_at_price((OrderRecordImmutable order, uint? _, AlgoDefinitionMetadata? meta) op) {
internal OrderRecordImmutable get_root_at_price((OrderRecordImmutable order, uint? _, AlgoData? __, AlgoDefinitionMetadata? meta) op) {
var order = op.order;
double p = order.price();
foreach (var o in lineage(order.ParentKey)) {
@@ -362,13 +348,15 @@ namespace mbts.spark {
}
}
public async void send_order((double price, Side? side, bool size_from_alternate) args) {
public async void send_order((double price, Side? side, MouseButton buton) args) {
if (args.buton == MouseButton.Middle) return;
var size_from_alternate = args.buton == MouseButton.Right;
if (order_properties_vm == null)
return;
var s = _settings_manager.current_settings();
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (args.size_from_alternate && s.alternate_order_size == 0))
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && s.alternate_order_size == 0))
return;
else if (!args.size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
else if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
size_calculator.flash_error();
} else {
var order = order_properties_vm.create(args.side.Value);
@@ -385,7 +373,7 @@ namespace mbts.spark {
}
}
uint size = args.size_from_alternate ? s.alternate_order_size : (uint)Math.Abs(size_calculator.value.Value);
uint size = size_from_alternate ? s.alternate_order_size : (uint)Math.Abs(size_calculator.value.Value);
await _model.order_gateway.send_order(order, args.price, size);
if (auto_clear)
size_calculator.value = 0;
@@ -413,12 +401,13 @@ namespace mbts.spark {
}
void _show_book() =>
_bus.post(SparkTags.ShowOrderBookTag, new bts.shell.orderbook.OrderBookStartupData {
filters = new bts.shell.orderbook.FilterStates {
security_criteria = new SecurityCriteria(security)
}
});
void _show_book() {
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
ob_settings.filters = new bts.shell.orderbook.FilterStates {
security_criteria = new SecurityCriteria(security)
};
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
}
void _show_ticket() {
if (order_properties_vm != null)
@@ -1,8 +1,10 @@
using bts;
using bts.api;
using bts.api.md.auxiliary;
using bts.md;
using bts.collections;
using bts.utils;
using bts.config;
using mbts.co2;
using mbts.ui;
using mbts.ui.controls;
@@ -17,11 +19,13 @@ using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using mbts.config;
namespace mbts.spark {
partial class LadderGridHost : Control {
@@ -56,7 +60,7 @@ namespace mbts.spark {
LadderTactileAdorner _tactile_adorner;
ulong _prices_updated;
NativePriceSize[] _bids = new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH], _asks = new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH];
bool _price_dirty = false;
bool _price_dirty = false, _piq_dirty = false;
double[] _price_list = null;
IComparer<double> _price_comparer;
double _last_origin_price;
@@ -257,11 +261,15 @@ namespace mbts.spark {
case NotifyCollectionChangedAction.Add:
foreach (LadderOrderVM item in e.NewItems) {
_ordershost.add_order(item);
item.piq_changed += _set_piq_dirty;
_set_piq_dirty();
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (LadderOrderVM item in e.OldItems) {
_ordershost.remove_order(item);
item.piq_changed -= _set_piq_dirty;
_set_piq_dirty();
}
break;
case NotifyCollectionChangedAction.Reset:
@@ -272,6 +280,9 @@ namespace mbts.spark {
throw new NotImplementedException();
}
}
void _set_piq_dirty() { _piq_dirty = true; }
#endregion
Column[] columns { get; set; } = new Column[0];
@@ -313,25 +324,56 @@ namespace mbts.spark {
Focus();
var point = e.GetPosition(this);
var column = _column_for_point(point);
if (column.column_type == ColumnType.Price && e.ChangedButton == MouseButton.Middle)
_scroll_to_center();
if (column.column_type == ColumnType.Price && e.ChangedButton == MouseButton.Middle) {
show_mbo_popup(point).ignore();
}
else if (_settings != null) {
var price = price_for_point(point);
Side? bs = null;
if (column.column_type == ColumnType.Bid) bs = Side.Buy;
if (column.column_type == ColumnType.Ask) bs = Side.Sell;
var pbs = (price, bs, e.ChangedButton == MouseButton.Right);
var pbs = (price, bs, e.ChangedButton);
if (Command != null && Command.CanExecute(pbs)) {
if (bs.HasValue) {
using (_tactile_adorner.show_busy(_row_for_point(point), bs.Value == Side.Buy ? ColumnType.Bid : ColumnType.Ask))
await Command.ExecuteAsync(pbs);
} else
}
else {
Command.Execute(pbs);
}
}
}
base.OnMouseDown(e);
}
private async Task show_mbo_popup(Point point) {
var query = MBOPopupModel.default_query.with(
security_id: _vm.model.security.Id,
price_level: price_for_point(point)
);
var model = new MBOPopupModel(_vm.model.mbo_service, _vm.model.security, query);
// Load the first set of orders so we know if there are any before showing the popup
var result = await model.load_orders();
if (result.success && result.value.orders.Count > 0) {
var view = new MBOPopupView(model, result.value);
var popup = new AutoClosePopup {
PlacementTarget = this,
PlacementRectangle = new Rect(point, point),
IsOpen = true,
Child = new Border {
BorderBrush = Brushes.Black,
BorderThickness = new Thickness(0.5),
Child = new ScrollViewer {
VerticalScrollBarVisibility = ScrollBarVisibility.Hidden,
HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled,
Content = view,
}
}
};
view.RequestClose += (o, _) => popup.IsOpen = false;
}
}
protected override void OnMouseDoubleClick(MouseButtonEventArgs e) {
var column = _column_for_point(e.GetPosition(this));
if (column.column_type == ColumnType.Price)
@@ -485,6 +527,7 @@ namespace mbts.spark {
_prices_updated = 0;
_price_comparer = Comparer<double>.Create((x, y) => security.ContractSpec.tick_equals(x, y) ? 0 : y.CompareTo(x));
_price_dirty = true;
_piq_dirty = true;
_vm.on_tick += _tick;
_settings_sub = _vm.settings.OnDispatcher().Subscribe(_settings_updated);
_volume_sub = _vm.model.order_gateway.md_auxiliary_model.security_volume_at_price(security.Id).Subscribe(t => _volume_container.write_value(t.state.Values));
@@ -580,8 +623,10 @@ namespace mbts.spark {
if (depth_changed)
_refresh_sizes();
}
// TODO - detect changes in PIQ to selectively redraw
_refresh_piq();
if (_piq_dirty) {
_refresh_piq();
_piq_dirty = false;
}
_refresh_volume_at_price();
_last_origin_price = _mkt_break_lines.origin;
_price_dirty = false;
@@ -622,8 +667,7 @@ namespace mbts.spark {
if (!_settings.piq_column.show)
return;
int origin_index = LadderRender.index_of(_price_list, _mkt_break_lines.origin, _price_comparer);
var piq_list = _vm.price_vs_piq;
LadderRender.render_piqs(_piq_vis_tup.visual, _last_vis_tup.glyph_context, origin_index, _price_list, piq_list.ToArray(), _settings.row_height, security.ContractSpec, _settings.piq_column);
LadderRender.render_piqs(_piq_vis_tup.visual, _last_vis_tup.glyph_context, origin_index, _price_list, _vm.price_vs_piq, _settings.row_height, security.ContractSpec, _settings.piq_column);
}
void _refresh_sizes() {
@@ -72,8 +72,7 @@ namespace mbts.spark {
panel.Children.Remove(item);
}
}
} //else
//Log.Ui.warn($"Failed to remove order ({order.id}): order not in {nameof(LadderOrdersHost)}'s {nameof(_orders)}.");
}
}
public void clear_orders() {
@@ -192,11 +191,8 @@ namespace mbts.spark {
Margin = Badge.DefaultMargin,
Orientation = Orientation.Horizontal,
MaxWidth = _col_width
//MaxHeight = _row_height,
//ClipToBounds = true
};
//var view_box = new Viewbox { Child = panel, Width = _col_width, Height = _row_height };
_position_panel(panel, price);
_stacks[price] = new PriceCellComponents(panel);
Children.Add(panel);
@@ -288,22 +288,22 @@ namespace mbts.spark {
}
// this is basically a copy of redner_sizes up above
internal static void render_piqs(DrawingVisual dv, GlyphContext glyph_context, int origin_index, double[] prices, (double price, uint piq)[] piqs, double row_height, ContractSpecificationImmutable spec, LadderColumnSettings settings) {
internal static void render_piqs(DrawingVisual dv, GlyphContext glyph_context, int origin_index, double[] prices, IEnumerable<(double price, uint piq)> pvp, double row_height, ContractSpecificationImmutable spec, LadderColumnSettings settings) {
using (var dc = dv.RenderOpen()) {
using (_draw_with_clip(dc, settings, row_height, prices.Length)) {
if (!prices.within_range(origin_index) || piqs.is_null_or_empty())
if (!prices.within_range(origin_index) || pvp.is_null_or_empty())
return;
var foreground = settings.foreground.rgb_brush();
int curr_index = origin_index;
double curr_price = prices[curr_index];
for (int i = 0; i < piqs.Length; i++) {
double price = piqs[i].price;
foreach (var entry in pvp) {
double price = entry.price;
// prices are in descending order, so get diff from curr_price - price
int tick_diff = spec.tickdiff_unchecked(curr_price, price);
curr_index += tick_diff;
curr_price = price;
if (curr_index >= 0 && curr_index < prices.Length) {
var gr = format_text(piqs[i].piq.ToString(), curr_index, row_height, settings, glyph_context);
var gr = format_text(entry.piq.ToString(), curr_index, row_height, settings, glyph_context);
dc.DrawGlyphRun(foreground, gr);
} else break;
}
@@ -141,8 +141,8 @@
<Setter Property="VerticalContentAlignment" Value="Top"/>
</Style>
</TabControl.ItemContainerStyle>
<!-- Fonts and Colors Tab -->
<TabItem Header="Fonts and Colors" VerticalAlignment="Stretch">
<!-- Column Settings Tab -->
<TabItem Header="Column Settings" VerticalAlignment="Stretch">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="27"/>
@@ -1,5 +1,6 @@
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.shell.orderbook;
using bts.utils;
@@ -27,6 +28,7 @@ using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
using static mbts.ui.controls.DIYGrid;
using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
using System.Windows.Input;
using System.Reactive.Disposables;
namespace mbts.spark {
// This interface is used to hide ColumnBase.SortDirection, and offer a SortDirection member that does NOT require a DependencyProperty lookup.
@@ -63,6 +65,7 @@ namespace mbts.spark {
partial class OrderBookVM {
public class OrderBookColumnCollection : ObservableCollection<ColumnBase>, IValueFactory<(FilterStates filters, IReadOnlyList<SortingCriteria> sorting)>, IDisposable {
readonly List<ICVOrderBookColumn> _active_comparers = new List<ICVOrderBookColumn>();
CompositeDisposable _resources = new CompositeDisposable();
public OrderBookColumnCollection(IBus bus, OrdersModel model, IObservable<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)> order_securities) {
// security
@@ -161,12 +164,37 @@ namespace mbts.spark {
nameof(OrderBookRowVM.time_in_force)),
DataGridFilterKnownTemplates.EnumValues,
_wrap_comparer(r => r.time_in_force)));
// (algo items)
var algo_items_source = model.subscribe_all_algos().SelectKeyValue(AlgoDataVM.from_data, d => AlgoID.from_algo_key(d.data.key)).OnDispatcher();
var algo_items_comp = AlgoCollectionHelper.algo_data_vm_comparer.to_non_generic();
var algo_items_group_desc = new PropertyGroupDescription(nameof(AlgoDataVM.group));
// order type
Add(order_type_column = new OrderBookOrderTypeColumn(
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, OrderType>(
utilities.OrderDataHelper.all_order_types,
ort => ort.order.order_type(),
nameof(OrderBookRowVM.order_type))));
Columns.OrderType,
nameof(OrderBookRowVM.badge_fill),
nameof(OrderBookRowVM.display_name),
_wrap_comparer(r => r.order_type),
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurityAlgoMeta, AlgoDataVM, AlgoID>(
o => new AlgoDataVM(o.algo.GetValueOrDefault()),
o => AlgoID.from_order_def(o.order.Def),
vm => AlgoID.from_algo_key(vm.data.key), out var order_type_filter_observer, out var order_type_items_view)));
_resources.Add(algo_items_source.Subscribe(order_type_filter_observer));
order_type_items_view.CustomSort = algo_items_comp;
order_type_items_view.GroupDescriptions.Add(algo_items_group_desc);
// root algo type
Add(root_algo_type_column = new OrderBookOrderTypeColumn(
Columns.RootAlgoType,
nameof(OrderBookRowVM.root_algo_badge_fill),
nameof(OrderBookRowVM.root_algo_display_name),
_wrap_comparer(r => r.order_tuple.root_algo.value.CustomCase),
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurityAlgoMeta, AlgoDataVM, AlgoID>(
o => new AlgoDataVM(o.root_algo),
o => AlgoID.from_algo_key(o.root_algo.key),
vm => AlgoID.from_algo_key(vm.data.key), out var root_algo_type_observer, out var root_algo_type_items_view),
DefaultColumnHeaders.RootAlgoType));
_resources.Add(algo_items_source.Subscribe(root_algo_type_observer));
root_algo_type_items_view.CustomSort = algo_items_comp;
root_algo_type_items_view.GroupDescriptions.Add(algo_items_group_desc);
// order sub type
Add(sub_type_column = new SimpleTextColumn<OrderSubType[]>(
Columns.SubType,
@@ -307,6 +335,7 @@ namespace mbts.spark {
public SimpleTextColumn<Venue[]> exchange_column { get; }
public SimpleTextColumn<TimeInForce[]> time_in_force_column { get; }
public OrderBookOrderTypeColumn order_type_column { get; }
public OrderBookOrderTypeColumn root_algo_type_column { get; }
public SimpleTextColumn<OrderSubType[]> sub_type_column { get; }
public SideColumn side_column { get; }
public SizeColumn qty_column { get; }
@@ -333,6 +362,7 @@ namespace mbts.spark {
exchanges = exchange_column.filter.get_state(),
tifs = time_in_force_column.filter.get_state(),
order_types = order_type_column.filter.get_state(),
root_algo_types = root_algo_type_column.filter.get_state(),
order_sub_types = sub_type_column.filter.get_state(),
sides = side_column.filter.get_state(),
qty = qty_column.filter.get_state(),
@@ -362,6 +392,7 @@ namespace mbts.spark {
filled_qty_column.filter.load_state(fs.qty_filled);
side_column.filter.load_state(fs.sides);
order_type_column.filter.load_state(fs.order_types);
root_algo_type_column.filter.load_state(fs.root_algo_types);
sub_type_column.filter.load_state(fs.order_sub_types);
time_in_force_column.filter.load_state(fs.tifs);
exchange_column.filter.load_state(fs.exchanges);
@@ -424,6 +455,7 @@ namespace mbts.spark {
}
public void Dispose() {
Disposable.dispose(ref _resources);
foreach (var col in this.OfType<ICVOrderBookColumn>())
(col.filter as IDisposable)?.Dispose();
Clear();
@@ -534,7 +566,7 @@ namespace mbts.spark {
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, _get_text(row), position, row_height,
font_brush, null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
RenderUtils.get_foreground(row, font_brush), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
}
#region Explicit Column Defs
@@ -566,21 +598,32 @@ namespace mbts.spark {
}
public class OrderBookOrderTypeColumn : OrderTypeColumn<Columns>, ICVOrderBookColumn {
readonly BindingBase _foreground_binding;
ListSortDirection? _cached_sort_direction;
public OrderBookOrderTypeColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, OrderType[]> filter)
: base(Columns.OrderType, new Binding(nameof(OrderBookRowVM.badge_fill)), new Binding(nameof(OrderBookRowVM.display_name)), filter) {
public OrderBookOrderTypeColumn(Columns key, string badge_fill_binding_path, string display_name_binding_path, IComparer<OrderBookRowVM> row_comparer, IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, AlgoID[]> filter, string header = DefaultColumnHeaders.OrderType)
: base(key, new Binding(badge_fill_binding_path), new Binding(display_name_binding_path), filter, header) {
id = key.ToString();
this.filter = filter;
font_weight_binding = RenderUtils.get_font_weight_binding();
_foreground_binding = RenderUtils.get_foreground_binding(this);
_cached_sort_direction = SortDirection;
this.comparer = row_comparer;
}
public new IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, OrderType[]> filter { get; }
public IComparer<OrderBookRowVM> comparer => _wrap_comparer(r => r.order_type);
public new IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, AlgoID[]> filter { get; }
public override string filter_template => DataGridFilterKnownTemplates.AlgoType;
public IComparer<OrderBookRowVM> comparer { get; }
IDataGridColumnFilter<OrderSecurityAlgoMeta> ICVOrderBookColumn.filter => filter;
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
public override FrameworkElement view_factory() {
var view = base.view_factory();
view.SetBinding(OrderTypeItem.ForegroundProperty, _foreground_binding);
return view;
}
protected override void OnSortDirectionChanged(ListSortDirection? new_value) {
_cached_sort_direction = new_value;
base.OnSortDirectionChanged(new_value);
@@ -612,7 +655,7 @@ namespace mbts.spark {
}
public class SecurityColumn : FrameworkElementColumnBase<Columns>, ICVOrderBookColumn, IFilterableColumn {
readonly BindingBase _font_weight_binding = RenderUtils.get_font_weight_binding();
readonly BindingBase _font_weight_binding = RenderUtils.get_font_weight_binding(), _foreground_binding;
readonly IValueConverter _expander_visiblilty_converter = new ui.converters.BooleanToVisibilityConverter { VisibilityForFalse = Visibility.Hidden };
readonly IValueConverter _margin_converter = new AnonymousValueConverter<int, Thickness>(level => new Thickness(10 * level, 0, 0, 0));
@@ -621,6 +664,7 @@ namespace mbts.spark {
public SecurityColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, SecurityCriteria> filter) : base(Columns.Security) {
id = key.ToString();
this.filter = filter;
_foreground_binding = RenderUtils.get_foreground_binding(this);
Header = DefaultColumnHeaders.Security;
Width = DefaultColumnWidths.Security;
_cached_sort_direction = SortDirection;
@@ -647,7 +691,7 @@ namespace mbts.spark {
security_view.SetBinding(TextBlock.FontSizeProperty, new Binding(nameof(font_size)) { Source = this });
security_view.SetBinding(TextBlock.TextProperty, nameof(OrderBookRowVM.security_text));
security_view.SetBinding(TextBlock.HorizontalAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
security_view.SetBinding(TextBlock.ForegroundProperty, new Binding(nameof(font_brush)) { Source = this });
security_view.SetBinding(TextBlock.ForegroundProperty, _foreground_binding);
security_view.SetBinding(TextBlock.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
security_view.SetBinding(TextBlock.FontWeightProperty, _font_weight_binding);
//
@@ -742,7 +786,7 @@ namespace mbts.spark {
// create the attach button and assign the menu to it
var attach_button = new Button() {
Name = "attach_button",
Content = "A",
Content = new Image() { Source = Icons.AttachAlgos },
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch,
Focusable = false,
@@ -864,7 +908,7 @@ namespace mbts.spark {
var detach_button = new Button() {
Name = "detach_button",
Content = "D",
Content = new Image() { Source = Icons.DetachAlgos },
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch,
Focusable = false,
@@ -1028,6 +1072,10 @@ namespace mbts.spark {
popup.SetBinding(Popup.DataContextProperty, new Binding("PlacementTarget.DataContext") {
RelativeSource = new RelativeSource(RelativeSourceMode.Self)
});
popup.Closed += (s, e) => {
if (popup.try_find_ancestor_of_type(out MiniOrderBookView mini_ob) && mini_ob.DataContext is OrderBookVM ob_vm)
ob_vm.is_attaching_orders = false;
};
var the_whole_thing = new Grid();
the_whole_thing.Children.Add(update_button);
@@ -1115,19 +1163,24 @@ namespace mbts.spark {
static class RenderUtils {
public static FontWeight
root_font_weight = FontWeights.Normal,
child_font_weight = FontWeights.Light;
child_font_weight = FontWeights.Normal;
static readonly IMultiValueConverter _foreground_converter =
ValueConverter.create<bool, Brush, Brush>(_get_foreground);
public static double font_size = FontSizes.Small;
public static readonly IValueConverter
_sub_type_text_converter = new ui.spark.converters.OrderSubTypeDisplayTextConverter(),
_side_brush_converter = new AnonymousValueConverter<Side, Brush>(s => s == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask),
_font_weight_converter = new AnonymousValueConverter<bool, FontWeight>(is_child => is_child ? FontWeights.Light : FontWeights.Normal);
_font_weight_converter = new AnonymousValueConverter<bool, FontWeight>(is_child => is_child ? child_font_weight : root_font_weight);
public static readonly IMultiValueConverter
_price_text_converter = new ui.spark.converters.TickDisplayConverter(),
_background_converter = new AnonymousMultiValueConverter<bool, OrderStatus, Brush>(
(selected, status) => selected || status != OrderStatus.Working ? Brushes.Transparent : BTSColors.Orders.working_order_background_brush);
static readonly bool _animate_child_order_rows = mbts.config.Configuration.get<bts.config.OrderBookOptions>().animate_child_order_rows;
public static BindingBase _get_background_binding() {
var mb = new MultiBinding { Converter = _background_converter };
mb.Bindings.Add(new Binding(nameof(ListBoxItem.IsSelected)) { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(ListBoxItem), 1) });
@@ -1135,9 +1188,29 @@ namespace mbts.spark {
return mb;
}
public static Brush get_side_brush(OrderBookRowVM r) => r.side == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask;
public static BindingBase get_font_weight_binding() => new Binding(nameof(OrderBookRowVM.is_child)) { Converter = _font_weight_converter };
}
public static BindingBase get_foreground_binding(ColumnBase col) {
var col_font_brush_binding = new Binding(nameof(ColumnBase.font_brush)) { Source = col };
if (!_animate_child_order_rows)
return col_font_brush_binding;
var mb = new MultiBinding { Converter = _foreground_converter };
mb.Bindings.Add(new Binding(nameof(OrderBookRowVM.is_child)));
mb.Bindings.Add(col_font_brush_binding);
return mb;
}
public static Brush get_side_brush(OrderBookRowVM r) => get_foreground(r, r.side == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask);
public static BindingBase get_font_weight_binding() => new Binding(nameof(OrderBookRowVM.is_child)) { Converter = _font_weight_converter };
public static Brush get_foreground(OrderBookRowVM row, Brush brush) =>
_animate_child_order_rows ? _get_foreground(row.is_child, brush) : brush;
static Brush _get_foreground(bool is_child, Brush brush) {
if (is_child) {
brush = brush.Clone();
brush.Opacity = 0.75;
}
return brush;
}
}
}
}
@@ -18,18 +18,16 @@ namespace mbts.spark {
const string _unknown_security_price_format_string = "0.#########";
const int _unknown_security_price_num_fractional_digits = 9;
readonly OrderBookVM _owner;
Brush _badge_fill;
Color? _root_algo_badge_color_backing;
Brush _badge_fill, _root_algo_badge_fill;
IRaiseCanExecuteChangedCommand _show_order_modify_cmd, _toggle_expanded_cmd, _cancel_cmd, _detach_command, _attach_command, _update_command, _modify_order_quantity_andor_price, _revert_prospective_order_quantity_andor_price;
bool _is_parent, _is_expanded, _is_selected, _is_attachable, _is_detachable;
int _depth_level;
SecurityImmutable? _security => order_tuple.security;
// for mini-ob
uint _prospective_quantity;
double _prospective_price;
public OrderBookRowVM(OrderBookVM owner, OrderSecurityAlgoMeta order_tuple, bool is_expanded) {
_owner = owner;
this.order_tuple = order_tuple;
@@ -37,11 +35,6 @@ namespace mbts.spark {
_is_expanded = is_expanded;
prospective_quantity = this.quantity;
prospective_price = this.price;
this.invalidated += OrderBookRowVM_invalidated;
}
private void OrderBookRowVM_invalidated() {
modify_order_quantity_andor_price?.TryRaiseCanExecuteChanged();
}
//// This takes care of the case where the OB has all rows visible
@@ -52,8 +45,8 @@ namespace mbts.spark {
//// handler
public IObservable<OrderSecurityAlgoMeta> subscribe_data() {
return Observable.FromEvent<Action, OrderSecurityAlgoMeta>(
cb => () => cb(order_tuple),
cb => invalidated += cb,
cb => () => cb(order_tuple),
cb => invalidated += cb,
cb => invalidated -= cb);
}
@@ -65,7 +58,7 @@ namespace mbts.spark {
public OrderSecurityAlgoMeta order_tuple { get; private set; }
public OrderRecordImmutable order => order_tuple.order;
public OrderMetadata order_metadata => _owner.get_metadata(this);
public Brush badge_fill => _badge_fill ?? (_badge_fill = order_tuple.badge_fill(order_metadata));
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(order_tuple.meta, order_metadata));
public uint order_key => order.Key;
public uint parent_order_key => order.ParentKey;
public uint security_key => order.security_key();
@@ -101,8 +94,14 @@ namespace mbts.spark {
public bool is_non_terminal => order.is_non_terminal();
public bool is_attachable { get => _is_attachable; set => SetVisual(ref _is_attachable, value); }
public bool is_detachable { get => _is_detachable; set => SetVisual(ref _is_detachable, value); }
public string filled_status_desc => order.filled_status_description(security.spec_or_default());
// Root Algo
public CustomOrderDefinition.CustomOneofCase root_algo_order_type => order_tuple.root_algo.value.CustomCase;
public OrderMetadata root_algo_order_metadata => _owner.get_metadata(root_algo_order_type);
public Brush root_algo_badge_fill => _root_algo_badge_fill ?? (_root_algo_badge_fill = new SolidColorBrush(_root_algo_badge_color));
public string root_algo_display_name => order_tuple.root_algo.name;
// Tree
public bool is_parent { get => _is_parent; set => Set(ref _is_parent, value); }
public bool is_child => order.has_parent();
@@ -148,32 +147,38 @@ namespace mbts.spark {
public ICommand cancel_command => _cancel_cmd ??
(_cancel_cmd = new DelegateCommand(() => _owner.cancel_order(this), () => status == OrderStatus.Working || status == OrderStatus.Disconnected));
public ICommand modify_order_quantity_andor_price => _modify_order_quantity_andor_price ??
(_modify_order_quantity_andor_price = new DelegateCommand(
() => {
if (is_qty_dirty && is_price_dirty) {
_owner.modify_order_price_and_quantity(this, prospective_price, prospective_quantity);
} else if (is_qty_dirty) {
_owner.modify_order_quantity(this, prospective_quantity);
} else if (is_price_dirty) {
_owner.modify_order_price(this, this.prospective_price);
public ICommand modify_order_quantity_andor_price {
get {
if (_modify_order_quantity_andor_price == null) {
_modify_order_quantity_andor_price = new DelegateCommand(execute, can_execute);
invalidated += _modify_order_quantity_andor_price.RaiseCanExecuteChanged;
// impl
void execute() {
if (is_qty_dirty && is_price_dirty) {
_owner.modify_order_price_and_quantity(this, prospective_price, prospective_quantity);
} else if (is_qty_dirty) {
_owner.modify_order_quantity(this, prospective_quantity);
} else if (is_price_dirty) {
_owner.modify_order_price(this, this.prospective_price);
}
_unselect_me();
}
_unselect_me();
},
bool can_execute() => status == OrderStatus.Working && (is_price_dirty || is_qty_dirty);
}
return _modify_order_quantity_andor_price;
}
}
public ICommand revert_prospective_order_quantity_andor_price => _revert_prospective_order_quantity_andor_price ??
(_revert_prospective_order_quantity_andor_price = new DelegateCommand(
() => {
prospective_price = price;
prospective_quantity = quantity;
},
() => this.status == OrderStatus.Working && (is_price_dirty || is_qty_dirty)
)
);
public ICommand revert_prospective_order_quantity_andor_price => _revert_prospective_order_quantity_andor_price ??
(_revert_prospective_order_quantity_andor_price = new DelegateCommand(
() => {
prospective_price = price;
prospective_quantity = quantity;
},
() => this.status == OrderStatus.Working && (is_price_dirty || is_qty_dirty)
)
);
public ICommand detach_command => _detach_command ??
(_detach_command = new DelegateCommand(() => {
_owner.detach_orders(this);
@@ -193,6 +198,18 @@ namespace mbts.spark {
_select_me();
})
);
// private props
Color _root_algo_badge_color {
get => _root_algo_badge_color_backing ?? (_root_algo_badge_color_backing = OrderUtils.badge_color(order_tuple.root_meta, root_algo_order_metadata)).Value;
set {
if (_root_algo_badge_color_backing != value) {
_root_algo_badge_color_backing = value;
_root_algo_badge_fill = null;
}
}
}
#region IHierarchicalRow
bool IHierarchicalRow.is_parent => is_parent;
bool IHierarchicalRow.is_expanded => is_expanded;
@@ -262,6 +279,15 @@ namespace mbts.spark {
// display name
if (old_order_tuple.display_name() != order_tuple.display_name())
RaisePropertyChanged(nameof(display_name));
// root badge fill
if (_root_algo_badge_color_backing.HasValue) {
// only need to invalidate it if it had a value (it is lazily instantiated)
_root_algo_badge_color = OrderUtils.badge_color(order_tuple.root_meta, root_algo_order_metadata);
RaisePropertyChanged(nameof(root_algo_badge_fill));
}
// root display name
if (old_order_tuple.root_algo.name != order_tuple.root_algo.name)
RaisePropertyChanged(nameof(root_algo_display_name));
// status
if ((Columns.Status & dirty) != 0)
RaisePropertyChanged(nameof(status));
@@ -288,7 +314,6 @@ namespace mbts.spark {
// this will update the cancel column in the mini ob
cancel_command.TryRaiseCanExecuteChanged();
}
// always refresh attach and detach b/c
// conditionally raise invalidated
if ((dirty & ViewRefresh.invalidate_mask) != 0)
@@ -3,6 +3,7 @@ using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.collections;
using bts.md;
using bts.shell;
using bts.shell.orderbook;
using bts.spark;
@@ -64,7 +65,14 @@ namespace mbts.spark {
_detachable_selected_orders = new HashSet<uint>();
FilterData _cached_filter_data;
public OrderBookVM(IObservable<string> owner_title, IBus bus, INotificationService notification_service, OrdersModel model, IWindowSettingsManager<OrderBookGridSettings> settings_manager, bool rows_are_initially_expanded = false) {
public OrderBookVM(
IObservable<string> owner_title,
IBus bus,
INotificationService notification_service,
OrdersModel model,
IWindowSettingsManager<OrderBookGridSettings> settings_manager,
bool rows_are_initially_expanded = false) {
_bus = bus;
_model = model;
_selected_params_mon = new SelectedOrderParamsMon(model.dynamic_helper);
@@ -160,7 +168,7 @@ namespace mbts.spark {
security_plurality = p;
RaisePropertyChanged(nameof(security_plurality));
}),
model.on_order_tuples.OnDispatcher().Subscribe(t => {
model.on_order_tuples_with_root.OnDispatcher().Subscribe(t => {
_cached_orders = t.state;
_row_collection.update(t.added, t.changed, t.removed.Select(o => o.order.Key));
}),
@@ -248,13 +256,20 @@ namespace mbts.spark {
var order_tups = selected_rows.Select(r => r.order_tuple).ToList();
var mod_algo_vm = attach_vm.algo_vm.algo_root;
await Task.WhenAll(order_tups.Select(t => {
var algo = t.order.Def.CustomDef.CloneToMutable();
var algo = t.order.Def.CustomDef.actual().CloneToMutable();
mod_algo_vm.assign_to(algo);
if (algo_template_key != 0) {
algo = new CustomOrderDefinition {
Composite = new CompositeOrderDefinition {
AlgoTemplateKey = algo_template_key,
Definition = algo
}
};
}
return _model.send_order(
t.order.Def.with(
attach_target_order_key: t.order.Key,
custom_def: algo,
algo_template_key: algo_template_key
custom_def: algo
),
double.IsNaN(price) ? t.order.price() : price,
size ?? t.order.quantity());
@@ -268,15 +283,14 @@ namespace mbts.spark {
Disposable.replace(ref attach_vm_hooks,
Disposable.Create(
connect_common_selected_params(attach_vm),
_selected_params_mon.subscribe_algos().Subscribe(algos => {
attach_vm.algo_vm.observe_algo_root().Subscribe(algo_root => {
bool show_price, show_size;
using (var en = algos.Select(a => a.CustomCase).Distinct().GetEnumerator()) {
if (show_price = show_size = en.MoveNext()) {
do {
if (show_price) show_price = AttachConf.show_price(en.Current);
if (show_size) show_size = AttachConf.show_size(en.Current);
} while ((show_price || show_size) && en.MoveNext());
}
if (algo_root == null) {
show_price = false;
show_size = false;
} else {
show_price = AttachConf.show_price(algo_root.order_type);
show_size = AttachConf.show_size(algo_root.order_type);
}
attach_vm.price_permissions = bool_to_perm(show_price);
attach_vm.size_permissions = bool_to_perm(show_size);
@@ -285,7 +299,7 @@ namespace mbts.spark {
}),
// custom templates (created by user)
_model.algo_model.definitions.OnDispatcher().Subscribe(u =>
AlgoCollectionHelper.update_persisted_definition_wrappers(attach_vm.algo_vm.algo_roots as ObservableKeyedCollection<IAlgoVM, AlgoKey>, u, mk_factory(true)))));
AlgoCollectionHelper.update_persisted_definition_wrappers(attach_vm.algo_vm.algo_roots_index, u, mk_factory(true)))));
// vm factory
Func<AlgoData, AlgoDefinitionVM> mk_factory(bool connect) =>
fd => {
@@ -532,9 +546,23 @@ namespace mbts.spark {
public bool try_get_parent(OrderBookRowVM row, out OrderSecurityAlgoMeta parent) =>
_cached_orders.TryGetValue(row.parent_order_key, out parent);
public OrderRecordImmutable get_root(OrderRecordImmutable r) {
while (r.ParentKey != 0 && _cached_orders.TryGetValue(r.ParentKey, out var parent_osam)) {
r = parent_osam.order;
}
return r;
}
internal void detach_orders(OrderBookRowVM order) =>
_detach_orders_async(new[] { order }).ignore();
public OrderSecurityAlgoMeta get_ordersecurityalgometa(OrderRecordImmutable order) {
_cached_orders.TryGetValue(order.Key, out var osam);
return osam;
}
public OrderMetadata get_metadata(CustomOrderDefinition.CustomOneofCase order_type) => _model.factory.get_metadata(order_type);
public OrderMetadata get_metadata(OrderBookRowVM row) => _model.factory.get_metadata(row.order_type);
internal OrderBookRowVM get_parent(OrderBookRowVM row) => _row_collection.get_parent(row.order_key);
@@ -598,7 +626,11 @@ namespace mbts.spark {
await Task.WhenAll(update_params.Select(t => _model.update_order(t.order, new_price: price, new_size: size, new_algo: t.new_algo)));
(OrderRecordImmutable order, CustomOrderDefinitionImmutable? new_algo) update_params_factory(OrderBookRowVM row) {
var mod_algo = row.order.Def.CustomDef.CloneToMutable();
mod_algo_vm.assign_to(mod_algo);
if (mod_algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite) {
mod_algo_vm.assign_to(mod_algo.Composite.Definition);
} else {
mod_algo_vm.assign_to(mod_algo);
}
var mod_algoi = new CustomOrderDefinitionImmutable(mod_algo);
return (row.order, OrderDataHelper.EqualsHelpers.equals(row.order.Def.CustomDef, mod_algoi) ? (CustomOrderDefinitionImmutable?)null : mod_algoi);
}
@@ -840,10 +872,9 @@ namespace mbts.spark {
revert();
}
public void revert() {
foreach (var r in param_rows)
r.param.revert();
}
public void revert() =>
param_rows.revert_all();
public IObservable<bool> subscribe_is_dirty() => _is_dirty.observe_current(true);
void _reset_ladder_vm() {
@@ -851,7 +882,10 @@ namespace mbts.spark {
return;
Disposable.replace(ref _ladder_vm,
_security_md.HasValue ?
LadderVM.create_readonly(_owner._bus, new LadderModel(security_md.Value, _owner._model, new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key)))) :
LadderVM.create_readonly(
_owner._bus,
new LadderModel(security_md.Value, _owner._model, _owner._model.mbo_service,
new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key)))) :
null);
RaisePropertyChanged(nameof(ladder_vm));
}
@@ -1104,4 +1138,4 @@ namespace mbts.spark {
!_algo_type_conf.TryGetValue(algo_type, out var result) || result.show_size;
}
}
}
}
@@ -27,8 +27,12 @@ namespace bts.shell.orderbook {
public Venue[] exchanges;
[ProtoMember(7)]
public TimeInForce[] tifs;
[ProtoMember(8)]
public CustomOrderDefinition.CustomOneofCase[] order_types;
//[ProtoMember(8)]
//public CustomOrderDefinition.CustomOneofCase[] order_types;
[ProtoMember(23)]
public AlgoID[] order_types;
[ProtoMember(24)]
public AlgoID[] root_algo_types;
[ProtoMember(9)]
public Side[] sides;
[ProtoMember(10)]
@@ -70,6 +74,7 @@ namespace bts.shell.orderbook {
EqualityComparer.safe_sequence_equals(exchanges, other.exchanges) &&
EqualityComparer.safe_sequence_equals(tifs, other.tifs) &&
EqualityComparer.safe_sequence_equals(order_types, other.order_types) &&
EqualityComparer.safe_sequence_equals(root_algo_types, other.root_algo_types) &&
EqualityComparer.safe_sequence_equals(sides, other.sides) &&
qty.safe_equals(other.qty) &&
price.safe_equals(other.price) &&
@@ -96,6 +101,7 @@ namespace bts.shell.orderbook {
EqualityComparer.safe_aggregate_hash(exchanges),
EqualityComparer.safe_aggregate_hash(tifs),
EqualityComparer.safe_aggregate_hash(order_types),
EqualityComparer.safe_aggregate_hash(root_algo_types),
EqualityComparer.safe_aggregate_hash(sides),
qty?.GetHashCode() ?? 0,
price?.GetHashCode() ?? 0,
@@ -128,7 +134,8 @@ namespace bts.shell.orderbook {
add_discrete_values(fs.accounts, o => o.order.account());
add_discrete_values(fs.exchanges, o => o.security?.Exchange ?? Venue.Unset);
add_discrete_values(fs.tifs, o => o.order.time_in_force());
add_discrete_values(fs.order_types, o => o.order.order_type());
add_discrete_values(fs.order_types, o => AlgoID.from_order_def(o.order.Def));
add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root_algo.key));
add_discrete_values(fs.order_sub_types, o => o.order.sub_type());
add_discrete_values(fs.sides, o => o.order.side());
add_discrete_values(fs.statuses, o => o.order.status());
@@ -191,4 +198,4 @@ namespace bts.shell.orderbook {
}
}
}
}
}
@@ -65,6 +65,10 @@ namespace bts.shell.orderbook {
[ProtoMember(25)]
public bool use_defaults;
/// <summary>
/// Please use <see cref="Default"/> to get a new instance (the only reason the default constructor still exists is for ProtoBuf deserialization).
/// </summary>
[Obsolete]
public OrderBookStartupData() {
show_right_panel = _default_show_right_panel;
show_order_params = _default_show_order_params;
@@ -130,31 +134,34 @@ namespace bts.shell.orderbook {
}
}
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.sorting_criterion);
public static OrderBookGridSettings default_mini_grid => new OrderBookGridSettings(GridSettings.Default.with(DefaultColumns.mini, DefaultColumns.sorting_criterion));
public static OrderBookGridSettings default_mini_grid => new OrderBookGridSettings(GridSettings.Default.with(DefaultColumns.mini, DefaultColumns.sorting_criterion)) {
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
};
static class DefaultColumns {
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.ToString(), DIYGrid.DefaultColumnHeaders.Security, width: DIYGrid.DefaultColumnWidths.Security);
public static GridColumnSettings symbol => new GridColumnSettings(Columns.Symbol.ToString(), DIYGrid.DefaultColumnHeaders.Symbol, width: DIYGrid.DefaultColumnWidths.Symbol, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings time_created => new GridColumnSettings(Columns.Time.ToString(), "Time", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings last_change => new GridColumnSettings(Columns.LastChange.ToString(), "Last Change", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings user => new GridColumnSettings(Columns.User.ToString(), DIYGrid.DefaultColumnHeaders.User, width: DIYGrid.DefaultColumnWidths.User, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings account => new GridColumnSettings(Columns.Account.ToString(), DIYGrid.DefaultColumnHeaders.Account, width: DIYGrid.DefaultColumnWidths.Account, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings exchange => new GridColumnSettings(Columns.Exchange.ToString(), DIYGrid.DefaultColumnHeaders.Exchange, width: DIYGrid.DefaultColumnWidths.Exchange, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings symbol => new GridColumnSettings(Columns.Symbol.ToString(), DIYGrid.DefaultColumnHeaders.Symbol, width: DIYGrid.DefaultColumnWidths.Symbol, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings time_created => new GridColumnSettings(Columns.Time.ToString(), "Time", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings last_change => new GridColumnSettings(Columns.LastChange.ToString(), "Last Change", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings user => new GridColumnSettings(Columns.User.ToString(), DIYGrid.DefaultColumnHeaders.User, width: DIYGrid.DefaultColumnWidths.User, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings account => new GridColumnSettings(Columns.Account.ToString(), DIYGrid.DefaultColumnHeaders.Account, width: DIYGrid.DefaultColumnWidths.Account, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings exchange => new GridColumnSettings(Columns.Exchange.ToString(), DIYGrid.DefaultColumnHeaders.Exchange, width: DIYGrid.DefaultColumnWidths.Exchange, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings time_in_force => new GridColumnSettings(Columns.TimeInForce.ToString(), DIYGrid.DefaultColumnHeaders.TimeInForce, width: DIYGrid.DefaultColumnWidths.TimeInForce);
public static GridColumnSettings order_type => new GridColumnSettings(Columns.OrderType.ToString(), DIYGrid.DefaultColumnHeaders.OrderType, width: DIYGrid.DefaultColumnWidths.OrderType);
public static GridColumnSettings sub_type => new GridColumnSettings(Columns.SubType.ToString(), "Sub Type", width: 80, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings side => new GridColumnSettings(Columns.Side.ToString(), "B/S", width: DIYGrid.DefaultColumnWidths.Side, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings root_algo_type => new GridColumnSettings(Columns.RootAlgoType.ToString(), DIYGrid.DefaultColumnHeaders.RootAlgoType, width: DIYGrid.DefaultColumnWidths.OrderType);
public static GridColumnSettings sub_type => new GridColumnSettings(Columns.SubType.ToString(), "Sub Type", width: 80, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings side => new GridColumnSettings(Columns.Side.ToString(), "B/S", width: DIYGrid.DefaultColumnWidths.Side, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings quantity => new GridColumnSettings(Columns.Size.ToString(), "Qty", width: DIYGrid.DefaultColumnWidths.Size, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings filled_quantity => new GridColumnSettings(Columns.Filled.ToString(), "Filled Qty", width: DIYGrid.DefaultColumnWidths.Filled, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings avg_price => new GridColumnSettings(Columns.AvgFilled.ToString(), "Avg Filled", width: DIYGrid.DefaultColumnWidths.AveragePrice, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings last_price => new GridColumnSettings(Columns.LastPrice.ToString(), "Last Filled", width: DIYGrid.DefaultColumnWidths.LastPrice, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings status => new GridColumnSettings(Columns.Status.ToString(), "Status", width: 80, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings last_message => new GridColumnSettings(Columns.LastMessage.ToString(), "Last Message", width: DIYGrid.DefaultColumnWidths.LastMessage, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, is_visible: false, width: DIYGrid.DefaultColumnWidths.SecurityKey, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings status => new GridColumnSettings(Columns.Status.ToString(), "Status", width: 80, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings last_message => new GridColumnSettings(Columns.LastMessage.ToString(), "Last Message", width: DIYGrid.DefaultColumnWidths.LastMessage, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, is_visible: false, width: DIYGrid.DefaultColumnWidths.SecurityKey, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
public static IEnumerable<SortingCriteria> sorting_criterion {
get {
@@ -173,6 +180,7 @@ namespace bts.shell.orderbook {
yield return exchange;
yield return time_in_force;
yield return order_type;
yield return root_algo_type;
yield return sub_type;
yield return side;
yield return quantity;
@@ -4,13 +4,12 @@
x:Name="_grid"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:order="clr-namespace:bts.api.orderman;assembly=spark_api"
xmlns:sp="clr-namespace:mbts.spark"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
InactiveSelectionOpacity="0.3"
>
<c:DIYGrid.Resources>
<ResourceDictionary>
@@ -109,63 +108,4 @@
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
</c:DIYGrid.InputBindings>
<c:DIYGrid.RowStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource DIYGridRowStyle}">
<Style.Triggers>
<!-- Completed order background -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding status}" Value="{u:Enum Completed, Type=order:OrderStatus}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="{x:Static Member=completed_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<!-- Error, RejectedBySpark, Disconnected order background -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding status}" Value="{u:Enum Error, Type=order:OrderStatus}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="{x:Static Member=error_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding status}" Value="{u:Enum RejectedBySpark, Type=order:OrderStatus}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="{x:Static Member=error_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding status}" Value="{u:Enum Disconnected, Type=order:OrderStatus}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="{x:Static Member=error_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<!-- Cancel order background -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding status}" Value="{u:Enum Canceled, Type=order:OrderStatus}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="{x:Static Member=canceled_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</c:DIYGrid.RowStyle>
<c:DIYGrid.RowTemplate>
<DataTemplate>
<sp:OrderBookRowView/>
</DataTemplate>
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
@@ -1,4 +1,11 @@
using bts.api.orderman;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.converters;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace mbts.spark {
internal partial class OrderBookGridView : DIYGrid {
@@ -41,11 +48,53 @@ namespace mbts.spark {
Attach = 1 << 25,
Detach = 1 << 26,
Update = 1 << 27,
Commit = 1 << 28
Commit = 1 << 28,
RootAlgoType = 1 << 29
}
public OrderBookGridView() =>
public OrderBookGridView() {
InitializeComponent();
var row_template_factory = new FrameworkElementFactory(typeof(OrderBookRowView));
row_template_factory.SetValue(OrderBookRowView.BackgroundProperty, RowBackgroundHelper.row_background_binding);
RowTemplate = new DataTemplate { VisualTree = row_template_factory };
}
static class RowBackgroundHelper {
public static readonly BindingBase row_background_binding;
static RowBackgroundHelper() {
var bg_binding = new MultiBinding();
bg_binding.Bindings.Add(new Binding(nameof(OrderBookRowVM.status)));
bg_binding.Bindings.Add(new Binding(nameof(ListBoxItem.IsSelected)) { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(ListBoxItem), 1) });
double overlay_opacity = config.Configuration.get<bts.config.OrderBookOptions>().status_color_opacity_when_selected;
if (overlay_opacity > 0.1 && overlay_opacity < 0.9) {
bg_binding.Converter = ValueConverter.create<OrderStatus, bool, Brush>((status, is_selected) => {
var color = _status_color(status);
return color.HasValue ? new SolidColorBrush(color.Value) { Opacity = is_selected ? overlay_opacity : 1.0 } : null;
});
} else {
bg_binding.Converter = ValueConverter.create<OrderStatus, bool, Brush>((status, is_selected) => {
if (is_selected)
return null;
var color = _status_color(status);
return color.HasValue ? new SolidColorBrush(color.Value) : null;
});
}
row_background_binding = bg_binding;
}
static Color? _status_color(OrderStatus status) {
switch (status) {
case OrderStatus.Canceled: return BTSColors.Orders.canceled_order_background;
case OrderStatus.Completed: return BTSColors.Orders.completed_order_background;
case OrderStatus.Disconnected:
case OrderStatus.RejectedBySpark:
case OrderStatus.Error:
return BTSColors.Orders.error_order_background;
default: return null;
}
}
}
}
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
@@ -174,9 +174,12 @@
SelectionHighlight="{Binding grid_settings_manager.core_manager.selection_highlight, Mode=OneWay}"
>
<FrameworkElement.Margin>
<Binding Source="{StaticResource GridSplitterWidth}">
<Binding Path="show_right_panel">
<Binding.Converter>
<cv:DoubleToThicknessConverter RightMultiplier="-1"/>
<cv:ValueConverterPipe>
<cv:BooleanToObjectConverter ValueForFalse="{u:Double 0}" ValueForTrue="{StaticResource GridSplitterWidth}"/>
<cv:DoubleToThicknessConverter RightMultiplier="-1"/>
</cv:ValueConverterPipe>
</Binding.Converter>
</Binding>
</FrameworkElement.Margin>
@@ -576,7 +579,7 @@
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
</Grid>
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom" Grid.ColumnSpan="2">
<StatusBarItem HorizontalAlignment="Right">
<TextBlock>
<Run Text="Orders:"/>
@@ -53,12 +53,19 @@ namespace mbts.spark {
d |= VisualStateNames.SlaveData.Data.Ladder;
return VisualStateNames.SlaveData.fmt_name(d);
});
static readonly IMultiValueConverter
_right_panel_expander_margin_converter = ValueConverter.create<bool, Visibility, Thickness>(
(is_expanded, scrollbar_vis) =>
!is_expanded && scrollbar_vis == Visibility.Visible ?
new Thickness(0, DIYGrid.DefaultColumnHeaderHeight, SystemParameters.VerticalScrollBarWidth, 0) :
new Thickness(0));
IDisposable _attach_op;
public OrderBookView(OrderBookVM vm) {
InitializeComponent();
DataContext = vm;
bind_right_panel_expander_margin();
var algo_editor_cm = new ContextMenu();
algo_editor_cm.Items.AddRange(order_mod_menu());
algo_editor.ContextMenu = algo_editor_cm;
@@ -138,6 +145,19 @@ namespace mbts.spark {
}
}
}
async void bind_right_panel_expander_margin() {
if (!orders_grid.IsLoaded)
await orders_grid.LoadedAsync();
if (orders_grid.try_find_descendent_of_type(out DIYGridRowsPresenter rows_presenter)) {
if (rows_presenter.try_find_descendent_of_type(out ScrollViewer scroll_viewer)) {
var mb = new MultiBinding { Converter = _right_panel_expander_margin_converter };
mb.Bindings.Add(new Binding(nameof(OrderBookVM.show_right_panel)) { Source = vm });
mb.Bindings.Add(new Binding(nameof(ScrollViewer.ComputedVerticalScrollBarVisibility)) { Source = scroll_viewer });
right_panel_expander_toggle.SetBinding(MarginProperty, mb);
}
}
}
}
public new OrderBookVM DataContext {
@@ -6,6 +6,7 @@ using mbts.ui;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Media;
using static mbts.ui.shell.ProtoBufSerializationHelper;
@@ -20,9 +21,17 @@ namespace bts.shell {
AlgoStudioWindowFactory _aswf;
[ImportingConstructor]
public TradingShellModule(IBus bus, IWindowManager wm, INotificationService notification_service, INotificationServiceDataCache notification_service_data_cache,
UserSettingsHub settings_hub, OrdersModel orders_model, PAndLModel pandl_model, ErrorReporter error_reporter) {
_lwf = new LadderWindowFactory(this, bus, wm, settings_hub, orders_model);
public TradingShellModule(
IBus bus,
IWindowManager wm,
INotificationService notification_service,
INotificationServiceDataCache notification_service_data_cache,
UserSettingsHub settings_hub,
OrdersModel orders_model,
PAndLModel pandl_model,
md.MBOService mbo,
ErrorReporter error_reporter) {
_lwf = new LadderWindowFactory(this, bus, wm, settings_hub, orders_model, mbo);
_obwf = new OrderBookWindowFactory(this, bus, notification_service, notification_service_data_cache, orders_model, settings_hub);
_fwf = new FillsWindowFactory(this, bus, settings_hub, orders_model);
_plwf = new PAndLWindowFactory(this, bus, settings_hub, pandl_model);
@@ -52,9 +61,8 @@ namespace bts.shell {
yield return _aswf;
}
public Task<IShellWindow> create(Stream load_stream) {
return dispatch_to_factory(load_stream, window_factories().ToLArray());
}
public Task<IShellWindow> create(Stream load_stream) =>
dispatch_to_factory(load_stream, window_factories().ToArray());
public void Dispose() {
foreach (var i in window_factories())
+1 -1
View File
@@ -857,7 +857,7 @@ namespace mbts.spark.utilities {
int phase_mult = 1;
switch (def.CustomCase) {
case CustomOrderDefinition.CustomOneofCase.Cover:
add_component(def.Cover.HedgeTicks);
add_component(def.Cover.HedgeTicks.GetHashCode());
add_component(_get_hash(_get_cover_child(def.Cover)));
add_component(_get_hash(_get_cover_hedge(def.Cover)));
break;
@@ -153,9 +153,21 @@ namespace mbts.spark {
d => d.load_committed(),
d => d == null ? Observable.Return(false) : d.subscribe_is_definition_dirty(),
out IObserver<AlgoDefinitionVM> revert_definition_observer);
// algo params dialog skirt
set_default_algo_params_cmd = ObservantCommand.create(() => {
algo_param_rows.commit_all();
_close_algo_popup();
}, out IObserver<bool> set_default_algo_params_observer);
revert_algo_params_cmd = ObservantCommand.create(() => {
algo_param_rows.revert_all();
_close_algo_popup();
}, out IObserver<bool> revert_algo_params_observer);
var algo_params_dirty_mon = new ReactiveThresholdMonitor<AlgoParamRowVM>(r => r.param.subscribe_is_dirty());
var nullable_security_md_source = security_md_source.Select(smd => new SecurityMD?(smd)).RefCountReplay(1);
//
_dispose = new CompositeDisposable(
algo_params_dirty_mon.Subscribe(set_default_algo_params_observer, revert_algo_params_observer),
ObservableAutoWrapper.connect_item_hooks(_algo_param_rows, algo_params_dirty_mon.join),
_selected_algo_definition.subscribe_changed(_selected_algo_definition_Changed),
as_demuxer().subscribe<AlgoDefinitionTemplateVM>(nameof(selected_algo_template)).Subscribe(
undo_observer,
@@ -358,6 +370,8 @@ namespace mbts.spark {
public ICommand save_definition_cmd { get; }
public ICommand save_template_cmd { get; }
public ICommand revert_definition_cmd { get; }
public ICommand set_default_algo_params_cmd { get; }
public ICommand revert_algo_params_cmd { get; }
public ICommand undo_cmd { get; }
public ICommand redo_cmd { get; }
public ICommand undo_definition_cmd { get; }
@@ -386,9 +400,22 @@ namespace mbts.spark {
if (_model.algo_model.templates_enabled()) {
selected_algo_template.algo_root.assign_to(order.CustomDef);
} else {
selected_algo_definition.algo_root.assign_to(order.CustomDef);
if (selected_algo_definition.key.mode == AlgoKey.Mode.Persisted)
order.AlgoTemplateKey = selected_algo_definition.key.persisted.id_uint;
var algo = new CustomOrderDefinition();
selected_algo_definition.algo_root.assign_to(algo);
switch (selected_algo_definition.key.mode) {
case AlgoKey.Mode.Persisted:
order.CustomDef = new CustomOrderDefinition {
Composite = new CompositeOrderDefinition {
AlgoTemplateKey = selected_algo_definition.key.persisted.id_uint,
Definition = algo
}
};
selected_algo_definition.algo_root.assign_to(order.CustomDef.Composite.Definition);
break;
default:
order.CustomDef = algo;
break;
}
}
return order;
}
@@ -411,6 +438,11 @@ namespace mbts.spark {
static IDisposable _connect_algo_param_rows(AlgoDefinitionVM def, ObservableAggregateCollection<AlgoParamRowVM> rows) =>
AlgoParamRowsHelper.connect(def, rows, false);
void _close_algo_popup() {
if (selected_algo_definition?.algo_root is IAlgoVM a)
a.is_expanded = false;
}
int? _get_favorite_algo_index(AlgoKey key) =>
key.mode == AlgoKey.Mode.Persisted && _favorite_algos_index.TryGetValue(key, out int i) ? i : (int?)null;
+23 -4
View File
@@ -58,21 +58,39 @@ namespace bts.api.orderman {
struct OrderSecurityAlgoMeta : IEquatable<OrderSecurityAlgoMeta> {
public readonly OrderRecordImmutable order;
public readonly SecurityImmutable? security;
/// TODO: refactor <see cref="algo"/> to 'studio_algo' or something that indicates it does not bear the same relation to this order, that <see cref="root_algo"/> does to the root order.
/// The former is meant to hold studio algo information, and nothing else. Naturally, it indicates whether or not this is a studio order by being non-null (<see cref="is_studio_algo"/>).
/// The latter, however, can be any kind of algo (studio, exchange, etc.); it holds information about the root-most algo in this order's branch.
public readonly AlgoData? algo;
public readonly AlgoDefinitionMetadata? meta;
public readonly AlgoData root_algo;
public readonly AlgoDefinitionMetadata? root_meta;
public OrderSecurityAlgoMeta(OrderRecordImmutable order, SecurityImmutable? security, AlgoData? algo, AlgoDefinitionMetadata? meta) {
public OrderSecurityAlgoMeta(OrderRecordImmutable order, SecurityImmutable? security, AlgoData? algo, AlgoDefinitionMetadata? meta, AlgoData? root_algo = null, AlgoDefinitionMetadata? root_meta = null) {
this.order = order;
this.security = security;
this.algo = algo;
this.meta = meta;
if (root_algo.HasValue) {
this.root_algo = root_algo.Value;
this.root_meta = root_meta;
} else {
this.root_algo = algo ?? new AlgoData(order.Def.CustomDef);
this.root_meta = meta;
}
}
public bool is_studio_algo => algo.HasValue;
public OrderSecurityAlgoMeta with(AlgoData? root_algo, AlgoDefinitionMetadata? root_meta = null) =>
new OrderSecurityAlgoMeta(order, security, algo, meta, root_algo, root_meta);
public bool Equals(OrderSecurityAlgoMeta other) =>
order == other.order &&
security == other.security &&
algo == other.algo &&
meta == other.meta;
meta == other.meta &&
root_algo == other.root_algo;
public override bool Equals(object obj) => EqualityComparer.equals_obj(this, obj);
@@ -81,9 +99,10 @@ namespace bts.api.orderman {
order.GetHashCode(),
security?.GetHashCode() ?? 0,
algo?.GetHashCode() ?? 0,
meta?.GetHashCode() ?? 0);
meta?.GetHashCode() ?? 0,
root_algo.GetHashCode());
public static bool operator ==(OrderSecurityAlgoMeta a, OrderSecurityAlgoMeta b) => a.Equals(b);
public static bool operator !=(OrderSecurityAlgoMeta a, OrderSecurityAlgoMeta b) => !a.Equals(b);
}
}
}
@@ -3,13 +3,13 @@
namespace mbts.spark {
static partial class OrderTypeVMFactory {
class CoverOrderVM : OrderTypeVMBase<CoverOrderDefinition, CoverOrderDefinitionImmutable> {
readonly Property<int?> _hedge_ticks;
readonly Property<uint?> _hedge_ticks;
public CoverOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
: base(key, display_name, editing_permissions) =>
_hedge_ticks = add_nullable_property(nameof(hedge_ticks), CoverOrderDefinition.HedgeTicksFieldNumber, 1, true);
_hedge_ticks = add_nullable_property(nameof(hedge_ticks), CoverOrderDefinition.HedgeTicksFieldNumber, 1u, true);
public int? hedge_ticks { get => _hedge_ticks.Value; set => _hedge_ticks.set(value); }
public uint? hedge_ticks { get => _hedge_ticks.Value; set => _hedge_ticks.set(value); }
protected override void validate() =>
assert(hedge_ticks.HasValue, nameof(hedge_ticks), "Invalid hedge ticks.");
@@ -58,6 +58,9 @@ namespace mbts.spark {
public static IAlgoVM dynamic(AlgoKey key, string display_name, string dynamic_type, AlgoEditingPermissions editing_permissions) => new DynamicOrderVM(key, dynamic_type, display_name, editing_permissions);
public static IAlgoVM create(AlgoData def_data, AlgoEditingPermissions editing_permissions = default_editing_permissions, bool is_expanded = false) {
if (def_data.value.CustomCase == AlgoType.Composite) {
def_data = new AlgoData(def_data.value.Composite.Definition, def_data.name, def_data.key);
}
var vm = create(def_data.key, def_data.name, def_data.value.CustomCase, editing_permissions, is_expanded);
vm.load_from(def_data.value);
return vm;
@@ -235,8 +238,11 @@ namespace mbts.spark {
!string.IsNullOrEmpty(display_name) && key.Key == (Key.A + display_name[0] - 'A');
public void load_from(AlgoDefi value) {
if (value.CustomCase != order_type)
return;
if (value.CustomCase != order_type) {
value = value.actual();
if (value.CustomCase != order_type)
return;
}
using (_def_change_notifier.defer_execution()) {
var algom = _descriptor.Accessor.GetValue(value.CloneToMutable()) as TMutable ?? new TMutable();
var security_src = Observable.Create<SecurityImmutable?>(o => {
@@ -268,8 +274,11 @@ namespace mbts.spark {
}
public virtual async Task<AlgoDefi?> load_from_async(AlgoDefi defi) {
if (defi.CustomCase != order_type)
return null;
if (defi.CustomCase != order_type) {
defi = defi.actual();
if (defi.CustomCase != order_type)
return null;
}
using (_def_change_notifier.defer_execution()) {
// TODO: each level of the order hierarchy could load in parallel
await on_load_from_async(defi);
@@ -1048,4 +1057,4 @@ namespace mbts.spark {
}
#endregion
}
}
}
@@ -5,16 +5,13 @@ using TopDefi = bts.api.orderman.TopOrderDefinitionImmutable;
namespace mbts.spark {
static partial class OrderTypeVMFactory {
class TopOrderVM : OrderTypeVMBase<TopDef, TopDefi> {
readonly Property<uint?> _watch_level_tick_offset;
readonly Property<QuantityMeasureImmutable?> _top_threshold;
public TopOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
: base(key, display_name, editing_permissions) {
_watch_level_tick_offset = add_nullable_property(nameof(watch_level_tick_offset), TopDef.WatchLevelTickOffsetFieldNumber, 0u);
_top_threshold = add_property<QuantityMeasure, QuantityMeasureImmutable>(nameof(top_threshold), TopDef.TopThresholdFieldNumber, new QuantityMeasureImmutable(new QuantityMeasure { Ratio = 1.0, Absolute = 1 }));
}
public uint? watch_level_tick_offset { get => _watch_level_tick_offset.Value; set => _watch_level_tick_offset.set(value); }
public QuantityMeasureImmutable? top_threshold { get => _top_threshold.Value; set => _top_threshold.set(value); }
}
}
+21 -12
View File
@@ -82,6 +82,21 @@ namespace mbts.spark {
public static string dynamic_order_type_display_name(this OrderRecordImmutable order) =>
order.HasDef && order.Def.HasCustomDef && order.Def.CustomDef.HasDynamic ? order.Def.CustomDef.Dynamic.DynamicOrderType : string.Empty;
public static uint algo_template_key(this OrderRecordImmutable order) =>
order.HasDef && order.Def.HasCustomDef && order.Def.CustomDef.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite ?
order.Def.CustomDef.Composite.AlgoTemplateKey : 0u;
/// <summary>
/// If the definitions is a <see cref="CompositeOrderDefinition"/>, returns the underlying algo, otherwise returns <paramref name="custom_def"/> iteself.
/// </summary>
/// <param name="custom_def">The definition to for which the underlying algo will be returned.</param>
/// <returns>The first non-composite algo definition in the specified <see cref="CustomOrderDefinition"/>.</returns>
public static CustomOrderDefinitionImmutable actual(this CustomOrderDefinitionImmutable custom_def) {
while (custom_def.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite)
custom_def = custom_def.Composite.Definition;
return custom_def;
}
public static OrderSubType sub_type(this OrderRecordImmutable order) =>
order.HasDef ? order.Def.SubType : OrderSubType.None;
@@ -125,7 +140,7 @@ namespace mbts.spark {
display_name(order_tuple.order, order_tuple.algo);
public static string display_name(OrderRecordImmutable order, AlgoData? algo) {
if (!order.has_parent() && algo.HasValue) {
if (algo.HasValue) {
return algo.Value.name;
} else if (order.HasDef && order.Def.HasCustomDef) {
return order.Def.CustomDef.HasDynamic ?
@@ -225,18 +240,12 @@ namespace mbts.spark {
}
return false;
}
public static Brush badge_fill(this OrderSecurityAlgoMeta order_sam, OrderMetadata order_meta) =>
badge_fill(order_sam.order, order_sam.meta, order_meta);
public static Brush badge_fill(OrderRecordImmutable order, AlgoDefinitionMetadata? algo_meta, OrderMetadata order_meta) {
/// TODO: figure out why <see cref="OrderDefinition.AlgoTemplateKey"/> is propagated to all descendant-algos (should really just belong to the algo-node/CustomDef that is the root of the AlgoTemplate).
/// In the meantime, the following 'has-no-parent' conditional will suffice as a workaround so child-algos will be colored properly (according to their own <see cref="CustomOrderDefinition.CustomOneofCase"/>).
if (!order.has_parent()) {
if (algo_meta?.badge_color is Color c)
return c.to_brush();
}
return order_meta.Color.rgb_brush();
}
public static Brush badge_fill(AlgoDefinitionMetadata? algo_meta, OrderMetadata order_meta) =>
new SolidColorBrush(badge_color(algo_meta, order_meta));
public static Color badge_color(AlgoDefinitionMetadata? algo_meta, OrderMetadata order_meta) =>
algo_meta?.badge_color ?? order_meta.Color.rgb_color();
public static string order_collection_summary(double best_price, int best_price_volume, int total_volume, ContractSpecificationImmutable spec) {
if (total_volume == 0)
+37 -23
View File
@@ -18,7 +18,6 @@ using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
using OrderUpdate = bts.api.core.TableUpdate<(bts.api.SecurityImmutable? sec, bts.api.orderman.OrderRecordImmutable order), uint>;
using OrderUpdatePiq = bts.api.core.TableUpdate<bts.api.orderman.OrderSecurity, uint>;
@@ -37,7 +36,8 @@ namespace mbts.spark {
readonly ErrorReporter _error;
readonly DynamicOrderHelper _dynamic_helper;
readonly OrderActivitySummarizer _activity;
readonly TableUpdateDemuxer<OrderRecordImmutable, uint, uint> _security_orders_demuxer, _security_working_orders_demuxer;
readonly TableUpdateDemuxer<OrderRecordImmutable, uint, uint> _security_orders_demuxer;
readonly TableUpdateDemuxer<OrderSecurity, uint, uint> _security_working_orders_with_piq_demuxer;
readonly TableUpdateDemuxer<PositionUnitImmutable, int, uint> _security_positions_demuxer;
IDisposable _dispose;
static readonly bool _piq_enabled = Configuration.get<bts.config.toggles.PlacementInQueue>().enabled;
@@ -82,20 +82,21 @@ namespace mbts.spark {
_activity = new OrderActivitySummarizer(bus, on_orders);
_security_orders_demuxer = new TableUpdateDemuxer<OrderRecordImmutable, uint, uint>(o => o.Key, o => o.security_key());
_security_working_orders_demuxer = new TableUpdateDemuxer<OrderRecordImmutable, uint, uint>(o => o.Key, o => o.security_key());
_security_working_orders_with_piq_demuxer = new TableUpdateDemuxer<OrderSecurity, uint, uint>(o => o.order.Key, o => o.order.security_key());
_security_positions_demuxer = new TableUpdateDemuxer<PositionUnitImmutable, int, uint>(o => o.RecordId, p => p.SecurityId);
var algo_meta_join = algo_model.definitions.LeftJoin<AlgoData, AlgoDefinitionMetadata, (AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>(
studio_algos_with_meta = algo_model.definitions.LeftJoin<AlgoData, AlgoDefinitionMetadata, (AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>(
algo_model.metadata_service.subscribe_metadata().Table(m => m.id),
d => d.key,
m => m.id,
(algo, k, meta) => (algo, meta),
(algo, k) => (algo, null),
t => t.algo.key);
t => t.algo.key)
.Cache();
var order_secs_algo_meta_join = on_orders.LeftJoin(
algo_meta_join,
studio_algos_with_meta,
t => t.order.Key,
am => am.algo.key,
t => new AlgoKey(new bts.persist.algo.AlgoPersistedKey(t.order.Def.AlgoTemplateKey)),
t => new AlgoKey(new bts.persist.algo.AlgoPersistedKey(t.order.algo_template_key())),
(os, okey, am, mkey) => new OrderSecurityAlgoMeta(os.order, os.sec, am.algo, am.meta),
(os, okey, mkey) => new OrderSecurityAlgoMeta(os.order, os.sec, null, null),
ot => ot.order.Key);
@@ -114,13 +115,13 @@ namespace mbts.spark {
(osam, _) => new OrderSecurity(osam.security, osam.order, null, osam.algo, osam.meta),
osp => osp.order.Key)
.Cache();
working_orders_with_piq =
on_order_tuples_with_piq.Scan(
ImmutableDictionary<uint, OrderSecurity>.Empty,
(workers, table) => _update_working_orders(workers, table))
.Replay(1).RefCount();
// order tuples with root
on_order_tuples_with_root = on_order_tuples.RootDependent(
o => o.order.Key,
o => o.order.ParentKey,
(osam, root_osam) => osam.with(root_osam.algo ?? new AlgoData(root_osam.order.Def.CustomDef), root_osam.meta)
).Cache();
// fills
var on_fills =
_service.subscribei_fills()
.Naggle(interval, scheduler)
@@ -152,10 +153,9 @@ namespace mbts.spark {
.Cache();
_dispose = Disposable.Create(
on_orders.Select(t => t.order, o => o.Key).Subscribe(_security_orders_demuxer),
on_orders
.Filter(order => order.order.is_active() && order.order.HasState && order.order.State.DisplayStatus == OrderState.Types.DisplayStatus.Visible, key => key.order.Key)
.Select(t => t.order, o => o.Key)
.Subscribe(_security_working_orders_demuxer),
on_order_tuples_with_piq
.Filter(os => os.order.is_active() && os.order.State.DisplayStatus == OrderState.Types.DisplayStatus.Visible, key => key.order.Key)
.Subscribe(_security_working_orders_with_piq_demuxer),
on_positions.Subscribe(_security_positions_demuxer),
_bus.subscribe(SparkTags.CreateOrder).Subscribe(a => send_order(a.definition, a.price, a.size).ignore()),
_bus.subscribe(SparkTags.RecreateOrder).Subscribe(a => a.ForEach(x => send_order(x.definition, x.price, x.size).ignore())),
@@ -172,19 +172,21 @@ namespace mbts.spark {
public IObservable<OrderUpdate> on_orders { get; }
public IObservable<OrderUpdatePiq> on_order_tuples_with_piq { get; }
public IObservable<TableUpdate<OrderSecurityAlgoMeta, uint>> on_order_tuples { get; }
public IObservable<TableUpdate<OrderSecurityAlgoMeta, uint>> on_order_tuples_with_root { get; }
public IObservable<TableUpdate<KeyValuePair<uint, bool>, uint>> on_orders_can_detach { get; }
public IObservable<TableUpdate<KeyValuePair<uint, bool>, uint>> on_orders_can_attach { get; }
public IObservable<IIncrementalUpdate<SecurityImmutable, uint>> on_order_securities { get; }
public IObservable<TableUpdate<FillTuple, string>> on_fill_tuples { get; }
public IObservable<IReadOnlyDictionary<uint,OrderSecurity>> working_orders { get; }
public IObservable<IReadOnlyDictionary<uint, OrderSecurity>> working_orders_with_piq { get; }
public IObservable<TableUpdate<PositionUnitImmutable, int>> on_positions { get; }
public IObservable<TableUpdate<(AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>> studio_algos_with_meta { get; }
public AdminModel admin_model { get; }
public MBOService mbo_service => _mbo_service;
public IAlgoStudioModel algo_model { get; }
public IObservable<TableUpdate<OrderRecordImmutable, uint>> on_security_working_orders(uint security) =>
_security_working_orders_demuxer.subscribe(security);
public IObservable<TableUpdate<OrderSecurity, uint>> on_security_working_orders_with_piq(uint security) =>
_security_working_orders_with_piq_demuxer.subscribe(security);
public IObservable<TableUpdate<OrderRecordImmutable, uint>> on_security_orders(uint security) =>
_security_orders_demuxer.subscribe(security);
@@ -228,9 +230,9 @@ namespace mbts.spark {
// order already filled to completion
return false;
}
var size = new_size ?? (uint)order.working_quantity();
var size = new_size ?? order.quantity();
var price = new_price ?? order.price();
if (size == order.working_quantity() && price == order.price() && !new_algo.HasValue) {
if (size == order.quantity() && price == order.price() && !new_algo.HasValue) {
// no parameters would be changed by this update
return false;
}
@@ -437,6 +439,18 @@ namespace mbts.spark {
return rod;
}).Select(r => r.last_update);
public static IObservable<TableUpdate<(AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>> subscribe_all_algos(this OrdersModel orders_model) =>
orders_model.studio_algos_with_meta.MergeTable(Observable.Return(
new TableUpdate<(AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>(
ImmutableDictionary<AlgoKey, (AlgoData algo, AlgoDefinitionMetadata? meta)>.Empty.AddRange(orders_model.factory.default_algos(OrderDataHelper.all_order_types).Select(kv => new KeyValuePair<AlgoKey, (AlgoData algo, AlgoDefinitionMetadata? meta)>(kv.Key, (kv.Value, null)))),
Array.Empty<(AlgoData,AlgoDefinitionMetadata?)>(),
Array.Empty<Change<(AlgoData,AlgoDefinitionMetadata?)>>(),
Array.Empty<(AlgoData,AlgoDefinitionMetadata?)>(),
d => d.algo.key
).as_snapshot()));
//public static IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_all_algos_with_meta(this OrdersModel orders_model) =>
public static async Task<OrderRecordImmutable?> order_from_key(this OrdersModel orders_model, uint order_key) {
(var tu, var success) = await orders_model.on_orders.FirstAsyncTask(t => t.state.ContainsKey(order_key), TimeSpan.FromSeconds(3));
if (success && tu.state.TryGetValue(order_key, out var rec)) {
+34 -3
View File
@@ -18,9 +18,9 @@
<RootNamespace>bts</RootNamespace>
<LangVersion>7.2</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors />
<Version>1.0.0</Version>
</PropertyGroup>
<WarningsAsErrors />
<Version>1.0.0</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../spark_api/spark_api.csproj" />
<ProjectReference Include="../spark_core/spark_core.csproj" />
@@ -81,16 +81,47 @@
</Compile>
</ItemGroup>
<ItemGroup>
<None Update="Modules\Trading\AlgoStudio\View\AlgoEditorGridView\AlgoEditorGridView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\AlgoStudio\View\AlgoEditorView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\Ladder\View\LadderSettingsDialog.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\Ladder\View\LadderSettingsView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\Ladder\View\LadderView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\Ladder\View\MiniLadderView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\OrderBook\MiniOrderBook\MiniOrderBookView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\OrderBook\MiniOrderBook\OrderBookView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\OrderBook\View\OrderBookGridView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\OrderBook\View\OrderBookToolBar.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\OrderBook\View\OrderBookView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\OrderBook\View\OrderParamsPanel.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="view\Controls\DIYGrid\Settings\SimpleGridSettingsDialog.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
</ItemGroup>
<Target Name="diagnostic" AfterTargets="PreBuildEvent">
</Target>
</Project>
@@ -188,7 +188,7 @@ namespace mbts.collections {
}
public IObservableCollection<T> items { get; }
public ObservableCollection<T> selected_items { get; } = new ObservableCollection<T>();
public ObservableCollectionEx<T> selected_items { get; } = new ObservableCollectionEx<T>();
public IEnumerable<TKey> selected_keys => selected_items.Select(_key_selector);
public bool try_select(TKey key) {
@@ -42,7 +42,7 @@ namespace mbts.collections
foreach (TModel m in e.OldItems) _remove(m);
break;
case NotifyCollectionChangedAction.Reset:
var old_views = Values.ToLArray();
var old_views = Values.ToArray();
Clear();
foreach (TValue v in old_views) ValueDestroyed?.Invoke(v);
foreach (TModel m in _models) _add(m);
@@ -33,6 +33,12 @@ namespace mbts.ui {
public ObservantCommand(Action execute) : base(_ => execute()) { }
public ObservantCommand(Action<object> execute) : base(execute) { }
public static ICommand create(Action execute, out IObserver<bool> observer) {
var cmd = new ObservantCommand(execute);
observer = cmd;
return cmd;
}
public static ICommand create<T>(Action<T> execute, Func<T, bool> get_can_execute, out IObserver<T> observer) {
T param = default;
bool can_execute = false;
@@ -54,19 +54,16 @@ namespace System.Reflection
/// <summary>
/// Gets the generic type arguments of the generic class, or interface implemented by the current Type.
/// </summary>
/// <param name="type">The type that implements or is derived from the generic type (imp_generic_def).</param>
/// <param name="imp_generic_def">The generic type definition which the current type implements (if an interface) or is derived from (if a class).</param>
/// <param name="type">The type that implements or is derived from the generic type (<paramref name="generic_def"/>).</param>
/// <param name="generic_def">The generic type definition which the current type implements (if an interface) or is derived from (if a class).</param>
/// <returns>The generic type arguments of the generic type definition implemented or derived from by the current type.</returns>
/// <exception cref="System.InvalidOperationException">
/// The parameter generic_def does not represent a generic type definition. That is, System.Type.IsGenericTypeDefinition returns false.
/// </exception>
public static Type[] GetGenericTypeArgs(Type type, Type generic_def) {
if (!generic_def.IsGenericTypeDefinition) throw MyExceptions.not_generic_type_definition(generic_def);
Type[] rv = null;
rv = generic_def.IsInterface ? _get_generic_interface_type_args(type, generic_def) : _get_generic_type_args(type, generic_def);
if (rv == null) throw MyExceptions.does_not_implement_or_derive_from(type, generic_def);
else return rv;
}
public static Type[] GetGenericTypeArgs(Type type, Type generic_def) =>
!generic_def.IsGenericTypeDefinition ? throw MyExceptions.not_generic_type_definition(generic_def) :
(generic_def.IsInterface ? _get_generic_interface_type_args(type, generic_def) : _get_generic_type_args(type, generic_def)) ??
throw MyExceptions.does_not_implement_or_derive_from(type, generic_def);
public static IDisposable detect_property_changes<T>(T obj, Action<string> on_prop_changed, params string[] props) where T : class {
var snapshot = props.ToDictionary(p => p, p => {
@@ -11,4 +11,6 @@ namespace bts.shell
{
Task<Size> MeasureContentSize();
}
interface ISlowMeasureContentSize : IMeasureContentSize { }
}
+10 -2
View File
@@ -205,10 +205,15 @@ namespace mbts.ui.shell {
return new Property<T>(this, exp.Member.Name, default_value);
}
// WILL invoke the get_value delegate
protected DelegateProperty<T> backing<T>(string name, Func<T> get_value, params string[] invalidating_props) {
return new DelegateProperty<T>(this, name, get_value, invalidating_props);
}
// will NOT invoke the get_value delegate
protected DelegateProperty<T> backing<T>(string name, Func<T> get_value, T default_value, params string[] invalidating_props) =>
new DelegateProperty<T>(this, name, get_value, default_value, invalidating_props);
protected PropertyAutoWrapper<TModel, TView> backing<TModel, TView>(string name, TModel initial_model, Func<TModel, TView> convert) =>
new PropertyAutoWrapper<TModel, TView>(this, name, initial_model, convert, EqualityComparer<TModel>.Default);
@@ -273,7 +278,10 @@ namespace mbts.ui.shell {
protected class DelegateProperty<T> : Property<T> {
public DelegateProperty(ViewModelBase owner, string name, Func<T> get_value, params string[] invalidating_props)
: base(owner, name, get_value()) {
: this(owner, name, get_value, get_value(), invalidating_props) { }
public DelegateProperty(ViewModelBase owner, string name, Func<T> get_value, T default_value, params string[] invalidating_props)
: base(owner, name, default_value) {
_get_value = get_value;
if (invalidating_props != null && invalidating_props.Length > 0) {
var iprop_set = new HashSet<string>(invalidating_props);
@@ -282,7 +290,7 @@ namespace mbts.ui.shell {
refresh();
};
}
}
}
readonly Func<T> _get_value;
@@ -116,7 +116,8 @@ namespace mbts.ui.controls {
HubAlias = "Hub Alias",
StripName = "Strip Name",
MicCode = "Mic Code",
PositionInQueue = "PIQ"
PositionInQueue = "PIQ",
RootAlgoType = "Root Algo"
;
}
@@ -8,16 +8,15 @@ namespace mbts.ui.controls {
internal class OrderTypeColumn<TColKey> : FrameworkElementColumnBase<TColKey>, IFilterableColumn {
readonly BindingBase _badge_fill_binding, _display_name_binding;
public OrderTypeColumn(TColKey key, BindingBase badge_fill_binding, Binding display_name_binding, IDataGridColumnFilter filter) : base(key) {
public OrderTypeColumn(TColKey key, BindingBase badge_fill_binding, Binding display_name_binding, IDataGridColumnFilter filter, string header = DefaultColumnHeaders.OrderType) : base(key) {
_badge_fill_binding = badge_fill_binding;
_display_name_binding = display_name_binding;
this.filter = filter;
Header = DefaultColumnHeaders.OrderType;
Width = DefaultColumnWidths.OrderType;
Header = header;
}
public IDataGridColumnFilter filter { get; }
public string filter_template => DataGridFilterKnownTemplates.DiscreteValues;
public virtual string filter_template => DataGridFilterKnownTemplates.DiscreteValues;
public BindingBase font_weight_binding { get; set; }
public override FrameworkElement view_factory() {
@@ -37,6 +36,7 @@ namespace mbts.ui.controls {
bind(FontSizeProperty, nameof(font_size));
bind(ForegroundProperty, nameof(font_brush));
bind(HorizontalAlignmentProperty, nameof(horizontal_alignment));
bind(OrderTypeItem.WidthProperty, nameof(ActualWidth));
return v;
//
void bind(DependencyProperty dp, string path) =>
@@ -211,7 +211,9 @@ namespace mbts.ui.controls {
ActiveSelectionHighlightKey = nameof(ActiveSelectionHighlightKey),
InactiveSelectionHighlightKey = nameof(InactiveSelectionHighlightKey),
SelectionStrokeKey = nameof(SelectionStrokeKey);
public const double DefaultRowHeight = 17;
public const double
DefaultRowHeight = 17,
DefaultColumnHeaderHeight = 30;
public static readonly Thickness
DefaultCellPadding = new Thickness(4, 2, 4, 2),
AnimatableCellPadding = new Thickness(8, 2, 4, 2);
@@ -240,6 +242,7 @@ namespace mbts.ui.controls {
public static readonly SolidColorBrush DefaultSelectionHighlight = new SolidColorBrush(Colors.MediumSlateBlue) { Opacity = 0.4 };
public static readonly Pen DefaultSelectionStroke = new Pen(Brushes.MediumSlateBlue, 1);
readonly NotifyCollectionChangedEventHandler _sort_descriptions_collection_changed_cb;
double _inactive_selection_opacity = 0.2;
DataContextHooks _data_context_hooks;
public static readonly DependencyProperty SelectionHighlightProperty =
@@ -247,7 +250,7 @@ namespace mbts.ui.controls {
nameof(SelectionHighlight),
(o, v) => {
o.Resources[ActiveSelectionHighlightKey] = v;
o.Resources[InactiveSelectionHighlightKey] = new SolidColorBrush(v.Color) { Opacity = 0.2 }.frozen();
o._reset_inactive_selection_brush();
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);
@@ -320,14 +323,24 @@ namespace mbts.ui.controls {
}
}
}));
Resources[InactiveSelectionHighlightKey] = new SolidColorBrush(SelectionHighlight.Color) { Opacity = 0.2 };
Resources[ActiveSelectionHighlightKey] = SelectionHighlight;
_reset_inactive_selection_brush();
_data_context_hooks = new DataContextHooks(this, DataContext);
DataContextChanged += OnDataContextChanged;
}
public event DIYGridSortingEventHandler Sorting;
public double InactiveSelectionOpacity {
get => _inactive_selection_opacity;
set {
if (value != _inactive_selection_opacity && value.is_within_range(0.1, 0.9)) {
_inactive_selection_opacity = value;
_reset_inactive_selection_brush();
}
}
}
public ItemContainerGenerator RowContainerGenerator =>
_row_presenter.ItemContainerGenerator;
@@ -374,6 +387,18 @@ namespace mbts.ui.controls {
void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) =>
Disposable.replace(ref _data_context_hooks, new DataContextHooks(this, e.NewValue));
void _reset_inactive_selection_brush() {
try {
if (TryFindResource(ActiveSelectionHighlightKey) is SolidColorBrush brush) {
var clone = brush.Clone();
if (_inactive_selection_opacity < clone.Opacity) {
clone.Opacity = _inactive_selection_opacity;
}
Resources[InactiveSelectionHighlightKey] = clone;
}
} catch { }
}
#region Column Handling
private void invalidate_column_layout(object sender = null, EventArgs args = null) {
var geometry = GetColumnGeometry().ToList();
@@ -589,7 +614,7 @@ namespace mbts.ui.controls {
_row_presenter.ItemContainerStyle = _row_style;
}
public Style RowStyle { get => GetValue(RowStyleProperty) as Style; set => SetValue(StyleProperty, value); }
public Style RowStyle { get => GetValue(RowStyleProperty) as Style; set => SetValue(RowStyleProperty, value); }
#endregion
public static readonly DependencyProperty CurrentRowProperty = DependencyProperty.Register(
@@ -913,4 +938,4 @@ namespace mbts.ui.controls {
}
});
}
}
}
@@ -75,7 +75,7 @@
<Setter Property="CommandParameter" Value="{Binding RelativeSource={RelativeSource Self}}"/>
<Setter Property="CommandTarget" Value="{Binding RelativeSource={RelativeSource AncestorType=bts:DIYGrid}}"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="Height" Value="30"/>
<Setter Property="Height" Value="{x:Static bts:DIYGrid.DefaultColumnHeaderHeight}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GridViewColumnHeader}">
@@ -11,7 +11,22 @@ using System.Windows.Media;
namespace mbts.ui.controls {
partial class DIYGrid {
public class HighPerformanceRow<TRowVM, TColKey> : FrameworkElement where TRowVM : class, IRowVM {
public class HighPerformanceRowBase : FrameworkElement {
protected Brush cached_background;
public static readonly DependencyProperty BackgroundProperty =
WPFHelper.create_dp<Brush, HighPerformanceRowBase>(
nameof(Background),
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o.cached_background = v);
public Brush Background {
get => GetValue(BackgroundProperty) as Brush;
set => SetValue(BackgroundProperty, value);
}
}
public class HighPerformanceRow<TRowVM, TColKey> : HighPerformanceRowBase where TRowVM : class, IRowVM {
double _row_height;
ObservableCollection<ColumnBase> _columns;
List<ColumnGeometry> _column_layout = new List<ColumnGeometry>();
@@ -85,6 +100,9 @@ namespace mbts.ui.controls {
_element_cells.VisualAtIndex(index);
protected override void OnRender(DrawingContext dc) {
if (cached_background != null) {
dc.DrawRectangle(cached_background, null, new Rect(0, 0, ActualWidth, ActualHeight));
}
if (_row != null) {
foreach (var cell in _column_layout) {
if (!cell.column.is_collapsed && cell.column is IRenderColumn<TRowVM> rc)
@@ -56,6 +56,7 @@ namespace mbts.spark.filters {
static class DataGridFilterKnownTemplates {
public const string
AlgoType = "AlgoTypeTemplate",
DoubleMinMax = "DoubleMinMaxTemplate",
IntMinMax = "IntMinMaxTemplate",
UIntMinMax = "UIntMinMaxTemplate",
@@ -1,6 +1,9 @@
using bts.collections;
using bts.api.core;
using bts.collections;
using bts.utils;
using mbts.collections;
using mbts.ui.shell;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -58,6 +61,14 @@ namespace mbts.spark.filters {
IEqualityComparer<TValue> equality_comparer = null,
IComparer<TValue> comparer = null
) => new DynamicItemsFilter<TRow, TValue, TState>(items, get_row_val, get_state, load_state, prop_name, equality_comparer ?? EqualityComparer<TValue>.Default, comparer);
public static IDataGridColumnPersistentFilter<TRow, TKey[]> CreateTableUpdate<TRow, TValue, TKey>(
Func<TRow, TValue> factory, Func<TRow, TKey> row_key, Func<TValue, TKey> key, out IObserver<TableUpdate<TValue, TKey>> items_observer, out ListCollectionView<TValue> items_view) where TKey : struct, IEquatable<TKey> {
var filter = new TableUpdateItemsFilter<TRow, TValue, TKey, TKey[]>(factory, row_key, key, keys => keys, keys => keys.ToArray());
items_observer = filter;
items_view = filter.items_view;
return filter;
}
#endregion
#region Implementation (private)
@@ -73,7 +84,7 @@ namespace mbts.spark.filters {
public IReadOnlyCollection<TValue> items { get; }
public ObservableCollectionEx<TValue> selected_items { get; } = new ObservableCollectionEx<TValue>();
public override TValue[] get_state() => selected_items.ToLArray();
public override TValue[] get_state() => selected_items.ToArray();
protected override void _clear() {
_destroy_snapshot();
selected_items.Clear();
@@ -133,10 +144,10 @@ namespace mbts.spark.filters {
) : this(get_row_val, get_state, load_state, prop_name, equality_comparer, comparer) {
//
_items_sub = items.OnDispatcher().Subscribe(update => {
foreach (var v in update.added)
_items_set.Add(v);
foreach (var v in update.removed.Except(_selected_items_set))
_items_set.Remove(v);
foreach (var v in update.added)
_items_set.Add(v);
_unsorted_items.sync_with(_items_set);
});
}
@@ -238,6 +249,75 @@ namespace mbts.spark.filters {
}
}
}
class TableUpdateItemsFilter<TRow, TValue, TKey, TState> : FilterVMBase<TRow, TValue, TState>, IObserver<TableUpdate<TValue, TKey>> where TKey : struct, IEquatable<TKey> {
readonly ObservableKeyedCollection<TValue, TKey> _items;
readonly KeyedMultiSelector<TValue, TKey> _selection_model;
readonly Func<TValue, TKey> _key;
readonly Func<TRow, TValue> _factory;
readonly Func<TRow, TKey> _row_key;
readonly Func<TState, IEnumerable<TKey>> _load_state;
readonly Func<IEnumerable<TKey>, TState> _get_state;
readonly IEqualityComparer<TValue> _comparer;
readonly HashSet<TKey> _selected_keys = new HashSet<TKey>(), _desired_keys = new HashSet<TKey>();
ListCollectionView<TValue> _items_view;
public TableUpdateItemsFilter(Func<TRow, TValue> factory, Func<TRow, TKey> row_key, Func<TValue, TKey> key, Func<TState, IEnumerable<TKey>> load_state, Func<IEnumerable<TKey>, TState> get_state)
: base(factory, null) {
_factory = factory;
_row_key = row_key;
_key = key;
_load_state = load_state;
_get_state = get_state;
_comparer = EqualityComparer.Create(_key);
_items = new ObservableKeyedCollection<TValue, TKey>(_key);
_selection_model = new KeyedMultiSelector<TValue, TKey>(_items, _key);
}
public ObservableCollection<TValue> items => _items;
public ListCollectionView<TValue> items_view => _items_view ?? (_items == null ? null : (_items_view = new ListCollectionView<TValue>(_items)));
public IObservableCollectionEx<TValue> selected_items => _selection_model?.selected_items;
void IObserver<TableUpdate<TValue, TKey>>.OnCompleted() { }
void IObserver<TableUpdate<TValue, TKey>>.OnError(Exception error) { }
void IObserver<TableUpdate<TValue, TKey>>.OnNext(TableUpdate<TValue, TKey> table) {
foreach (var v in table.removed)
_items.Remove(_key(v));
foreach (var ch in table.changes)
_items.Replace(_key(ch.Old), ch.New);
foreach (var v in table.added)
_items.Add(v);
var newly_selected_keys = _desired_keys.Where(_selection_model.try_select).ToList();
if (newly_selected_keys.Count > 0) {
_selected_keys.AddRange(newly_selected_keys);
_desired_keys.removeRange(newly_selected_keys);
_raise_filter_changed();
}
}
public override TState get_state() => _get_state(_selection_model.selected_items.Select(_key));
protected override bool _get_active() => _selection_model.selected_items.Count > 0;
protected override void _apply() {
_selected_keys.SetItems(selected_items.Select(_key));
base._apply();
}
protected override void _clear() {
_destroy_snapshot();
_selection_model.selected_items.Clear();
_close();
_raise_filter_changed();
}
protected override bool _filter(TValue value) => _selected_keys.Contains(_key(value));
protected override void _load_state_internal(TState state) {
foreach (var k in _load_state(state).empty_if_null()) {
if (!_selection_model.try_select(k))
_desired_keys.Add(k);
}
}
}
#endregion
[Browsable(false)]
@@ -262,4 +342,4 @@ namespace mbts.spark.filters {
}
}
}
}
}
@@ -14,7 +14,7 @@ namespace mbts.spark.filters {
protected FilterVMBase(Func<TRow, TValue> get_row_val, string prop_name) {
_get_row_val = get_row_val;
this.prop_name = prop_name;
_active = backing(nameof(active), () => HasErrors ? new bool?() : _get_active());
_active = backing(nameof(active), () => HasErrors ? new bool?() : _get_active(), false);
_snapshot = _get_snapshot();
apply = new DelegateCommand(_apply);
cancel = new DelegateCommand(_cancel);
@@ -6,6 +6,7 @@
xmlns:msp="clr-namespace:mbts.spark"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Resources/ComboBoxes.xaml"/>
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/DoubleMinMaxFilterControl.xaml"/>
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/IntMinMaxFilterControl.xaml"/>
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/UIntMinMaxFilterControl.xaml"/>
@@ -146,6 +147,30 @@
/>
</usp:FilterContentControl>
</DataTemplate>
<DataTemplate x:Key="AlgoTypeTemplate">
<usp:FilterContentControl
ApplyCommandParameter="{Binding}"
ApplyCommand="{x:Static c:DataGridFilterProperties.CommitCommand}"
ClearCommandParameter="{Binding}"
ClearCommand="{x:Static c:DataGridFilterProperties.ClearCommand}"
>
<c:ListBoxEx
ItemsSource="{Binding items_view}"
Selection="{Binding selected_items}"
SelectionMode="Multiple"
ItemContainerStyle="{StaticResource ListBoxItemCheckBoxStyle}"
>
<c:ListBoxEx.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource ComboBox.DelineatedGroupContainerStyle}"/>
</c:ListBoxEx.GroupStyle>
<c:ListBoxEx.ItemTemplate>
<DataTemplate>
<usp:OrderTypeItem DisplayName="{Binding name}" BadgeFill="{Binding badge_fill}" Margin="6,0,0,0"/>
</DataTemplate>
</c:ListBoxEx.ItemTemplate>
</c:ListBoxEx>
</usp:FilterContentControl>
</DataTemplate>
<DataTemplate x:Key="YesNoTemplate">
<usp:FilterContentControl
ApplyCommandParameter="{Binding}"
@@ -532,7 +532,7 @@
<VisualState Name="{StaticResource CannotEditValuesState}">
<VisualState.Storyboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_HedgeTicks_TickUpDownNullableInt" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_HedgeTicks_TickUpDownNullableUInt" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
@@ -556,7 +556,7 @@
<TextBlock VerticalAlignment="Center">Hedge Ticks</TextBlock>
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="This is the number of ticks to offset the hedge."/>
</StackPanel>
<c:TickUpDownNullableInt x:Name="PART_HedgeTicks_TickUpDownNullableInt" Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding hedge_ticks}"/>
<c:TickUpDownNullableUInt x:Name="PART_HedgeTicks_TickUpDownNullableUInt" Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding hedge_ticks}"/>
<uag:AlgoPickerNodeView
x:Name="PART_Hedge_AlgoPickerNodeView"
Grid.Row="2"
@@ -233,6 +233,22 @@
<TextBlock Text="{Binding Definition.name, RelativeSource={RelativeSource TemplatedParent}, StringFormat='{}Save \&apos;{0}\&apos;'}"/>
</MenuItem.ToolTip>
</MenuItem>
<MenuItem
x:Name="PART_SetDefaults_MenuItem"
Command="{TemplateBinding SetDefaultsCommand}"
Style="{StaticResource AlgoMenuItem}"
>
<MenuItem.Header>
<Image Source="{x:Static u:Icons.RubberStamp}"/>
</MenuItem.Header>
<MenuItem.ToolTip>
<TextBlock>
Make current paramaters the local defaults.
<LineBreak/>
<Italic>These values will be lost when the ladder closes.</Italic>
</TextBlock>
</MenuItem.ToolTip>
</MenuItem>
<MenuItem
x:Name="PART_Undo_MenuItem"
Command="{TemplateBinding UndoCommand}"
@@ -291,13 +307,11 @@
CommandParameter="{Binding Definition, RelativeSource={RelativeSource TemplatedParent}}"
HorizontalContentAlignment="Center"
Style="{StaticResource AlgoMenuItem}"
ToolTip="Revert parameters to the local defaults."
>
<MenuItem.Header>
<Image Source="{x:Static u:Icons.UndoCircular}"/>
</MenuItem.Header>
<MenuItem.ToolTip>
<TextBlock Text="{Binding Definition.name, RelativeSource={RelativeSource TemplatedParent}, StringFormat='{}Undo changes to \&apos;{0}\&apos;'}"/>
</MenuItem.ToolTip>
</MenuItem>
<MenuItem
x:Name="PART_IsFavorite_MenuItem"
@@ -124,8 +124,8 @@ namespace mbts.ui.spark.algo {
public static readonly DependencyProperty ShowRevertDefinitionProperty =
WPFHelper.create_dp<bool, AlgoTemplateToolBar>(
nameof(ShowRevertDefinition),
(o, v) => VisualStateNames.RevertDefinition.apply(o._vsm, v),
coerce_require_persisted);
VisualStateNames.RevertDefinition.apply/*,
coerce_require_persisted*/);
public bool ShowRevertDefinition { get => (bool)GetValue(ShowRevertDefinitionProperty); set => SetValue(ShowRevertDefinitionProperty, value); }
#endregion
@@ -156,7 +156,7 @@ namespace mbts.ui.spark.algo {
(o, v) => {
o.CoerceValue(ShowRenameDefinitionProperty);
o.CoerceValue(ShowSaveDefinitionProperty);
o.CoerceValue(ShowRevertDefinitionProperty);
//o.CoerceValue(ShowRevertDefinitionProperty);
o.CoerceValue(ShowUndoRedoButtonsProperty);
});
@@ -214,6 +214,12 @@ namespace mbts.ui.spark.algo {
public ICommand SaveDefinitionCommand { get => GetValue(SaveDefinitionCommandProperty) as ICommand; set => SetValue(SaveDefinitionCommandProperty, value); }
#endregion
#region SetDefaults
public static readonly DependencyProperty SetDefaultsCommandProperty =
WPFHelper.create_dp<ICommand, AlgoTemplateToolBar>(nameof(SetDefaultsCommand));
public ICommand SetDefaultsCommand { get => GetValue(SetDefaultsCommandProperty) as ICommand; set => SetValue(SetDefaultsCommandProperty, value); }
#endregion
#region Revert
public static readonly DependencyProperty RevertCommandProperty =
WPFHelper.create_dp<ICommand, AlgoTemplateToolBar>(nameof(RevertCommand));
@@ -435,7 +441,7 @@ namespace mbts.ui.spark.algo {
public static class VisualStateNames {
public static void apply_all(AlgoTemplateToolBar toolbar) {
UndoRedo.apply(toolbar._vsm, toolbar.ShowUndoRedoButtons);
RevertDefinition.apply(toolbar._vsm, toolbar.ShowRevertDefinition);
RevertDefinition.apply(toolbar, toolbar.ShowRevertDefinition);
RenameDefinition.apply(toolbar._vsm, toolbar.ShowRenameDefinition);
SaveDefinition.apply(toolbar._vsm, toolbar.ShowSaveDefinition);
TemplatesDropDown.apply(toolbar._vsm, toolbar.ShowTemplatesDropDown);
@@ -457,8 +463,8 @@ namespace mbts.ui.spark.algo {
Active = $"{GroupName}{VisualStates.Active}",
Inactive = $"{GroupName}{VisualStates.Inactive}";
public static void apply(VisualStateModel vsm, bool active) =>
vsm.set_state(GroupName, active ? Active : Inactive);
public static void apply(AlgoTemplateToolBar owner, bool active) =>
owner.Dispatcher.InvokeAsync(() => owner._vsm.set_state(GroupName, active ? Active : Inactive));
}
public static class RenameDefinition {
public const string GroupName = nameof(RenameDefinition);
@@ -150,7 +150,8 @@
UndoDefinitionCommand="{Binding undo_definition_cmd}"
RedoDefinitionCommand="{Binding redo_definition_cmd}"
ShowUndoRedoButtons="{DynamicResource {x:Static sp:AlgoStudioWindow.ShowAlgoToolBarUndoRedoDefinitionKey}}"
RevertDefinitionCommand="{Binding revert_definition_cmd}"
SetDefaultsCommand="{Binding set_default_algo_params_cmd}"
RevertDefinitionCommand="{Binding revert_algo_params_cmd}"
ShowRevertDefinition="{DynamicResource {x:Static sp:AlgoStudioWindow.ShowAlgoToolBarRevertDefinitionKey}}"
SaveCommand="{Binding save_template_cmd}"
SaveDefinitionCommand="{Binding save_definition_cmd}"
@@ -202,6 +203,7 @@
x:Name="ParamGridView"
Grid.Row="1"
Margin="12,8,12,12"
AnimateDirty="False"
CanUserChangeAlgoRoot="False"
AlgoParamRows="{Binding algo_param_rows}"
Visibility="Visible"
@@ -17,10 +17,6 @@
<cv:KeyValue Key="{x:Static Ratio, MemberType={u:Nested sp:QuantityMeasure.QuantityOneofCase}}" Value="{u:True}"/>
<cv:KeyValue Key="{x:Static Absolute, MemberType={u:Nested sp:QuantityMeasure.QuantityOneofCase}}" Value="{u:False}"/>
</cv:ValueMap>
<cv:ValueMap x:Key="QuantityCaseVisualStateConverter" Default="{x:Null}">
<cv:KeyValue Key="{x:Static Absolute, MemberType={u:Nested sp:QuantityMeasure.QuantityOneofCase}}" Value="{x:Static Absolute, MemberType={u:Nested uag:QuantityMeasureControl.VisualStateNames.QuantityCase}}"/>
<cv:KeyValue Key="{x:Static Ratio, MemberType={u:Nested sp:QuantityMeasure.QuantityOneofCase}}" Value="{x:Static Ratio, MemberType={u:Nested uag:QuantityMeasureControl.VisualStateNames.QuantityCase}}"/>
</cv:ValueMap>
<!--Component Styles-->
<Style x:Key="AbsoluteTickUpDownStyle" TargetType="c:TickUpDownUInt">
<Setter Property="MinWidth" Value="{StaticResource TickUpDownMinWidth}"/>
@@ -52,13 +48,20 @@
<u:StateManager.Groups>
<u:StateGroupsManager AttachToMembers="True">
<u:StateDefinitionCollection>
<u:StateDefinition State="{Binding Path='(u:StateDefinition.Group).(u:StateGroupsManager.Target).(Grid.TemplatedParent).(uag:QuantityMeasureControl.QuantityCase)', RelativeSource={RelativeSource Self}, Converter={StaticResource QuantityCaseVisualStateConverter}}"/>
<u:StateDefinition State="{Binding Path='(u:StateDefinition.Group).(u:StateGroupsManager.Target).(Grid.TemplatedParent).(uag:QuantityMeasureControl.QuantityCase)', RelativeSource={RelativeSource Self}, Converter={x:Static uag:QuantityMeasureControl.QuantityCaseVisualStateConverter}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested uag:QuantityMeasureControl.VisualStateNames.QuantityCase}}">
<VisualState/>
<VisualState Name="{x:Static None, MemberType={u:Nested uag:QuantityMeasureControl.VisualStateNames.QuantityCase}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Absolute_TickUpDown" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static Absolute, MemberType={u:Nested uag:QuantityMeasureControl.VisualStateNames.QuantityCase}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Absolute_TickUpDown" Storyboard.TargetProperty="(UIElement.Visibility)">
@@ -113,13 +116,20 @@
<u:StateManager.Groups>
<u:StateGroupsManager AttachToMembers="True" OverrideTarget="True" Target="{Binding Path='(Border.TemplatedParent)', Source={u:DirectReference Border}}">
<u:StateDefinitionCollection>
<u:StateDefinition State="{Binding Path='(u:StateDefinition.Group).(u:StateGroupsManager.Target).(uag:QuantityMeasureControl.QuantityCase)', RelativeSource={RelativeSource Self}, Converter={StaticResource QuantityCaseVisualStateConverter}}"/>
<u:StateDefinition State="{Binding Path='(u:StateDefinition.Group).(u:StateGroupsManager.Target).(uag:QuantityMeasureControl.QuantityCase)', RelativeSource={RelativeSource Self}, Converter={x:Static uag:QuantityMeasureControl.QuantityCaseVisualStateConverter}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested uag:QuantityMeasureControl.VisualStateNames.QuantityCase}}">
<VisualState/>
<VisualState Name="{x:Static None, MemberType={u:Nested uag:QuantityMeasureControl.VisualStateNames.QuantityCase}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Absolute_TickUpDown" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static Absolute, MemberType={u:Nested uag:QuantityMeasureControl.VisualStateNames.QuantityCase}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Absolute_TickUpDown" Storyboard.TargetProperty="(UIElement.Visibility)">
@@ -1,10 +1,12 @@
using bts.spark;
using mbts.ui.controls;
using mbts.ui.converters;
using mbts.ui.utilities;
using mbts.ui.utility;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
namespace mbts.ui.spark.algo {
@@ -12,6 +14,10 @@ namespace mbts.ui.spark.algo {
/// Interaction logic for QuantityMeasure.xaml
/// </summary>
class QuantityMeasureControl : Control {
// converters
public static readonly IValueConverter QuantityCaseVisualStateConverter =
ValueConverter.create<QuantityMeasure.QuantityOneofCase, string>(VisualStateNames.QuantityCase.fmt_state_name);
// defaults
static readonly QuantityMeasureImmutable _default_quantity_measure = new QuantityMeasureImmutable(new QuantityMeasure { Absolute = 0 });
static uint _default_absolute => _default_quantity_measure.QuantityCase == bts.spark.QuantityMeasure.QuantityOneofCase.Ratio ? _default_quantity_measure.Absolute : default;
@@ -173,8 +179,12 @@ namespace mbts.ui.spark.algo {
public const string GroupName = nameof(QuantityCase);
public static readonly string
Absolute = $"{GroupName}{nameof(Absolute)}",
Ratio = $"{GroupName}{nameof(Ratio)}";
None = fmt_state_name(bts.spark.QuantityMeasure.QuantityOneofCase.None),
Absolute = fmt_state_name(bts.spark.QuantityMeasure.QuantityOneofCase.Absolute),
Ratio = fmt_state_name(bts.spark.QuantityMeasure.QuantityOneofCase.Ratio);
public static string fmt_state_name(bts.spark.QuantityMeasure.QuantityOneofCase quantity_case) =>
$"{GroupName}{quantity_case}";
}
}
}
@@ -1,28 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
namespace mbts.ui.converters
{
public class EnumCaseStringConverter<T> : IValueConverter where T : struct
{
namespace mbts.ui.converters {
class EnumCaseStringConverter<T> : IValueConverter where T : struct {
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
T t = default(T);
if (value is T)
t = (T)value;
string raw_str = t.ToString();
return raw_str.Substring(0, 1) + new String(raw_str.Skip(1).Select(Char.ToLower).ToLArray());
string raw_str = (value is T t ? t : default).ToString();
return raw_str.Substring(0, 1) + new string(raw_str.Skip(1).Select(char.ToLower).ToArray());
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
T t = default(T);
string str = value as string;
if (str != null)
Enum.TryParse<T>(str, true, out t);
return t;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) =>
value is string str && Enum.TryParse(str, true, out T result) ? result : default;
}
}
}
@@ -106,12 +106,20 @@ namespace mbts.ui
var effect = DragDrop.DoDragDrop(_source, data, DragDropEffects.Move);
}
catch (Exception error) { Log.Ui.warn("Dragdrop failed: " + error.ToString()); }
await show_document(_source, _source_window, true);
_source = null;
_source_window = null;
/// Reentrancy Risk:
/// During the await period for <see cref="show_document(TabDocument, FloatingWindow, bool)"/>,
/// the new window hooks up to the dragdrop events via <see cref="attach_dragdrop_events(ItemsControl)"/>.
/// It must be assured that the new window does not get a callback into <see cref="container_PreviewMouseMove(ItemsControl, MouseEventArgs)"/>
/// and attempt to create a second <see cref="DragDropDocumentPreview"/> for the same instance <see cref="_source"/>.
/// For this reason, <see cref="_source"/> is set to null before <see cref="show_document(TabDocument, FloatingWindow, bool)"/> is called.
_source_image = null;
_target = null;
_dddp = null;
var src = _source;
var src_win = _source_window;
_source = null;
_source_window = null;
await show_document(src, src_win, true);
}
}
}
@@ -0,0 +1,23 @@
using bts.api;
using mbts.spark;
using System;
using System.Windows;
using System.Windows.Input;
namespace mbts.ui.spark {
static class RoutedSparkCommands {
public static RoutedCommand LaunchAlgoStudioCommand = new RoutedCommand();
public static readonly ExecutedRoutedEventHandler
LaunchAlgoStudioExecuted = _executed(SparkTags.ShowAlgoStudioTag);
public static readonly CanExecuteRoutedEventHandler
LaunchAlgoStudioCanExecute = (s, e) => e.CanExecute = true;
static ExecutedRoutedEventHandler _executed<T>(ITag<T> tag) =>
(sender, e) => {
if (sender is FrameworkElement el && el.TryFindResource("Bus") is IBus bus)
bus.post(tag, ConvertUtils.try_convert<T>(e.Parameter));
};
}
}
@@ -30,6 +30,12 @@ namespace mbts.ui {
static void _on_preview_mouse_wheel(object sender, MouseWheelEventArgs e) {
if (!e.Handled && sender is ScrollViewer sv && !_reentrancy_list.Contains(e)) {
if (e.Source is GridViewColumnHeader && Window.GetWindow(sv) == null) {
// This is a hack specifically for the DIYGrid filter popup (namely, the DiscreteValues ListBox-based template - it doesn't scroll otherwise)...i really, really hate this bug.
// The end of the cycle approaches, so unf there is not time to dig deeper into this and find a cleaner fix.
// - John, 05/06/2019
return;
}
var preview_event_arg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta) {
RoutedEvent = UIElement.PreviewMouseWheelEvent,
Source = sender
@@ -171,6 +171,9 @@ namespace mbts.ui.shell
var shell_window = document.ViewModel.Model;
var mcs = shell_window as bts.shell.IMeasureContentSize;
if (mcs != null) {
if (shell_window is ISlowMeasureContentSize) {
Show();
}
var size =
shell_window is ISlowWindow || shell_window is ISharedWindow ?
await await document.ViewModel.Dispatcher.InvokeAsync(() => mcs.MeasureContentSize()) :
@@ -19,19 +19,71 @@ namespace bts.settings {
var from_persist = new UserUISettings(user_list.Users.First(u => u.Login == from).Id, persist, null);
var workspaces = from_persist.query<WorkspaceDefinition>(WorkspacesModel.WORKSPACE_NAMESPACE, ".*").Result;
ConsoleEx.WriteLine("Workspace Descriptions", ConsoleColor.Cyan);
foreach(var w in workspaces) {
var r = from ws in w.value.windows
group ws by ws.owner into grouping
select new { key = grouping.Key.Replace("bts.shell.", ""), window_count = grouping.Count() };
string desc = string.Empty;
foreach(var i in r) {
desc += $"{Environment.NewLine}\t{i.window_count} - {i.key}";
}
ConsoleEx.Write($"{w.key}", ConsoleColor.Yellow);
ConsoleEx.WriteLine($" - {w.value.windows?.Count} windows - {w.value.named_windows?.Count} named windows - {desc}");
}
ConsoleEx.WriteLine("");
var selected_ws = ConsoleEx.prompt("Choose workspace:", workspaces.Select(w => w.key));
var to = ConsoleEx.prompt("To User", user_names);
string copy = "Copy to another user",
delete = "Delete it",
export = "Export to file";
var name = ConsoleEx.input_prompt("Rename workspace?", selected_ws);
var action = ConsoleEx.prompt($"What do you want to do with '{selected_ws}'", copy, delete, export);
var confirm = ConsoleEx.prompt($"copy workspace '{selected_ws}' from {from} to {to} and name it '{name}'. Confirm?", "Yes", "No");
if (confirm == "Yes") {
var ws_blob = workspaces.First(w => w.key == selected_ws).value;
var to_persist = new UserUISettings(user_list.Users.First(u => u.Login == to).Id, persist, null);
to_persist.set(WorkspacesModel.WORKSPACE_NAMESPACE, name, ws_blob).Wait();
Console.WriteLine("Done");
if (action == copy) {
RunCopy();
}
else if (action == delete) {
var confirm = ConsoleEx.prompt($"Delete workspace `{selected_ws}` from {from}. Confirm?", "Yes", "No");
if (confirm == "Yes") {
var ws = workspaces.First(w => w.key == selected_ws);
from_persist.clear(ws.ns, ws.key).Wait();
Console.WriteLine("Done");
}
}
else if (action == export) {
var name = ConsoleEx.input_prompt("Filename: ", selected_ws + ".workspace");
if (System.IO.File.Exists(name)) {
var confirm = ConsoleEx.prompt($"'{name}' exists. Overwrite?", "Yes", "No");
if (confirm == "No") {
return;
}
}
var ws_blob = workspaces.First(w => w.key == selected_ws).value;
using (var file = System.IO.File.OpenWrite(name)) {
ProtoBuf.Serializer.SerializeWithLengthPrefix(file, ws_blob, ProtoBuf.PrefixStyle.Fixed32);
}
Console.WriteLine($"Export '{selected_ws}' to '{name}'. Done.");
}
// End function
void RunCopy() {
var to = ConsoleEx.prompt("To User", user_names);
var name = ConsoleEx.input_prompt("Rename workspace?", selected_ws);
var confirm = ConsoleEx.prompt($"copy workspace '{selected_ws}' from {from} to {to} and name it '{name}'. Confirm?", "Yes", "No");
if (confirm == "Yes") {
var ws_blob = workspaces.First(w => w.key == selected_ws).value;
var to_persist = new UserUISettings(user_list.Users.First(u => u.Login == to).Id, persist, null);
to_persist.set(WorkspacesModel.WORKSPACE_NAMESPACE, name, ws_blob).Wait();
Console.WriteLine("Done");
}
}
}
}
}
@@ -1,4 +1,5 @@
using bts.api;
using bts.collections;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.shell;
@@ -69,6 +70,7 @@ namespace mbts.spark
_current_row = backing(nameof(current_row), default(TRow));
readonly Property<TRow> _current_row;
public event Action<(IEnumerable<TRow> removed, IEnumerable<TRow> added)> selection_changed;
public new TRow current_row { get => _current_row.Value; set => _current_row.Value = value ?? selected_rows.FirstOrDefault(); }
public override IObservable<TRow> current_row_observable => _current_row.Select(v => v.newval);
@@ -77,35 +79,10 @@ namespace mbts.spark
RowSelectionHelper.apply_changes(selected_rows, e);
if (selected_rows.Count == 0) _current_row.Value = null;
else if (_current_row.Value == null) _current_row.Value = selected_rows[0];
selection_changed?.Invoke((e.RemovedItems.OfTypeSafe<TRow>(), e.AddedItems.OfTypeSafe<TRow>()));
}
}
abstract class SecurityGrid<TRow, TRowModel, TCol> : SecurityGridBase<TRow, TRowModel>, IHandleCellSelection where TRow : class, ISecurityRow
{
protected SecurityGrid(IObservable<IEnumerable<TRowModel>> row_models, IEnumerable<SortingCriteria> sorting_criteria)
: this(RXCollection.Create(row_models), sorting_criteria) { }
protected SecurityGrid(IObservable<IEnumerable<TRowModel>> row_models, IEqualityComparer<TRowModel> comparer, IEnumerable<SortingCriteria> sorting_criteria)
: this(RXCollection.Create(row_models, comparer), sorting_criteria) { }
protected SecurityGrid(IRXCollection<TRowModel> row_models, IEnumerable<SortingCriteria> sorting_criteria) : base(row_models, sorting_criteria) =>
_current_cell = backing<DataGridCellInfo>(nameof(current_cell));
readonly Property<DataGridCellInfo> _current_cell;
IDictionary<TRow, HashSet<TCol>> _selection_map = new Dictionary<TRow, HashSet<TCol>>();
public DataGridCellInfo current_cell {
get => _current_cell.Value;
set { if (value.IsValid) _current_cell.Value = value; }
}
public override TRow current_row => _current_cell.Value.Item as TRow ?? selected_rows.FirstOrDefault();
public override IObservable<TRow> current_row_observable => _current_cell.Select(v => v.newval.Item as TRow);
public IDictionary<TRow, HashSet<TCol>> get_selection_map() => new Dictionary<TRow, HashSet<TCol>>(_selection_map);
void IHandleCellSelection.cell_selection_changed(IList<DataGridCellInfo> added, IList<DataGridCellInfo> removed) =>
CellSelectionMapper.apply_changes(_selection_map, added, removed, selected_rows);
}
static class SecurityGridExtensions
{
public static IObservable<SecurityMD?> current_security_md_observable<TRow, TRowModel>(this SecurityGridBase<TRow, TRowModel> security_grid, IBus bus)
@@ -122,23 +99,14 @@ namespace mbts.spark
where TRow : class, ISecurityRow =>
security_grid.selected_securities.ObserveSelf();
public static IObservable<IReadOnlyList<SecurityImmutable>> sorted_selected_securities_observable<TRow, TRowModel>(this SecurityGridBase<TRow, TRowModel> security_grid)
where TRow : class, ISecurityRow =>
security_grid.selected_rows
.ObserveSelf()
.Select(rows =>
rows.Select(
row => (security: row.security, row_index: security_grid.rows_view.indexOf(row))
)
.OrderBy(sec_vs_index => sec_vs_index.row_index)
.Select(sec_vs_index => sec_vs_index.security)
.ToReadOnlyList()
);
public static IObservable<IReadOnlyList<SecurityMD>> selected_security_mds_observable<TRow, TRowModel>(this SecurityMDGrid<TRow, TRowModel> security_md_grid)
where TRow : class, ISecurityMDRow =>
security_md_grid.selected_rows.ObserveSelf(true).Select(rows => rows.Select(r => r.md).ToReadOnlyList());
Observable.Create<IReadOnlyList<SecurityMD>>(o => {
var sec_md_list = security_md_grid.selected_rows.WrapSelectorRO(r => r.md);
o.OnNext(sec_md_list);
security_md_grid.selection_changed += on_selection_changed;
return Disposable.Create(() => security_md_grid.selection_changed += on_selection_changed);
void on_selection_changed((IEnumerable<TRow> removed, IEnumerable<TRow> added) change) => o.OnNext(sec_md_list);
});
}
}
@@ -21,16 +21,4 @@ namespace mbts.spark
public IObservable<SecurityMD?> current_security_md =>
current_row_observable.Select(r => r?.md).DistinctUntilChanged(EqualityComparer.CreateNullable<SecurityMD, uint>(md => md.security.Id));
}
abstract class SecurityMDGrid<TRow, TRowModel, TCol> : SecurityGrid<TRow, TRowModel, TCol> where TRow : class, ISecurityMDRow
{
protected SecurityMDGrid(IObservable<IEnumerable<TRowModel>> row_models, IEnumerable<SortingCriteria> sorting_criteria)
: base(row_models, sorting_criteria) { }
protected SecurityMDGrid(IObservable<IEnumerable<TRowModel>> row_models, IEqualityComparer<TRowModel> comparer, IEnumerable<SortingCriteria> sorting_criteria)
: base(row_models, comparer, sorting_criteria) { }
public IObservable<SecurityMD?> current_security_md =>
current_row_observable.Select(r => r?.md).DistinctUntilChanged(EqualityComparer.CreateNullable<SecurityMD, uint>(md => md.security.Id));
}
}
@@ -0,0 +1,133 @@
using bts.api;
using bts.api.core;
using bts.md;
using mbts.config;
using mbts.ui.utility.text;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
namespace mbts.spark {
public struct MBOPopupData {
public readonly Side side;
public readonly IReadOnlyList<OrderPositionImmutable> orders;
public MBOPopupData(Side side, IReadOnlyList<OrderPositionImmutable> orders) {
this.side = side;
this.orders = orders;
}
}
public struct MBOPopupDrawParams {
public MBOPopupData data;
public double width => 300.0;
public double height;
public FormattedText header;
public static readonly Thickness margin = new Thickness(10, 5, 5, 5);
//columns
public double row_height => header.Height + 2;
public double bar_height => 0.6 * row_height;
public List<FormattedText> row_numbers;
public double numbers_width;
public List<FormattedText> row_labels;
public double scale;
}
public class MBOPopupModel {
const double font_size = 9.0;
readonly MBOService _mbo;
readonly SecurityImmutable _security;
readonly MBOSecurityQueryImmutable _query;
readonly Dictionary<string, FormattedText> _cache;
int _max_order_size = 1;
double _height = 0;
double _number_width = 0;
public MBOPopupModel(MBOService mbo, SecurityImmutable security, MBOSecurityQueryImmutable query) {
_mbo = mbo;
_security = security;
_query = query;
// prime the cache with numbers on the left hand side
_cache =
Enumerable.Range(1, 1+(int)default_query.Limit)
.AsParallel()
.Select(i => TextUtils.GetFormattedText(i.ToString(), font_size))
.ToDictionary(ft => ft.Text);
}
FormattedText format_text(string s, bool bold=false) {
// cache FormattedText for the life of the popup
if (_cache.TryGetValue(s, out FormattedText ft)) {
return ft;
}
else
ft = bold ? TextUtils.GetFormattedTextBold(s, font_size) : TextUtils.GetFormattedText(s, font_size);
_cache.TryAdd(s, ft);
return ft;
}
public async Task<Result<MBOPopupData>> load_orders() {
if (_mbo == null) return new Result<MBOPopupData>(ApiError.NoServer);
var data = await _mbo.get_orders_for_security_async(_query);
if (data.success) {
var side = data.value.side();
IEnumerable<OrderPosition> orders = new OrderPosition[0];
if (side == Side.Buy) orders = data.value.Bid;
if (side == Side.Sell) orders = data.value.Ask;
return new Result<MBOPopupData>(new MBOPopupData(side, orders.Select(o => (OrderPositionImmutable)o).ToList()));
}
else {
return new Result<MBOPopupData>(data.code);
}
}
string buy_sell_text(Side side) {
switch (side) {
case Side.Buy: return "BUY";
case Side.Sell: return "SELL";
default:
case Side.Undefined: return "";
}
}
public MBOPopupDrawParams compute_layout(MBOPopupData p) {
var pdp = new MBOPopupDrawParams { data = p };
pdp.header = format_text(
$"{buy_sell_text(p.side)} {_security.security_desc()} @ {_security.ContractSpec.format(_query.PriceLevel)}", true);
var margin = MBOPopupDrawParams.margin;
var text_margin = margin.Left + margin.Right;
pdp.row_numbers = p.orders.Select((o, i) => format_text((i+1).ToString())).ToList();
pdp.numbers_width = _number_width = Math.Max(_number_width, (int)pdp.row_numbers.Max(r => r.Width) + text_margin);
pdp.row_labels = p.orders.Select(o => format_text(o.Size.ToString())).ToList();
var max_label_width = pdp.row_labels.Max(l => l.Width) + text_margin;
var available_width = pdp.width - pdp.numbers_width - max_label_width;
_max_order_size = Math.Max(p.orders.Max(o => o.Size), _max_order_size);
pdp.scale = available_width / _max_order_size;
pdp.height = _height =
Math.Max(_height,
pdp.header.Height + margin.Top + margin.Bottom + (pdp.row_height * p.orders.Count) + margin.Bottom);
return pdp;
}
#region static
public static readonly MBOSecurityQueryImmutable default_query = (MBOSecurityQueryImmutable)
new MBOSecurityQuery {
Limit = Configuration.get<bts.config.LadderKnobs>().max_mbo_orders
};
#endregion
}
}
@@ -0,0 +1,86 @@
using System.Windows;
using System.Windows.Media;
using System.Collections.Generic;
using bts.api.orderman;
using System.Linq;
using bts.md;
using bts;
using mbts.ui;
using System.Windows.Input;
using System.Threading.Tasks;
using System;
using mbts.ui.utility.text;
using bts.api;
using System.Windows.Threading;
namespace mbts.spark {
internal class MBOPopupView : UIElement, IRequestViewClose {
readonly Brush _background = BTSColors.LadderSpecial.default_column_background;
readonly MBOPopupModel _model;
MBOPopupDrawParams _dp;
public MBOPopupView(MBOPopupModel model, MBOPopupData p) {
_model = model;
_dp = model.compute_layout(p);
IsVisibleChanged += (o, _) => poll_orders();
}
private async void poll_orders() {
while (IsVisible) {
var result = await _model.load_orders();
if (result.success && result.value.orders.Count > 0) {
Log.Ui.diag($"MBOpopup got {result.value.orders.Count} orders");
_dp = _model.compute_layout(result.value);
InvalidateMeasure();
InvalidateVisual();
}
else RequestClose?.Invoke(this, null);
await Task.Delay(250);
}
}
public event EventHandler RequestClose;
protected override Size MeasureCore(Size availableSize) {
return new Size(_dp.width, _dp.height);
}
static Thickness margin => MBOPopupDrawParams.margin;
protected override void OnRender(DrawingContext dc) {
base.OnRender(dc);
// draw background
dc.DrawRectangle(_background, null, new Rect(0, 0, _dp.width, _dp.height));
// draw header
dc.DrawText(_dp.header, new Point(margin.Left, margin.Top));
// draw grid
draw_grid(dc, _dp, _dp.header.Height + margin.Top + margin.Bottom);
}
static Brush brush_for(Side side, OrderPositionImmutable order) {
if (order.OrderId != 0) return Brushes.Black;
switch (side) {
case Side.Buy: return BTSColors.MarketData.bid_brush;
case Side.Sell: return BTSColors.MarketData.ask_brush;
default:
return Brushes.DarkGray;
}
}
static void draw_grid(DrawingContext dc, MBOPopupDrawParams dp, double offset) {
for (int x = 0; x < dp.data.orders.Count(); ++x) {
// draw numbers
var number = dp.row_numbers[x];
var left_offset = dp.numbers_width - number.Width - 5;
dc.DrawText(number, new Point(left_offset, offset));
// draw rectangles
var order = dp.data.orders[x];
var rect = new Rect(dp.numbers_width, offset + (dp.row_height-dp.bar_height)/2, order.Size * dp.scale, dp.bar_height);
dc.DrawRectangle(brush_for(dp.data.side, order), null,rect);
// draw sizes
var label = dp.row_labels[x];
dc.DrawText(label, new Point(rect.Right+margin.Right, offset));
offset += dp.row_height;
}
}
}
}
@@ -0,0 +1,19 @@
using bts.api;
using bts.md;
namespace mbts.spark {
static class PositionDataExtensions {
public static Side side(this PositionData pd) {
var bidlen = pd.Bid.Count;
var asklen = pd.Ask.Count;
if (bidlen > 0 && asklen == 0) {
return Side.Buy;
}
else if (asklen > 0 && bidlen == 0) {
return Side.Sell;
}
return Side.Undefined;
}
}
}
@@ -2,7 +2,15 @@
using System.Collections.Generic;
namespace mbts.spark {
static class SecurityDataHelper {
static class SecurityDataHelper {
// Ice
public static string market_description(this SecurityImmutable security) => security.HasIce ? security.Ice.MarketDescription : string.Empty;
public static string product_name(this SecurityImmutable security) => security.HasIce ? security.Ice.ProductName : string.Empty;
public static string cleared_alias(this SecurityImmutable security) => security.HasIce ? security.Ice.ClearedAlias : string.Empty;
public static string hub_alias(this SecurityImmutable security) => security.HasIce ? security.Ice.HubAlias : string.Empty;
public static string strip_name(this SecurityImmutable security) => security.HasIce ? security.Ice.StripName : string.Empty;
public static string mic_code(this SecurityImmutable security) => security.HasIce ? security.Ice.MicCode : string.Empty;
public static class EqualityComparer {
public static readonly IEqualityComparer<SecurityMD> MDComparer = bts.utils.EqualityComparer.Create<SecurityMD>(Equals, GetHashCode);
public static bool Equals(SecurityMD x, SecurityMD y) => x.security.Id == y.security.Id;
@@ -0,0 +1,45 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace mbts.ui.controls {
class AutoClosePopup : System.Windows.Controls.Primitives.Popup {
public AutoClosePopup() {
StaysOpen = false;
CommandBindings.Add(new CommandBinding(
ApplicationCommands.Close,
(o,_) => { IsOpen = false; }
));
InputBindings.Add(new KeyBinding(ApplicationCommands.Close, Key.Escape, ModifierKeys.None));
}
public new FrameworkElement Child {
get => base.Child as FrameworkElement;
set {
var old_child = Child;
base.Child = value;
hook_child_events(value);
}
}
private void hook_child_events(FrameworkElement fe) {
MouseDown += OnMouseDown;
fe.MouseDown += OnMouseDown;
fe.PreviewMouseDown += OnMouseDown;
if (fe is IRequestViewClose vc) {
vc.RequestClose += (o,_) => IsOpen = false;
}
}
private void OnMouseDown(object sender, MouseButtonEventArgs e) {
if (sender == this && e.MiddleButton == MouseButtonState.Pressed) IsOpen = false;
else {
if (!e.Handled) {
IsOpen = false;
}
}
}
}
}
@@ -67,6 +67,10 @@ namespace mbts.co2
[DllImport("libspark")]
internal unsafe static extern void md_consumer_get_system_md_symbols(raw_usersymbol_callback cb);
[DllImport("libspark")]
internal unsafe static extern void dump_co2_memory_to_disk(IntPtr consumer, string path);
//
// Relay control
+9 -3
View File
@@ -37,7 +37,10 @@ namespace mbts.co2
unsafe void init() {
_raw_sec_cb = (key, buf, len, handle) => {
var security = Co2NativeMethods.deserialize<Security>(buf, len);
var next = new SecurityAndHandle() { security = security, handle = new MDConsumerHandle(handle) };
var next = new SecurityAndHandle() {
security = security,
handle = new MDConsumerHandle(_consumer, security, handle)
};
_securities.Add(next);
_on_security?.Invoke(new List<SecurityAndHandle>(1) { next });
};
@@ -67,7 +70,7 @@ namespace mbts.co2
public MDConsumerHandle try_get_handle(security_key key) {
Co2NativeMethods.BlitPointerTuple raw;
if (Co2NativeMethods.md_consumer_get_handle(_consumer, key, out raw)) {
return new MDConsumerHandle(raw);
return new MDConsumerHandle(this._consumer, null, raw);
}
return null;
}
@@ -77,7 +80,10 @@ namespace mbts.co2
List<SecurityAndHandle> result = new List<SecurityAndHandle>();
Co2NativeMethods.raw_security_callback cb = (key, buf, len, handle) => {
var security = Co2NativeMethods.deserialize<Security>(buf, len);
result.Add(new SecurityAndHandle() { security = new Security(security), handle = new MDConsumerHandle(handle) });
result.Add(
new SecurityAndHandle() {
security = new Security(security),
handle = new MDConsumerHandle(_consumer, security, handle) });
};
Co2NativeMethods.md_consumer_walk_security_list(_consumer, cb);
return result;
+11 -2
View File
@@ -156,13 +156,22 @@ namespace mbts.co2 {
//
// Internal
internal MDConsumerHandle(Co2NativeMethods.BlitPointerTuple tuple) {
internal MDConsumerHandle(
IntPtr consumer,
Security security,
Co2NativeMethods.BlitPointerTuple tuple) {
_header = (ColdHeader*)tuple.cold_header.ToPointer();
_cold = (ColdEntry*)tuple.cold_data.ToPointer();
if (has_depth) {
_hot_depth = (HotDepthEntry*)tuple.hot.ToPointer();
Log.assert(_hot_depth->key == _cold->security_key);
if (_hot_depth->key != _cold->security_key) {
var temp_path = Path.Combine(Path.GetTempPath(), "btsshm");
Co2NativeMethods.dump_co2_memory_to_disk(consumer, temp_path);
Log.assert(
_hot_depth->key == _cold->security_key,
$"Please attach the contents of '${temp_path}' to the bug report you are about to file. security:{security?.Id} cold:{_cold->security_key} hot:{_hot->key}");
}
}
else {
_hot = (HotEntry*)tuple.hot.ToPointer();
@@ -67,8 +67,8 @@ namespace bts.api.core {
while (true) {
var response = await check_stream_access(method_id, qualifier);
if (response.success) {
if (response.value.Authorized) {
// success. Break loop and subscribe
if (response.value.Authorized) {
// success. Break loop and subscribe
break;
}
else {
@@ -94,7 +94,8 @@ namespace bts.api.core {
// Authorized. Stream events
unsubscribe_temp_sub();
return SubscribeNative(method, qualifier, parser, converter, observer);
});
});
Log.QBus.diag($"Subscribing for sub: '{method}/{qualifier}'");
return result;
}
private Task<Result<StreamAccessResponse>> check_stream_access(int method_id, string qualifier) {
@@ -145,6 +146,7 @@ namespace bts.api.core {
};
var subscription = ApiNativeMethods.api_client_subscribe(_client, method, qualifier, cb, IntPtr.Zero);
return FinalizeableAnonymousDisposable.Create((disposing) => {
Log.QBus.diag($"Unsubscribing for sub: '{method}/{qualifier}'");
ApiNativeMethods.api_client_unsubscribe(_client, subscription);
subscription = IntPtr.Zero;
GC.KeepAlive(cb);
+13
View File
@@ -206,6 +206,8 @@ namespace bts.api.core {
}
public class TableUpdate<T, TKey> where TKey : IEquatable<TKey> {
// warning: this does not have a key_extractor
public static readonly TableUpdate<T, TKey> empty = new TableUpdate<T, TKey>(ImmutableDictionary<TKey, T>.Empty, Array.Empty<T>(), Array.Empty<Change<T>>(), Array.Empty<T>(), null);
readonly ImmutableDictionary<TKey, T> _state;
@@ -254,6 +256,7 @@ namespace bts.api.core {
if (new_state.TryGetValue(kv.Key, out T new_val)) {
if (!value_comparer.Equals(kv.Value, new_val)) {
changes.Add(new Change<T>(kv.Value, new_val));
builder[kv.Key] = new_val;
}
} else {
removed.Add(kv.Value);
@@ -427,6 +430,16 @@ namespace bts.api.core {
}
public static class ObservableUpdateExtensions {
public static IEnumerable<T> Old<T>(this IEnumerable<Change<T>> changes) {
foreach (var ch in changes)
yield return ch.Old;
}
public static IEnumerable<T> New<T>(this IEnumerable<Change<T>> changes) {
foreach (var ch in changes)
yield return ch.New;
}
internal static bool has_changes<T, TKey>(this TableUpdate<T, TKey> table) where TKey : IEquatable<TKey> =>
table.added.Any() || table.changes.Any() || table.removed.Any();
+4
View File
@@ -53,6 +53,10 @@ namespace bts.utils {
for (int i = 1; i < num2; i++) if ((num1 * i) % num2 == 0) return i * num1;
return num1 * num2;
}
public static bool is_within_range(this double value, double min, double max) =>
value >= min && value <= max;
public static int within_range(int min, int max, int value) {
if (value < min) return min;
if (value > max) return max;
@@ -27,11 +27,11 @@ namespace System.Collections.Generic {
public static IEnumerable<T> Intersect<T, TKey>(this IEnumerable<T> enumerable, IEnumerable<TKey> second, Func<T, TKey> key_selector) =>
enumerable.Where(t => second.Contains(key_selector(t)));
public static T[] ToLArray<T>(this IEnumerable<T> enumerable) =>
enumerable.ToList().ToArray();
public static IEnumerable<T> OfTypeSafe<T>(this IEnumerable source) =>
source == null ? Enumerable.Empty<T>() : source.OfType<T>();
public static TAccumulate AggregateWhile<T, TAccumulate>(this IEnumerable<T> source, TAccumulate seed, Func<TAccumulate, T, TAccumulate> aggregate, Func<TAccumulate, T, bool> predicate) =>
AggregateWhile(source, seed, aggregate, predicate, (curr, el, next) => curr);
AggregateWhile(source, seed, aggregate, predicate, (curr, el, next) => curr);
public static TAccumulate AggregateWhile<T, TAccumulate>(this IEnumerable<T> source, TAccumulate seed, Func<TAccumulate, T, TAccumulate> aggregate, Func<TAccumulate, T, bool> predicate, TAccumulate early_break_rv) =>
AggregateWhile(source, seed, aggregate, predicate, (curr, el, next) => early_break_rv);
@@ -75,6 +75,7 @@ namespace bts.collections {
yield return v;
}
}
// update
public void update(IEnumerable<T> added, IEnumerable<T> changed, IEnumerable<TKey> removed) {
foreach (var k in removed) {
@@ -17,7 +17,7 @@ namespace bts.test {
}
[AttributeUsage(AttributeTargets.Method)]
public class TestAttribute : Attribute {
public uint Timeout = 10;
public uint Timeout = 10; //timeout in seconds
}
// Use constructor for setup
+124 -3
View File
@@ -2,10 +2,121 @@
using bts.collections;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
namespace bts.utils {
public static class UpdateExtensions {
public static IObservable<TableUpdate<T, TKey>> RootDependent<T, TKey>(
this IObservable<TableUpdate<T, TKey>> source,
Func<T, TKey> key,
Func<T, TKey> parent_key,
Func<T, T, T> with_root)
where TKey : IEquatable<TKey> =>
RootDependent(source, key, parent_key, with_root, v => v, EqualityComparer<T>.Default, default).SelectKeyValue(kv => kv.Value, key);
// TODO: unit tests
public static IObservable<TableUpdate<KeyValuePair<TKey, TResult>, TKey>> RootDependent<T, TKey, TResult>(
this IObservable<TableUpdate<T, TKey>> source,
Func<T, TKey> key,
Func<T, TKey> parent_key,
Func<T, T, TResult> get_result,
Func<T, TResult> get_result_no_parent,
IEqualityComparer<TResult> result_comparer,
TKey no_parent_key)
where TKey : IEquatable<TKey> =>
Observable.Create<TableUpdate<KeyValuePair<TKey, TResult>, TKey>>(observer => {
var value_comparer = EqualityComparer.Create<KeyValuePair<TKey, TResult>, TResult>(kv => kv.Value, result_comparer);
var state = DictionaryHelper.empty<TKey, T>();
var output = TableUpdate<KeyValuePair<TKey, TResult>, TKey>.mk_empty(kv => kv.Key);
var parent_to_children = new SetMap<TKey, T>();
return source.Subscribe(update);
//
void update(TableUpdate<T, TKey> input) {
state = input.state;
var builder = output.to_builder();
// execute all parent_to_children changes first, so descendent-propagation will be efficient (otherwise propagation might be needlessly performed on values that are yet to be removed, or performed with the wrong root, if the real root is yet to be added).
foreach (var v in input.removed.Concat(input.changes.Select(ch => ch.Old))) {
var k = input.key_extractor(v);
parent_to_children.remove(parent_key(v), v);
}
foreach (var v in input.added.Concat(input.changed)) {
var pk = parent_key(v);
if (!pk.Equals(no_parent_key))
parent_to_children.add(pk, v);
}
foreach (var v in input.removed) {
var k = input.key_extractor(v);
builder.remove(k);
if (parent_to_children.try_lookup(k, out ISet<T> children)) {
foreach (var child in children) {
var ck = key(child);
builder.add_or_update(new KeyValuePair<TKey, TResult>(ck, get_result_no_parent(child)));
foreach (var d in descendents(ck))
builder.add_or_update(new KeyValuePair<TKey, TResult>(key(d), get_result(d, child)));
}
}
}
foreach (var v in input.changed) {
var k = input.key_extractor(v);
var root = get_root(v);
TResult result;
if (key(root).Equals(k)) {
result = get_result_no_parent(v);
foreach (var d in descendents(k))
builder.add_or_update(new KeyValuePair<TKey, TResult>(key(d), get_result(d, v)));
} else {
result = get_result(v, root);
}
builder.add_or_update(new KeyValuePair<TKey, TResult>(k, result));
}
foreach (var v in input.added) {
var k = input.key_extractor(v);
var pk = parent_key(v);
TResult result;
if (pk.Equals(no_parent_key)) {
result = get_result_no_parent(v);
foreach (var d in descendents(k))
builder.add_or_update(new KeyValuePair<TKey, TResult>(key(d), get_result(d, v)));
} else {
parent_to_children.add(pk, v);
var root = get_root(v);
if (key(root).Equals(k)) {
result = get_result_no_parent(v);
foreach (var d in descendents(k))
builder.add_or_update(new KeyValuePair<TKey, TResult>(key(d), get_result(d, v)));
} else {
result = get_result(v, root);
}
}
builder.add_or_update(new KeyValuePair<TKey, TResult>(k, result));
}
var new_output = builder.to_table_checked(value_comparer);
if (new_output.has_changes())
observer.OnNext(output = new_output);
}
T get_root(T value) {
var pk = parent_key(value);
while (!pk.Equals(no_parent_key) && state.TryGetValue(pk, out var parent)) {
value = parent;
pk = parent_key(value);
}
return value;
}
IEnumerable<T> descendents(TKey k) {
if (parent_to_children.try_lookup(k, out ISet<T> children)) {
foreach (var child in children) {
yield return child;
foreach (var d in descendents(key(child)))
yield return d;
}
}
}
});
public static IObservable<TableUpdate<T, TKey>> MergeTable<T, TKey>(this IObservable<TableUpdate<T, TKey>> source, IObservable<TableUpdate<T, TKey>> other) where TKey : IEquatable<TKey> =>
source.AggregateLatest(other, _Merge<T, TKey>.source_change_del, _Merge<T, TKey>.other_change_del, () => TableUpdate<T, TKey>.empty, TableUpdate<T, TKey>.empty, TableUpdate<T, TKey>.empty);
public static IObservable<TableUpdate<KeyValuePair<TKey, TResult>, TKey>> ParentDependent<T, TKey, TResult>(
this IObservable<TableUpdate<T, TKey>> source,
Func<T, TKey> key,
@@ -24,7 +135,6 @@ namespace bts.utils {
Func<T, TResult> get_result_no_parent,
IEqualityComparer<TResult> result_comparer,
TKey no_parent_key)
where TResult : IEquatable<TResult>
where TKey : IEquatable<TKey> =>
Observable.Create<TableUpdate<KeyValuePair<TKey, TResult>, TKey>>(observer => {
var value_comparer = EqualityComparer.Create<KeyValuePair<TKey, TResult>, TResult>(kv => kv.Value, result_comparer);
@@ -65,12 +175,23 @@ namespace bts.utils {
}
var result = builder.to_table_checked(value_comparer);
if (result.has_changes())
observer.OnNext(output = builder.to_table());
observer.OnNext(output = result);
}
TResult lookup_result(T child) {
var pk = parent_key(child);
return !pk.Equals(no_parent_key) && state.TryGetValue(pk, out T parent) ? get_result(child, parent) : get_result_no_parent(child);
}
});
static class _Merge<T, TKey> where TKey : IEquatable<TKey> {
public static readonly Func<TableUpdate<T, TKey>, Change<TableUpdate<T, TKey>>, TableUpdate<T, TKey>, TableUpdate<T, TKey>> source_change_del = _source_changed_impl;
public static readonly Func<TableUpdate<T, TKey>, TableUpdate<T, TKey>, Change<TableUpdate<T, TKey>>, TableUpdate<T, TKey>> other_change_del = _other_changed_impl;
static TableUpdate<T, TKey> _source_changed_impl(TableUpdate<T, TKey> target, Change<TableUpdate<T, TKey>> source_change, TableUpdate<T, TKey> other_table) =>
TableUpdate<T, TKey>.update(target, source_change.New.added, source_change.New.changes, source_change.New.removed, source_change.New.key_extractor);
static TableUpdate<T, TKey> _other_changed_impl(TableUpdate<T, TKey> target, TableUpdate<T, TKey> source_table, Change<TableUpdate<T, TKey>> other_change) =>
TableUpdate<T, TKey>.update(target, other_change.New.added, other_change.New.changes, other_change.New.removed, other_change.New.key_extractor);
}
}
}
}
+1 -1
View File
@@ -61,7 +61,7 @@
<None Include="$(OutputPath)\net471\spark_excel_example_2018.xlsm" pack="true" PackagePath=".\excel\spark_excel_example_2018.xlsm" />
<None Include="$(OutputPath)\net471\spark_excel-addin64-packed.xll" pack="true" PackagePath=".\excel\spark_excel-addin64-packed.xll" />
<None Include="$(OutputPath)\net471\spark_excel-addin64.dna" pack="true" PackagePath=".\excel\spark_excel-addin64.dna" />
<None Include="../../../doc/user/content/bts_spark_sdk/Client/**" pack="true" PackagePath=".\doc\" />
<None Include="doc/**" pack="true" PackagePath=".\doc\" />
</ItemGroup>
<Target Name="CopyProjectReferencesToPackage" DependsOnTargets="ResolveReferences" Condition= " '$(SparkRoot)' != '' ">
@@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks;
using bts.api;
using bts.api.admin;
using bts.api.core;
using bts.api.test;
using bts.test;
using bts.api.orderman;
using mbts;
using mbts.tests;
using bts.co2;
using bts.api.orderman.algo;
namespace bts.tests {
[TestGroup(TestType = TestType.System)]
public class AlgoDefinitionTests : IDisposable {
List<IDisposable> _resources;
ServiceContext _context;
//OrderService _order_service;
AdminService _admin_service;
AlgoDefinitionService _algo_definition_service;
IEnumerable<AlgoDefinitionRecordImmutable> _non_deleted_defs;
Random _rng = new Random();
public AlgoDefinitionTests() {
_context = new ServiceContext();
//_order_service = _context.locate<OrderService>();
_admin_service = _context.locate<AdminService>();
_algo_definition_service = _context.locate<AlgoDefinitionService>();
_resources = new List<IDisposable> {
_context,
_admin_service,
_algo_definition_service,
_algo_definition_service.subscribei_algo_definitions().Subscribe(defs => _non_deleted_defs = defs.all.Where( x => x.Metadata.IsActive))
};
var auth = _context.login("bts", "");
auth.Wait(3000);
}
[Test(Timeout = 25)]
public async Task test_crud_algo_definitions() {
var created_algo_records = new List<AlgoDefinitionRecord>();
var watch = new System.Diagnostics.Stopwatch();
watch.Start();
var user_data = await AdminUtilities.get_user_data(_admin_service, "bts");
int num_created = 0;
if (user_data.HasValue) {
// create 100 algo defs
while (num_created < 100) {
var result = await _algo_definition_service.save_algo_definition_async(
new AlgoDefinitionUpdateRequest {
UserId = user_data.Value.Id,
Key = 0,
Algo = generate_random_algo_definition()
}
);
Assert.IsTrue(result.value.Success, "There was a problem creating an algo defintion");
created_algo_records.Add(result.value.Record);
num_created++;
}
// update the 100 we just created algo defs
foreach(var r in created_algo_records) {
var def = r.Algo;
def.Name += " modified";
await _algo_definition_service.save_algo_definition_async(
new AlgoDefinitionUpdateRequest {
UserId = user_data.Value.Id,
Key = r.Key,
Algo = def
}
);
}
// delete all non-deleted algo defs
Assert.IsTrue(_non_deleted_defs.Count() > 0, "There are no algo definitions to delete.");
foreach (var i in _non_deleted_defs.Select(x => x.Key).ToList()) {
var result = await _algo_definition_service.remove_algo_definition_async(new AlgoDefinitionRemoveRequest { Key = i });
Assert.IsTrue(result.value.Success, "There was a problem removing an algo definition");
}
Assert.IsTrue(_non_deleted_defs.Count() == 0, "There was a problem removing all algo definitions");
watch.Stop();
Console.WriteLine($"Execution Time: {watch.ElapsedMilliseconds} ms");
}
}
// not quite random yet
private AlgoDefinition generate_random_algo_definition() {
return new AlgoDefinition {
Definition = new CustomOrderDefinition {
Iceberg = new IcebergOrderDefinition {
ShowQuantity = _rng.NextDouble() * 100,
Child = new CustomOrderDefinition {
Limit = new LimitOrderDefinition { }
}
},
},
Name = $"Test Algo Def: {generate_name(_rng, _rng.Next(4, 15))}"
};
}
// https://stackoverflow.com/questions/14687658/random-name-generator-in-c-sharp
public static string generate_name(Random r, int len) {
string[] consonants = { "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "l", "n", "p", "q", "r", "s", "sh", "zh", "t", "v", "w", "x" };
string[] vowels = { "a", "e", "i", "o", "u", "ae", "y" };
string name = "";
name += consonants[r.Next(consonants.Length)].ToUpper();
name += vowels[r.Next(vowels.Length)];
int b = 2; //b tells how many times a new letter has been added. It's 2 right now because the first two letters are already in the name.
while (b < len) {
name += consonants[r.Next(consonants.Length)];
b++;
name += vowels[r.Next(vowels.Length)];
b++;
}
return name;
}
public void Dispose() {
Disposable.dispose(_resources);
}
}
}
@@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks;
using bts.api;
using bts.api.admin;
using bts.api.core;
using bts.test;
using bts.api.orderman;
using mbts;
using bts.co2;
using bts.tests.utils;
namespace bts.tests {
[TestGroup(TestType = TestType.System)]
class IcebergAlgoTests : TestBase {
public IcebergAlgoTests() { }
[Test(Timeout = 25)]
public async Task test_ib_create_and_takeout_market() {
// find a security with no market
var s = await MarketDataUtilities.get_security(_md_relay, "CL", SecurityType.Future, update => update.Direct.Asks.Count == 0 && update.Direct.Bids.Count == 0);
Assert.IsTrue(s.HasValue, "Could not find a security with no maket");
// create a market in the instrument
List<Result<Response>> orders_response_list = new List<Result<Response>>();
var bid_order_def = OrderDefinitionUtilities.create_limit_order_def(s.Value.Id, Side.Buy, _test_account, _test_user);
var ask_order_def = OrderDefinitionUtilities.create_limit_order_def(s.Value.Id, Side.Sell, _test_account, _test_user);
orders_response_list.Add(await OrderUtilities.send_order(_order_service, bid_order_def, 55.00, 50));
orders_response_list.Add(await OrderUtilities.send_order(_order_service, ask_order_def, 55.05, 50));
List<OrderRecordImmutable> test_orders = new List<OrderRecordImmutable>();
foreach (var i in orders_response_list) {
var iret = (await OrderUtilities.wait_for_filtered_orders(
_order_service,
rows => rows.Where(o => o.Key == i.value.OrderKey && o.HasState && o.State.Status == OrderStatus.Working).Count() == 1,
rows => rows.Where(o => o.Key == i.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
Assert.IsTrue(iret.Count() == 1, "This order should have been 'working'");
test_orders.AddRange(iret);
}
// create an IB that will cross this market
var ib_order_def = OrderDefinitionUtilities.create_iceberg_order_def(s.Value.Id, Side.Buy, _test_account, _test_user, 3);
var ib_order_result = await OrderUtilities.send_order(_order_service, ib_order_def, 55.05, 30);
var ib_filtered_order = (await OrderUtilities.wait_for_filtered_orders(
_order_service,
rows => rows.Where(x => x.Key == ib_order_result.value.OrderKey && x.HasState && x.State.Status == OrderStatus.Completed).Count() == 1,
rows => rows.Where(x => x.Key == ib_order_result.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
var ib_child_orders = (await OrderUtilities.wait_for_filtered_orders(
_order_service,
rows => rows.Where(x => x.ParentKey == ib_order_result.value.OrderKey && x.HasState && x.State.Status == OrderStatus.Completed).Count() == 10,
rows => rows.Where(x => x.ParentKey == ib_order_result.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
Assert.IsTrue(ib_filtered_order.Count() == 1, "This IB order should have been 'Completed'");
Assert.IsTrue(ib_child_orders.Count() == 10, "There should have been 10 completed child orders");
var filled_childen = (await OrderUtilities.wait_for_filtered_fills(
_order_service,
rows => rows.Where(x => ib_child_orders.Select(o => o.Key).Contains(x.OrderKey)).Count() == 10,
rows => rows.Where(x => ib_child_orders.Select(o => o.Key).Contains(x.OrderKey)))).result ?? Enumerable.Empty<FillImmutable>();
Assert.IsTrue(filled_childen.Count() == 10, "There should have been 10 fills");
Assert.IsTrue(filled_childen.All(x =>
TestUtils.test_price_equality(x.LastPrice, 55.05, s.Value.ContractSpec.ticksize(x.LastPrice)) && x.LastShares == 3), "All child fills should have been filled at price 55.50 with quantity 3");
// cleanup
Assert.IsTrue(await OrderUtilities.cancel_orders(_order_service, test_orders), "Unable to cancel test orders");
}
[Test(Timeout = 25)]
public async Task test_ib_avg_fill() {
// find a security with no market
var s = await MarketDataUtilities.get_security(_md_relay, "CL", SecurityType.Future, update => update.Direct.Asks.Count == 0 && update.Direct.Bids.Count == 0);
Assert.IsTrue(s.HasValue, "Could not find a security with no maket");
// create a market in the instrument
List<Result<Response>> orders_response_list = new List<Result<Response>>();
var bid_order_def = OrderDefinitionUtilities.create_limit_order_def(s.Value.Id, Side.Buy, _test_account, _test_user);
var ask_order_def = OrderDefinitionUtilities.create_limit_order_def(s.Value.Id, Side.Sell, _test_account, _test_user);
orders_response_list.Add(await OrderUtilities.send_order(_order_service, ask_order_def, 56.02, 1));
orders_response_list.Add(await OrderUtilities.send_order(_order_service, ask_order_def, 56.01, 1));
orders_response_list.Add(await OrderUtilities.send_order(_order_service, ask_order_def, 56.00, 1));
orders_response_list.Add(await OrderUtilities.send_order(_order_service, ask_order_def, 55.50, 5));
orders_response_list.Add(await OrderUtilities.send_order(_order_service, ask_order_def, 55.01, 20));
orders_response_list.Add(await OrderUtilities.send_order(_order_service, bid_order_def, 55.00, 1));
orders_response_list.Add(await OrderUtilities.send_order(_order_service, bid_order_def, 54.99, 1));
orders_response_list.Add(await OrderUtilities.send_order(_order_service, bid_order_def, 54.98, 1));
orders_response_list.Add(await OrderUtilities.send_order(_order_service, bid_order_def, 54.97, 1));
orders_response_list.Add(await OrderUtilities.send_order(_order_service, bid_order_def, 54.96, 1));
orders_response_list.Add(await OrderUtilities.send_order(_order_service, bid_order_def, 54.95, 1));
List<OrderRecordImmutable> test_orders = new List<OrderRecordImmutable>();
foreach (var i in orders_response_list) {
var iret = (await OrderUtilities.wait_for_filtered_orders(
_order_service,
rows => rows.Where(o => o.Key == i.value.OrderKey && o.HasState && o.State.Status == OrderStatus.Working).Count() == 1,
rows => rows.Where(o => o.Key == i.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
Assert.IsTrue(iret.Count() == 1, "This order should have been 'working'");
test_orders.AddRange(iret);
}
// create an IB that will cross this market
var ib_order_def = OrderDefinitionUtilities.create_iceberg_order_def(s.Value.Id, Side.Buy, _test_account, _test_user, 1);
var ib_order_result = await OrderUtilities.send_order(_order_service, ib_order_def, 56.02, 26);
var ib_filtered_order = (await OrderUtilities.wait_for_filtered_orders(
_order_service,
rows => rows.Where(x => x.Key == ib_order_result.value.OrderKey && x.HasState && x.State.Status == OrderStatus.Completed).Count() == 1,
rows => rows.Where(x => x.Key == ib_order_result.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
var ib_child_orders = (await OrderUtilities.wait_for_filtered_orders(
_order_service,
rows => rows.Where(x => x.ParentKey == ib_order_result.value.OrderKey && x.HasState && x.State.Status == OrderStatus.Completed).Count() == 10,
rows => rows.Where(x => x.ParentKey == ib_order_result.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
Assert.IsTrue(ib_filtered_order.Count() == 1, "This IB order should have been 'Completed'");
Assert.IsTrue(ib_filtered_order.All(x =>
TestUtils.test_price_equality(x.State.AvgPrice, 55.1423, s.Value.ContractSpec.ticksize(x.State.AvgPrice))), "IB avg price should be 55.1423");
// cleanup
Assert.IsTrue(await OrderUtilities.cancel_orders(_order_service, test_orders), "Unable to cancel test orders");
}
}
}
@@ -0,0 +1,194 @@
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.test;
using bts.tests.utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks;
namespace bts.tests {
/// <summary>
/// For buy orders:
/// triggers when: curr_bid.price > initial_bid && current_bid.qty >= join_threshold.
/// i.e. When the join is first entered, it looks at the market to capture the initial bid and
/// then it waits until the current bid is above that level AND that the qty at that level is greater
/// that some threshold.
/// </summary>
[TestGroup(TestType = TestType.System)]
public class JoinAlgoTests : TestBase {
public JoinAlgoTests() { }
[Test(Timeout = 25)]
public async Task test_join_price() {
double initial_price = 54.99;
double ask_price = 55.01;
// find a security with no market
var s = await MarketDataUtilities.get_security(_md_relay, "CL", SecurityType.Future, update => update.Direct.Asks.Count == 0 && update.Direct.Bids.Count == 0);
Assert.IsTrue(s.HasValue, "Could not find a security with no maket");
// create a market in the instrument
List<Result<Response>> orders_response_list = new List<Result<Response>>();
var bid_order_def = OrderDefinitionUtilities.create_limit_order_def(s.Value.Id, Side.Buy, _test_account, _test_user);
var ask_order_def = OrderDefinitionUtilities.create_limit_order_def(s.Value.Id, Side.Sell, _test_account, _test_user);
orders_response_list.Add(await OrderUtilities.send_order(_order_service, bid_order_def, 55.00, 10));
orders_response_list.Add(await OrderUtilities.send_order(_order_service, ask_order_def, ask_price, 1));
List<OrderRecordImmutable> test_orders = new List<OrderRecordImmutable>();
foreach (var i in orders_response_list) {
var iret = (await OrderUtilities.wait_for_filtered_orders(
_order_service,
rows => rows.Where(o => o.Key == i.value.OrderKey && o.HasState && o.State.Status == OrderStatus.Working).Count() == 1,
rows => rows.Where(o => o.Key == i.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
Assert.IsTrue(iret.Count() == 1, "This order should have been 'working'");
test_orders.AddRange(iret);
}
// create a join order
var join_order_def = OrderDefinitionUtilities.create_join_order_definition(s.Value.Id, Side.Buy, _test_account, _test_user, new spark.QuantityMeasure() { Absolute = 1 });
var join_order_response = await OrderUtilities.send_order(_order_service, join_order_def, initial_price, 1);
var join_order = (await OrderUtilities.wait_for_filtered_orders(
_order_service,
rows => rows.Where(o => o.Key == join_order_response.value.OrderKey && o.HasState && o.State.Status == OrderStatus.Working).Count() == 1,
rows => rows.Where(o => o.Key == join_order_response.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
Assert.IsTrue(join_order.Count() == 1, "The join order should have 'working'");
test_orders.Add(join_order.First());
// verify the join order's child is working at the specefied price
// note: the price doesn't actually matter in the join order
var join_child_order = (await OrderUtilities.wait_for_filtered_orders(
_order_service,
rows => rows.Where(o => o.ParentKey == join_order.First().Key &&
o.HasState && (o.State.Status == OrderStatus.Working &&
TestUtils.test_price_equality(o.State.Price, initial_price, s.Value.ContractSpec.ticksize(o.State.Price)))).Count() == 1,
rows => rows.Where(o => o.ParentKey == join_order.First().Key))).result ?? Enumerable.Empty<OrderRecordImmutable>();
Assert.IsTrue(join_child_order.Count() == 1, $"The join child order should have been 'working' at {initial_price}");
// now we move the best bid to become the best offer
var original_bid_order = test_orders.Where(o => o.Def.Side == Side.Buy).First();
var bid_order_response = await OrderUtilities.modify_order(_order_service, original_bid_order, ask_price);
var modified_bid_order = (await OrderUtilities.wait_for_filtered_orders(
_order_service,
rows => rows.Where(o => o.Key == bid_order_response.value.OrderKey && o.HasState &&
TestUtils.test_price_equality(o.State.Price, ask_price, s.Value.ContractSpec.ticksize(o.State.Price))).Count() == 1,
rows => rows.Where(o => o.Key == bid_order_response.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
Assert.IsTrue(modified_bid_order.Count() == 1, "The working bid should have become the best offer");
// and we finally verify that the child order has joined the market at the appropriate price
join_child_order = (await OrderUtilities.wait_for_filtered_orders(
_order_service,
rows => rows.Where(o => o.ParentKey == join_order.First().Key &&
o.HasState && (o.State.Status == OrderStatus.Working &&
TestUtils.test_price_equality(o.State.Price, ask_price, s.Value.ContractSpec.ticksize(o.State.Price)))).Count() == 1,
rows => rows.Where(o => o.ParentKey == join_order.First().Key))).result ?? Enumerable.Empty<OrderRecordImmutable>();
Assert.IsTrue(join_child_order.Count() == 1, $"The join child order should now have been 'working' at {ask_price}");
test_orders.Add(join_child_order.First());
// cleanup
Assert.IsTrue(await OrderUtilities.cancel_orders(_order_service, test_orders), "Unable to cancel test orders");
}
[Test(Timeout = 25)]
public async Task test_join_qty_threshold() {
double initial_price = 54.99;
double ask_price = 55.01;
uint ask_size = 1;
uint qty_threshold = 15;
// find a security with no market
var s = await MarketDataUtilities.get_security(_md_relay, "CL", SecurityType.Future, update => update.Direct.Asks.Count == 0 && update.Direct.Bids.Count == 0);
Assert.IsTrue(s.HasValue, "Could not find a security with no maket");
// create a market in the instrument
List<Result<Response>> orders_response_list = new List<Result<Response>>();
var bid_order_def = OrderDefinitionUtilities.create_limit_order_def(s.Value.Id, Side.Buy, _test_account, _test_user);
var ask_order_def = OrderDefinitionUtilities.create_limit_order_def(s.Value.Id, Side.Sell, _test_account, _test_user);
orders_response_list.Add(await OrderUtilities.send_order(_order_service, bid_order_def, 55.00, 10));
orders_response_list.Add(await OrderUtilities.send_order(_order_service, ask_order_def, ask_price, ask_size));
List<OrderRecordImmutable> test_orders = new List<OrderRecordImmutable>();
foreach (var i in orders_response_list) {
var iret = (await OrderUtilities.wait_for_filtered_orders(
_order_service,
rows => rows.Where(o => o.Key == i.value.OrderKey && o.HasState && o.State.Status == OrderStatus.Working).Count() == 1,
rows => rows.Where(o => o.Key == i.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
Assert.IsTrue(iret.Count() == 1, "This order should have been 'working'");
test_orders.AddRange(iret);
}
// create a join order
var join_order_def = OrderDefinitionUtilities.create_join_order_definition(s.Value.Id, Side.Buy, _test_account, _test_user, new spark.QuantityMeasure() { Absolute = qty_threshold });
var join_order_response = await OrderUtilities.send_order(_order_service, join_order_def, initial_price, 1);
var join_order = (await OrderUtilities.wait_for_filtered_orders(
_order_service,
rows => rows.Where(o => o.Key == join_order_response.value.OrderKey && o.HasState && o.State.Status == OrderStatus.Working).Count() == 1,
rows => rows.Where(o => o.Key == join_order_response.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
Assert.IsTrue(join_order.Count() == 1, "The join order should have 'working'");
test_orders.Add(join_order.First());
// verify the join order's child is working at the specefied price
// note: the price doesn't actually matter in the join order
var join_child_order = (await OrderUtilities.wait_for_filtered_orders(
_order_service,
rows => rows.Where(o => o.ParentKey == join_order.First().Key &&
o.HasState && (o.State.Status == OrderStatus.Working &&
TestUtils.test_price_equality(o.State.Price, initial_price, s.Value.ContractSpec.ticksize(o.State.Price)))).Count() == 1,
rows => rows.Where(o => o.ParentKey == join_order.First().Key))).result ?? Enumerable.Empty<OrderRecordImmutable>();
Assert.IsTrue(join_child_order.Count() == 1, $"The join child order should have been 'working' at {initial_price}");
// now we move the best bid to become the best offer
var top_bid_order = test_orders.Where(o => o.Def.Side == Side.Buy).First();
var bid_order_response = await OrderUtilities.modify_order(_order_service, top_bid_order, ask_price);
var modified_bid_order = (await OrderUtilities.wait_for_filtered_orders(
_order_service,
rows => rows.Where(o => o.Key == bid_order_response.value.OrderKey && o.HasState &&
TestUtils.test_price_equality(o.State.Price, ask_price, s.Value.ContractSpec.ticksize(o.State.Price))).Count() == 1,
rows => rows.Where(o => o.Key == bid_order_response.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
Assert.IsTrue(modified_bid_order.Count() == 1, "The working bid should have become the best offer");
// verify the join order's child is *still* working at the initial price
// b/c the quantity threshold has not yet been met
join_child_order = (await OrderUtilities.wait_for_filtered_orders(
_order_service,
rows => rows.Where(o => o.ParentKey == join_order.First().Key &&
o.HasState && (o.State.Status == OrderStatus.Working &&
TestUtils.test_price_equality(o.State.Price, initial_price, s.Value.ContractSpec.ticksize(o.State.Price)))).Count() == 1,
rows => rows.Where(o => o.ParentKey == join_order.First().Key))).result ?? Enumerable.Empty<OrderRecordImmutable>();
Assert.IsTrue(join_child_order.Count() == 1, $"The join child order should still have been 'working' at {initial_price}");
// now we modify our top bid's volume to meet the join's threshold
// note that we must add the intial ask_size to the qty_threshold b/c we have partially filled our bid order
// when we moved the bid up.
var target_qty = qty_threshold + ask_size;
bid_order_response = await OrderUtilities.modify_order(_order_service, modified_bid_order.First(), target_qty);
modified_bid_order = (await OrderUtilities.wait_for_filtered_orders(
_order_service,
rows => rows.Where(o => o.Key == bid_order_response.value.OrderKey && o.HasState && o.State.Quantity == target_qty).Count() == 1,
rows => rows.Where(o => o.Key == bid_order_response.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
Assert.IsTrue(modified_bid_order.Count() == 1, $"The working bid should have its quantity modified to {target_qty}");
// now that the qty threshold has been met, verify that the child order has joined the market at the appropriate price
join_child_order = (await OrderUtilities.wait_for_filtered_orders(
_order_service,
rows => rows.Where(o => o.ParentKey == join_order.First().Key &&
o.HasState && (o.State.Status == OrderStatus.Working &&
TestUtils.test_price_equality(o.State.Price, ask_price, s.Value.ContractSpec.ticksize(o.State.Price)))).Count() == 1,
rows => rows.Where(o => o.ParentKey == join_order.First().Key))).result ?? Enumerable.Empty<OrderRecordImmutable>();
Assert.IsTrue(join_child_order.Count() == 1, $"The join child order should now have been 'working' at {ask_price}");
test_orders.Add(join_child_order.First());
// cleanup
Assert.IsTrue(await OrderUtilities.cancel_orders(_order_service, test_orders), "Unable to cancel test orders");
}
}
}
@@ -0,0 +1,144 @@
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.test;
using bts.tests.utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks;
namespace bts.tests {
[TestGroup(TestType = TestType.System)]
public class LeanAlgoTests : TestBase {
public LeanAlgoTests() { }
[Test(Timeout = 25)]
public async Task test_lean_price() {
double initial_price = 54.98;
double ask_price_1 = 55.00;
double ask_price_2 = 55.01;
uint ask_size = 10;
uint lean_order_size = 30;
uint cross_threshold = ask_size - 1;
// find a security with no market
var s = await MarketDataUtilities.get_security(_md_relay, "CL", SecurityType.Future, update => update.Direct.Asks.Count == 0 && update.Direct.Bids.Count == 0);
Assert.IsTrue(s.HasValue, "Could not find a security with no maket");
// create a market in the instrument
List<Result<Response>> orders_response_list = new List<Result<Response>>();
var bid_order_def = OrderDefinitionUtilities.create_limit_order_def(s.Value.Id, Side.Buy, _test_account, _test_user);
var ask_order_def = OrderDefinitionUtilities.create_limit_order_def(s.Value.Id, Side.Sell, _test_account, _test_user);
orders_response_list.Add(await OrderUtilities.send_order(_order_service, bid_order_def, 54.99, 1));
orders_response_list.Add(await OrderUtilities.send_order(_order_service, ask_order_def, ask_price_1, ask_size));
orders_response_list.Add(await OrderUtilities.send_order(_order_service, ask_order_def, ask_price_2, ask_size));
orders_response_list.Add(await OrderUtilities.send_order(_order_service, ask_order_def, ask_price_2 + 0.01, ask_size));
orders_response_list.Add(await OrderUtilities.send_order(_order_service, ask_order_def, ask_price_2 + 0.02, ask_size));
List<OrderRecordImmutable> test_orders = new List<OrderRecordImmutable>();
foreach (var i in orders_response_list) {
var iret = (await OrderUtilities.wait_for_filtered_orders(
_order_service,
rows => rows.Where(o => o.Key == i.value.OrderKey && o.HasState && o.State.Status == OrderStatus.Working).Count() == 1,
rows => rows.Where(o => o.Key == i.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
Assert.IsTrue(iret.Count() == 1, "This order should have been 'working'");
test_orders.AddRange(iret);
}
// create a lean order, 2 tick chase (default)
var lean_order_def = OrderDefinitionUtilities.create_lean_order_definition(s.Value.Id, Side.Buy, _test_account, _test_user, new spark.QuantityMeasure() { Absolute = cross_threshold });
var lean_order_response = await OrderUtilities.send_order(_order_service, lean_order_def, initial_price, lean_order_size);
var lean_order = (await OrderUtilities.wait_for_filtered_orders(
_order_service,
rows => rows.Where(o => o.Key == lean_order_response.value.OrderKey && o.HasState && o.State.Status == OrderStatus.Working).Count() == 1,
rows => rows.Where(o => o.Key == lean_order_response.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
Assert.IsTrue(lean_order.Count() == 1, "The lean order should have 'working'");
test_orders.Add(lean_order.First());
// verify the lean order's child is working at the specified price
// note: the price doesn't actually matter in the lean order
var lean_child_order = (await OrderUtilities.wait_for_filtered_orders(
_order_service,
rows => rows.Where(o => o.ParentKey == lean_order.First().Key &&
o.HasState && (o.State.Status == OrderStatus.Working &&
TestUtils.test_price_equality(o.State.Price, initial_price, s.Value.ContractSpec.ticksize(o.State.Price)))).Count() == 1,
rows => rows.Where(o => o.ParentKey == lean_order.First().Key))).result ?? Enumerable.Empty<OrderRecordImmutable>();
Assert.IsTrue(lean_child_order.Count() == 1, $"The lean child order should have been 'working' at {initial_price}");
// now we modify the opposite inside market qty to breach the lean threshold
var target_ask_order = test_orders.Where(o => o.Def.Side == Side.Sell &&
TestUtils.test_price_equality(o.State.Price, ask_price_1, s.Value.ContractSpec.ticksize(o.State.Price))).First();
var ask_order_response = await OrderUtilities.modify_order(_order_service, target_ask_order, cross_threshold);
var modified_ask_order = (await OrderUtilities.wait_for_filtered_orders(
_order_service,
rows => rows.Where(o => o.Key == ask_order_response.value.OrderKey && o.HasState &&
o.State.Quantity == cross_threshold).Count() == 1,
rows => rows.Where(o => o.Key == ask_order_response.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
Assert.IsTrue(modified_ask_order.Count() == 1, "The opposite inside market should have been adjusted to the Lean's cross threshold");
// verify that the child order has leaned into the market at the appropriate price and been parially filled appropriately
// this is the initial trigger payup
lean_child_order = (await OrderUtilities.wait_for_filtered_orders(
_order_service,
rows => rows.Where(o => o.ParentKey == lean_order.First().Key &&
o.HasState && (
o.State.Status == OrderStatus.Working &&
TestUtils.test_price_equality(o.State.Price, ask_price_1, s.Value.ContractSpec.ticksize(o.State.Price)) &&
o.State.FilledQuantity == 9)
).Count() == 1,
rows => rows.Where(o => o.ParentKey == lean_order.First().Key))).result ?? Enumerable.Empty<OrderRecordImmutable>();
Assert.IsTrue(lean_child_order.Count() == 1, $"Initial trigger: The lean child order should now have been 'working' at {ask_price_1}");
test_orders.Add(lean_child_order.First());
// now we modify the opposite inside market qty again to start checking that the case is working
target_ask_order = test_orders.Where(o => o.Def.Side == Side.Sell &&
TestUtils.test_price_equality(o.State.Price, ask_price_2, s.Value.ContractSpec.ticksize(o.State.Price))).First();
ask_order_response = await OrderUtilities.modify_order(_order_service, target_ask_order, cross_threshold);
modified_ask_order = (await OrderUtilities.wait_for_filtered_orders(
_order_service,
rows => rows.Where(o => o.Key == ask_order_response.value.OrderKey && o.HasState &&
o.State.Quantity == cross_threshold).Count() == 1,
rows => rows.Where(o => o.Key == ask_order_response.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
Assert.IsTrue(modified_ask_order.Count() == 1, "The opposite inside market should have been adjusted to the Lean's cross threshold");
// verify that the child order has chased and that it's filled quantity has increased
lean_child_order = (await OrderUtilities.wait_for_filtered_orders(
_order_service,
rows => rows.Where(o => o.ParentKey == lean_order.First().Key &&
o.HasState && (
o.State.Status == OrderStatus.Working &&
TestUtils.test_price_equality(o.State.Price, ask_price_2, s.Value.ContractSpec.ticksize(o.State.Price)) &&
o.State.FilledQuantity == 9)
).Count() == 1,
rows => rows.Where(o => o.ParentKey == lean_order.First().Key))).result ?? Enumerable.Empty<OrderRecordImmutable>();
Assert.IsTrue(lean_child_order.Count() == 1, $"First Chase: The lean child order should now have been 'working' at {ask_price_1}");
test_orders.Add(lean_child_order.First());
// TODO:
// verify the parent order also reflects the increase filled quantity from the child order
// TODO:
// instigate another chase
// TODO:
// verify parent order again reflects
// TODO:
// instigate another chanse
// TODO:
// verify we are *not* chasing any longer, verify appropriate order statuses
// cleanup
Assert.IsTrue(await OrderUtilities.cancel_orders(_order_service, test_orders), "Unable to cancel test orders");
}
}
}
Loaded 100 of 176 files, more files were not shown because too many files have changed in this diff. Show more