Compare commits

..
Author SHA1 Message Date
Pedro Pinto f871dfe142 Merged. 2019-04-01 14:48:40 -04:00
Pedro Pinto d44de204ee Merge remote-tracking branch 'origin/ice_md' 2019-04-01 14:34:32 -04:00
Pedro Pinto 04beea61eb Updated release notes. 2019-04-01 14:34:03 -04:00
Pedro Pinto 2181b92970 Merged 2019-04-01 14:25:48 -04:00
Pedro Pinto 693c730f51 Update release notes. 2019-04-01 14:24:44 -04:00
Kevin Darby 0ccba5bb0b Merged in fix_sim (pull request #202)
RTM: Fix ladder highlighting and aggressor marking in sim

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-04-01 18:24:09 +00:00
Pedro Pinto 3458be59d7 Merge branch 'master' of bitbucket.org:bluetradesys/spark 2019-04-01 14:17:52 -04:00
Pedro Pinto 7dc9aa0710 Uppdate release notes. 2019-04-01 14:17:02 -04:00
Pedro Pinto 88d6dd0f00 Merge remote-tracking branch 'origin/SPARK-2162-notificationservice-cleanup' 2019-04-01 14:11:34 -04:00
Eric Harding ce4c46da04 Add some logging to spark shell shutdown sequence 2019-04-01 14:03:09 -04:00
Pedro Pinto 59c1f6a511 Updated release notes 2019-04-01 12:58:44 -04:00
Pedro Pinto 938caf9a4f Updated release notes. 2019-04-01 12:55:51 -04:00
Pedro Pinto df4f061b6c Merged 2019-04-01 12:47:38 -04:00
Jake Norbut 8afee21df7 hooking up the yes/no filter to the column 2019-04-01 11:21:02 -05:00
Pedro Pinto f34014eb25 Merged SPARK-2198 again 2019-04-01 12:11:58 -04:00
Jake Norbut b07eec08b4 Yes No formatting in row 2019-04-01 10:36:25 -05:00
Pedro Pinto 964274f5c4 Updated release notes. 2019-04-01 10:02:45 -04:00
Pedro Pinto c16941977c Merge remote-tracking branch 'origin/SPARK-2189' 2019-04-01 09:55:38 -04:00
Pedro Pinto 6b19119ab2 Updated release notes 2019-04-01 09:54:11 -04:00
Pedro Pinto ea42086e96 Merge branch 'SPARK-2198' of bitbucket.org:bluetradesys/spark 2019-04-01 09:39:05 -04:00
Pedro Pinto 292873b189 Update release notes. 2019-04-01 09:37:01 -04:00
Pedro Pinto 8545a5ef38 Merge branch 'SPARK-2150' 2019-04-01 09:30:03 -04:00
Pedro Pinto 6a52fff3d5 Updated release notes. 2019-04-01 09:28:33 -04:00
John Colvin 4ec6fafbc2 Merged in SPARK-2160-shell-windows-offscreen (pull request #195)
RTM: SPARK-2160 shell windows come up off screen sometimes

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2019-04-01 13:11:20 +00:00
Pedro Pinto b08e2c2ee6 Updated release notes. 2019-04-01 09:08:58 -04:00
Pedro Pinto 8bdd033501 Merge branch 'Documentation' 2019-04-01 09:04:51 -04:00
Taha Afzal 76466e83f0 changing hedger addiiton log from error to info 2019-03-29 17:26:36 -04:00
Kevin Darby 2540dc0510 ops notes 2019-03-29 15:20:35 -04:00
Kevin Darby f0132c84b6 Merge branch 'master' of bitbucket.org:bluetradesys/spark into fix_sim 2019-03-29 12:57:37 -04:00
Jake Norbut 57ca9596c8 changing column name 2019-03-29 11:05:15 -05:00
Jake Norbut bf4953f6dc adding TradedToday column to pnl 2019-03-29 09:45:32 -05:00
Jake Norbut c277fcd503 adding has_day_trades property 2019-03-29 09:44:55 -05:00
Jake Norbut dff57a43dd Adding TradedToday to Column enum 2019-03-29 09:43:53 -05:00
Jake Norbut 39c71d0c16 adding has day trades field to pnl's FilterState proto 2019-03-29 09:39:21 -05:00
Jake Norbut 009e72953c adding SecurityType default fitler; technical_debt++ 2019-03-28 15:31:56 -05:00
Jake Norbut 635163c895 hiding exch, start pnl, total net pos, and total net pnl by default 2019-03-28 15:03:54 -05:00
Jake Norbut 95e96a6afc remove pnl rows from which there is no net pos or day pos 2019-03-28 13:52:29 -05:00
Kevin Darby c7f0e2dd58 Merge branch 'master' of bitbucket.org:bluetradesys/spark into ice_md 2019-03-26 18:43:38 -04:00
Jake Norbut f5e79a65a3 relocating comment after PR edit 2019-03-26 11:17:37 -05:00
Jake Norbut c958f4bf1a PR suggestion - making row_vm -> communication a bit more flexibile 2019-03-26 11:15:15 -05:00
Eric Harding 95939a5534 SPARK-2087 add GE to default symbol list 2019-03-25 10:22:52 -04:00
Kevin Darby 9a7164b42b Merge branch 'ice_md' of bitbucket.org:bluetradesys/spark into ice_md 2019-03-25 10:10:04 -04:00
Kevin Darby f347fda47f PR details 2019-03-25 10:09:37 -04:00
Kevin Darby d75ab3a63f default to no ice symbols 2019-03-22 23:12:24 -04:00
Kevin Darby b30f59227e note to reference jetfix on the precision 2019-03-22 23:11:01 -04:00
Kevin Darby ef60d4200a Fix ladder highlighting and aggressor marking in sim 2019-03-22 17:56:19 -04:00
Jake Norbut ed6f0b8bdb Merge branch 'master' of bitbucket.org:bluetradesys/spark into SPARK-2150 2019-03-22 15:17:21 -05:00
Jake Norbut 766116eac4 adding confirmation to OB cancel buys and cancel sells 2019-03-22 09:33:49 -05:00
Jake Norbut 8d49de3f61 simplying boolean comparison 2019-03-21 14:31:59 -05:00
Jake Norbut 16cf4554c5 refactoring into a much simpler implementation 2019-03-21 14:24:32 -05:00
Jake Norbut 6b17efee60 Converting cancel all command to new SparkConfirmationCommandParamObserver 2019-03-21 13:44:17 -05:00
Jake Norbut 5d64213455 Adding new SparkConfirmationCommandParamObserver, which adds a confirmation func to SparkCommandParamObserver 2019-03-21 13:40:21 -05:00
John Colvin 0ef44e92aa SPARK-2160 pr edit: refer to 'source_window_rect' for width/height params instead of window (easy way to avoid extra dp lookups) 2019-03-20 10:46:47 -05:00
Kevin Darby ee2f76553b tests pass 2019-03-19 17:54:37 -04:00
Kevin Darby c1887b9786 launch.json 2019-03-19 15:28:11 -04:00
Kevin Darby 1fd5e51b9e reenabled cme, now we can get both ice and cme 2019-03-19 15:23:40 -04:00
John Colvin 56d3a27e7f SPARK-2162 / SPARK-2164 INotificationService - remove fake strongly typed 'link<T>' extension method 2019-03-19 13:12:04 -05:00
John Colvin 1c65b1d87a rename generic type arg 'TPredicateBuilder' -> 'TPredicateData' 2019-03-19 13:00:25 -05:00
John Colvin 693910f093 SPARK-2162 / SPARK-2163 GridSettingsManager/VM - replace IPredicateBuilder dependency with predicate-data factory(delegate) 2019-03-19 12:58:02 -05:00
Kevin Darby 498b38ebcb Merge branch 'master' of bitbucket.org:bluetradesys/spark into ice_md 2019-03-18 16:00:59 -04:00
John Colvin fb91206e0f pr edit: get window dimensions with window.ActualWidth/Height 2019-03-18 13:56:17 -05:00
John Colvin 91f4de00f8 pr edit: should not have changed while-loop to if-condition 2019-03-18 13:54:01 -05:00
John Colvin 28980f10ae SPARK-2160 shell windows come up off screen sometimes 2019-03-18 13:41:29 -05:00
Michael Harding 04e006b812 Add new Spreader and Algo studio videos to the manual 2019-03-15 10:44:58 -04:00
Jake Norbut 5b959ca5bd OB window command update when selected row's row vm changes 2019-03-12 14:39:25 -05:00
Kevin Darby 7ac54242e0 Merge branch 'master' of bitbucket.org:bluetradesys/spark into ice_md 2019-03-08 16:19:04 -05:00
Kevin Darby 1ebf8219c2 ice descriptions in mview 2019-03-08 16:17:53 -05:00
Kevin Darby f0b14cb8dc forgot to init new string fields 2019-03-08 16:10:02 -05:00
Kevin Darby 7259f81ab5 forgot to init new string fields 2019-03-08 16:03:49 -05:00
Kevin Darby 3b902b61dd ice spreads 2019-03-08 15:55:10 -05:00
Kevin Darby 6cfef51e3d subrepo 2019-03-07 18:12:39 -05:00
Kevin Darby 76edb94905 ticks.... 2019-03-07 18:10:11 -05:00
Kevin Darby e4bf9fc747 ice market data 2019-03-07 17:56:28 -05:00
Kevin Darby d488ff23ba I misunderstood how onixs wants to do the ice threading. fixed 2019-03-07 11:38:38 -05:00
Kevin Darby 6b6fd9c40a more ice 2019-03-06 14:58:09 -05:00
Kevin Darby f7265740e7 ICE securities, from the actual ICE 2019-03-05 18:08:20 -05:00
Kevin Darby 724da31316 adding data files 2019-02-28 17:50:46 -05:00
Kevin Darby 2ae5b50445 ice builds with .so 2019-02-28 15:01:35 -05:00
Kevin Darby dfcf38289f move externals up for ice .so 2019-02-28 14:44:13 -05:00
Kevin Darby 1c54b0140b ready to test, but we need to solve a link issue 2019-02-27 16:41:38 -05:00
Kevin Darby e034fd2623 Merge branch 'master' of bitbucket.org:bluetradesys/spark into ice_md 2019-02-26 18:23:23 -05:00
Kevin Darby a3b8d579a1 hooked up all ice callbacks except books 2019-02-26 18:20:22 -05:00
Kevin Darby 9d2cdb702d getting somewhere 2019-02-26 16:00:26 -05:00
Kevin Darby d0784feddd more ice 2019-02-25 10:34:58 -05:00
Kevin Darby 4a64e45d36 translated onixs::Handler settings to our config. 2019-02-15 18:08:26 -05:00
Kevin Darby 97f09768bb got the boilerplate up, now just need to get the right ice handlers and options in there 2019-02-14 17:55:34 -05:00
Kevin Darby 2b4add85a2 kinda copy / pastey stuff from cme 2019-02-12 13:00:47 -05:00
Kevin Darby 4c798f6db4 initial push 2019-02-11 16:14:41 -05:00
Kevin Darby b2e275124f update externals with onixs ice docs 2019-02-11 13:35:14 -05:00
Kevin Darby 36e4f62a53 moved externals pointer forward to capture onixs ice addition 2019-02-11 12:38:00 -05:00
331 changed files with 8290 additions and 9032 deletions

No files matched your search

+9 -3
View File
@@ -75,6 +75,7 @@
"name": "SparkVM Launch",
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
"args": ["run"],
"stopAtEntry": false,
@@ -96,7 +97,12 @@
"description": "Skip STL",
"text": "skip -gfi /usr/include/c++/7/bits/*.h",
"ignoreFailures": true
}
},
{
"description": "LD LIBRARY",
"text": "set env LD_LIBRARY_PATH=.",
"ignoreFailures": false
}
],
"pipeTransport": {
"pipeCwd": "${workspaceRoot}",
@@ -208,10 +214,10 @@
"name": "MD Tests",
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
"args": ["test", "[md]"],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
"cwd": "/home/bts/spark/build/ninja_debug/server/",
"environment": [],
"externalConsole": true,
"MIMode": "gdb",
+2
View File
@@ -55,6 +55,8 @@ else ()
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/web ${CMAKE_BINARY_DIR}/server
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/data_files ${CMAKE_BINARY_DIR}/server
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/src/lib/libspark.so ${CMAKE_BINARY_DIR}/server
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.13.0.0 ${CMAKE_BINARY_DIR}/server
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libACE-5.7.5.so ${CMAKE_BINARY_DIR}/server
COMMAND mkdir ARGS -p ${CMAKE_BINARY_DIR}/server/plugins
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/sdk/examples/order_plugin_example.so ${CMAKE_BINARY_DIR}/server/plugins/
COMMENT "Creating server runtime layout"
@@ -5,6 +5,7 @@ sort_by = "weight"
insert_anchor_links = "right"
+++
{{ youtube(id="RZU9Wj0cETo") }}
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/XEZpuPAgMC0" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
@@ -406,6 +406,8 @@ insert_anchor_links = "right"
## Algo Studio
{{ youtube(id="DHIa5tnifKc") }}
- The BTS Algo Studio was built for users to experience algo composability
never seen before in a professional futures trading system.
+20
View File
@@ -4,6 +4,26 @@ weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## 1.5.7
### User Visible Changes
- New videos have been added to the manual describing the [Algo Studio](./bts_spark/Trading/index.md#algo-studio) and [Spreader](./bts_spark/Spreader/index.md)
- Fixed a bug that occasionally would cause windows to be position outside the visible screen on startup
- Fixed a bug in the order book form that would occasionally cause the Recreate Order option to be unavailable
- Added a confirmation step to Cancel All, Cancel Buys and Cancel Sells commands
- Fixed Aggressor coloring and ladder high/low coloring in the SIM execution gateway
- Added initial support for ICE market data
- In an effort to streamline the PnL report we've made the following changes:
- The PnL report will by default filter for Futures contracts only
- If you wish to see Spread contracts again you may change Security Type column filter
- The PnL report will now only show rows in which there is a position (net and/or day)
- The Exchange, Start PnL, Total Net Pos, and Total Net PnL columns are now hidden by default
- These columns can be restored by toggling their visibility within the PnL's Settings form
- A new Traded Today column has been added to the PnL
- Filtering on this column will allow a user to restrict the report to only show today's position
### Internal changes
- Improved organization of the shell notification architecture
- Added logging to help troubleshoot shell exit hang
## 1.5.6
### User Visible Changes
- Fixed a bug that could cause limits to be erroneously enforced
+5
View File
@@ -54,3 +54,8 @@ img {
background: #ccc;
border: 1px solid #aaa;
}
iframe {
width:560px;
height:315px;
}
+3
View File
@@ -1,3 +1,6 @@
1.5.7
- Fixed Aggressor coloring and ladder high/low coloring in SIM
1.4.2
- Be sure the system has a bts user before deployment. bts is the superuser, he has all permissions and can see all market data
- After deployment re-permission all existing users for trading, riskadmin, and/or md admin permissions as appropriate
+1
View File
@@ -66,6 +66,7 @@ namespace mbts.ui.shell
protected override void OnExit(ExitEventArgs e)
{
Log.Gen.info("Application.OnExit");
if (_runtime != null) _runtime.Dispose();
utils.BTSNativeMethods.finit_spark_lib();
base.OnExit(e);
-6
View File
@@ -22,9 +22,7 @@ namespace mbts.ui {
AddBoxesGeometryKey = nameof(AddBoxesGeometryKey),
AddFileGeometryKey = nameof(AddFileGeometryKey),
AddFileThinGeometryKey = nameof(AddFileThinGeometryKey),
AttachAlgosGeometryKey = nameof(AttachAlgosGeometryKey),
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
DetachAlgosGeometryKey = nameof(DetachAlgosGeometryKey),
DiskGeometryKey = nameof(DiskGeometryKey),
DiskFileGeometryKey = nameof(DiskFileGeometryKey),
DocumentGeometryKey = nameof(DocumentGeometryKey),
@@ -41,7 +39,6 @@ namespace mbts.ui {
PaymentGeometryKey = nameof(PaymentGeometryKey),
PencilGeometryKey = nameof(PencilGeometryKey),
PlayGeometryKey = nameof(PlayGeometryKey),
ProhibitedGeometryKey = nameof(ProhibitedGeometryKey),
StarGeometryKey = nameof(StarGeometryKey),
ToolsGeometryKey = nameof(ToolsGeometryKey),
TrashGeometryKey = nameof(TrashGeometryKey),
@@ -118,7 +115,6 @@ 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 Beaker { get { return load_resource_and_freeze("Beaker"); } }
public static ImageSource Book { get { return load_resource_and_freeze("Book"); } }
@@ -143,7 +139,6 @@ 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 Disk => create_icon(DiskGeometryKey);
public static ImageSource DiskFile => create_icon(DiskFileGeometryKey);
public static ImageSource Display { get { return load_resource_and_freeze("Display"); } }
@@ -163,7 +158,6 @@ namespace mbts.ui {
public static ImageSource Knight { get { return load_resource_and_freeze("Knight"); } }
public static ImageSource Payment => create_icon(PaymentGeometryKey);
public static ImageSource Pencil => create_icon(PencilGeometryKey);
public static ImageSource Prohibited => create_icon(ProhibitedGeometryKey);
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);
-63
View File
@@ -40,69 +40,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
Credit: Icon made by Chanut from www.flaticon.com-->
<sys:String x:Key="{x:Static mbts:Icons.ProhibitedGeometryKey}">
M32.759,0C14.696,0,0,14.695,0,32.759s14.695,32.759,32.759,32.759s32.759-14.695,32.759-32.759S50.822,0,32.759,0z
M6,32.759C6,18.004,18.004,6,32.759,6c6.648,0,12.734,2.443,17.419,6.472L12.472,50.178C8.443,45.493,6,39.407,6,32.759z
M32.759,59.518c-5.948,0-11.447-1.953-15.895-5.248l37.405-37.405c3.295,4.448,5.248,9.947,5.248,15.895
C59.518,47.514,47.514,59.518,32.759,59.518z
</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
@@ -137,6 +137,12 @@ namespace mbts.spark {
public ICommand ask_implied_size_click { get; }
public ICommand working_asks_click { get; }
public ICommand default_command { get; }
public string market_description {get;}
public string cleared_alias { get; }
public string product_description { get; }
public string hub_alias { get; }
public string strip_name{ get; }
public string mic_code { get; }
public ObservableCollection<RowAnimation> row_animations { get; } = new ObservableCollection<RowAnimation>();
@@ -57,6 +57,13 @@ namespace mbts.spark {
add_col(Columns.SessionLow, "Low", r => r.format_price(r.session_low), nameof(Row.session_low), DefaultColumnWidths.Price, session_low_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
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);
//
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) {
@@ -194,6 +201,14 @@ namespace mbts.spark {
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_ask_volume_filter { get; } = new IntMinMax<Row>(r => r.working_ask_volume, nameof(Row.working_ask_volume));
public IDataGridColumnPersistentFilter<Row, ULongMinMaxState> volume_filter { get; } = new ULongMinMax<Row>(r => r.volume, nameof(Row.volume));
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
public IDataGridColumnPersistentFilter<Row, string> market_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MarketDescription : "", nameof(Row.market_description));
public IDataGridColumnPersistentFilter<Row, string> cleared_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ClearedAlias : "", nameof(Row.cleared_alias));
public IDataGridColumnPersistentFilter<Row, string> product_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ProductName : "", nameof(Row.product_description));
public IDataGridColumnPersistentFilter<Row, string> hub_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.HubAlias : "", nameof(Row.hub_alias));
public IDataGridColumnPersistentFilter<Row, string> strip_name_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.StripName : "", nameof(Row.strip_name));
public IDataGridColumnPersistentFilter<Row, string> mic_code_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MicCode : "", nameof(Row.mic_code));
public FilterStates get_filter_states() {
return new FilterStates {
@@ -216,7 +231,13 @@ namespace mbts.spark {
working_bid_volume = working_bid_volume_filter.get_state(),
working_ask_volume = working_ask_volume_filter.get_state(),
volume = volume_filter.get_state(),
security_key = security_key_filter.get_state()
security_key = security_key_filter.get_state(),
market_description = market_description_filter.get_state(),
cleared_alias = cleared_alias_filter.get_state(),
product_description = product_description_filter.get_state(),
hub_alias = hub_alias_filter.get_state(),
strip_name = strip_name_filter.get_state(),
mic_code = mic_code_filter.get_state(),
};
}
#endregion
@@ -8,7 +8,9 @@ namespace mbts.ui.spark {
/// Interaction logic for MarketSheetsView.xaml
/// </summary>
partial class MarketViewView : UserControl {
public enum Columns { Security, Volume, Last, LastSize, Position, WorkingBids, Bid, BidMergedSize, BidDirectSize, BidImpliedSize, Ask, AskMergedSize, AskDirectSize, AskImpliedSize, WorkingAsks, Settlement, DayChange, SessionLow, SessionHigh, SecurityKey }
public enum Columns { Security, Volume, Last, LastSize, Position, WorkingBids, Bid, BidMergedSize, BidDirectSize, BidImpliedSize, Ask, AskMergedSize, AskDirectSize, AskImpliedSize, WorkingAsks, Settlement, DayChange, SessionLow, SessionHigh, SecurityKey,
MarketDescription, ClearedAlias, ProductDescription, HubAlias, StripName, MicCode
}
public MarketViewView() {
InitializeComponent();
@@ -16,7 +16,7 @@ using System.Windows;
namespace mbts.spark {
class MarketViewWindow : SparkWindow<MarketViewWindowFactory.StartupData>, ISharedWindow {
const string _window_type = "MarketView", _window_type_display_name = "Market View";
const string _window_type = "MarketView", _thread_id = "MarketView", _window_type_display_name = "Market View";
readonly OrdersModel _orders_model;
readonly AllowOnce _init_guard = new AllowOnce("MarketView");
readonly IWindowSettingsManager<GridSettings> _settings_manager;
@@ -32,7 +32,6 @@ namespace mbts.spark {
_state = state;
_settings_manager = settings_manager;
Icon.Freeze();
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().market_view ? "MarketView" : null;
}
event Action<MarketViewWindowFactory.StartupData> _state_changed;
@@ -48,7 +47,7 @@ namespace mbts.spark {
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public string thread_id { get; }
public string thread_id => _thread_id;
public void thread_init() {
_init_guard.check_access();
@@ -97,6 +97,12 @@ namespace bts.shell {
public static GridColumnSettings session_low => new GridColumnSettings(col_id(Columns.SessionLow), DefaultColumnHeaders.SessionLow, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings session_high => new GridColumnSettings(col_id(Columns.SessionHigh), DefaultColumnHeaders.SessionHigh, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, is_visible: false, width: DefaultColumnWidths.SecurityKey);
public static GridColumnSettings market_description => new GridColumnSettings(col_id(Columns.MarketDescription), DefaultColumnHeaders.MarketDescription, is_visible: false, width: DefaultColumnWidths.SecurityKey);
public static GridColumnSettings cleared_alias => new GridColumnSettings(col_id(Columns.ClearedAlias), DefaultColumnHeaders.ClearedAlias, is_visible: false, width: DefaultColumnWidths.SecurityKey);
public static GridColumnSettings product_description => new GridColumnSettings(col_id(Columns.ProductDescription), DefaultColumnHeaders.ProductDescription, is_visible: false, width: DefaultColumnWidths.SecurityKey);
public static GridColumnSettings hub_alias => new GridColumnSettings(col_id(Columns.HubAlias), DefaultColumnHeaders.HubAlias, is_visible: false, width: DefaultColumnWidths.SecurityKey);
public static GridColumnSettings strip_name => new GridColumnSettings(col_id(Columns.StripName), DefaultColumnHeaders.StripName, is_visible: false, width: DefaultColumnWidths.SecurityKey);
public static GridColumnSettings mic_code => new GridColumnSettings(col_id(Columns.MicCode), DefaultColumnHeaders.MicCode, is_visible: false, width: DefaultColumnWidths.SecurityKey);
@@ -128,6 +134,12 @@ namespace bts.shell {
yield return session_low;
yield return session_high;
yield return security_key;
yield return market_description;
yield return cleared_alias;
yield return product_description;
yield return hub_alias;
yield return strip_name;
yield return mic_code;
}
}
@@ -177,6 +189,18 @@ namespace bts.shell {
public UIntMinMaxState ask_direct_size;
[ProtoMember(20)]
public UIntMinMaxState ask_implied_size;
[ProtoMember(21)]
public string market_description;
[ProtoMember(22)]
public string cleared_alias;
[ProtoMember(23)]
public string product_description;
[ProtoMember(24)]
public string hub_alias;
[ProtoMember(25)]
public string strip_name;
[ProtoMember(26)]
public string mic_code;
}
}
}
@@ -19,7 +19,7 @@ using static bts.shell.TimeAndSalesWindowFactory;
using static mbts.ui.controls.DIYGrid;
namespace mbts.spark {
class TimeAndSalesWindow : SparkWindow<StartupData>, ISharedWindow {
class TimeAndSalesWindow : SparkWindow<StartupData>, ISlowWindow {
const string _window_type = "TimeAndSales", _window_type_display_name = "Time And Sales";
readonly MDConsumer _consumer;
readonly TimeAndSalesWindowFactory _factory;
@@ -48,7 +48,7 @@ namespace mbts.spark {
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public string thread_id { get; }
public override StartupData SaveState => _state = _vm.value.create();
public override void Dispose() =>
@@ -16,7 +16,7 @@ using System.Windows;
using static bts.shell.SyntheticManagementWindowFactory;
namespace mbts.spark {
class SyntheticManagementWindow : SparkWindow<StartupData>, ISharedWindow {
class SyntheticManagementWindow : SparkWindow<StartupData>, ISlowWindow {
const string _window_type = "SyntheticManagement";
SingleValuePump<SyntheticManagementDialogVM> _vm = new SingleValuePump<SyntheticManagementDialogVM>();
StartupData _state;
@@ -33,20 +33,8 @@ namespace mbts.spark {
_state = state;
_settings_manager = settings_manager;
Icon.Freeze();
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().synthetic_management ? "SyntheticManagement" : null;
}
public override string window_type => _window_type;
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public override StartupData SaveState => _state = _vm.value.create();
public override double MinWidth =>
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public string thread_id { get; }
public void thread_init() {
Log.notnull(_state);
@@ -113,6 +101,16 @@ namespace mbts.spark {
else _view.DataContext = _vm.value;
}
public override string window_type => _window_type;
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public override StartupData SaveState => _state = _vm.value.create();
public override double MinWidth =>
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public override void Dispose() {
Disposable.dispose(ref _vm);
Commands.OfType<IDisposable>().DisposeAll();
@@ -19,6 +19,8 @@
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Controls/Order/AlgoTemplateTreeView.xaml"/>
<ResourceDictionary Source="/view/Controls/Order/AlgoPickerNodeView.xaml"/>
<ResourceDictionary Source="/view/Controls/Order/AlgoContentTemplateSelector.xaml"/>
</ResourceDictionary.MergedDictionaries>
<!--Primitive Constants-->
<sys:Double x:Key="HorizontalPush1">4</sys:Double>
@@ -557,14 +559,71 @@
</Grid>
</RadioButton>
</Grid>
<usp:AlgoEditorView
<GroupBox
x:Name="content_host"
Grid.Row="2"
DataContext="{Binding content_source}"
Background="Transparent"
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
BorderBrush="{Binding content_source.algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
Margin="0,6,0,0"
Padding="12,8"
/>
>
<GroupBox.Style>
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}">
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Style.Triggers>
<Trigger Property="BorderBrush" Value="Transparent">
<Setter Property="BorderThickness" Value="0"/>
</Trigger>
<Trigger Property="usp:AlgoStudioView.IsMouseOverNodeContent" Value="True">
<Setter Property="BorderThickness" Value="2"/>
</Trigger>
</Style.Triggers>
</Style>
</GroupBox.Style>
<GroupBox.Header>
<ComboBox
Grid.Column="3"
ToolTipService.ShowOnDisabled="True"
SelectedItem="{Binding selected_definition.algo_root}"
ItemsSource="{Binding selected_definition.algos_view}"
Margin="{StaticResource Margin.LP1}"
Width="140"
>
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<DataTrigger Binding="{Binding content_source}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding selected_definition.templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:True}">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding selected_definition.templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:False}">
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="ToolTip" Value="Cannot change structure while templates are present."/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
<ComboBox.ItemTemplate>
<DataTemplate>
<usp:OrderTypeItem DisplayName="{Binding display_name}" BadgeFill="{Binding meta.Color, Converter={StaticResource ColorToBrushConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</GroupBox.Header>
<ContentControl
Grid.IsSharedSizeScope="True"
Content="{Binding selected_definition.algo_root}"
ContentTemplateSelector="{StaticResource AlgoTemplateSelector}"
Margin="{StaticResource Margin.TP1}"
>
<ContentControl.Resources>
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}"/>
</ContentControl.Resources>
</ContentControl>
</GroupBox>
<c:DialogSkirt Grid.Row="3" Margin="{StaticResource Margin.TP2}" Background="Transparent">
<c:DialogSkirt.RightItems>
<x:ArrayExtension Type="{x:Type Button}">
@@ -1,6 +1,8 @@
using mbts.spark;
using mbts.ui.converters;
using mbts.ui.extensions;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
@@ -16,11 +18,26 @@ namespace mbts.ui.spark {
public AlgoStudioView() {
InitializeComponent();
DataContextChanged += (s, e) => set_controller();
content_host.PreviewMouseMove += content_host_PreviewMouseMove;
content_host.MouseEnter += content_host_MouseEnter;
content_host.MouseLeave += content_host_MouseLeave;
set_controller();
// local methods
void set_controller() => Disposable.replace(ref _controller, AlgoStudioViewControllerFactory.create(DataContext, _tree_view, name_text_box, custom_badge_color_picker_button, custom_badge_color_picker_popup));
void content_host_PreviewMouseMove(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
void content_host_MouseEnter(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
void content_host_MouseLeave(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
void refresh_is_mouse_over_content_host(MouseEventArgs e) => content_host.set_current_if_changed(IsMouseOverNodeContentProperty, this.ChildAtPoint<GroupBox>(e.GetPosition(this)) == content_host);
}
#region IsMouseOverNodeContent
public static readonly DependencyProperty IsMouseOverNodeContentProperty =
DependencyProperty.RegisterAttached("IsMouseOverNodeContent", typeof(bool), typeof(AlgoStudioView), new PropertyMetadata(false));
public static bool GetIsMouseOverNodeContent(DependencyObject d) => (bool)d.GetValue(IsMouseOverNodeContentProperty);
public static void SetIsMouseOverNodeContent(DependencyObject d, bool value) => d.SetValue(IsMouseOverNodeContentProperty, value);
#endregion
public new AlgoStudioVM DataContext {
get => base.DataContext as AlgoStudioVM;
set => base.DataContext = value;
@@ -13,7 +13,7 @@ using System.Windows;
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
namespace mbts.spark {
internal class AlgoStudioWindow : SparkWindow<StartupData>, ISharedWindow {
internal class AlgoStudioWindow : SparkWindow<StartupData>, IShellWindow {
public const string
NAME = "Algo Studio",
TemplatesEnabledKey = nameof(TemplatesEnabledKey),
@@ -37,7 +37,7 @@ namespace mbts.spark {
: base(NAME, bus, factory, SparkCommands.ShowAlgoStudio.icon) {
_model = model;
_state = state;
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().algo_studio ? "AlgoStudio" : null;
thread_init();
}
public override StartupData SaveState => _state = _vm.create();
@@ -45,12 +45,12 @@ namespace mbts.spark {
public override string window_type => NAME;
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public string thread_id => NAME;
public override double MinWidth =>
SPARK_MENU_COLLAPSED_WIDTH +
LAUNCHER_BUTTON_BORDER_WIDTH +
DEFAULT_CONTENT_WIDTH;
public override double MinHeight => DEFAULT_CONTENT_HEIGHT;
public string thread_id { get; }
bts.config.AlgoStudioOptions _ui_configuration => _model.algo_model.ui_configuration;
@@ -1,9 +1,8 @@
using bts.api.orderman;
using bts.utils;
using mbts.spark.utilities;
using mbts.ui.utility;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
namespace mbts.spark {
@@ -11,6 +10,12 @@ namespace mbts.spark {
public readonly string name;
public readonly AlgoStructureKey structure;
public AlgoNameStructureKey(CustomOrderDefinitionImmutable definition)
: this(definition.CustomCase.type_id(), definition) { }
public AlgoNameStructureKey(string name, CustomOrderDefinitionImmutable definition)
: this(name, new AlgoStructureKey(definition)) { }
public AlgoNameStructureKey(CustomOrderDefinition.CustomOneofCase base_type)
: this(base_type.type_id(), new AlgoStructureKey(base_type)) { }
@@ -26,22 +31,31 @@ namespace mbts.spark {
public static bool operator !=(AlgoNameStructureKey a, AlgoNameStructureKey b) => !a.Equals(b);
}
[ProtoContract]
struct AlgoStructureKey : IEquatable<AlgoStructureKey> {
[ProtoMember(1)]
readonly int[] _structure;
public AlgoStructureKey(CustomOrderDefinition.CustomOneofCase base_type)
: this(new[] { (int)base_type }) { }
public AlgoStructureKey(CustomOrderDefinitionImmutable definition, IMaskIndexInt32<string> dynamic_type_index) =>
_structure = AlgoStructureSerializer.to_array(definition, dynamic_type_index);
public AlgoStructureKey(CustomOrderDefinitionImmutable definition) {
int[] s = new[] { (int)definition.CustomCase };
_build_structure(definition, ref s, 1);
_structure = s;
}
public AlgoStructureKey(int[] structure) =>
_structure = structure;
public IReadOnlyList<int> structure => _structure ?? Array.Empty<int>();
public CustomOrderDefinitionImmutable build_def() {
int pos = 0;
var mdef = new CustomOrderDefinition();
_build_def(_structure, ref pos, ref mdef);
return new CustomOrderDefinitionImmutable(mdef);
}
public bool Equals(AlgoStructureKey other) =>
/// <see cref="Enumerable.SequenceEqual{TSource}(IEnumerable{TSource}, IEnumerable{TSource})"/> is not optimized to check Count/Length, since the args are <see cref="IEnumerable{T}"/>.
_structure.Length == other._structure.Length &&
_structure.SequenceEqual(other._structure);
@@ -53,105 +67,35 @@ namespace mbts.spark {
public static bool operator ==(AlgoStructureKey a, AlgoStructureKey b) => a.Equals(b);
public static bool operator !=(AlgoStructureKey a, AlgoStructureKey b) => !a.Equals(b);
}
static class AlgoStructureSerializer {
const int
_dynamic_mask = 1 << 31,
_child_mask_offset = 30;
static void _build_structure(CustomOrderDefinitionImmutable definition, ref int[] structure, int pos) =>
_build_structure(definition.CloneToMutable(), ref structure, pos);
public static CustomOrderDefinitionImmutable build_algoi(this AlgoStructureKey structure_key, DynamicOrderHelper dynamic_helper) =>
parsei(structure_key.structure, dynamic_helper);
public static CustomOrderDefinitionImmutable build_algo(this AlgoStructureKey structure_key, DynamicOrderHelper dynamic_helper) =>
parsei(structure_key.structure, dynamic_helper);
public static AlgoStructureKey structure_key(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
new AlgoStructureKey(to_array(algo, dynamic_type_index));
public static AlgoStructureKey structure_key(CustomOrderDefinition algo, IMaskIndexInt32<string> dynamic_type_index) =>
new AlgoStructureKey(to_array(algo, dynamic_type_index));
public static int[] to_array(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
to_array(algo.CloneToMutable(), dynamic_type_index);
public static int[] to_array(CustomOrderDefinition algo, IMaskIndexInt32<string> dynamic_type_index) {
int[] s = new[] { (int)algo.CustomCase };
_build_structure(algo, ref s, dynamic_type_index);
return s;
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, int pos) {
var cad = OrderDataHelper.get_child_algo_data(algo);
for (int di = 0; di < cad.Count; di++) {
if (cad[di].get_child(algo) is CustomOrderDefinition child_algo) {
ArrayHelper.ensure_capacity(ref structure, pos + 1);
int del_mask = 1 << (31 - di);
structure[pos++] = (int)child_algo.CustomCase | del_mask;
_build_structure(child_algo, ref structure, pos);
break;
}
}
ArrayHelper.trim(ref structure, pos);
}
public static CustomOrderDefinitionImmutable from_array(int[] structure, DynamicOrderHelper dynamic_helper) =>
parsei(structure, dynamic_helper);
public static CustomOrderDefinitionImmutable parsei(IReadOnlyList<int> structure, DynamicOrderHelper dynamic_helper) =>
new CustomOrderDefinitionImmutable(parse(structure, dynamic_helper));
public static CustomOrderDefinition parse(IReadOnlyList<int> structure, DynamicOrderHelper dynamic_helper) {
if (structure == null || structure.Count == 0)
return new CustomOrderDefinition();
int pos = 1;
var mdef = OrderDataHelper.create_algo((CustomOrderDefinition.CustomOneofCase)structure[0]);
_build_def(structure, ref pos, ref mdef, dynamic_helper);
return mdef;
}
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, IMaskIndexInt32<string> dynamic_type_index) {
if (structure == null || structure.Length == 0)
structure = new int[1];
structure[0] = (int)algo.CustomCase;
int p = 1;
_build_structure(algo, ref structure, ref p, dynamic_type_index);
ArrayHelper.trim(ref structure, p);
}
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, ref int pos, IMaskIndexInt32<string> dynamic_type_index) {
IEnumerable<(CustomOrderDefinition child_algo, int field_pos)> children;
if (algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
ArrayHelper.ensure_capacity(ref structure, pos + 1);
structure[pos++] = dynamic_type_index.to_int(algo.Dynamic.DynamicOrderType) | _dynamic_mask;
children = algo.Dynamic.OrderParams.Where(kv => kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef).Select((kv, i) => (kv.Value.AsOrderDef, i));
} else {
static void _build_def(int[] structure, ref int pos, ref CustomOrderDefinition algo) {
for (int i = pos; i < structure.Length; i++) {
var cad = OrderDataHelper.get_child_algo_data(algo);
children = cad.Select((c, i) => (c.get_child(algo), i));
children = children.Where(c => c.child_algo != null);
}
foreach ((var child, int field_pos) in children) {
ArrayHelper.ensure_capacity(ref structure, pos + 1);
structure[pos++] = EnumMaskIndexInt32<CustomOrderDefinition.CustomOneofCase>.to_int(child.CustomCase) | _child_algo_mask(field_pos);
_build_structure(child, ref structure, ref pos, dynamic_type_index);
}
}
/// <summary>
/// Gets a mask for a particular child-algo field of a particular algo definition.
/// </summary>
/// <param name="field_pos">The index wrt the deterministic enumeration of child algos for a particular definition (e.g., Hedge = 0, Child = 1).</param>
static int _child_algo_mask(int field_pos) => 1 << (_child_mask_offset - field_pos);
static void _build_def(IReadOnlyList<int> structure, ref int pos, ref CustomOrderDefinition algo, DynamicOrderHelper dynamic_helper) {
var local_algo = algo;
IEnumerable<(Action<CustomOrderDefinition> assign_child, int field_pos)> children;
if (algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
var dyn_def = algo.Dynamic;
dyn_def.DynamicOrderType = dynamic_helper.dynamic_type_index.from_int(structure[pos++] ^ _dynamic_mask);
dynamic_helper.add_dynamic_params(ref dyn_def);
children = dyn_def.OrderParams
.Where(kv => kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef)
.Select((kv, i) => (new Action<CustomOrderDefinition>(child_algo => kv.Value.AsOrderDef = child_algo), i));
} else {
children = OrderDataHelper.get_child_algo_data(algo)
.Select((c, i) => (new Action<CustomOrderDefinition>(child_algo => c.assign_child(local_algo, child_algo)), i));
}
using (var children_en = children.GetEnumerator()) {
while (pos < structure.Count && children_en.MoveNext()) {
(var assign_child, int field_pos) = children_en.Current;
int mask = _child_algo_mask(field_pos);
if ((structure[pos] & mask) == mask) {
var type = EnumMaskIndexInt32<CustomOrderDefinition.CustomOneofCase>.from_int(structure[pos++] ^ mask);
var child_algo = OrderDataHelper.create_algo(type);
_build_def(structure, ref pos, ref child_algo, dynamic_helper);
assign_child(child_algo);
for (int di = 0; di < cad.Count; di++) {
int mask = 1 << (31 - di);
if ((structure[i] & mask) == mask) {
var type = (CustomOrderDefinition.CustomOneofCase)(structure[i] ^ mask);
var child_algo = OrderDataHelper.Defaults.CustomOrderDefinition(type);
pos++;
_build_def(structure, ref pos, ref child_algo);
cad[di].assign_child(algo, child_algo);
}
}
}
@@ -46,8 +46,7 @@ namespace mbts.spark {
readonly DeferrableActionWrapper _refresh_on_local_changes;
readonly ReactiveThresholdMonitor<AlgoDefinitionTemplateVM> _any_templates_dirty_monitor = new ReactiveThresholdMonitor<AlgoDefinitionTemplateVM>(t => t.observe_is_dirty());
readonly Property<AlgoFormats> _selected_format;
readonly EnumMaskInt32<AlgoEditingPermissions> _permissions_mask = new EnumMaskInt32<AlgoEditingPermissions>();
AlgoEditingPermissions _editing_permissions;
int? _favorite_index;
Brush _badge_fill;
Snapshot? _committed;
@@ -55,43 +54,29 @@ namespace mbts.spark {
TableUpdateBuffer<AlgoTemplateData, AlgoKey> _template_update_buffer;
bool _connected;
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, bool connect = false, AlgoFormats init_format = AlgoFormats.Exchange, AlgoEditingPermissions editing_permissions = (AlgoEditingPermissions)int.MaxValue) {
var rv = new AlgoDefinitionVM(model, data, init_format, editing_permissions);
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, bool connect = false, AlgoFormats init_format = AlgoFormats.Exchange) {
var rv = new AlgoDefinitionVM(model, data, init_format);
if (connect)
rv.connect();
return rv;
}
public static AlgoDefinitionVM create_placeholder(OrdersModel model, CustomOrderDefinitionImmutable def, string name, AlgoFormats init_format) =>
new AlgoDefinitionVM(model, new AlgoData(def, name, new AlgoKey(Guid.NewGuid())), init_format, (AlgoEditingPermissions)int.MaxValue);
new AlgoDefinitionVM(model, new AlgoData(def, name, new AlgoKey(Guid.NewGuid())), init_format);
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoFormats init_format, AlgoEditingPermissions init_permissions) {
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoFormats init_format = AlgoFormats.Exchange) {
// init core data
_model = model;
_permissions_mask.mask = init_permissions;
_permissions_mask.raw = init_permissions;
var resources = new CompositeDisposable();
if (!model.algo_model.ui_configuration.can_modify_definition_values)
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditValues | AlgoEditingPermissions.AllowSaveValues);
if (!model.algo_model.ui_configuration.can_modify_definition_structure)
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure);
else if (model.algo_model.templates_enabled()) {
resources.Add(_templates.Subscribe((s, e) => {
if (_templates.Count == 0) {
editing_permissions_mask |= AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure;
} else {
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure);
}
}));
}
key = def_data.key;
algos_view = ObservableAutoWrapper.create_sorted_wrapper(_algo_roots, AlgoCollectionHelper.algo_vm_comparer);
_template_update_buffer = new TableUpdateBuffer<AlgoTemplateData, AlgoKey>(t => t.key);
_templates_with_custom = new ObservableAppendCollection<AlgoDefinitionTemplateVM>(templates);
templates_view = (ListCollectionView)CollectionViewSource.GetDefaultView(_templates_with_custom);
custom_badge_color_palettes.AddRange(ColorPalette.all_presets(model.factory).Select(p => new ColorPaletteVM(p)));
_editing_permissions = _get_permissions();
_version = new VersionContainer<Snapshot>(_snapshot);
var conf = _model.algo_model.ui_configuration;
var resources = new CompositeDisposable();
// init backing props
_name = backing(nameof(name), def_data.name);
_abbreviation = backing(nameof(abbreviation), string.Empty);
@@ -102,7 +87,7 @@ namespace mbts.spark {
_is_selected = backing(nameof(is_selected), true);
_is_dirty = backing(nameof(is_dirty), () => key.mode == AlgoKey.Mode.Unpersisted || !_committed.HasValue || !_committed.Value.Equals(_version.value));
var factory_def_data = def_data.key.mode == AlgoKey.Mode.Factory ? def_data : new AlgoData(def_data.value);
_algo_root = backing(nameof(algo_root), _algo_roots.GetOrAdd(factory_def_data.key, _ => OrderTypeVMFactory.create(factory_def_data)));
_algo_root = backing(nameof(algo_root), _algo_roots.GetOrAdd(factory_def_data.key, _ => OrderTypeVMFactory.create(factory_def_data, _editing_permissions)));
_visible_algo_root = backing<IAlgoVM>(nameof(visible_algo_root));
_can_redo = backing(nameof(can_redo), () => _edit_stack.get_can_redo(_version.value));
_can_undo = backing(nameof(can_undo), () => _edit_stack.get_can_undo(_version.value));
@@ -128,6 +113,7 @@ namespace mbts.spark {
as_demuxer().subscribe_many(_refresh_on_local_changes.execute, nameof(this.name), nameof(abbreviation), nameof(badge_fill)),
_edit_pane_vs_model.add_source(VisualStateNames.BadgeSelector.GroupName, custom_badge_color.observe_nullable(true).Select(t => t.HasValue ? VisualStateNames.BadgeSelector.Active : VisualStateNames.BadgeSelector.Inactive)),
_edit_pane_vs_model.add_source(VisualStateNames.Abbreviation.GroupName, _abbreviation.observe_current(true).Select(a => string.IsNullOrEmpty(a) ? VisualStateNames.Abbreviation.Unpopulated : VisualStateNames.Abbreviation.Populated)),
templates.ObserveSelf().Select(_ => _get_permissions()).DistinctUntilChanged().OnDispatcher().Subscribe(on_editing_permissions),
_algo_root.observe_current(true).SubscribeHookLatest(vm => {
if (vm == null)
return Disposable.empty;
@@ -166,22 +152,16 @@ namespace mbts.spark {
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Inactive);
break;
}
var permission_src = _permissions_mask.observe_result();
resources.Add(_selected_format.observe_current(true).CombineLatest(permission_src, (fmt, perms) => (fmt, perms))
.SubscribeHookLatest(t => {
if ((t.perms & AlgoEditingPermissions.AllowEditStructure) != 0) {
using (_refresh_on_local_changes.defer_execution()) {
var algo_data = model.factory.default_algos(t.fmt);
var default_algo = new AlgoKey(OrderDataHelper.get_default_algo(t.fmt));
return this.load_algo_data_recursive(algo_data, default_algo);
}
}
return Disposable.empty;
}));
resources.Add(permission_src.Subscribe(_ => RaisePropertyChanged(nameof(editing_permissions))));
resources.Add(this.connect_data(permission_src));
resources.Add(_selected_format.observe_current(true).SubscribeHookLatest(f => {
using (_refresh_on_local_changes.defer_execution()) {
var algo_data = model.factory.default_algos(f);
var default_algo = new AlgoKey(OrderDataHelper.get_default_algo(f));
return this.load_algo_data_recursive(algo_data, default_algo);
}
}));
resources.Add(this.connect_data(Observable.Return(model)));
_dispose = resources;
on_editing_permissions(_get_permissions());
await algo_root.load_from_async(def_data.value);
_version.increment_version();
_edit_stack.reset(_version.value);
@@ -189,6 +169,11 @@ namespace mbts.spark {
}
}
// local methods
void on_editing_permissions(AlgoEditingPermissions eps) {
_editing_permissions = eps;
foreach (var vm in algo_roots)
vm.set_permissions(eps);
}
void on_can_redo_changed(bool oldval, bool newval) => can_redo_changed?.Invoke();
void on_can_undo_changed(bool oldval, bool newval) => can_undo_changed?.Invoke();
void on_local_changed() {
@@ -245,9 +230,6 @@ namespace mbts.spark {
// visual states
public ObservableCollection<string> edit_pane_visual_states => _edit_pane_vs_model.states;
public ObservableCollection<string> tree_view_visual_states => _tree_view_vs_model.states;
// editing permissions
public AlgoEditingPermissions editing_permissions_mask { get => _permissions_mask.mask; set => _permissions_mask.mask = value; }
public AlgoEditingPermissions editing_permissions => _permissions_mask.result;
#region IAlgoSelector
event PropertyChange<IAlgoVM> IAlgoSelector.selected_algo_changed { add => algo_root_changed += value; remove => algo_root_changed -= value; }
@@ -266,7 +248,7 @@ namespace mbts.spark {
void _load_algo_data(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo) {
var orig_algo = algo_root?.id;
_algo_roots.SyncWithDictionary(algo_data, d => OrderTypeVMFactory.create(d, is_expanded: true));
_algo_roots.SyncWithDictionary(algo_data, d => OrderTypeVMFactory.create(d, _editing_permissions, true));
if (!try_select(orig_algo) && !try_select(default_algo))
algo_root = algo_roots.FirstOrDefault();
//
@@ -316,19 +298,6 @@ namespace mbts.spark {
public IDisposable buffer_updates() => _template_update_buffer.buffer_updates();
public void load_def(CustomOrderDefinitionImmutable def) {
var key = OrderDataHelper.get_shallow_factory_key(def);
if (algo_root?.id == key) {
algo_root.load_from(def);
} else if (_algo_roots.TryGetValue(key, out IAlgoVM vm)) {
vm.load_from(def);
algo_root = vm;
} else {
algo_roots.Add(vm = _mk_algo_root(def));
algo_root = vm;
}
}
public void load(AlgoData data) {
_assert_can_load(data.key);
if (algo_root != null)
@@ -340,7 +309,7 @@ namespace mbts.spark {
// pass a copy of the persisted state - rather than use local values - because if something goes wrong, it is better to be in sync with data fresh from the server/api.
public AlgoDefinitionVM to_persisted(AlgoData def) =>
new AlgoDefinitionVM(_model, def, selected_format, editing_permissions_mask);
new AlgoDefinitionVM(_model, def);
public AlgoData create_algo_def() =>
new AlgoData(algo_root.definition, name, key);
@@ -353,6 +322,16 @@ namespace mbts.spark {
public override int GetHashCode() => key.GetHashCode();
public override string ToString() => algo_root == null ? $"!:{name}" : $"{algo_root.order_type}:{name}";
AlgoEditingPermissions _get_permissions() {
var permissions = AlgoEditingPermissions.None;
if (_model.algo_model.ui_configuration.can_modify_definition_values) {
permissions |= AlgoEditingPermissions.AllowEditValues | AlgoEditingPermissions.AllowSaveValues;
}
if (_model.algo_model.ui_configuration.can_modify_definition_structure || (_model.algo_model.templates_enabled() && _templates.Count == 0))
permissions |= AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure;
return permissions;
}
void _assert_can_load(AlgoKey data_key) {
if (key.mode != data_key.mode)
throw new InvalidOperationException($"{typeof(AlgoDefinitionVM)} '{nameof(key)}.{nameof(AlgoKey.mode)}' must be {data_key.mode} (actually {key.mode}), to load specified data.");
@@ -383,7 +362,7 @@ namespace mbts.spark {
}
IAlgoVM _mk_algo_root(CustomOrderDefinitionImmutable definition) =>
OrderTypeVMFactory.create(new AlgoData(definition), is_expanded: true);
OrderTypeVMFactory.create(new AlgoData(definition), editing_permissions: _editing_permissions, true);
void _on_metadata(AlgoDefinitionMetadata metadata) {
using (_refresh_on_local_changes.supress_execution()) {
@@ -1,119 +0,0 @@
<UserControl
x:Class="mbts.ui.spark.AlgoEditorView"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sp="clr-namespace:mbts.spark"
xmlns:u="clr-namespace:mbts.ui"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Controls/Order/AlgoPickerNodeView.xaml"/>
<ResourceDictionary Source="/view/Controls/Order/AlgoContentTemplateSelector.xaml"/>
</ResourceDictionary.MergedDictionaries>
<!--Primitive Constants-->
<sys:Double x:Key="HorizontalPush1">4</sys:Double>
<sys:Double x:Key="HorizontalPush2">8</sys:Double>
<sys:Double x:Key="HorizontalPush3">12</sys:Double>
<sys:Double x:Key="VerticalPush1">4</sys:Double>
<sys:Double x:Key="VerticalPush2">8</sys:Double>
<sys:Double x:Key="VerticalPush3">12</sys:Double>
<Thickness x:Key="Margin.LP1" Left="{StaticResource HorizontalPush1}"/>
<Thickness x:Key="Margin.LP2" Left="{StaticResource HorizontalPush2}"/>
<Thickness x:Key="Margin.LP3" Left="{StaticResource HorizontalPush3}"/>
<Thickness x:Key="Margin.TP1" Top="{StaticResource VerticalPush1}"/>
<Thickness x:Key="Margin.TP2" Top="{StaticResource VerticalPush2}"/>
<!--Converters-->
<uspcv:OrderTypeBrushConverter x:Key="OrderTypeBrushConverter" DefaultBrush="Transparent"/>
<!--Default Styles-->
<Style x:Key="ContentHostGroupBoxStyle" TargetType="GroupBox">
<Setter Property="Padding" Value="8"/>
</Style>
<Style TargetType="TextBox">
<Setter Property="u:TextBoxBehavior.EnterKeyBehavior" Value="CommitAndRelease"/>
<Setter Property="u:TextBoxBehavior.ReleaseOnEscape" Value="True"/>
</Style>
<Style TargetType="uag:AlgoPickerNodeView" BasedOn="{StaticResource {x:Type uag:AlgoPickerNodeView}}">
<Setter Property="DropDownStyleKey" Value="{Binding DataContext, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorView}, Converter={x:Static usp:AlgoContentTemplateSelector.AlgoDropDownStyleKeyConverter}}"/>
</Style>
</ResourceDictionary>
</UserControl.Resources>
<GroupBox
x:Name="content_host"
Grid.Row="2"
Background="Transparent"
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
Margin="0,6,0,0"
Padding="12,8"
>
<GroupBox.Style>
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}">
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Style.Triggers>
<Trigger Property="BorderBrush" Value="Transparent">
<Setter Property="BorderThickness" Value="0"/>
</Trigger>
<Trigger Property="usp:AlgoEditorView.IsMouseOverNodeContent" Value="True">
<Setter Property="BorderThickness" Value="2"/>
</Trigger>
</Style.Triggers>
</Style>
</GroupBox.Style>
<GroupBox.Header>
<ComboBox
ToolTipService.ShowOnDisabled="True"
Width="140"
Margin="{StaticResource Margin.LP1}"
ItemsSource="{Binding algos_view}"
SelectedItem="{Binding algo_root}"
>
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<DataTrigger Binding="{Binding templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:True}">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:False}">
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="ToolTip" Value="Cannot change structure while templates are present."/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
<ComboBox.IsEnabled>
<Binding Path="editing_permissions">
<Binding.Converter>
<cv:EnumHasFlagsConverter>
<sp:AlgoEditingPermissions>AllowEditStructure</sp:AlgoEditingPermissions>
</cv:EnumHasFlagsConverter>
</Binding.Converter>
</Binding>
</ComboBox.IsEnabled>
<ComboBox.ItemTemplate>
<DataTemplate>
<usp:OrderTypeItem DisplayName="{Binding display_name}" BadgeFill="{Binding meta.Color, Converter={StaticResource ColorToBrushConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</GroupBox.Header>
<ContentControl
Grid.IsSharedSizeScope="True"
Content="{Binding algo_root}"
ContentTemplateSelector="{StaticResource AlgoTemplateSelector}"
Margin="{StaticResource Margin.TP1}"
Visibility="{Binding algo_root.can_expand, Converter={StaticResource BooleanToVisibilityConverter}}"
>
<ContentControl.Resources>
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}"/>
</ContentControl.Resources>
</ContentControl>
</GroupBox>
</UserControl>
@@ -1,28 +0,0 @@
using mbts.ui.extensions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace mbts.ui.spark {
partial class AlgoEditorView : UserControl {
public AlgoEditorView() {
InitializeComponent();
content_host.PreviewMouseMove += content_host_PreviewMouseMove;
content_host.MouseEnter += content_host_MouseEnter;
content_host.MouseLeave += content_host_MouseLeave;
// local methods
void content_host_PreviewMouseMove(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
void content_host_MouseEnter(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
void content_host_MouseLeave(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
void refresh_is_mouse_over_content_host(MouseEventArgs e) => content_host.set_current_if_changed(IsMouseOverNodeContentProperty, this.ChildAtPoint<GroupBox>(e.GetPosition(this)) == content_host);
}
#region IsMouseOverNodeContent
public static readonly DependencyProperty IsMouseOverNodeContentProperty =
DependencyProperty.RegisterAttached("IsMouseOverNodeContent", typeof(bool), typeof(AlgoEditorView), new PropertyMetadata(false));
public static bool GetIsMouseOverNodeContent(DependencyObject d) => (bool)d.GetValue(IsMouseOverNodeContentProperty);
public static void SetIsMouseOverNodeContent(DependencyObject d, bool value) => d.SetValue(IsMouseOverNodeContentProperty, value);
#endregion
}
}
@@ -14,13 +14,10 @@ using mbts.ui.shell;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Windows.Data;
@@ -45,7 +42,7 @@ namespace mbts.spark {
public FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IWindowSettingsManager<GridSettings> settings_manager)
: this(bus, model, state_obs, _get_observable(bus, model, state_obs), settings_manager) { }
public FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IConnectableObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
_model = model;
_settings_manager = settings_manager;
/// Columns
@@ -101,17 +98,16 @@ namespace mbts.spark {
// grid settings
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, rows);
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
if (_settings_manager != null)
grid_settings_manager.load_from(_settings_manager.current_settings());
grid_settings_manager.load_from(settings_manager.current_settings());
//
_dispose = new CompositeDisposable(
_dispose = Disposable.Create(
ObservableAutoWrapper.connect(col_tups, columns, ct => ct.col),
fills_source.OnDispatcher().Subscribe(t => {
_rows.update(t);
Set(ref _total_row_count, t.state.Count, nameof(total_row_count));
}),
state_obs.OnDispatcher().Subscribe(s => {
if (s.grid != null && _settings_manager != null) {
if (s.grid != null) {
// this means we are not using defaults,
// Note: settings_manager in turn notifies grid_settings_manager of this update
_settings_manager.instance_settings = s.grid;
@@ -124,8 +120,8 @@ namespace mbts.spark {
};
rows_manager.on_settings(fs, s.sorting_criterion);
}),
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
(fills_source as IConnectableObservable<TableUpdate<FillTuple, string>>)?.Connect() ?? Disposable.empty
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
fills_source.Connect()
);
// local methods
void add_col<TFilter>(Columns col_key, string header, Func<Row, string> get_text, IDataGridColumnPersistentFilter<FillTuple, TFilter> filter, string filter_template, double width) =>
@@ -157,12 +153,12 @@ namespace mbts.spark {
public IEnumerable<SortingCriteria> get_sorting_criterion() =>
_sort_manager.create();
public void on_sorting(object sender, DIYGridSortingEventArgs e) =>
_sort_manager.on_sorting(e);
static Columns _col_key(ColumnBase column) =>
((IKeyedColumn<Columns>)column).key;
void IHandleGridSorting.on_sorting(object sender, DIYGridSortingEventArgs e) =>
_sort_manager.on_sorting(e);
public FilterStates get_filters() =>
_filters_manager.create();
@@ -1,49 +0,0 @@
using mbts.ui;
using mbts.ui.controls;
using System;
namespace mbts.spark {
/// <summary>
/// This class provides a stable DataContext for a fills view, with an internal <see cref="FillsVM"/> that may be disposed and recreated as needed.
/// </summary>
class FillsVMContainer : IHandleGridSorting, IDisposable, IWillLetYouKnowWhenIDispose {
bool _disposed;
FillsVM _value;
public event Action<bool> OnDisposed;
public FillsVM value {
get => _value;
set {
if (value != _value) {
if (_disposed)
throw new ObjectDisposedException(nameof(FillsVMContainer));
Disposable.replace(ref _value, value);
if (_value != null) {
rows.add_collection(_value.rows);
columns.add_collection(_value.columns);
} else {
rows.clear_collections();
columns.clear_collections();
}
}
}
}
public ObservableAggregateCollection<FillsVM.Row> rows { get; } = new ObservableAggregateCollection<FillsVM.Row>();
public ObservableAggregateCollection<DIYGrid.ColumnBase> columns { get; } = new ObservableAggregateCollection<DIYGrid.ColumnBase>();
public void Dispose() {
if (_disposed)
throw new ObjectDisposedException(nameof(FillsVMContainer));
value = null;
OnDisposed?.Invoke(true);
_disposed = true;
}
public void on_sorting(object sender, DIYGridSortingEventArgs e) {
if (!_disposed)
_value?.on_sorting(sender, e);
}
}
}
@@ -18,6 +18,7 @@ using StartupData = bts.shell.FillsWindowFactory.StartupData;
namespace mbts.spark {
class FillsWindow : SparkWindow<StartupData>, ISharedWindow {
public const string window_type_name = "Fills";
const string _thread_id = "Fills";
readonly AllowOnce _init_guard = new AllowOnce("Fills");
readonly OrdersModel _orders_model;
readonly IWindowSettingsManager<GridSettings> _settings_manager;
@@ -32,7 +33,6 @@ namespace mbts.spark {
_orders_model = orders_model;
_state = state;
_settings_manager = settings_manager;
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().fills ? "Fills" : null;
}
event Action<FillsWindowFactory.StartupData> _state_changed;
@@ -48,7 +48,7 @@ namespace mbts.spark {
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public string thread_id { get; }
public string thread_id => _thread_id;
public void thread_init() {
_init_guard.check_access();
@@ -33,7 +33,7 @@ namespace bts.shell {
_settings_hub = settings_hub;
_orders_model = orders_model;
_md_auxiliary_model = md_auxiliary_model;
_ladder_conf = new LadderConfig(Configuration.get<bts.config.LadderKnobs>(), Configuration.get<bts.config.MultiThreadedWindows>().ladder);
_ladder_conf = new LadderConfig(Configuration.get<bts.config.LadderKnobs>(), Configuration.get<bts.config.toggles.LaddersOnThreads>().enabled);
_dispose = Disposable.Create(
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
bus.subscribe(SparkTags.ShowLadderTag).OnDispatcher().Subscribe(s => show_windows(new[] { s })),
@@ -95,7 +95,7 @@
<PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Fill" Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Stroke" Color="#FF818181"/>
<Style x:Key="{x:Static sp:OrderBookGridView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
<Style x:Key="{x:Static sp:OrderBookView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Width" Value="16"/>
<Setter Property="Height" Value="16"/>
@@ -20,7 +20,7 @@ namespace mbts.spark {
}
}
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookView.Columns> {
public MiniOrderBookRowView() : base() { }
}
}
@@ -21,13 +21,14 @@ using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Media;
using static bts.shell.OrderBookWindowFactory;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
using static mbts.spark.OrderBookView;
using static mbts.ui.controls.DIYGrid;
using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
namespace mbts.spark {
// This interface is used to hide ColumnBase.SortDirection, and offer a SortDirection member that does NOT require a DependencyProperty lookup.
interface ICVOrderBookColumn : IKeyedColumn<Columns>, IGridColumn {
string sort_member_path { get; }
ListSortDirection? SortDirection { get; set; }
IDataGridColumnFilter<OrderSecurityAlgoMeta> filter { get; }
IComparer<OrderBookRowVM> comparer { get; }
@@ -77,6 +78,7 @@ namespace mbts.spark {
Add(symbol_column = new SimpleTextColumn<string[]>(
Columns.Symbol,
DefaultColumnHeaders.Symbol,
nameof(OrderBookRowVM.symbol),
r => r.symbol,
DefaultColumnWidths.Symbol,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string>(
@@ -90,6 +92,7 @@ namespace mbts.spark {
Add(time_created_column = new SimpleTextColumn<TimeFilterTuple>(
Columns.Time,
"Time",
nameof(OrderBookRowVM.time_created),
r => r.time_created_text,
DefaultColumnWidths.Time,
new TimeFilterVM<OrderSecurityAlgoMeta>(
@@ -101,6 +104,7 @@ namespace mbts.spark {
Add(last_change_column = new SimpleTextColumn<TimeFilterTuple>(
Columns.LastChange,
"Last Change",
nameof(OrderBookRowVM.last_change),
r => r.last_change_text,
DefaultColumnWidths.Time,
new TimeFilterVM<OrderSecurityAlgoMeta>(
@@ -112,6 +116,7 @@ namespace mbts.spark {
Add(user_column = new SimpleTextColumn<string[]>(
Columns.User,
DefaultColumnHeaders.User,
nameof(OrderBookRowVM.user),
r => r.user,
DefaultColumnWidths.User,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string>(
@@ -124,6 +129,7 @@ namespace mbts.spark {
Add(account_column = new SimpleTextColumn<string[]>(
Columns.Account,
DefaultColumnHeaders.Account,
nameof(OrderBookRowVM.account),
r => r.account,
DefaultColumnWidths.Account,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string, string[]>(
@@ -138,6 +144,7 @@ namespace mbts.spark {
Add(exchange_column = new SimpleTextColumn<Venue[]>(
Columns.Exchange,
DefaultColumnHeaders.Exchange,
nameof(OrderBookRowVM.exchange),
r => r.exchange.ToString(),
DefaultColumnWidths.Exchange,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, Venue>(
@@ -150,6 +157,7 @@ namespace mbts.spark {
Add(time_in_force_column = new SimpleTextColumn<TimeInForce[]>(
Columns.TimeInForce,
DefaultColumnHeaders.TimeInForce,
nameof(OrderBookRowVM.time_in_force),
r => r.time_in_force.ToString(),
DefaultColumnWidths.TimeInForce,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, TimeInForce>(
@@ -168,13 +176,14 @@ namespace mbts.spark {
Add(sub_type_column = new SimpleTextColumn<OrderSubType[]>(
Columns.SubType,
"Sub Type",
nameof(OrderBookRowVM.order_sub_type),
r => r.order_sub_type_text,
80,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, OrderSubType>(
EnumHelper.get_values<OrderSubType>().ToList(),
ort => ort.order.sub_type(),
nameof(OrderBookRowVM.order_sub_type)),
OrderBookGridView.SubTypeFilterTemplateKey,
SubTypeFilterTemplateKey,
_wrap_comparer(r => r.order_sub_type)));
// side
Add(side_column = new SideColumn(
@@ -196,6 +205,7 @@ namespace mbts.spark {
Add(price_column = new SimpleTextColumn<DoubleMinMaxState>(
Columns.Price,
"Price",
nameof(OrderBookRowVM.price),
r => r.price_text,
DefaultColumnWidths.Price,
new DoubleMinMax<OrderSecurityAlgoMeta>(
@@ -211,6 +221,7 @@ namespace mbts.spark {
Add(avg_price_column = new SimpleTextColumn<DoubleMinMaxState>(
Columns.AvgFilled,
"Avg Price",
nameof(OrderBookRowVM.avg_price),
r => r.avg_price_text,
DefaultColumnWidths.Price,
new DoubleMinMax<OrderSecurityAlgoMeta>(
@@ -222,6 +233,7 @@ namespace mbts.spark {
Add(last_price_column = new SimpleTextColumn<DoubleMinMaxState>(
Columns.LastPrice,
"Last Filled",
nameof(OrderBookRowVM.last_price),
r => r.last_price_text,
DefaultColumnWidths.Price,
new DoubleMinMax<OrderSecurityAlgoMeta>(
@@ -233,6 +245,7 @@ namespace mbts.spark {
Add(status_column = new SimpleTextColumn<OrderStatus[]>(
Columns.Status,
"Status",
nameof(OrderBookRowVM.status),
r => r.status.ToString(),
80,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, OrderStatus>(
@@ -250,6 +263,7 @@ namespace mbts.spark {
Add(security_key_column = new SimpleTextColumn<string>(
Columns.SecurityKey,
DefaultColumnHeaders.SecurityKey,
nameof(OrderBookRowVM.security_key),
r => r.security_key.ToString(),
DefaultColumnWidths.SecurityKey,
new RegexFilterVM<OrderSecurityAlgoMeta>(
@@ -261,6 +275,7 @@ namespace mbts.spark {
Add(order_key_column = new SimpleTextColumn<string>(
Columns.OrderKey,
DefaultColumnHeaders.OrderKey,
nameof(OrderBookRowVM.order_key),
r => r.order_key.ToString(),
DefaultColumnWidths.OrderKey,
new RegexFilterVM<OrderSecurityAlgoMeta>(
@@ -272,6 +287,7 @@ namespace mbts.spark {
Add(parent_key_column = new SimpleTextColumn<string>(
Columns.ParentKey,
"Parent Key",
nameof(OrderBookRowVM.parent_order_key),
r => r.parent_order_key.ToString(),
DefaultColumnWidths.ParentKey,
new RegexFilterVM<OrderSecurityAlgoMeta>(
@@ -279,17 +295,6 @@ namespace mbts.spark {
nameof(OrderBookRowVM.parent_order_key)),
DataGridFilterKnownTemplates.Regex,
_wrap_comparer(r => r.parent_order_key)));
// attached target order key
Add(attached_target_order_key_column = new SimpleTextColumn<string>(
Columns.AttachedTargetOrderKey,
DefaultColumnHeaders.AttachedTargetOrderKey,
r => r.order.attached_target_order_key().ToString(),
DefaultColumnWidths.AttachedTargetOrderKey,
new RegexFilterVM<OrderSecurityAlgoMeta>(
ort => ort.order.attached_target_order_key().ToString(),
nameof(OrderBookRowVM.parent_order_key)),
DataGridFilterKnownTemplates.Regex,
_wrap_comparer(r => r.order.attached_target_order_key())));
}
public event Action filter_changed;
@@ -318,13 +323,10 @@ namespace mbts.spark {
public SimpleTextColumn<string> security_key_column { get; }
public SimpleTextColumn<string> order_key_column { get; }
public SimpleTextColumn<string> parent_key_column { get; }
public SimpleTextColumn<string> attached_target_order_key_column { get; }
public (FilterStates filters, IReadOnlyList<SortingCriteria> sorting) create() => (
new FilterStates {
order_key_regex = order_key_column.filter.get_state(),
parent_key_regex = parent_key_column.filter.get_state(),
attached_target_order_key_regex = attached_target_order_key_column.filter.get_state(),
time_created = time_created_column.filter.get_state(),
users = user_column.filter.get_state(),
accounts = account_column.filter.get_state(),
@@ -362,8 +364,6 @@ namespace mbts.spark {
time_in_force_column.filter.load_state(fs.tifs);
exchange_column.filter.load_state(fs.exchanges);
order_key_column.filter.load_state(fs.order_key_regex);
parent_key_column.filter.load_state(fs.parent_key_regex);
attached_target_order_key_column.filter.load_state(fs.attached_target_order_key_regex);
avg_price_column.filter.load_state(fs.avg_price);
last_price_column.filter.load_state(fs.last_price);
status_column.filter.load_state(fs.statuses);
@@ -480,12 +480,13 @@ namespace mbts.spark {
protected readonly Func<OrderBookRowVM, string> _get_text;
GlyphContext _root_glyph_context, _child_glyph_context;
protected OrderbookColumnBase(Columns col_key, string header, Func<OrderBookRowVM, string> get_text, double width,
protected OrderbookColumnBase(Columns col_key, string header, Func<OrderBookRowVM, string> get_text, string sort_prop, double width,
IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, T> filter, string filter_template, IComparer<OrderBookRowVM> comparer) : base(col_key) {
id = key.ToString();
Width = width;
Header = header;
_get_text = get_text;
sort_member_path = sort_prop;
this.filter = filter;
_filter_template = filter_template;
_cached_sort_direction = SortDirection;
@@ -514,9 +515,9 @@ namespace mbts.spark {
}
public class SimpleTextColumn<TFilterState> : OrderbookColumnBase<TFilterState>, IRenderColumn<OrderBookRowVM> {
public SimpleTextColumn(Columns key, string header, Func<OrderBookRowVM, string> get_text, double width,
public SimpleTextColumn(Columns key, string header, string sort_prop, Func<OrderBookRowVM, string> get_text, double width,
IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, TFilterState> filter, string filter_template, IComparer<OrderBookRowVM> comparer)
: base(key, header, get_text, width, filter, filter_template, comparer) { }
: base(key, header, get_text, sort_prop, width, filter, filter_template, comparer) { }
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,
@@ -526,7 +527,7 @@ namespace mbts.spark {
#region Explicit Column Defs
public class SideColumn : OrderbookColumnBase<Side[]>, IRenderColumn<OrderBookRowVM> {
public SideColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, Side[]> filter)
: base(Columns.Side, "B/S", r => r.side.ToString(), DefaultColumnWidths.Side, filter, DataGridFilterKnownTemplates.EnumValues, _wrap_comparer(r => r.side)) { }
: base(Columns.Side, "B/S", r => r.side.ToString(), nameof(OrderBookRowVM.side), DefaultColumnWidths.Side, filter, DataGridFilterKnownTemplates.EnumValues, _wrap_comparer(r => r.side)) { }
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.side.ToString(), position, row_height,
@@ -535,7 +536,9 @@ namespace mbts.spark {
public class SizeColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
public SizeColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, UIntMinMaxState> filter)
: base(Columns.Size, "Qty", r => r.quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.quantity)) { }
: base(Columns.Size, "Qty", r => r.quantity.ToString(), nameof(OrderBookRowVM.quantity), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.quantity)) {
sort_member_path = nameof(OrderBookRowVM.quantity);
}
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.quantity.ToString(), position, row_height,
@@ -544,7 +547,9 @@ namespace mbts.spark {
public class FilledColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
public FilledColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, UIntMinMaxState> filter)
: base(Columns.Filled, "Filled Qty", r => r.filled_quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.filled_quantity)) { }
: base(Columns.Filled, "Filled Qty", r => r.filled_quantity.ToString(), nameof(OrderBookRowVM.filled_quantity), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.filled_quantity)) {
sort_member_path = nameof(OrderBookRowVM.filled_quantity);
}
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.filled_quantity.ToString(), position, row_height,
@@ -557,6 +562,7 @@ namespace mbts.spark {
: base(Columns.OrderType, new Binding(nameof(OrderBookRowVM.badge_fill)), new Binding(nameof(OrderBookRowVM.display_name)), filter) {
id = key.ToString();
this.filter = filter;
sort_member_path = nameof(OrderBookRowVM.order_type);
font_weight_binding = RenderUtils.get_font_weight_binding();
_cached_sort_direction = SortDirection;
}
@@ -581,7 +587,8 @@ namespace mbts.spark {
id = key.ToString();
this.filter = filter;
Header = "Last Message";
Width = DefaultColumnWidths.LastMessage;
Width = DIYGrid.DefaultColumnWidths.LastMessage;
sort_member_path = nameof(OrderBookRowVM.last_msg);
tooltip_binding = new Binding(nameof(OrderBookRowVM.last_msg));
}
@@ -609,6 +616,7 @@ namespace mbts.spark {
this.filter = filter;
Header = DefaultColumnHeaders.Security;
Width = DefaultColumnWidths.Security;
sort_member_path = nameof(OrderBookRowVM.security);
_cached_sort_direction = SortDirection;
}
@@ -626,7 +634,7 @@ namespace mbts.spark {
toggle_button.SetBinding(ToggleButton.IsCheckedProperty, new Binding(nameof(OrderBookRowVM.is_expanded)));
toggle_button.SetBinding(ToggleButton.MarginProperty, new Binding(nameof(OrderBookRowVM.depth_level)) { Converter = _margin_converter });
toggle_button.SetBinding(ToggleButton.VisibilityProperty, new Binding(nameof(OrderBookRowVM.is_parent)) { Converter = _expander_visiblilty_converter });
toggle_button.SetResourceReference(ToggleButton.StyleProperty, OrderBookGridView.ExpandCollapseToggleStyleKey);
toggle_button.SetResourceReference(ToggleButton.StyleProperty, ExpandCollapseToggleStyleKey);
//
var security_view = new TextBlock { Foreground = font_brush };
security_view.SetBinding(TextBlock.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
@@ -643,7 +651,6 @@ namespace mbts.spark {
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
grid.Children.Add(toggle_button);
grid.Children.Add(security_view);
grid.VerticalAlignment = VerticalAlignment.Center;
//
var border = new Border { Child = grid };
@@ -6,14 +6,16 @@ using mbts.ui;
using mbts.ui.controls;
using mbts.ui.shell;
using System;
using System.Reactive;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Reactive.Linq;
namespace mbts.spark {
partial class OrderBookRowVM : DisposableViewModelBase, IEquatable<OrderBookRowVM>, DIYGrid.IRowVM {
partial class OrderBookRowVM : DisposableViewModelBase, IEquatable<OrderBookRowVM>, DIYGrid.IRowVM, DIYGrid.IActionableRowVM {
const string _unknown_security_price_format_string = "0.#########";
const int _unknown_security_price_num_fractional_digits = 9;
readonly OrderBookVM _owner;
@@ -44,13 +46,25 @@ namespace mbts.spark {
modify_order_price?.TryRaiseCanExecuteChanged();
}
//// This takes care of the case where the OB has all rows visible
//// and an order is cancelled outside of the OB (say via the ladder).
//// Since the OB window Commands only revaluate their CanExecuteChanged
//// when the OrderBookVM.selected_order_changes, this emulates the selected
//// order changing since the current_row setter invokes the selected_order_changed
//// handler
public IObservable<OrderSecurityAlgoMeta> subscribe_data() {
return Observable.FromEvent<Action, OrderSecurityAlgoMeta>(
cb => () => cb(order_tuple),
cb => invalidated += cb,
cb => invalidated -= cb);
}
protected override void Dispose(bool disposing) {
this.invalidated -= OrderBookRowVM_invalidated;
base.Dispose(disposing);
}
public event Action invalidated;
public event Action<(OrderSecurityAlgoMeta oldval, OrderSecurityAlgoMeta newval)> updated;
// Core
public OrderSecurityAlgoMeta order_tuple { get; private set; }
@@ -128,8 +142,8 @@ namespace mbts.spark {
public ICommand toggle_command => _toggle_expanded_cmd ?? (
_toggle_expanded_cmd = new DelegateCommand(() => is_expanded = !is_expanded));
public ICommand show_order_modify_cmd =>
_show_order_modify_cmd ?? (_show_order_modify_cmd = new DelegateCommand(() => _owner.show_order_modify(this), () => status == OrderStatus.Working));
ICommand DIYGrid.IActionableRowVM.default_command => _show_order_modify_cmd ??
(_show_order_modify_cmd = new DelegateCommand(() => _owner.show_order_modify(this), () => status == OrderStatus.Working));
public ICommand cancel_command => _cancel_cmd ??
(_cancel_cmd = new DelegateCommand(() => _owner.cancel_order(this), () => status == OrderStatus.Working));
@@ -171,10 +185,10 @@ namespace mbts.spark {
public void update(OrderSecurityAlgoMeta order_tuple) {
Log.assert(order_tuple.order.Key == this.order.Key, "OrderBookRowVM: update_order was called with an order with the wrong key.");
bool had_security = _security.HasValue;
var old_order_tuple = this.order_tuple;
var old_meta = this.order_tuple.meta;
// _show_order_modify_cmd is lazy (instantiated in the default_command getter); it might be null, don't not force instantiation
bool? could_modify_execute = _show_order_modify_cmd?.CanExecute(null);
OrderRecordImmutable old_order = old_order_tuple.order;
OrderRecordImmutable old_order = order;
this.order_tuple = order_tuple;
// most cells are "render-cells," which don't use databindings - the only way to update such cells is to raise the invalidated event
bool invalidate = !(
@@ -201,12 +215,10 @@ namespace mbts.spark {
// need to raise invalidated to refresh the price cells (the security.ContractSpec determines how they are formatted - Ticks!)
invalidate = true;
}
if (old_order_tuple.meta?.badge_color != order_tuple.meta?.badge_color) {
if (old_meta?.badge_color != order_tuple.meta?.badge_color) {
_badge_fill = null;
RaisePropertyChanged(nameof(badge_fill));
}
if (old_order_tuple.display_name() != order_tuple.display_name())
RaisePropertyChanged(nameof(display_name));
if (old_order.status() != order.status()) {
invalidate = true;
RaisePropertyChanged(nameof(status));
@@ -216,7 +228,6 @@ namespace mbts.spark {
}
if (could_modify_execute.HasValue && could_modify_execute.Value != _show_order_modify_cmd.CanExecute(null))
_show_order_modify_cmd.RaiseCanExecuteChanged();
updated?.Invoke((old_order_tuple, order_tuple));
if (invalidate)
raise_invalidated();
}
@@ -1,12 +1,9 @@
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.shell;
using bts.spark;
using bts.utils;
using mbts.persist;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
@@ -14,149 +11,68 @@ using mbts.ui.shell;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Input;
using static bts.shell.OrderBookWindowFactory;
using static mbts.spark.OrderBookView;
using _GridSettingsManager =
mbts.ui.GridSettingsManager<
mbts.spark.OrderBookRowVM,
bts.api.orderman.OrderSecurity,
bts.shell.INotificationServiceDataCache,
mbts.OrderRecordTuplePredicate,
bts.shell.OrderBookWindowFactory.OrderBookGridSettings>;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
namespace mbts.spark {
partial class OrderBookVM : DisposableViewModelBase, IHandleRowSelection, IHandleGridSorting {
const double
_min_visible_right_panel_width = 200,
_min_visible_fills_slave_grid_height = 100;
partial class OrderBookVM : ViewModelBase, IDisposable, IHandleRowSelection, IHandleGridSorting, IWillLetYouKnowWhenIDispose {
readonly IBus _bus;
readonly OrdersModel _model;
readonly Property<OrderBookRowVM> _current_row;
readonly Property<bool> _fills_slave_grid_is_visible;
readonly OrderBookRowCollection _row_collection;
readonly ObservableCollection<OrderBookRowVM> _selected_rows = new ObservableCollection<OrderBookRowVM>();
readonly OrderBookColumnCollection _columns;
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
readonly SelectedAlgosMonitor _selected_algo_mon;
double
_raw_right_panel_width = OrderBookStartupData.Default.right_panel_width,
_raw_fills_slave_grid_height = OrderBookStartupData.Default.fills_slave_grid_height;
readonly List<IDisposable> _resources;
OrderBookStartupData _state;
bool
_algo_editor_is_visible = OrderBookStartupData.Default.algo_editor_is_visible,
_is_attaching_algos,
_is_row_collection_populated;
bool _is_row_collection_populated;
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_algo_mon = new SelectedAlgosMonitor(model.dynamic_helper);
order_mod_vm = new OrderParamsVM(this, AlgoDefinitionVM.create(model, new AlgoData(new CustomOrderDefinitionImmutable(new CustomOrderDefinition { Limit = new LimitOrderDefinition() })), editing_permissions: AlgoEditingPermissions.AllowEditValues));
order_mod_vm.algo_vm.editing_permissions_mask = AlgoEditingPermissions.None;
order_mod_vm.algo_vm.algo_root = null;
attach_vm = new OrderParamsVM(this);
_fills_slave_grid_is_visible = backing(nameof(fills_slave_grid_is_visible), false);
_settings_manager = settings_manager;
_columns = new OrderBookColumnCollection(bus, model, model.on_order_securities.Select(x => (x.added.Values, x.removed.Values)));
// we use an ObservableAppendCollection b/c the mini-ob needs a cancel column at the front
columns = new ObservableAppendCollection<DIYGrid.ColumnBase>(_columns);
_row_collection = new OrderBookRowCollection(ort => new OrderBookRowVM(this, ort, rows_are_initially_expanded), _columns.get_sorting_data().comparer) { filter = _columns.get_filter() };
_row_collection.CollectionChanged += _row_collection_CollectionChanged;
_row_collection.CollectionChanged += _row_collection_CollectionChanged;
_current_row = backing<OrderBookRowVM>(nameof(current_row));
_columns.filter_changed += on_filter_changed;
collapse_all_rows_command = new DelegateCommand(_row_collection.collapse_all);
expand_all_rows_command = new DelegateCommand(_row_collection.expand_all);
var modify_selected_algos_async_cmd = new AsyncCommand(_modify_selected_algos_async, () => _selected_algo_mon.all_active);
modify_selected_algos_command = modify_selected_algos_async_cmd;
var send_attached_orders_async_cmd = new AsyncCommand(send_attached_orders_async, () => _selected_algo_mon.all_active);
send_attached_orders_cmd = send_attached_orders_async_cmd;
cancel_attach_orders_cmd = new DelegateCommand(() => is_attaching_algos = false);
toggle_algo_editor_is_visible_cmd = new DelegateCommand(() => algo_editor_is_visible = !algo_editor_is_visible);
toggle_fills_slave_grid_is_visible_cmd = new DelegateCommand(() => fills_slave_grid_is_visible = !fills_slave_grid_is_visible);
grid_settings_manager = _create_grid_settings_manager(owner_title, bus, notification_service, model, rows, core_columns);
IDisposable attach_vm_hooks = null;
begin_attaching += on_begin_attaching;
end_attaching += on_end_attaching;
concat_disposable(
connect_common_selected_params(order_mod_vm),
_selected_algo_mon.subscribe_all_active().Subscribe(on_all_selected_orders_active_changed),
_resources = new List<IDisposable> {
model.on_order_tuples.OnDispatcher().Subscribe(_row_collection.update),
grid_settings_manager,
order_mod_vm.algo_vm.connect_data(Observable.Return(model)),
order_mod_vm.algo_vm.connect_data(_selected_algo_mon.subscribe_common_spreader_sec()),
order_mod_vm.algo_vm,
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(_selected_algo_mon.on_security_md_map),
attach_vm,
_fills_slave_grid_is_visible.observe_current(true)
.Subscribe(is_visible => fills_vm.value = is_visible ?
new FillsVM(
bus, model, Observable.Return(FillsWindowFactory.StartupData.Default()),
model.on_fill_tuples.Filter(mk_fills_slave_predicate_src(), ft => ft.fill.FillKey, _ => false), null
) :
null),
Disposable.Create(() => {
_columns.filter_changed -= on_filter_changed;
_row_collection.CollectionChanged -= _row_collection_CollectionChanged;
begin_attaching -= on_begin_attaching;
end_attaching -= on_end_attaching;
Disposable.dispose(ref attach_vm_hooks);
Disposable.dispose(_row_collection);
}),
_columns);
_columns,
_current_row.SubscribeHookLatest(
r => r.newval.subscribe_data().Subscribe(
data => selected_order_changed?.Invoke(data.order)
)
)
};
if (settings_manager != null) {
concat_disposable(settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager));
}
IObservable<Func<FillTuple, bool>> mk_fills_slave_predicate_src() =>
selected_rows.ObserveSelf(true)
.BufferLatest(TimeSpan.FromSeconds(0.2))
.Select(_ => mk_fills_slave_predicate());
Func<FillTuple, bool> mk_fills_slave_predicate() {
var keys = new HashSet<uint>(selected_rows.SelectMany(r => new[] { r }.Concat(_row_collection.get_descendent_rows(r))).Select(r => r.order_key));
return ft => keys.Contains(ft.fill.OrderKey);
_resources.Add(settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager));
}
void on_filter_changed() => _row_collection.filter = _columns.get_filter();
void on_all_selected_orders_active_changed(bool newval) {
order_mod_vm.algo_vm.editing_permissions_mask = newval ? AlgoEditingPermissions.AllowEditValues : AlgoEditingPermissions.None;
modify_selected_algos_async_cmd.RaiseCanExecuteChanged();
send_attached_orders_async_cmd.RaiseCanExecuteChanged();
}
async Task send_attached_orders_async() {
is_attaching_algos = false;
try {
double price = attach_vm.price;
uint size = attach_vm.size_calculator.value ?? 0;
var algo = attach_vm.algo_vm.algo_root.created_assigned();
var order_tups = selected_rows.Select(r => r.order_tuple);
await Task.WhenAll(order_tups.Select(t => _model.send_order(t.order.Def.with(attach_target_order_key: t.order.Key, custom_def: algo), price, size)));
} catch (Exception ex) {
Log.Ui.error($"Error sending attached orders: {ex}.");
}
}
void on_begin_attaching() => Disposable.replace(ref attach_vm_hooks, connect_common_selected_params(attach_vm));
void on_end_attaching() => Disposable.dispose(ref attach_vm_hooks);
IDisposable connect_common_selected_params(OrderParamsVM vm) =>
Disposable.Create(
_selected_algo_mon.subscribe_common_algo().Subscribe(newval => {
if (newval.HasValue) {
order_mod_vm.algo_vm.load_def(newval.Value);
} else {
order_mod_vm.algo_vm.algo_root = null;
}
}),
_selected_algo_mon.subscribe_common_sec_md().Subscribe(newval => vm.security_md = newval),
_selected_algo_mon.subscribe_price().Subscribe(newval => vm.price = newval),
_selected_algo_mon.subscribe_size().Subscribe(newval => vm.size_calculator.value = newval));
}
private void _row_collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
@@ -164,7 +80,7 @@ namespace mbts.spark {
}
public event Action<OrderRecordImmutable?> selected_order_changed;
public event Action begin_attaching, end_attaching;
public event Action<bool> OnDisposed;
public ObservableAppendCollection<DIYGrid.ColumnBase> columns { get; }
public ObservableCollection<DIYGrid.ColumnBase> core_columns => _columns;
@@ -183,56 +99,11 @@ namespace mbts.spark {
}
}
public OrderRecordImmutable? current_order => current_row?.order;
public AlgoDefinitionVM algo_vm => order_mod_vm.algo_vm;
public OrderParamsVM order_mod_vm { get; }
public FillsVMContainer fills_vm { get; } = new FillsVMContainer();
public bool fills_slave_grid_is_visible { get => _fills_slave_grid_is_visible.Value; set => _fills_slave_grid_is_visible.set(value); }
public OrderParamsVM attach_vm { get; }
public ObservableCollection<OrderBookRowVM> selected_rows { get; } = new ObservableCollection<OrderBookRowVM>();
public IEnumerable<OrderRecordImmutable> selected_orders => selected_rows.Select(r => r.order);
public IEnumerable<OrderRecordImmutable> selected_orders => _selected_rows.Select(r => r.order);
public _GridSettingsManager grid_settings_manager { get; }
public bool is_attaching_algos {
get => _is_attaching_algos;
set {
if (Set(ref _is_attaching_algos, value))
(value ? begin_attaching : end_attaching)?.Invoke();
}
}
public ICommand collapse_all_rows_command { get; }
public ICommand expand_all_rows_command { get; }
public ICommand modify_selected_algos_command { get; }
public ICommand send_attached_orders_cmd { get; }
public ICommand cancel_attach_orders_cmd { get; }
public ICommand toggle_algo_editor_is_visible_cmd { get; }
public ICommand toggle_fills_slave_grid_is_visible_cmd { get; }
public bool algo_editor_is_visible {
get => _algo_editor_is_visible;
set {
if (Set(ref _algo_editor_is_visible, value))
RaisePropertyChanged(nameof(right_panel_width));
}
}
public double raw_right_panel_width => _raw_right_panel_width;
public double right_panel_width {
get => algo_editor_is_visible ? Math.Max(raw_right_panel_width, _min_visible_right_panel_width) : 0;
set {
if (_raw_right_panel_width != value) {
_raw_right_panel_width = value;
if (algo_editor_is_visible)
RaisePropertyChanged();
}
}
}
public double fills_slave_grid_height {
get => algo_editor_is_visible ? Math.Max(_raw_fills_slave_grid_height, _min_visible_fills_slave_grid_height) : 0;
set {
if (_raw_fills_slave_grid_height != value) {
_raw_fills_slave_grid_height = value;
if (algo_editor_is_visible)
RaisePropertyChanged();
}
}
}
public bool is_row_collection_populated {
get => _is_row_collection_populated;
set => Set(ref _is_row_collection_populated, value);
@@ -243,18 +114,16 @@ namespace mbts.spark {
if (e.RemovedItems != null) {
foreach (var row_vm in e.RemovedItems.Cast<OrderBookRowVM>()) {
row_vm.is_selected = false;
_selected_algo_mon.on_row_unselected(row_vm);
}
}
if (e.AddedItems != null) {
foreach (var row_vm in e.AddedItems.Cast<OrderBookRowVM>()) {
row_vm.is_selected = true;
_selected_algo_mon.on_row_selected(row_vm);
}
}
RowSelectionHelper.apply_changes(selected_rows, e);
RowSelectionHelper.apply_changes(_selected_rows, e);
selected_order_changed?.Invoke(current_order);
} catch(Exception ex) {
} catch (Exception ex) {
// [John]I observed a strange null-reference exception in this callback when the launcher was shutting down (see https://bluetradesys.atlassian.net/browse/SPARK-2089)
Log.Ui.error($"[{nameof(OrderBookVM)}] Error handling selection change: {ex}");
}
@@ -263,16 +132,12 @@ namespace mbts.spark {
public OrderBookStartupData create() {
var (filters, sorting) = _columns.create();
var gs = grid_settings_manager.create();
return new OrderBookStartupData(filters, gs.general.with(sorting_criterion: sorting), _algo_editor_is_visible, raw_right_panel_width, _raw_fills_slave_grid_height, fills_slave_grid_is_visible, gs.predicate_animations, use_defaults: _settings_manager.instance_settings == null);
return new OrderBookStartupData(filters, gs.general.with(sorting_criterion: sorting), gs.predicate_animations, use_defaults: _settings_manager.instance_settings == null);
}
public void load_from(OrderBookStartupData state) {
if (state == null) return;
_state = state;
algo_editor_is_visible = state.algo_editor_is_visible;
right_panel_width = state.right_panel_width;
fills_slave_grid_height = state.fills_slave_grid_height;
fills_slave_grid_is_visible = state.fills_slave_grid_is_visible;
var gs = state.grid_settings_wrapper();
if (gs != null) {
grid_settings_manager.load_from(gs);
@@ -317,13 +182,6 @@ namespace mbts.spark {
}
}
public void begin_attach_algos() => is_attaching_algos = true;
public void detach_selected_algos() =>
_detach_orders(selected_rows.Where(r => r.order.can_detach()).ToList()).ignore();
public OrderMetadata get_metadata(OrderBookRowVM row) => _model.factory.get_metadata(row.order_type);
internal void show_order_modify(OrderBookRowVM row) =>
_bus.post(SparkTags.ShowModifyOrderTag, row.order_key);
@@ -359,40 +217,11 @@ namespace mbts.spark {
dr.is_visible = false;
}
async Task _attach_orders(IReadOnlyList<OrderBookRowVM> rows, OrderBookRowVM target) {
Log.Ui.info($"{nameof(OrderBookVM)} begin attaching orders ({rows.Count} total)...");
try {
await Task.WhenAll(rows.Select(r => _model.cancel_order(r.order_key)));
} catch (Exception ex) {
Log.Ui.error($"Error canceling orders, in advance of attachment: {ex}.");
}
Log.Ui.info($"{nameof(OrderBookVM)} succesfully canceled orders ({rows.Count} total), in advance of attachment. Begin sending attached orders ({rows.Count} total)...");
try {
await Task.WhenAll(rows.Select(r => _model.send_order(r.order.Def.with(attach_target_order_key: target.order_key), r.order.State.Price, (uint)r.order.working_quantity())));
} catch (Exception ex) {
Log.Ui.error($"Error sending attached orders: {ex}.");
}
Log.Ui.info($"{nameof(OrderBookVM)} succesfully sent attached orders ({rows.Count} total).");
}
public OrderMetadata get_metadata(OrderBookRowVM row) => _model.factory.get_metadata(row.order_type);
async Task _detach_orders(IReadOnlyList<OrderBookRowVM> orders) {
Log.Ui.info($"{nameof(OrderBookVM)} begin detaching orders ({orders.Count} total)...");
try {
await Task.WhenAll(orders.Select(r => _model.detach_order(r.order_key)));
} catch (Exception ex) {
Log.Ui.error($"{nameof(OrderBookVM)} error detaching orders: {ex}.");
}
}
async Task _modify_selected_algos_async() {
var malgo = order_mod_vm.algo_vm.algo_root?.created_assigned();
var algo = malgo == null ? (CustomOrderDefinitionImmutable?)null : new CustomOrderDefinitionImmutable(malgo);
double? price = DoubleHelper.nan_nullable(order_mod_vm.price);
uint? size = order_mod_vm.size_calculator.value;
foreach (var r in selected_rows) {
var ralgo = algo.HasValue && !OrderDataHelper.EqualsHelpers.equals(r.order.Def.CustomDef, algo.Value) ? algo : null;
await _model.update_order(r.order, new_price: price, new_size: size, new_algo: ralgo);
}
public void Dispose() {
Disposable.dispose(_resources);
OnDisposed?.Invoke(true);
}
static _GridSettingsManager _create_grid_settings_manager(
@@ -407,21 +236,25 @@ namespace mbts.spark {
col_settings_flags[c.ToString()] = GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableFontWeight;
}
return new _GridSettingsManager(
owner_title, bus, notification_service, model, rows, columns,
owner_title, bus, model, rows, columns,
() => new OrderRecordTuplePredicate(),
() => {
var pvm = PredicateBuilderVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
var pvm = PredicateVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
OrderRecordTuplePredicateVMFactory.get_oneofs(bus, model), rp => rp.predicate_type_case);
pvm.selected_predicate_type = pvm.predicate_types.FirstOrDefault();
return pvm;
},
col_id => {
var pvm = PredicateBuilderVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
var pvm = PredicateVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
OrderRecordTuplePredicateVMFactory.get_oneofs(bus, model), rp => rp.predicate_type_case);
pvm.selected_predicate_type = _corresponding_predicate_type(EnumHelper.parse_or_default<Columns>(col_id));
return pvm;
},
(gen, anims) => new OrderBookGridSettings(gen, anims.Select(a => new OrderRecordTuplePredicateAnimations(a.predicate_builder, a.cell_animations, a.row_animations, a.sound_notifications))),
(notification_service == null) ? null : notification_service.data_cache,
(gen, anims) => new OrderBookGridSettings(gen, anims.Select(a => new OrderRecordTuplePredicateAnimations(a.predicate_data, a.cell_animations, a.row_animations, a.sound_notifications))),
p => p.create_predicate(notification_service.data_cache),
p => p.create_notification_predicate(notification_service.data_cache),
notification_service.link_to_order,
notification_service.link_to_order,
col_settings_flags);
}
@@ -447,225 +280,8 @@ namespace mbts.spark {
case Columns.SecurityKey: return OrderRecordTuplePredicate.PredicateTypeOneofCase.SecurityKey;
case Columns.OrderKey: return OrderRecordTuplePredicate.PredicateTypeOneofCase.OrderKey;
case Columns.ParentKey: return OrderRecordTuplePredicate.PredicateTypeOneofCase.ParentKey;
case Columns.AttachedTargetOrderKey: return OrderRecordTuplePredicate.PredicateTypeOneofCase.AttachedTargetOrderKey;
default: return default;
}
}
static IObservable<ISet<uint>> _subscribe_selected_order_keys(ObservableCollection<OrderBookRowVM> selected_rows) =>
Observable.Create<ISet<uint>>(o => {
bool waiting_for_mouse_up = false;
var sub_container = new DisposableContainer();
var builder = ImmutableHashSet<uint>.Empty.ToBuilder();
foreach (var row in selected_rows)
builder.Add(row.order_key);
var selected_keys = builder.ToImmutable();
o.OnNext(selected_keys);
return Disposable.Create(selected_rows.Subscribe(on_selection_changed), sub_container);
void on_selection_changed(object sender, NotifyCollectionChangedEventArgs e) {
if (waiting_for_mouse_up)
return;
switch (e.Action) {
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Replace:
foreach (OrderBookRowVM row in e.NewItems)
selected_keys = selected_keys.Add(row.order_key);
break;
case NotifyCollectionChangedAction.Remove:
foreach (OrderBookRowVM row in e.OldItems)
selected_keys = selected_keys.Remove(row.order_key);
break;
case NotifyCollectionChangedAction.Reset:
selected_keys = ImmutableHashSet<uint>.Empty;
break;
default: return;
}
if (Mouse.LeftButton == MouseButtonState.Pressed) {
waiting_for_mouse_up = true;
wait_for_mouse_up();
async void wait_for_mouse_up() {
await Wait.until_true(() => Mouse.LeftButton == MouseButtonState.Released, TimeSpan.FromSeconds(60), 333);
builder = ImmutableHashSet<uint>.Empty.ToBuilder();
foreach (var row in selected_rows)
builder.Add(row.order_key);
selected_keys = builder.ToImmutable();
o.OnNext(selected_keys);
waiting_for_mouse_up = false;
}
} else {
o.OnNext(selected_keys);
}
}
});
public class OrderParamsVM : DisposableViewModelBase {
readonly SelectorProperty<AccountTuple, AccountTuple?> _accounts;
readonly OrderBookVM _owner;
SecurityMD? _security_md;
double _price = 0;
public OrderParamsVM(OrderBookVM owner)
: this(owner,
AlgoDefinitionVM.create(
owner._model,
new AlgoData(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg)),
editing_permissions: AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowEditValues)) { }
public OrderParamsVM(OrderBookVM owner, AlgoDefinitionVM algo_vm) {
_owner = owner;
size_calculator = new CalculatorLogic(null, Observable.Return(LadderSettings.Default.calculator_buttons));
this.algo_vm = algo_vm;
_accounts = backing(
nameof(selected_account),
_model.factory.subscribe_account_tuples(),
_model.admin_model.on_account_tuples.Where(ts => ts.Count > 0).Select(ts => new InlineUserProperty<AccountTuple>(ts[0])).Take(1),
a => new AccountTuple?(a),
() => accounts.Cast<AccountTuple?>().FirstOrDefault(),
EqualityComparer<AccountTuple?>.Default,
new[] { new PropertyValidation<AccountTuple?>(a => (a.HasValue && !string.IsNullOrWhiteSpace(a.Value.name) && accounts.Contains(a.Value), "Please select an account")) }.ToReadOnlyList());
set_price_command = new DelegateCommand<double>(p => price = p);
bbo = new BBOVM { security_md = security_md };
_dispose = Disposable.Create( bbo, algo_vm);
}
public ReadOnlyObservableCollection<AccountTuple> accounts => _accounts.items;
public AccountTuple? selected_account { get => _accounts.selected_item; set => _accounts.selected_item = value; }
public SecurityImmutable? security => _owner._selected_algo_mon.common_sec;
public SecurityMD? security_md {
get => _security_md;
set {
if (Set(ref _security_md, value)) {
RaisePropertyChanged(nameof(security), nameof(spec));
bbo.security_md = value;
}
}
}
public ContractSpecificationImmutable? spec => security?.ContractSpec;
public double price { get => _price; set => Set(ref _price, value); }
public CalculatorLogic size_calculator { get; }
public AlgoDefinitionVM algo_vm { get; }
public BBOVM bbo { get; }
public ICommand send_order { get; }
public ICommand set_price_command { get; }
OrdersModel _model => _owner._model;
}
/// <summary>
/// Monitors selected rows, and determines whether or not they are all active-orders, with common parameters.
/// </summary>
class SelectedAlgosMonitor {
static readonly TimeSpan _throttle = TimeSpan.FromSeconds(0.1);
readonly CommonValueMonitor<AlgoSecurityKey> _algo_mon = new CommonValueMonitor<AlgoSecurityKey>();
readonly CommonValueMonitor<SecurityImmutable?> _spreader_sec_mon = new CommonValueMonitor<SecurityImmutable?>();
readonly CommonValueMonitor<SecurityImmutable?> _sec_mon = new CommonValueMonitor<SecurityImmutable?>();
readonly CommonValueMonitor<double> _price_mon = new CommonValueMonitor<double>(double.NaN);
readonly CommonValueMonitor<uint> _size_mon = new CommonValueMonitor<uint>();
readonly CommonValueMonitor<AccountTuple> _account_mon = new CommonValueMonitor<AccountTuple>();
readonly CommonValueMonitor<bool> _active_mon = new CommonValueMonitor<bool>();
readonly CommonValueMonitorMultiplexor<OrderSecurityAlgoMeta> _group_mon = new CommonValueMonitorMultiplexor<OrderSecurityAlgoMeta>();
readonly DynamicOrderHelper _dyn_helper;
IReadOnlyDictionary<uint, SecurityMD> _security_md_map = DictionaryHelper.empty<uint, SecurityMD>();
public SelectedAlgosMonitor(DynamicOrderHelper dyn_helper) {
_dyn_helper = dyn_helper;
_group_mon.add_member_mon(_algo_mon, _algo_key);
_group_mon.add_member_mon(_spreader_sec_mon, _get_spreader_sec, s => s.HasValue);
_group_mon.add_member_mon(_sec_mon, osam => osam.security, s => s.HasValue);
_group_mon.add_member_mon(_price_mon, osam => osam.order.price());
_group_mon.add_member_mon(_size_mon, osam => osam.order.quantity());
_group_mon.add_member_mon(_account_mon, osam => new AccountTuple(osam.order.account_id(), osam.order.account()));
_group_mon.add_member_mon(_active_mon, osam => osam.order.is_active());
}
event Action _security_md_map_changed;
public CustomOrderDefinitionImmutable? common_algo => _algo_mon.common_value_nullable()?.algo;
public SecurityImmutable? common_sec => _sec_mon.common_value;
public SecurityMD? common_sec_md => common_sec is SecurityImmutable sec && _security_md_map.TryGetValue(sec.Id, out SecurityMD smd) ? smd : (SecurityMD?)null;
public SecurityImmutable? common_spreader_sec => _spreader_sec_mon.common_value;
public double common_price => _price_mon.try_get_common_value(out double p) ? p : double.NaN;
public uint? common_size => _size_mon.common_value_nullable();
public AccountTuple? common_account => _account_mon.common_value_nullable();
public bool all_active => _active_mon.common_value;
public IObservable<CustomOrderDefinitionImmutable?> subscribe_common_algo() => _algo_mon.SubscribeThrottledNullable(_throttle).Select(ask => ask.HasValue ? ask.Value.algo: (CustomOrderDefinitionImmutable?)null);
public IObservable<SecurityImmutable?> subscribe_common_spreader_sec() => _spreader_sec_mon.SubscribeThrottled(_throttle);
public IObservable<SecurityImmutable?> subscribe_common_sec() => _sec_mon.SubscribeThrottled(_throttle);
public IObservable<SecurityMD?> subscribe_common_sec_md() =>
Observable.Create<SecurityMD?>(o => {
SecurityMD? last_value = common_sec_md;
o.OnNext(last_value);
_sec_mon.common_value_changed += on_common_sec_changed;
_security_md_map_changed += refresh;
return Disposable.Create(() => {
_sec_mon.common_value_changed -= on_common_sec_changed;
_security_md_map_changed -= refresh;
});
void on_common_sec_changed(SecurityImmutable? _) => refresh();
void refresh() {
var curr = common_sec_md;
if (curr != last_value)
o.OnNext(last_value = curr);
}
});
public IObservable<double> subscribe_price() => _price_mon.SubscribeThrottled(_throttle);
public IObservable<uint?> subscribe_size() => _size_mon.SubscribeThrottledNullable(_throttle);
public IObservable<AccountTuple?> subscribe_account() => _account_mon.SubscribeThrottledNullable(_throttle);
public IObservable<bool> subscribe_all_active() => _active_mon.SubscribeThrottled(_throttle);
public void on_row_selected(OrderBookRowVM row) {
_group_mon.add(row.order_tuple);
row.updated += _on_row_updated;
}
public void on_row_unselected(OrderBookRowVM row) {
_group_mon.remove(row.order_tuple);
row.updated -= _on_row_updated;
}
public void on_security_md_map(IReadOnlyDictionary<uint, SecurityMD> security_md_map) {
_security_md_map = security_md_map;
_security_md_map_changed?.Invoke();
}
void _on_row_updated((OrderSecurityAlgoMeta oldval, OrderSecurityAlgoMeta newval) ch) {
_group_mon.remove(ch.oldval);
_group_mon.add(ch.newval);
}
static SecurityImmutable? _get_spreader_sec(OrderSecurityAlgoMeta osam) =>
OrderDataHelper.enumerate_algo_hierarchy(osam.order.Def.CustomDef.CloneToMutable())
.Any(algo => algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Spreader) ?
osam.security : null;
AlgoSecurityKey _algo_key(OrderSecurityAlgoMeta osam) =>
new AlgoSecurityKey(osam.order.Def.CustomDef, _get_spreader_sec(osam), AlgoStructureSerializer.structure_key(osam.order.Def.CustomDef, _dyn_helper.dynamic_type_index));
SecurityMD? _get_sec_md(OrderSecurityAlgoMeta osam) =>
osam.security.HasValue && _security_md_map.TryGetValue(osam.security.Value.Id, out SecurityMD smd) ? smd : (SecurityMD?)null;
struct AlgoSecurityKey {
public readonly CustomOrderDefinitionImmutable algo;
public readonly SecurityImmutable? security;
public readonly AlgoStructureKey key;
public AlgoSecurityKey(CustomOrderDefinitionImmutable algo, SecurityImmutable? security, AlgoStructureKey key) {
this.algo = algo;
this.security = security;
this.key = key;
}
public bool Equals(AlgoSecurityKey other) =>
security == other.security &&
key == other.key;
public override bool Equals(object obj) => obj is AlgoSecurityKey other && Equals(other);
public override int GetHashCode() => key.GetHashCode();
public static bool operator ==(AlgoSecurityKey a, AlgoSecurityKey b) => a.Equals(b);
public static bool operator !=(AlgoSecurityKey a, AlgoSecurityKey b) => !a.Equals(b);
}
}
}
}
@@ -0,0 +1,168 @@
<UserControl x:Class="mbts.spark.OrderBookView" x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:sp="clr-namespace:mbts.spark"
xmlns:uc="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"
xmlns:order="clr-namespace:bts.api.orderman;assembly=spark_api"
mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="1080"
FocusManager.IsFocusScope="True"
Background="{StaticResource CommonBackground}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
</ResourceDictionary.MergedDictionaries>
<uspcv:TickDisplayConverter x:Key="TickDisplayConverter"/>
<uspcv:OrderSubTypeDisplayTextConverter x:Key="OrderSubTypeLongDisplayTextConverter" UseLongName="True" />
<DataTemplate x:Key="{x:Static sp:OrderBookView.SubTypeFilterTemplateKey}">
<usp:FilterContentControl
ApplyCommandParameter="{Binding}"
ApplyCommand="{x:Static c:DataGridFilterProperties.CommitCommand}"
ClearCommandParameter="{Binding}"
ClearCommand="{x:Static c:DataGridFilterProperties.ClearCommand}"
>
<c:ListBoxEx
ItemsSource="{Binding items}"
Selection="{Binding selected_items}"
SelectionMode="Multiple"
ItemContainerStyle="{StaticResource ListBoxItemCheckBoxStyle}"
>
<c:ListBoxEx.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource OrderSubTypeLongDisplayTextConverter}}"/>
</DataTemplate>
</c:ListBoxEx.ItemTemplate>
</c:ListBoxEx>
</usp:FilterContentControl>
</DataTemplate>
<Style x:Key="{x:Static sp:OrderBookView.RowStyleKey}" TargetType="ListBoxItem" BasedOn="{StaticResource DIYGridRowStyle}">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding status}" Value="{uc:Enum Working, Type=order:OrderStatus}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="{x:Static Member=working_order_background_brush, MemberType={uc:Nested uc:BTSColors.Orders}}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
</Style>
<DataTemplate x:Key="{x:Static sp:OrderBookView.RowTemplateKey}">
<sp:OrderBookRowView/>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<AdornerDecorator>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<c:DIYGrid
x:Name="_grid"
Grid.Row="0"
Columns="{Binding columns}"
CurrentRow="{Binding current_row, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Rows="{Binding rows}"
RowStyle="{StaticResource {x:Static sp:OrderBookView.RowStyleKey}}"
RowTemplate="{StaticResource {x:Static sp:OrderBookView.RowTemplateKey}}"
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
>
<c:DIYGrid.Resources>
<ResourceDictionary>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Fill" Color="#FF595959"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Stroke" Color="#FF262626"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Stroke" Color="#FF27C7F7"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Fill" Color="#FFCCEEFB"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Stroke" Color="#FF1CC4F7"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Fill" Color="#FF82DFFB"/>
<PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Fill" Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Stroke" Color="#FF818181"/>
<Style x:Key="{x:Static sp:OrderBookView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Width" Value="16"/>
<Setter Property="Height" Value="16"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Background="Transparent" Height="16" Padding="0,5,5,5" Width="16">
<Path x:Name="ExpandPath" Data="{StaticResource TreeArrow}" Fill="{StaticResource TreeViewItem.TreeArrow.Static.Fill}" Stroke="{StaticResource TreeViewItem.TreeArrow.Static.Stroke}">
<Path.RenderTransform>
<RotateTransform Angle="135" CenterY="3" CenterX="3"/>
</Path.RenderTransform>
</Path>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="RenderTransform" TargetName="ExpandPath">
<Setter.Value>
<RotateTransform Angle="180" CenterY="3" CenterX="3"/>
</Setter.Value>
</Setter>
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Fill}"/>
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Stroke}"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Stroke}"/>
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Fill}"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"/>
<Condition Property="IsChecked" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Stroke}"/>
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Fill}"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type ToolTip}">
<Style.Triggers>
<Trigger Property="Content" Value="{x:Static sys:String.Empty}">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
<Trigger Property="Content" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</c:DIYGrid.Resources>
<c:DIYGrid.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
</c:DIYGrid.InputBindings>
<c:DIYGrid.CommandBindings>
<CommandBinding
Command="{x:Static c:DIYGrid.DefaultRowCommand}"
Executed="{x:Static c:DIYGrid.DefaultRowActionExecuted}"
CanExecute="{x:Static c:DIYGrid.DefaultRowActionCanExecute}"
/>
</c:DIYGrid.CommandBindings>
</c:DIYGrid>
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
<StatusBarItem HorizontalAlignment="Right">
<TextBlock>
<Run Text="Orders:"/>
<Run Text="{Binding rows.Count, Mode=OneWay}" />
<Run Text="of"/>
<Run Text="{Binding rows.total_row_count, Mode=OneWay}" />
</TextBlock>
</StatusBarItem>
</StatusBar>
</Grid>
</AdornerDecorator>
</UserControl>
@@ -1,7 +1,11 @@
using mbts.ui.controls;
using System.Windows.Controls;
namespace mbts.spark {
internal partial class OrderBookGridView : DIYGrid {
internal partial class OrderBookView : UserControl {
// IMPORTANT: these enums are stored as strings in the general-purpose GridColumnSettings proto, and are used to ID THE COLUMNS!
// Do NOT RENAME them! However, if you must, be sure to write an IShellWindowMigrationRule or something to account for backwards compatability.
public enum Columns { Security, Symbol, Time, LastChange, User, Account, Exchange, TimeInForce, OrderType, SubType, Side, Size, Price, Filled, AvgFilled, LastPrice, Status, LastMessage, SecurityKey, OrderKey, ParentKey, Cancel, EditableQty, EditablePrice }
public const string
SubTypeFilterTemplateKey = "SubTypeFilterTemplate",
ExpandCollapseToggleStyleKey = "ExpandCollapseToggleStyle",
@@ -10,41 +14,13 @@ namespace mbts.spark {
CVRowStyleKey = "CVRowStyle",
CVRowTemplateKey = "CVRowTemplateKey";
// IMPORTANT: these enums are stored as strings in the general-purpose GridColumnSettings proto, and are used to ID THE COLUMNS!
// Do NOT RENAME them! However, if you must, be sure to write an IShellWindowMigrationRule or something to account for backwards compatability.
public enum ColumnKeys {
Security,
Symbol,
Time,
LastChange,
User,
Account,
Exchange,
TimeInForce,
OrderType,
SubType,
Side,
Size,
Price,
Filled,
AvgFilled,
LastPrice,
Status,
LastMessage,
SecurityKey,
OrderKey,
ParentKey,
AttachedTargetOrderKey,
Cancel,
EditableQty,
EditablePrice
}
public OrderBookGridView() =>
public OrderBookView(OrderBookVM vm) {
InitializeComponent();
DataContext = vm;
}
}
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookView.Columns> {
public OrderBookRowView() : base() { }
}
}
@@ -2,31 +2,27 @@
using bts.api.orderman;
using bts.shell;
using bts.spark;
using bts.view;
using mbts.persist;
using mbts.ui;
using mbts.ui.converters;
using mbts.ui.extensions;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
//using IOrderBookWSM = mbts.persist.IWindowSettingsManager<mbts.spark.GridSettingsWithNotifications<mbts.spark.OrderRecordTuple, mbts.OrderRecordTuplePredicate>>;
using OrderBookGridSettings = bts.shell.OrderBookWindowFactory.OrderBookGridSettings;
using OrderBookGridSettingsVM = mbts.spark.GridSettingsVM<bts.api.orderman.OrderSecurity, mbts.OrderRecordTuplePredicate, bts.shell.OrderBookWindowFactory.OrderBookGridSettings>;
namespace mbts.spark {
internal class OrderBookWindow : SparkWindow<OrderBookWindowFactory.OrderBookStartupData>, ISharedWindow {
const string _window_type = "OrderBook", _default_title = "Order Book";
const string _window_type = "OrderBook", ORDERBOOK_THREAD_ID = "OrderBook", _default_title = "Order Book";
readonly INotificationService _notification_service;
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
IDisposable _attachment_exit_sub;
OrderBookWindowFactory.OrderBookStartupData _state;
OrdersModel _model;
OrderBookVM _vm;
@@ -34,6 +30,17 @@ namespace mbts.spark {
OrderBookWindowFactory _factory;
SparkCommand[] _commands;
SparkCommand _show_fills_command
, _modify_order_command
, _cancel_buy_orders_command
, _cancel_sell_orders_command
, _cancel_all_orders_command
, _cancel_selected_orders_command
, _recreate_selected_orders_command
, _toggle_collapse_all_command
, _toggle_expand_all_command
, _show_grid_settings_command;
public OrderBookWindow(OrdersModel model, OrderBookWindowFactory factory, IBus bus, INotificationService notification_service,
OrderBookWindowFactory.OrderBookStartupData state, ImageSource icon, IWindowSettingsManager<OrderBookGridSettings> settings_manager)
: base(_default_title, bus, factory, SparkCommands.ShowOrders.icon) {
@@ -42,7 +49,6 @@ namespace mbts.spark {
_notification_service = notification_service;
_state = state;
_settings_manager = settings_manager;
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().order_book ? "OrderBook" : null;
}
public override OrderBookWindowFactory.OrderBookStartupData SaveState => _state = _vm?.create();
@@ -55,11 +61,10 @@ namespace mbts.spark {
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public string thread_id { get; }
public string thread_id => ORDERBOOK_THREAD_ID;
public override void Dispose() {
_commands.OfType<IDisposable>().DisposeAll();
Disposable.dispose(ref _attachment_exit_sub);
Disposable.dispose(ref _vm);
}
@@ -68,62 +73,78 @@ namespace mbts.spark {
_vm.load_from(_state);
_view = new OrderBookView(_vm);
var show_fills_command = SparkCommands.ShowFills.complete(() => new FillsWindowFactory.StartupData(_vm.selected_orders.Select(r => r.Key).ToArray()));
_show_fills_command = SparkCommands.ShowFills.complete(() => new FillsWindowFactory.StartupData(_vm.selected_orders.Select(r => r.Key).ToArray()));
var toggle_right_panel_is_visible_command = new SparkCommandCommand(
get_toggle_algo_editor_cmd_title(),
Icons.ModifyOrder,
_vm.toggle_algo_editor_is_visible_cmd,
new KeyGesture(Key.M, ModifierKeys.Control | ModifierKeys.Shift));
_modify_order_command = SparkCommands.ShowModifyOrder.complete(
() => _vm.current_order.Value.Key,
can_execute: () => {
if (_vm.current_order.HasValue) {
return _vm.current_order.Value.State.Status == bts.api.orderman.OrderStatus.Working;
}
return false;
}
);
var cancel_buy_orders_command = SparkCommands.CancelBuyOrders.complete(
_cancel_buy_orders_command = SparkCommands.CancelBuyOrders.complete(
_model.working_orders.Select(os => os.Values.Where(o => o.order.side() == Side.Buy).Select(o => o.order.Key)),
wbids => wbids.Any()
wbids => wbids.Any(),
() => {
var resp = BTSMessageBox.Show(
"Are you sure you want to cancel all working buy orders?",
"Cancel Buy Orders",
BTSMessageBoxButtons.Yes | BTSMessageBoxButtons.No,
MessageBoxImage.Question);
return resp == BTSMessageBoxButtons.Yes;
}
);
var cancel_sell_orders_command = SparkCommands.CancelSellOrders.complete(
_cancel_sell_orders_command = SparkCommands.CancelSellOrders.complete(
_model.working_orders.Select(os => os.Values.Where(o => o.order.side() == Side.Sell).Select(o => o.order.Key)),
wasks => wasks.Any()
wasks => wasks.Any(),
() => {
var resp = BTSMessageBox.Show(
"Are you sure you want to cancel all working sell orders?",
"Cancel Sell Orders",
BTSMessageBoxButtons.Yes | BTSMessageBoxButtons.No,
MessageBoxImage.Question);
return resp == BTSMessageBoxButtons.Yes;
}
);
var cancel_all_orders_command = SparkCommands.CancelOrders.complete(
_cancel_all_orders_command = SparkCommands.CancelOrders.complete(
_model.working_orders.Select(os => os.Select(o => o.Key)),
worders => worders.Any()
worders => worders.Any(),
() => {
var resp = BTSMessageBox.Show(
"Are you sure you want to cancel all working orders?",
"Cancel All Orders",
BTSMessageBoxButtons.Yes | BTSMessageBoxButtons.No,
MessageBoxImage.Question);
return resp == BTSMessageBoxButtons.Yes;
}
);
var cancel_selected_orders_command = SparkCommands.CancelSelectedOrders.complete(
_cancel_selected_orders_command = SparkCommands.CancelSelectedOrders.complete(
() => _vm.selected_orders.Where(x => x.status() == OrderStatus.Working).Select(x => x.Key),
can_execute: () => _vm.selected_orders.Any(x => x.status() == OrderStatus.Working)
);
var recreate_selected_orders_command = SparkCommands.RecreateSelectedOrders.complete(
_recreate_selected_orders_command = SparkCommands.RecreateSelectedOrders.complete(
() =>
_vm.selected_orders.Where(x => passes_recreate_search_criteria(x))
_vm.selected_orders.Where(x => _passes_recreate_search_criteria(x))
.Select(x => (x.Def, x.State.Price, x.State.Quantity - x.State.FilledQuantity)),
can_execute: () => _vm.selected_orders.Any(x => passes_recreate_search_criteria(x))
can_execute: () => _vm.selected_orders.Any(x => _passes_recreate_search_criteria(x))
);
var attach_selected_orders_command = new SparkCommandCommand(
"Attach To Selected Orders",
Icons.AttachAlgos,
new DelegateCommand(_vm.begin_attach_algos, () => _vm.selected_rows.Count > 0),
new KeyGesture(Key.A, ModifierKeys.Control | ModifierKeys.Shift));
var detach_selected_orders_command = new SparkCommandCommand(
"Detach Selected Orders",
Icons.DetachAlgos,
new DelegateCommand(_vm.detach_selected_algos, () => _vm.selected_rows.Count > 0),
new KeyGesture(Key.D, ModifierKeys.Control | ModifierKeys.Shift));
var toggle_collapse_all_command = new SparkCommandCommand("Collapse All",
_toggle_collapse_all_command = new SparkCommandCommand("Collapse All",
Icons.RightArrow,
_vm.collapse_all_rows_command);
var toggle_expand_all_command = new SparkCommandCommand("Expand All",
_toggle_expand_all_command = new SparkCommandCommand("Expand All",
Icons.DownArrow,
_vm.expand_all_rows_command);
var show_grid_settings_command = SparkCommands.window_settings_command(new DialogCommand(() => {
_show_grid_settings_command = SparkCommands.window_settings_command(new DialogCommand(() => {
var gs_vm = new WindowSettingsDialogVM<OrderBookGridSettings, OrderBookGridSettingsVM>(
_settings_manager,
us => us.order_book_grid,
@@ -148,44 +169,31 @@ namespace mbts.spark {
return dialog;
}));
_commands = new SparkCommand[] {
show_fills_command,
toggle_right_panel_is_visible_command,
cancel_selected_orders_command,
cancel_buy_orders_command,
cancel_sell_orders_command,
cancel_all_orders_command,
recreate_selected_orders_command,
attach_selected_orders_command,
detach_selected_orders_command,
toggle_collapse_all_command,
toggle_expand_all_command,
show_grid_settings_command
_commands = new SparkCommand[] {
_show_fills_command,
_modify_order_command,
_cancel_selected_orders_command,
_cancel_buy_orders_command,
_cancel_sell_orders_command,
_cancel_all_orders_command,
_recreate_selected_orders_command,
_toggle_collapse_all_command,
_toggle_expand_all_command,
_show_grid_settings_command
};
_vm.as_demuxer().subscribe<bool>(nameof(OrderBookVM.algo_editor_is_visible))
.Subscribe(_ => toggle_right_panel_is_visible_command.title = get_toggle_algo_editor_cmd_title());
_vm.selected_order_changed += vm_selected_order_changed;
// local methods
string get_toggle_algo_editor_cmd_title() =>
$"{(_vm.algo_editor_is_visible ? "Hide" : "Show")} Order Params";
bool passes_recreate_search_criteria(OrderRecordImmutable record) =>
(record.status() == OrderStatus.Canceled || record.status() == OrderStatus.Error) &&
!record.has_parent() &&
record.State.Quantity > record.State.FilledQuantity;
void vm_selected_order_changed(OrderRecordImmutable? e) {
cancel_selected_orders_command.raiseCanExecuteChanged();
recreate_selected_orders_command.raiseCanExecuteChanged();
attach_selected_orders_command.raiseCanExecuteChanged();
detach_selected_orders_command.raiseCanExecuteChanged();
}
_vm.selected_order_changed += _vm_selected_order_changed;
}
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
Disposable.replace(ref _attachment_exit_sub, parent_view.SubscribePreviewKeyDown((s, e) => {
if (e.Key == Key.Escape || (e.Key == Key.Z && KeyboardExtensions.IsCtrlDown()))
_vm.is_attaching_algos = false;
}));
private bool _passes_recreate_search_criteria(OrderRecordImmutable record) {
return (record.status() == OrderStatus.Canceled || record.status() == OrderStatus.Error)
&& !record.has_parent()
&& record.State.Quantity > record.State.FilledQuantity;
}
private void _vm_selected_order_changed(OrderRecordImmutable? e) {
_modify_order_command.raiseCanExecuteChanged();
_cancel_selected_orders_command.raiseCanExecuteChanged();
_recreate_selected_orders_command.raiseCanExecuteChanged();
}
}
}
@@ -12,11 +12,10 @@ using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
using static mbts.spark.OrderBookView;
namespace bts.shell {
internal class OrderBookWindowFactory : ShellWindowFactory<OrderBookWindowFactory.OrderBookStartupData> {
@@ -50,57 +49,24 @@ namespace bts.shell {
[ProtoContract]
public class OrderBookStartupData {
const bool _default_algo_editor_is_visible = true;
const double
_default_right_panel_width = 400,
_default_fills_slave_grid_height = 138;
// ALREADY USED TAGS
// [ProtoMember(5)]
// public readonly List<SortingCriteria> sort_criteria;
// filters
//[ProtoMember(5)]
//public readonly List<SortingCriteria> sort_criteria;
[ProtoMember(10)]
public FilterStates filters;
// grid
[ProtoMember(15)]
[Obsolete]
public GridSettings grid;
// algo editor is visible
[ProtoMember(16)]
[DefaultValue(_default_algo_editor_is_visible)]
public bool algo_editor_is_visible { get; set; }
// right panel width
[ProtoMember(17)]
[DefaultValue(_default_right_panel_width)]
public double right_panel_width { get; set; }
// fills slave grid height
[ProtoMember(18)]
[DefaultValue(_default_fills_slave_grid_height)]
public double fills_slave_grid_height { get; set; }
// fills slave grid is visible
[ProtoMember(19)]
public bool fills_slave_grid_is_visible { get; set; }
// predicate animations
[ProtoMember(20)]
[Obsolete]
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
// use defaults
[ProtoMember(25)]
public bool use_defaults;
public OrderBookStartupData() {
algo_editor_is_visible = _default_algo_editor_is_visible;
right_panel_width = _default_right_panel_width;
fills_slave_grid_height = _default_fills_slave_grid_height;
}
public OrderBookStartupData() { }
public OrderBookStartupData(FilterStates filters, GridSettings grid = default, bool algo_editor_is_visible = false, double right_panel_width = 0, double fills_slave_grid_height = 0, bool fills_slave_grid_is_visible = false, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null, bool use_defaults = false) {
public OrderBookStartupData(FilterStates filters, GridSettings grid = default, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null, bool use_defaults = false) {
this.filters = filters;
this.use_defaults = use_defaults;
this.algo_editor_is_visible = algo_editor_is_visible;
this.right_panel_width = right_panel_width;
this.fills_slave_grid_height = fills_slave_grid_height;
this.fills_slave_grid_is_visible = fills_slave_grid_is_visible;
#pragma warning disable CS0612 // Type or member is obsolete
this.grid = grid;
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
@@ -117,12 +83,9 @@ namespace bts.shell {
public static OrderBookStartupData Default =>
new OrderBookStartupData(
new FilterStates { statuses = new[] { OrderStatus.Working } },
_default_general_grid,
_default_algo_editor_is_visible,
_default_right_panel_width,
_default_fills_slave_grid_height,
use_defaults: true);
new FilterStates {
statuses = new[] { OrderStatus.Working }
}, _default_general_grid, use_defaults: true);
public static OrderBookGridSettings DefaultGrid => new OrderBookGridSettings(_default_general_grid);
@@ -152,7 +115,6 @@ namespace bts.shell {
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, is_visible: false, width: DIYGrid.DefaultColumnWidths.SecurityKey);
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey);
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey);
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey);
public static IEnumerable<SortingCriteria> sorting_criterion {
get {
@@ -183,12 +145,11 @@ namespace bts.shell {
yield return security_key;
yield return order_key;
yield return parent_key;
yield return attached_target_order_key;
}
}
private static HashSet<GridColumnSettings> _mini_mode_columns_to_hide = new HashSet<GridColumnSettings> {
time_created, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key, attached_target_order_key
time_created, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key
};
public static IEnumerable<GridColumnSettings> mini {
@@ -247,17 +208,11 @@ namespace bts.shell {
public api.orderman.OrderSubType[] order_sub_types;
[ProtoMember(20)]
public string[] symbols;
[ProtoMember(21)]
public string parent_key_regex;
[ProtoMember(22)]
public string attached_target_order_key_regex;
public bool Equals(FilterStates other) =>
other != null &&
security_criteria.safe_equals(other.security_criteria) &&
order_key_regex == other.order_key_regex &&
parent_key_regex == other.parent_key_regex &&
attached_target_order_key_regex == other.attached_target_order_key_regex &&
time_created.Equals(other.time_created) &&
EqualityComparer.safe_sequence_equals(users, other.users) &&
EqualityComparer.safe_sequence_equals(accounts, other.accounts) &&
@@ -275,32 +230,6 @@ namespace bts.shell {
last_change.Equals(other.last_change) &&
EqualityComparer.safe_sequence_equals(order_sub_types, other.order_sub_types) &&
EqualityComparer.safe_sequence_equals(symbols, other.symbols);
public override bool Equals(object obj) => Equals(obj as FilterStates);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
security_criteria?.GetHashCode() ?? 0,
order_key_regex?.GetHashCode() ?? 0,
parent_key_regex?.GetHashCode() ?? 0,
attached_target_order_key_regex?.GetHashCode() ?? 0,
time_created.GetHashCode(),
EqualityComparer.safe_aggregate_hash(users),
EqualityComparer.safe_aggregate_hash(accounts),
EqualityComparer.safe_aggregate_hash(exchanges),
EqualityComparer.safe_aggregate_hash(tifs),
EqualityComparer.safe_aggregate_hash(order_types),
EqualityComparer.safe_aggregate_hash(sides),
qty?.GetHashCode() ?? 0,
price?.GetHashCode() ?? 0,
qty_filled?.GetHashCode() ?? 0,
avg_price?.GetHashCode() ?? 0,
last_price?.GetHashCode() ?? 0,
EqualityComparer.safe_aggregate_hash(statuses),
exchange_message_regex?.GetHashCode() ?? 0,
last_change.GetHashCode(),
EqualityComparer.safe_aggregate_hash(order_sub_types),
EqualityComparer.safe_aggregate_hash(symbols));
}
[ProtoContract]
@@ -311,7 +240,7 @@ namespace bts.shell {
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
public OrderBookGridSettings() { }
public OrderBookGridSettings(GridSettings general = default, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null, double right_panel_width = double.NaN) {
public OrderBookGridSettings(GridSettings general = default, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null) {
this.general = general;
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
if (predicate_animations != null)
@@ -1,63 +0,0 @@
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.controls.adorners;
using mbts.ui.extensions;
using System;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Input;
namespace mbts.spark {
partial class OrderBookView {
public class OrderBookAttachmentTargetAdorner : DIYGridRowHighlighterAdorner, IDisposable {
IDisposable _attach_op;
bool _is_active, _is_disposed;
#region IsActive
public static readonly DependencyProperty IsActiveProperty =
WPFHelper.create_dp<bool, OrderBookAttachmentTargetAdorner>(
nameof(IsActive),
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => {
o._is_active = v;
Disposable.dispose(ref o._attach_op);
if (v && !o._is_disposed) {
o.IsHitTestVisible = true;
o._attach_op = Disposable.Create(
o.SubscribePreviewMouseDown((s, e) => {
if (e.ChangedButton == MouseButton.Left) {
if (e.ClickCount > 1 && o.TargetRow is OrderBookRowVM r) {
e.Handled = true;
o._raise_target_row_selected(r);
}
}
}),
o.AdornedElement.SubscribeMouseHoverRow<OrderBookRowVM>().Subscribe(row => o.TargetRow = row));
} else {
o.IsHitTestVisible = false;
o.TargetRow = null;
}
},
false);
public bool IsActive {
get => (bool)GetValue(IsActiveProperty);
set => SetValue(IsActiveProperty, value);
}
public event Action<OrderBookRowVM> TargetRowSelected;
#endregion
public OrderBookAttachmentTargetAdorner(DIYGrid grid) : base(grid) { }
public void Dispose() {
if (!_is_disposed) {
_is_disposed = true;
Disposable.dispose(ref _attach_op);
}
}
void _raise_target_row_selected(OrderBookRowVM row) => TargetRowSelected?.Invoke(row);
}
}
}
@@ -1,132 +0,0 @@
<c:DIYGrid
x:Class="mbts.spark.OrderBookGridView"
x:ClassModifier="internal"
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"
>
<c:DIYGrid.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
</ResourceDictionary.MergedDictionaries>
<!--Primitive Resources (Brushes, Colors, etc.)-->
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Fill" Color="#FF595959"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Stroke" Color="#FF262626"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Stroke" Color="#FF27C7F7"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Fill" Color="#FFCCEEFB"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Stroke" Color="#FF1CC4F7"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Fill" Color="#FF82DFFB"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Fill" Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Stroke" Color="#FF818181"/>
<PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
<!--Converters-->
<uspcv:OrderSubTypeDisplayTextConverter x:Key="OrderSubTypeLongDisplayTextConverter" UseLongName="True" />
<!--DataTemplates-->
<DataTemplate x:Key="{x:Static sp:OrderBookGridView.SubTypeFilterTemplateKey}">
<usp:FilterContentControl
ApplyCommandParameter="{Binding}"
ApplyCommand="{x:Static c:DataGridFilterProperties.CommitCommand}"
ClearCommandParameter="{Binding}"
ClearCommand="{x:Static c:DataGridFilterProperties.ClearCommand}"
>
<c:ListBoxEx
ItemsSource="{Binding items}"
Selection="{Binding selected_items}"
SelectionMode="Multiple"
ItemContainerStyle="{StaticResource ListBoxItemCheckBoxStyle}"
>
<c:ListBoxEx.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource OrderSubTypeLongDisplayTextConverter}}"/>
</DataTemplate>
</c:ListBoxEx.ItemTemplate>
</c:ListBoxEx>
</usp:FilterContentControl>
</DataTemplate>
<!--Styles-->
<Style x:Key="{x:Static sp:OrderBookGridView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Width" Value="16"/>
<Setter Property="Height" Value="16"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Background="Transparent" Height="16" Padding="0,5,5,5" Width="16">
<Path x:Name="ExpandPath" Data="{StaticResource TreeArrow}" Fill="{StaticResource TreeViewItem.TreeArrow.Static.Fill}" Stroke="{StaticResource TreeViewItem.TreeArrow.Static.Stroke}">
<Path.RenderTransform>
<RotateTransform Angle="135" CenterY="3" CenterX="3"/>
</Path.RenderTransform>
</Path>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="RenderTransform" TargetName="ExpandPath">
<Setter.Value>
<RotateTransform Angle="180" CenterY="3" CenterX="3"/>
</Setter.Value>
</Setter>
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Fill}"/>
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Stroke}"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Stroke}"/>
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Fill}"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"/>
<Condition Property="IsChecked" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Stroke}"/>
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Fill}"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type ToolTip}">
<Style.Triggers>
<Trigger Property="Content" Value="{x:Static sys:String.Empty}">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
<Trigger Property="Content" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</c:DIYGrid.Resources>
<c:DIYGrid.InputBindings>
<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>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding status}" Value="{u:Enum Working, Type=order:OrderStatus}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="{x:Static Member=working_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,393 +0,0 @@
<UserControl
x:Class="mbts.spark.OrderBookView"
x:ClassModifier="internal"
x:Name="Root"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sp="clr-namespace:mbts.spark"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Grid.Row="0"
FocusManager.IsFocusScope="True"
Background="{StaticResource CommonBackground}"
>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
<ResourceDictionary Source="/Modules/Trading/OrderBook/View/OrderParamsPanel.xaml"/>
</ResourceDictionary.MergedDictionaries>
<sys:Double x:Key="GridSplitterWidth">5</sys:Double>
<sys:Double x:Key="FillsSlaveGridMinHeight">64</sys:Double>
<SolidColorBrush x:Key="AlgoAttachmentBackground" Color="#55009500"/>
<cv:GridLengthConverter x:Key="GridLengthPixelConverter" UnitType="Pixel"/>
<cv:ValueMap x:Key="AlgoAttachmentStateConverter" Default="{x:Static Inactive, MemberType={u:Nested sp:OrderBookView.VisualStateNames.AlgoAttachment}}">
<cv:KeyValue Key="{u:True}" Value="{x:Static Active, MemberType={u:Nested sp:OrderBookView.VisualStateNames.AlgoAttachment}}"/>
</cv:ValueMap>
<Style TargetType="GridSplitter">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{StaticResource Control.MediumGrayBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource Control.MediumGrayBrush}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="{x:Static usp:OrderParamsPanel.BestBidPriceButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip DataContext="{Binding PlacementTarget.Parent.DataContext, RelativeSource={RelativeSource Self}}">
<TextBlock Text="{Binding security, StringFormat='Best Bid Price ({0})'}"/>
</ToolTip>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{x:Static usp:OrderParamsPanel.BestBidSizeButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Command" Value="{Binding DataContext.size_calculator.set_value_command, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
<Setter Property="CommandParameter" Value="{Binding BestBidSize, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip DataContext="{Binding PlacementTarget.Parent.DataContext, RelativeSource={RelativeSource Self}}">
<TextBlock Text="{Binding security, StringFormat='Best Bid Size ({0})'}"/>
</ToolTip>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{x:Static usp:OrderParamsPanel.BestAskPriceButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip DataContext="{Binding PlacementTarget.Parent.DataContext, RelativeSource={RelativeSource Self}}">
<TextBlock Text="{Binding security, StringFormat='Best Ask Price ({0})'}"/>
</ToolTip>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{x:Static usp:OrderParamsPanel.BestAskSizeButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Command" Value="{Binding DataContext.size_calculator.set_value_command, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
<Setter Property="CommandParameter" Value="{Binding BestAskSize, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip DataContext="{Binding PlacementTarget.Parent.DataContext, RelativeSource={RelativeSource Self}}">
<TextBlock Text="{Binding security, StringFormat='Best Ask Size ({0})'}"/>
</ToolTip>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{x:Static usp:OrderParamsPanel.SizeCalculatorButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Command" Value="{Binding DataContext.size_calculator.click, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</ResourceDictionary>
</UserControl.Resources>
<AdornerDecorator>
<Grid x:Name="ContentRoot">
<u:StateManager.Groups>
<u:StateGroupsManager Target="{Binding Source={u:DirectReference ContentRoot}}">
<u:StateDefinitionCollection>
<u:StateDefinition State="{Binding DataContext.is_attaching_algos, Source={u:DirectReference ContentRoot}, Converter={StaticResource AlgoAttachmentStateConverter}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="AlgoAttachmentStates">
<VisualState Name="{x:Static Active, MemberType={u:Nested sp:OrderBookView.VisualStateNames.AlgoAttachment}}">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_AlgoAttachmentRoot_Border" Storyboard.TargetProperty="(UIElement.Opacity)">
<DiscreteDoubleKeyFrame KeyTime="0" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Orders_DIYGrid" Storyboard.TargetProperty="(FrameworkElement.Cursor)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Cursors.Pen}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_FillsToggle_Hyperlink" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="modify_algos_button" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="attach_orders_dialog_skirt" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="attachment_algo_editor" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static Inactive, MemberType={u:Nested sp:OrderBookView.VisualStateNames.AlgoAttachment}}">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_AlgoAttachmentRoot_Border" Storyboard.TargetProperty="(UIElement.Opacity)">
<DiscreteDoubleKeyFrame KeyTime="0" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Orders_DIYGrid" Storyboard.TargetProperty="(FrameworkElement.Cursor)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Cursors.Arrow}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_FillsToggle_Hyperlink" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="modify_algos_button" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="attach_orders_dialog_skirt" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="attachment_algo_editor" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="{Binding right_panel_width, Converter={StaticResource GridLengthPixelConverter}, Mode=TwoWay}"/>
</Grid.ColumnDefinitions>
<sp:OrderBookGridView
x:Name="PART_Orders_DIYGrid"
Background="Transparent"
Columns="{Binding columns}"
CurrentRow="{Binding current_row, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Rows="{Binding rows}"
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
Cursor="Arrow"
/>
<Border
x:Name="PART_AlgoAttachmentRoot_Border"
Width="{Binding ActualWidth, ElementName=PART_Orders_DIYGrid}"
Height="{Binding ActualHeight, ElementName=PART_Orders_DIYGrid}"
Background="{StaticResource AlgoAttachmentBackground}"
Opacity="0"
IsHitTestVisible="False"
/>
<GridSplitter
Grid.Column="1"
Panel.ZIndex="1"
HorizontalAlignment="Left"
VerticalAlignment="Stretch"
Width="{StaticResource GridSplitterWidth}"
BorderThickness="1,0,0,0"
/>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid x:Name="RightPanelRoot">
<u:StateManager.Groups>
<u:StateGroupsManager Target="{Binding Source={u:DirectReference RightPanelRoot}}">
<u:StateDefinitionCollection>
<u:StateDefinition State="{Binding DataContext.fills_slave_grid_is_visible, Source={u:DirectReference RightPanelRoot}, Converter={x:Static sp:OrderBookView.FillsPanelVisualStateConverter}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested sp:OrderBookView.VisualStateNames.FillsPanel}}">
<VisualState Name="{x:Static Active, MemberType={u:Nested sp:OrderBookView.VisualStateNames.FillsPanel}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Fills_DIYGrid" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_FillsAlgo_GridSplitter" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(Grid.Row)">
<DiscreteInt32KeyFrame KeyTime="0" Value="2"/>
</Int32AnimationUsingKeyFrames>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(Grid.RowSpan)">
<DiscreteInt32KeyFrame KeyTime="0" Value="1"/>
</Int32AnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static Inactive, MemberType={u:Nested sp:OrderBookView.VisualStateNames.FillsPanel}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Fills_DIYGrid" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_FillsAlgo_GridSplitter" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(Grid.Row)">
<DiscreteInt32KeyFrame KeyTime="0" Value="1"/>
</Int32AnimationUsingKeyFrames>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(Grid.RowSpan)">
<DiscreteInt32KeyFrame KeyTime="0" Value="2"/>
</Int32AnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding Source={StaticResource GridSplitterWidth}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition MinHeight="{StaticResource FillsSlaveGridMinHeight}" Height="{Binding fills_slave_grid_height, Converter={StaticResource GridLengthPixelConverter}, Mode=TwoWay}" MaxHeight="{Binding ActualHeight, ElementName=RightPanelRoot, Converter={x:Static sp:OrderBookView.FillsSlaveGridMaxHeightConverter}}"/>
<RowDefinition MinHeight="{x:Static sp:OrderBookView.GridSplitterHeight}" Height="*"/>
</Grid.RowDefinitions>
<TextBlock x:Name="PART_FillsToggle_Hyperlink" Grid.Column="1">
<TextBlock.Margin>
<Thickness Left="{StaticResource DialogSkirtPadding.Left}" Top="4"/>
</TextBlock.Margin>
<Hyperlink Command="{Binding toggle_fills_slave_grid_is_visible_cmd}">
<Hyperlink.Inlines>
<Run Text="{Binding current_row, Converter={x:Static sp:OrderBookView.OrderDescriptionConverter}, Mode=OneWay}"/>
</Hyperlink.Inlines>
</Hyperlink>
</TextBlock>
<c:DIYGrid x:Name="PART_Fills_DIYGrid" Grid.Column="1" Grid.Row="1" Columns="{Binding fills_vm.columns}" Rows="{Binding fills_vm.rows}">
<c:DIYGrid.Margin>
<Thickness Bottom="{x:Static sp:OrderBookView.GridSplitterHeight}"/>
</c:DIYGrid.Margin>
<c:DIYGrid.RowTemplate>
<DataTemplate>
<usp:FillsRowView/>
</DataTemplate>
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
<GridSplitter
x:Name="PART_FillsAlgo_GridSplitter"
Grid.Column="1"
Grid.Row="1"
Panel.ZIndex="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Height="{x:Static sp:OrderBookView.GridSplitterHeight}"
BorderThickness="0,1,0,0"
/>
<GroupBox
x:Name="order_params_group_box"
Grid.Column="1"
Grid.Row="2"
Padding="12"
BorderThickness="1"
Header="Order Parameters"
HeaderTemplate="{StaticResource BoldHeaderTemplate}"
>
<usp:OrderParamsPanel
x:Name="order_params_panel"
DataContext="{Binding order_mod_vm}"
Price="{Binding price}"
Size="{Binding size_calculator.value}"
Accounts="{Binding accounts}"
SelectedAccount="{Binding selected_account}"
BestBidPrice="{Binding bbo.bid}"
BestBidSize="{Binding bbo.bid_size}"
BestAskPrice="{Binding bbo.ask}"
BestAskSize="{Binding bbo.ask_size}"
SizePresets="{Binding size_calculator.buttons}"
Security="{Binding security}"
AlgoVM="{Binding algo_vm}"
IsEnabled="{Binding IsEnabled, ElementName=modify_algos_button}"
>
<usp:OrderParamsPanel.CommandBindings>
<CommandBinding Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}" Executed="{x:Static usp:OrderParamsPanel.SetPriceExecuted}"/>
</usp:OrderParamsPanel.CommandBindings>
</usp:OrderParamsPanel>
</GroupBox>
<Grid
x:Name="attachment_algo_editor"
Grid.Row="1"
Grid.RowSpan="2"
Grid.Column="1"
HorizontalAlignment="Left"
MinWidth="320"
Margin="0,4,0,0"
Visibility="Collapsed"
>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<GroupBox
Padding="12"
Header="Attached Order Parameters"
HeaderTemplate="{StaticResource BoldHeaderTemplate}"
>
<usp:OrderParamsPanel
x:Name="attached_order_params_panel"
DataContext="{Binding attach_vm}"
Price="{Binding price}"
Size="{Binding size_calculator.value}"
Accounts="{Binding accounts}"
SelectedAccount="{Binding selected_account}"
BestBidPrice="{Binding bbo.bid}"
BestBidSize="{Binding bbo.bid_size}"
BestAskPrice="{Binding bbo.ask}"
BestAskSize="{Binding bbo.ask_size}"
SizePresets="{Binding size_calculator.buttons}"
Security="{Binding security}"
AlgoVM="{Binding algo_vm}"
IsEnabled="{Binding IsEnabled, ElementName=send_attached_orders_button}"
>
<usp:OrderParamsPanel.CommandBindings>
<CommandBinding Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}" Executed="{x:Static usp:OrderParamsPanel.SetPriceExecuted}"/>
</usp:OrderParamsPanel.CommandBindings>
</usp:OrderParamsPanel>
</GroupBox>
<StackPanel
x:Name="attach_orders_dialog_skirt"
Grid.Row="1"
VerticalAlignment="Top"
HorizontalAlignment="Right"
Margin="{StaticResource DialogSkirtPadding}"
Orientation="Horizontal"
Visibility="Collapsed"
>
<Button x:Name="send_attached_orders_button" Command="{Binding send_attached_orders_cmd}" Style="{StaticResource {x:Static c:DialogSkirt.DialogSkirtButtonStyleKey}}">
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding selected_rows.Count, StringFormat='Send Attached Orders ({0})'}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsEnabled, RelativeSource={RelativeSource AncestorType=Button}}" Value="False">
<Setter Property="Text" Value="Send Attached Orders"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Button>
<Button u:Behavior.CloseWindow="False" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding cancel_attach_orders_cmd}" Style="{StaticResource CancelButton}"/>
</StackPanel>
</Grid>
</Grid>
<Button x:Name="modify_algos_button" Grid.Row="1" HorizontalAlignment="Right" Margin="{StaticResource DialogSkirtPadding}" Command="{Binding modify_selected_algos_command}">
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding selected_rows.Count, StringFormat='Modify Algos ({0})'}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsEnabled, RelativeSource={RelativeSource AncestorType=Button}}" Value="False">
<Setter Property="Text" Value="Modify Algos"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Button>
</Grid>
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
<StatusBarItem HorizontalAlignment="Right">
<TextBlock>
<Run Text="Orders:"/>
<Run Text="{Binding rows.Count, Mode=OneWay}" />
<Run Text="of"/>
<Run Text="{Binding rows.total_row_count, Mode=OneWay}" />
</TextBlock>
</StatusBarItem>
</StatusBar>
</Grid>
</AdornerDecorator>
</UserControl>
@@ -1,127 +0,0 @@
using mbts.ui;
using mbts.ui.converters;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
namespace mbts.spark {
internal partial class OrderBookView : UserControl {
public const double
GridSplitterHeight = 5,
MinAlgoEditorHeight = 64,
MinAttachOrderParamsPanelWidth = 340,
AttachOrderParamsPanelWidthRatio = 0.33;
public static readonly IValueConverter
OrderDescriptionConverter = ValueConverter.create<OrderBookRowVM, string>(row => row == null ? string.Empty : row.order.short_description(row.security)),
FillsSlaveGridMaxHeightConverter = ValueConverter.create<double, double>(right_panel_height => right_panel_height - (GridSplitterHeight + MinAlgoEditorHeight)),
FillsPanelVisualStateConverter = ValueConverter.create<bool, string>(show => show ? VisualStateNames.FillsPanel.Active : VisualStateNames.FillsPanel.Inactive),
AttachmentTargetCursorConverter = ValueConverter.create<bool, Cursor>(is_active => is_active ? Cursors.Pen : Cursors.Arrow);
public OrderBookView(OrderBookVM vm) {
InitializeComponent();
DataContext = vm;
const int delay_ms = 25;
AttachOp? attach_op = null; ;
vm.begin_attaching += on_begin_attaching;
vm.end_attaching += on_end_attaching;
vm.SubscribeDisposed(() => {
if (attach_op.HasValue) {
vm.right_panel_width = attach_op.Value.raw_right_panel_width;
vm.fills_slave_grid_is_visible = attach_op.Value.fills_slave_grid_is_visible;
}
vm.begin_attaching -= on_begin_attaching;
vm.end_attaching -= on_end_attaching;
});
async void on_begin_attaching() {
if (attach_op.HasValue) {
// begin-attaching is already underway (prob won't happen in practice as this event is triggered by user interaction)
return;
}
attach_op = new AttachOp(vm.algo_editor_is_visible, vm.fills_slave_grid_is_visible, vm.raw_right_panel_width);
vm.fills_slave_grid_is_visible = false;
vm.algo_editor_is_visible = true;
double target_width = Math.Max(ActualWidth * AttachOrderParamsPanelWidthRatio, MinAttachOrderParamsPanelWidth);
if (vm.right_panel_width < target_width) {
foreach (double incr in _get_incremental_widths(attach_op.Value.right_panel_is_visible ? attach_op.Value.raw_right_panel_width : 0, target_width)) {
vm.right_panel_width = incr;
await Task.Delay(delay_ms);
if (!attach_op.HasValue)
break;
}
}
}
async void on_end_attaching() {
if (!attach_op.HasValue) {
// end-attaching is already underway (prob won't happen in practice as this event is triggered by user interaction)
return;
}
var incr_widths = _get_incremental_widths(vm.right_panel_width, attach_op.Value.raw_right_panel_width);
vm.algo_editor_is_visible = attach_op.Value.right_panel_is_visible;
vm.fills_slave_grid_is_visible = attach_op.Value.fills_slave_grid_is_visible;
vm.right_panel_width = attach_op.Value.raw_right_panel_width;
attach_op = null;
foreach (double incr in incr_widths) {
vm.right_panel_width = incr;
await Task.Delay(delay_ms);
if (attach_op.HasValue)
break;
}
}
}
static IEnumerable<double> _get_incremental_widths(double from, double to) {
var list = new List<double> { to };
int incr = 1;
if (from < to) {
do {
list.Add(to - (incr * 0.6666));
list.Add(to - incr);
from += incr;
incr += incr;
} while (from < to);
} else if (from > to) {
do {
list.Add(to + incr);
incr += incr;
to += incr;
} while (from > to);
}
for (int i = list.Count - 1; i >= 0; i--) {
yield return list[i];
}
}
struct AttachOp {
public readonly bool right_panel_is_visible, fills_slave_grid_is_visible;
public readonly double raw_right_panel_width;
public AttachOp(bool right_panel_is_visible, bool fills_slave_grid_is_visible, double raw_right_panel_width) {
this.right_panel_is_visible = right_panel_is_visible;
this.fills_slave_grid_is_visible = fills_slave_grid_is_visible;
this.raw_right_panel_width = raw_right_panel_width;
}
}
public static class VisualStateNames {
public static class FillsPanel {
public const string GroupName = nameof(FillsPanel);
public static readonly string
Active = $"{GroupName}{VisualStates.Active}",
Inactive = $"{GroupName}{VisualStates.Inactive}";
}
public static class AlgoAttachment {
public const string GroupName = nameof(AlgoAttachment);
public static readonly string
Active = $"{GroupName}{VisualStates.Active}",
Inactive = $"{GroupName}{VisualStates.Inactive}";
}
}
}
}
@@ -1,142 +0,0 @@
<ResourceDictionary
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"
>
<ControlTemplate x:Key="OrderParamsPanelTemplate" TargetType="usp:OrderParamsPanel">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush2}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<usp:PriceEntryControl
ContractSpec="{Binding ContractSpec, RelativeSource={RelativeSource TemplatedParent}}"
Value="{Binding Price, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
ToolTip="Price"
/>
<Button
x:Name="best_bid_button"
Grid.Column="2"
HorizontalAlignment="Stretch"
Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}"
CommandParameter="{Binding BestBidPrice, RelativeSource={RelativeSource TemplatedParent}}"
Foreground="{u:BTSColor MarketData.bid}"
FontWeight="Bold"
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestBidPriceButtonStyleKey}}"
>
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
<Binding Path="BestBidPrice" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Path="ContractSpec" RelativeSource="{RelativeSource TemplatedParent}"/>
</MultiBinding>
</Button>
<Button
x:Name="best_ask_button"
Grid.Column="4"
HorizontalAlignment="Stretch"
Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}"
CommandParameter="{Binding BestAskPrice, RelativeSource={RelativeSource TemplatedParent}}"
Foreground="{u:BTSColor MarketData.ask}"
FontWeight="Bold"
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestAskPriceButtonStyleKey}}"
>
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
<Binding Path="BestAskPrice" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Path="ContractSpec" RelativeSource="{RelativeSource TemplatedParent}"/>
</MultiBinding>
</Button>
</Grid>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush2}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<c:TickUpDownNullableUInt Value="{Binding Size, RelativeSource={RelativeSource TemplatedParent}}" ToolTip="Size"/>
<Button
x:Name="best_bid_size_button"
Grid.Column="2"
HorizontalAlignment="Stretch"
Content="{Binding BestBidSize, RelativeSource={RelativeSource TemplatedParent}}"
Foreground="{u:BTSColor MarketData.bid}"
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestBidSizeButtonStyleKey}}"
/>
<Button
x:Name="best_ask_size_button"
Grid.Column="4"
HorizontalAlignment="Stretch"
Content="{Binding BestAskSize, RelativeSource={RelativeSource TemplatedParent}}"
Foreground="{u:BTSColor MarketData.ask}"
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestAskSizeButtonStyleKey}}"
/>
<ItemsControl Grid.Row="2" Grid.RowSpan="2" Margin="-2,-2,-2,0" ItemsSource="{Binding SizePresets, RelativeSource={RelativeSource TemplatedParent}}" Focusable="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3" Focusable="False"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
HorizontalAlignment="Stretch"
Margin="2,4,2,0"
Content="{Binding}"
Focusable="False"
Style="{DynamicResource {x:Static usp:OrderParamsPanel.SizeCalculatorButtonStyleKey}}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Grid Grid.Row="2" Grid.RowSpan="2" Grid.Column="2" Grid.ColumnSpan="3">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ComboBox Margin="0,2,0,4" ItemsSource="{Binding Accounts, RelativeSource={RelativeSource TemplatedParent}}" SelectedItem="{Binding SelectedAccount, RelativeSource={RelativeSource TemplatedParent}}" ToolTip="Choose Account"/>
<ComboBox Grid.Row="1">
<ComboBox.Style>
<Style TargetType="ComboBox" BasedOn="{StaticResource DisabledPlaceholderComboBox}">
<Setter Property="ItemsSource">
<Setter.Value>
<x:ArrayExtension Type="{x:Type sys:String}">
<sys:String>Time In Force (N/A)</sys:String>
</x:ArrayExtension>
</Setter.Value>
</Setter>
</Style>
</ComboBox.Style>
</ComboBox>
</Grid>
</Grid>
<usp:AlgoEditorView x:Name="algo_editor" Grid.Row="4" DataContext="{Binding AlgoVM, RelativeSource={RelativeSource TemplatedParent}}"/>
</Grid>
</ControlTemplate>
<Style TargetType="usp:OrderParamsPanel">
<Setter Property="Template" Value="{StaticResource OrderParamsPanelTemplate}"/>
</Style>
</ResourceDictionary>
@@ -1,188 +0,0 @@
using bts.api;
using mbts.spark;
using System;
using System.Collections;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace mbts.ui.spark {
class OrderParamsPanel : Control {
public const string
BestBidPriceButtonStyleKey = nameof(BestBidPriceButtonStyleKey),
BestBidSizeButtonStyleKey = nameof(BestBidSizeButtonStyleKey),
BestAskPriceButtonStyleKey = nameof(BestAskPriceButtonStyleKey),
BestAskSizeButtonStyleKey = nameof(BestAskSizeButtonStyleKey),
SizeCalculatorButtonStyleKey = nameof(SizeCalculatorButtonStyleKey);
public static readonly RoutedCommand SetPriceCommand = new RoutedCommand();
public static readonly ExecutedRoutedEventHandler SetPriceExecuted =
(s, e) => {
if (s is OrderParamsPanel opp)
opp.SetCurrentValue(PriceProperty, ConvertUtils.try_convert(e.Parameter, out double d) ? d : double.NaN);
};
public static readonly CanExecuteRoutedEventHandler
SetPriceCanExecute = (s, e) => e.CanExecute = e.Parameter is double,
SetSizeCanExecute = (s, e) => e.CanExecute = e.Parameter is uint;
static OrderParamsPanel() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(OrderParamsPanel), new FrameworkPropertyMetadata(typeof(OrderParamsPanel)));
}
#region Price
public static readonly DependencyProperty PriceProperty =
WPFHelper.create_dp<double, OrderParamsPanel>(nameof(Price), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, double.NaN);
public double Price {
get => (double)GetValue(PriceProperty);
set => SetValue(PriceProperty, value);
}
#endregion
#region Size
public static readonly DependencyProperty SizeProperty =
WPFHelper.create_dp<uint?, OrderParamsPanel>(nameof(Size), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);
public uint? Size {
get => GetValue(SizeProperty) as uint?;
set => SetValue(SizeProperty, value);
}
#endregion
#region SizePresets
public static readonly DependencyProperty SizePresetsProperty =
WPFHelper.create_dp<IEnumerable, OrderParamsPanel>(nameof(SizePresets), Enumerable.Empty<string>());
public IEnumerable SizePresets {
get => GetValue(SizePresetsProperty) as IEnumerable;
set => SetValue(SizePresetsProperty, value);
}
#endregion
#region BestBidPrice
public static readonly DependencyProperty BestBidPriceProperty =
WPFHelper.create_dp<double?, OrderParamsPanel>(nameof(BestBidPrice));
public double? BestBidPrice {
get => GetValue(BestBidPriceProperty) as double?;
set => SetValue(BestBidPriceProperty, value);
}
#endregion
#region BestBidSize
public static readonly DependencyProperty BestBidSizeProperty =
WPFHelper.create_dp<uint?, OrderParamsPanel>(nameof(BestBidSize));
public uint? BestBidSize {
get => GetValue(BestBidSizeProperty) as uint?;
set => SetValue(BestBidSizeProperty, value);
}
#endregion
#region BestAskPrice
public static readonly DependencyProperty BestAskPriceProperty =
WPFHelper.create_dp<double?, OrderParamsPanel>(nameof(BestAskPrice));
public double? BestAskPrice {
get => GetValue(BestAskPriceProperty) as double?;
set => SetValue(BestAskPriceProperty, value);
}
#endregion
#region BestAskSize
public static readonly DependencyProperty BestAskSizeProperty =
WPFHelper.create_dp<uint?, OrderParamsPanel>(nameof(BestAskSize));
public uint? BestAskSize {
get => GetValue(BestAskSizeProperty) as uint?;
set => SetValue(BestAskSizeProperty, value);
}
#endregion
#region Accounts
public static readonly DependencyProperty AccountsProperty =
WPFHelper.create_dp<IEnumerable, OrderParamsPanel>(nameof(Accounts), Enumerable.Empty<string>());
public IEnumerable Accounts {
get => GetValue(AccountsProperty) as IEnumerable;
set => SetValue(AccountsProperty, value);
}
#endregion
#region SelectedAccount
public static readonly DependencyProperty SelectedAccountProperty =
WPFHelper.create_dp<object, OrderParamsPanel>(nameof(SelectedAccount));
public object SelectedAccount {
get => GetValue(SelectedAccountProperty);
set => SetValue(SelectedAccountProperty, value);
}
#endregion
#region TimeInForces
public static readonly DependencyProperty TimeInForcesProperty =
WPFHelper.create_dp<IEnumerable, OrderParamsPanel>(nameof(TimeInForces), Enumerable.Empty<string>());
public IEnumerable TimeInForces {
get => GetValue(TimeInForcesProperty) as IEnumerable;
set => SetValue(TimeInForcesProperty, value);
}
#endregion
#region SelectedTimeInForce
public static readonly DependencyProperty SelectedTimeInForceProperty =
WPFHelper.create_dp<object, OrderParamsPanel>(nameof(SelectedTimeInForce));
public object SelectedTimeInForce {
get => GetValue(SelectedTimeInForceProperty);
set => SetValue(SelectedTimeInForceProperty, value);
}
#endregion
#region Security
public static readonly DependencyProperty SecurityProperty =
WPFHelper.create_dp<SecurityImmutable?, OrderParamsPanel>(
nameof(Security),
(o, v) => o.CoerceValue(ContractSpecProperty));
public SecurityImmutable? Security {
get => GetValue(SecurityProperty) as SecurityImmutable?;
set => SetValue(SecurityProperty, value);
}
#endregion
#region ContractSpec
public static readonly DependencyProperty ContractSpecProperty =
WPFHelper.create_dp<ContractSpecificationImmutable?, OrderParamsPanel>(
nameof(ContractSpec),
null,
(d, base_value) => d.Security?.ContractSpec ?? base_value ?? SecurityDataHelper.Defaults.ContractSpecificationImmutable);
public ContractSpecificationImmutable? ContractSpec {
get => GetValue(ContractSpecProperty) as ContractSpecificationImmutable?;
set => SetValue(ContractSpecProperty, value);
}
#endregion
#region AlgoVM
public static readonly DependencyProperty AlgoVMProperty =
WPFHelper.create_dp<AlgoDefinitionVM, OrderParamsPanel>(
nameof(AlgoVM),
(o, v) => o._algo_editor?.SetCurrentValue(VisibilityProperty, v != null ? Visibility.Visible : Visibility.Collapsed));
public AlgoDefinitionVM AlgoVM {
get => GetValue(AlgoVMProperty) as AlgoDefinitionVM;
set => SetValue(AlgoVMProperty, value);
}
#endregion
AlgoEditorView _algo_editor;
public override void OnApplyTemplate() {
_algo_editor = GetTemplateChild("algo_editor") as AlgoEditorView;
_algo_editor?.SetCurrentValue(VisibilityProperty, AlgoVM != null ? Visibility.Visible : Visibility.Collapsed);
}
}
}
@@ -84,17 +84,17 @@ namespace bts.shell {
static class DefaultColumns {
public static GridColumnSettings account => new GridColumnSettings(col_id(Columns.Account), DefaultColumnHeaders.Account, width: DefaultColumnWidths.Account);
public static GridColumnSettings exchange => new GridColumnSettings(col_id(Columns.Exchange), DefaultColumnHeaders.Exchange, width: DefaultColumnWidths.Exchange);
public static GridColumnSettings exchange => new GridColumnSettings(col_id(Columns.Exchange), DefaultColumnHeaders.Exchange, false, width: DefaultColumnWidths.Exchange);
public static GridColumnSettings symbol => new GridColumnSettings(col_id(Columns.Symbol), DefaultColumnHeaders.Symbol, width: DefaultColumnWidths.Symbol);
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
public static GridColumnSettings security_type => new GridColumnSettings(col_id(Columns.SecurityType), DefaultColumnHeaders.SecurityType, width: DefaultColumnWidths.SecurityType);
public static GridColumnSettings start_net_position => new GridColumnSettings(col_id(Columns.StartNetPos), DefaultColumnHeaders.StartNetPos, width: DefaultColumnWidths.Start_Net_Pos, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings start_pnl => new GridColumnSettings(col_id(Columns.StartPNL), DefaultColumnHeaders.StartPnL, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings start_pnl => new GridColumnSettings(col_id(Columns.StartPNL), DefaultColumnHeaders.StartPnL, false, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings day_long_pnl => new GridColumnSettings(col_id(Columns.DayLong), DefaultColumnHeaders.DayLong, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings day_short_pnl => new GridColumnSettings(col_id(Columns.DayShort), DefaultColumnHeaders.DayShort, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings day_pnl => new GridColumnSettings(col_id(Columns.DayPNL), DefaultColumnHeaders.DayPnL, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings total_net_pos => new GridColumnSettings(col_id(Columns.TotalNetPos), DefaultColumnHeaders.TotalNetPos, width: DefaultColumnWidths.Total_Net_Pos, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings total_pnl => new GridColumnSettings(col_id(Columns.TotalPNL), DefaultColumnHeaders.TotalPnL, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings total_net_pos => new GridColumnSettings(col_id(Columns.TotalNetPos), DefaultColumnHeaders.TotalNetPos, false, width: DefaultColumnWidths.Total_Net_Pos, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings total_pnl => new GridColumnSettings(col_id(Columns.TotalPNL), DefaultColumnHeaders.TotalPnL, false, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings pnl_price => new GridColumnSettings(col_id(Columns.PNLPrice), DefaultColumnHeaders.PnLPrice, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static IEnumerable<SortingCriteria> default_sorting_criterion {
@@ -158,6 +158,8 @@ namespace bts.shell {
// ProtoMember 13 cannot be used further
[ProtoMember(14)]
public SecurityType[] security_types;
[ProtoMember(15)]
public bool[] has_day_trades;
}
}
@@ -12,7 +12,6 @@ using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using OrderTuple = System.ValueTuple<bts.api.SecurityImmutable?, bts.api.orderman.OrderRecordImmutable>;
namespace bts.shell {
interface INotificationService {
@@ -20,40 +19,14 @@ namespace bts.shell {
INotificationServiceDataCache data_cache { get; }
IDisposable link_to_activity(IPredicate<Activity> predicate, SoundNotification sound);
IDisposable link_to_activity(INotificationPredicate<Activity> toast_notification_predicate);
IDisposable link_to_order(IPredicate<OrderTuple> predicate, SoundNotification sound);
IDisposable link_to_order(INotificationPredicate<OrderTuple> toast_notification_predicate);
}
static class NotificationServiceExtensions {
/// TODO: remove this extension and force callers to use the appropriate 'link' method in <see cref="INotificationService"/> (it is smelly, and is NOT ACTUALLY STRONGLY TYPED)
public static IDisposable link<T>(this INotificationService notification_service, IPredicate<T> predicate, SoundNotification sound) {
if (predicate is IPredicate<OrderTuple> orp)
return notification_service.link_to_order(orp, sound);
if (predicate is IPredicate<OrderSecurity> osp) {
// Note: watch out for implicit types as generic type arguments (although OrderSecurity can be implicitly cast to OrderTuple, IPredicate<OrderSecurity> CANNOT be implicitly cast to IPredicate<OrderTuple>)
return notification_service.link_to_order(PredicateBuilder.create<OrderTuple>(ot => osp.trigger(new OrderSecurity(ot.Item1, ot.Item2))), sound);
}
if (predicate is IPredicate<Activity> ap)
return notification_service.link_to_activity(ap, sound);
Log.Ui.error($"Unable to activate sound-trigger (unrecognized predicate type '{typeof(T)}').");
return Disposable.empty;
}
/// TODO: remove this extension and force callers to use the appropriate 'link' method in <see cref="INotificationService"/> (it is smelly, and is NOT ACTUALLY STRONGLY TYPED)
public static IDisposable link<T>(this INotificationService notification_service, INotificationPredicate<T> toast_notification_predicate) {
if (toast_notification_predicate is INotificationPredicate<OrderTuple> orp)
return notification_service.link_to_order(orp);
if (toast_notification_predicate is INotificationPredicate<Activity> ap)
return notification_service.link_to_activity(ap);
Log.Ui.error($"Unable to activate toast-notification-trigger (unrecognized predicate type '{typeof(T)}').");
return Disposable.empty;
}
IDisposable link_to_order(IPredicate<OrderSecurity> predicate, SoundNotification sound);
IDisposable link_to_order(INotificationPredicate<OrderSecurity> toast_notification_predicate);
}
[Export(typeof(INotificationService))]
partial class NotificationService : INotificationService, IDisposable {
readonly NotificationProcessor<Activity> _activity_notifications;
readonly NotificationProcessor<OrderTuple> _order_notifications;
readonly NotificationProcessor<OrderSecurity> _order_notifications;
readonly DataCache _data_cache = new DataCache();
IDisposable _dispose;
@@ -62,7 +35,7 @@ namespace bts.shell {
public NotificationService(IBus bus, INotifier notifier, OrdersModel orders_model) {
this.notifier = notifier;
_activity_notifications = new NotificationProcessor<Activity>(notifier);
_order_notifications = new NotificationProcessor<OrderTuple>(notifier);
_order_notifications = new NotificationProcessor<OrderSecurity>(notifier);
_dispose = Disposable.Create(
bus.subscribe(SparkTags.LogActivity).Subscribe(_on_activity),
orders_model.on_orders.Subscribe(_on_orders)
@@ -75,8 +48,8 @@ namespace bts.shell {
public IDisposable link_to_activity(IPredicate<Activity> predicate, SoundNotification sound) => _activity_notifications.link(predicate, sound);
public IDisposable link_to_activity(INotificationPredicate<Activity> toast_notification_predicate) => _activity_notifications.link(toast_notification_predicate);
public IDisposable link_to_order(IPredicate<OrderTuple> predicate, SoundNotification sound) => _order_notifications.link(predicate, sound);
public IDisposable link_to_order(INotificationPredicate<OrderTuple> toast_notification_predicate) => _order_notifications.link(toast_notification_predicate);
public IDisposable link_to_order(IPredicate<OrderSecurity> predicate, SoundNotification sound) => _order_notifications.link(predicate, sound);
public IDisposable link_to_order(INotificationPredicate<OrderSecurity> toast_notification_predicate) => _order_notifications.link(toast_notification_predicate);
public void Dispose() => Disposable.dispose(ref _dispose);
void _on_activity(Activity activity) =>
@@ -79,12 +79,10 @@ namespace mbts.spark {
RequestClose?.Invoke(this, new EventArgs());
}
});
bbo = new BBOVM();
_dispose = Disposable.Create(
size_calculator,
properties_vm,
security_md_source.OnDispatcher().Subscribe(smd => bbo.security_md = smd),
bbo,
bbo = new BBOVM(security_md_source),
order_source.OnDispatcher().Subscribe(ort => {
key = ort.order.Key;
if (DoubleHelper.IsNaNOrInfinity(price))
+1 -3
View File
@@ -49,7 +49,7 @@ namespace mbts.spark {
) {
_security = backing(nameof(security), security_md_source.Select(smd => smd.security), SecurityDataHelper.Defaults.SecurityImmutable);
properties_vm = new OrderPropertiesEditorVM(model, security_md_source, props_source.Select(p => p.order_props));
bbo = new BBOVM();
bbo = new BBOVM(security_md_source);
send_order = new DelegateCommand<Side>(side => {
bool price_valid = _validate_price(), size_valid = size_calculator.validate();
if (price_valid && size_valid && !HasErrors && !properties_vm.HasErrors) {
@@ -59,8 +59,6 @@ namespace mbts.spark {
});
set_price_command = new DelegateCommand<double>(set_price);
_dispose = Disposable.Create(
bbo,
security_md_source.OnDispatcher().Subscribe(smd => bbo.security_md = smd),
size_calculator = new CalculatorLogic(0, calculator_buttons),
props_source.OnDispatcher().Subscribe(p => on_props(p.price, p.size))
);
@@ -2,7 +2,6 @@
using bts.api.core;
using bts.api.orderman;
using bts.shell;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.ui.controls;
@@ -84,18 +83,25 @@ namespace bts.spark {
public static string modified_description(OrderRecordImmutable old_order, OrderRecordImmutable new_order, ContractSpecificationImmutable? spec) {
double old_price = old_order.State.Price, new_price = new_order.State.Price;
uint old_size = old_order.State.Quantity, new_size = new_order.State.Quantity;
uint old_attached_target_key = old_order.attached_target_order_key(), new_attached_target_key = new_order.attached_target_order_key();
return StringBuilderEx.join(prefix, change_descs());
//
string prefix() => $"({old_order.Key}) Changed";
IEnumerable<string> change_descs() {
if (old_price != new_price)
yield return $"price from {spec.format(old_price)} to {spec.format(new_price)}";
if (old_size != new_size)
yield return $"size from {old_size} to {new_size}, working {new_order.working_quantity()}";
if (old_attached_target_key != new_attached_target_key)
yield return $"attached target from {old_attached_target_key} to {new_attached_target_key}";
string modified_size_partial_description(int working_size) {
return $"size from {old_size} to {new_size}, working {working_size}";
}
string modified_price_partial_description() {
return $"price from {spec.format(old_price)} to {spec.format(new_price)}";
}
if (old_price != new_price || old_size != new_size) {
StringBuilder sb = new StringBuilder($"({old_order.Key}) Changed ");
if (old_price != new_price) {
sb.Append(modified_price_partial_description());
if (old_size != new_size)
sb.Append($", {modified_size_partial_description(new_order.working_quantity())}");
}
else
sb.Append(modified_size_partial_description(new_order.working_quantity()));
return sb.ToString();
}
else
return string.Empty;
}
static string _header(uint key) => $"({key})";
@@ -178,31 +184,21 @@ namespace bts.spark {
format_cancel_error(order, security?.ContractSpec, error));
}
internal void update_order(OrderRecordImmutable order, double price, uint size, bool algo_changed) =>
_log_order_update(order, price, size, algo_changed, "change from");
internal void update_order_failed(OrderRecordImmutable order, double price, uint size, bool algo_changed) =>
_log_order_update(order, price, size, algo_changed, "failed to change from");
void _log_order_update(OrderRecordImmutable order, double price, uint size, bool algo_changed, string change_desc_prefix) {
if (price == order.price() && size == order.working_quantity() && !algo_changed) {
// nothing actually changed
return;
}
const string algo_change_text = "change algo parameters";
internal void update_order(OrderRecordImmutable order, double price, uint size) {
var security = try_get_security(order.Def.SecurityKey);
var spec = security?.ContractSpec;
var desc_builder = new StringBuilderEx();
if (price != order.price() || size != order.working_quantity()) {
desc_builder.Add(order.State.Quantity.ToString(), "@", spec.format(order.State.Price), "to ", size.ToString(), "@", spec.format(price));
if (algo_changed)
desc_builder.Add("; ", algo_change_text);
} else if (algo_changed) {
desc_builder.Add(algo_change_text);
}
desc_builder.Insert(0, $"{change_desc_prefix} ");
desc_builder.Insert(0, $"({order.Key}) ");
log_status(ActivityLogCategory.modify, security, desc_builder.ToString());
log_status(ActivityLogCategory.modify,
security,
$"({order.Key}) change from {order.State.Quantity}@{spec.format(order.State.Price)} to {size}@{spec.format(price)}");
}
internal void update_order_failed(OrderRecordImmutable order, double price, uint size) {
var security = try_get_security(order.Def.SecurityKey);
var spec = security?.ContractSpec;
log_status(ActivityLogCategory.modify,
security,
$"({order.Key}) failed to change from {order.State.Quantity}@{spec.format(order.State.Price)} to {size}@{spec.format(price)}");
}
}
}
@@ -346,9 +346,9 @@ namespace mbts.spark {
_time_in_force.observe_current(start_immediately);
public IObservable<IAlgoVM> observe_order_type(bool start_immediately = false) =>
_model.algo_model.templates_enabled() ?
_selected_algo_template.observe_current(start_immediately).Select(t => t?.algo_root) :
_selected_algo_definition.observe_selected_item(start_immediately).Select(d => d?.algo_root);
_model.algo_model.templates_enabled() ?
_selected_algo_template.observe_current(start_immediately).Select(t => t?.algo_root) :
_selected_algo_definition.observe_selected_item(start_immediately).Select(d => d?.algo_root);
public OrderDefinition create(Side side) {
if (HasErrors)
@@ -11,7 +11,7 @@ using AlgoDefi = bts.api.orderman.CustomOrderDefinitionImmutable;
using AlgoType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
namespace mbts.spark {
class DefaultAlgoVM : DisposableViewModelBase, IAlgoVM, IDynamicAlgoVMImpl, IObserver<SecurityMD?>, IObserver<OrdersModel>, IObserver<AlgoEditingPermissions> {
class DefaultAlgoVM : DisposableViewModelBase, IAlgoVM, IDynamicAlgoVMImpl, IObserver<SecurityMD?>, IObserver<OrdersModel> {
static readonly AlgoDefi _empty = new AlgoDefi(new AlgoDef());
readonly VersionContainer<AlgoDefi> _definition;
AlgoFormats _format;
@@ -37,7 +37,10 @@ namespace mbts.spark {
impl.is_expanded = value;
}
}
public AlgoEditingPermissions editing_permissions => _editing_permissions;
public bool can_edit_values => impl?.can_edit_values ?? false;
public bool can_edit_structure => impl?.can_edit_structure ?? false;
public bool can_save_values => impl?.can_save_values ?? false;
public bool can_save_structure => impl?.can_save_structure ?? false;
public string display_name => impl?.display_name ?? "(Default)";
public AlgoDefi definition => _definition.value;
public ObservableCollection<IAlgoSelector> child_algo_selectors { get; } = new ObservableCollection<IAlgoSelector>();
@@ -45,6 +48,10 @@ namespace mbts.spark {
public void assign_to(AlgoDef value) => value.MergeFrom(definition.CloneToMutable());
public bool key_match(KeyEventArgs e) => false;
public void load_from(AlgoDefi value) => impl?.load_from(value);
public void set_permissions(AlgoEditingPermissions editing_permissions) {
_editing_permissions = editing_permissions;
impl?.set_permissions(editing_permissions);
}
public Task<AlgoDefi?> load_from_async(AlgoDefi definition) =>
impl?.load_from_async(definition) ?? Task.FromResult<AlgoDefi?>(definition);
@@ -91,13 +98,6 @@ namespace mbts.spark {
_on_impl_definition_changed();
}
void IObserver<AlgoEditingPermissions>.OnCompleted() { }
void IObserver<AlgoEditingPermissions>.OnError(Exception error) { }
void IObserver<AlgoEditingPermissions>.OnNext(AlgoEditingPermissions editing_permissions) {
Set(ref _editing_permissions, editing_permissions, nameof(this.editing_permissions));
(impl as IObserver<AlgoEditingPermissions>)?.OnNext(editing_permissions);
}
void _on_impl_definition_changed() {
var orig_def = definition;
_definition.increment_version();
@@ -107,4 +107,4 @@ namespace mbts.spark {
RaisePropertyChanged(nameof(order_type), nameof(meta));
}
}
}
}
@@ -2,7 +2,6 @@ using bts.api;
using bts.api.orderman;
using bts.utils;
using Google.Protobuf.Reflection;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
@@ -12,9 +11,8 @@ using System.Reactive.Linq;
namespace mbts.spark {
[Export]
class DynamicOrderHelper {
public readonly IMaskIndexInt32<string> dynamic_type_index;
public class DynamicOrderHelper
{
static readonly IReadOnlyDictionary<DynamicOrderParam.ValueOneofCase, (FieldDescriptor field_descriptor, Type field_type)> _descriptor_case_index =
ProtoHelper.get_oneof_descriptor_index<DynamicOrderParam, DynamicOrderParam.ValueOneofCase>(nameof(DynamicOrderParam.ValueCase));
@@ -23,7 +21,6 @@ namespace mbts.spark {
private DynamicOrderHelper(OrderService order_service)
{
_dynamic_types = order_service.get_registered_dynamic_order_types(new VoidMessage());
dynamic_type_index = new MaskIndexInt32<string>(_dynamic_types.DynamicOrderDesc.Select(d => d.DynamicOrderType).ToList());
}
public void add_dynamic_order_defs(List<CustomOrderDefinitionImmutable> defs,
@@ -45,25 +42,20 @@ namespace mbts.spark {
internal DynamicOrderDefinition create_def(string dynamic_type) {
var desc = get_descriptor(dynamic_type);
var def = new DynamicOrderDefinition { DynamicOrderType = dynamic_type };
add_dynamic_params(ref def);
return def;
}
internal void add_dynamic_params(ref DynamicOrderDefinition dynamic_def) {
var desc = get_descriptor(dynamic_def.DynamicOrderType);
var def = new DynamicOrderDefinition();
foreach (var p in desc.ParamDesc) {
if (_is_child_algo_desc(p)) {
dynamic_def.OrderParams[p.ParamName] = new DynamicOrderParam { AsOrderDef = new CustomOrderDefinition() };
def.OrderParams[p.ParamName] = new DynamicOrderParam { AsOrderDef = new CustomOrderDefinition() };
} else if (_is_value_param_desc(p)) {
var value_case = _get_value_case(p.ParamType.Primitive);
if (_descriptor_case_index.TryGetValue(value_case, out (FieldDescriptor fd, Type type) tup)) {
var dop = new DynamicOrderParam();
tup.fd.Accessor.SetValue(dop, ConvertUtils.get_default(tup.type));
dynamic_def.OrderParams[p.ParamName] = dop;
def.OrderParams[p.ParamName] = dop;
}
}
}
return def;
}
static DynamicOrderParam.ValueOneofCase _get_value_case(DynamicOrderPrimitiveDescriptor primitive_descriptor_case) {
@@ -22,13 +22,17 @@ namespace mbts.spark {
CustomOrderDefinition.CustomOneofCase order_type { get; }
bool is_bts_type { get; }
OrderMetadata meta { get; }
AlgoEditingPermissions editing_permissions { get; }
bool can_expand { get; }
bool is_expanded { get; set; }
bool can_edit_values { get; }
bool can_edit_structure { get; }
bool can_save_values { get; }
bool can_save_structure { get; }
string display_name { get; }
CustomOrderDefinitionImmutable definition { get; }
ObservableCollection<IAlgoSelector> child_algo_selectors { get; }
bool key_match(KeyEventArgs e);
void set_permissions(AlgoEditingPermissions editing_permissions);
Task<CustomOrderDefinitionImmutable?> load_from_async(CustomOrderDefinitionImmutable definition);
}
@@ -87,4 +91,4 @@ namespace mbts.spark {
ObservableAutoWrapper.connect_item_hooks(vm.child_algo_selectors, s => load_algo_data_recursive(s, algo_data, default_algo)));
}
}
}
}
@@ -95,32 +95,38 @@ namespace mbts.spark {
public static FieldDescriptor find_descriptor<T>() =>
_algo_type_index.TryGetValue(typeof(T), out FieldDescriptor fd) ?
fd : throw new NotSupportedException($"No {nameof(AlgoDef)} {nameof(FieldDescriptor)} exists for type '{typeof(T)}.'");
public static Func<TMutable, TImmutable> immutable_factory<TMutable, TImmutable>() where TMutable : IMessage, new() where TImmutable : IImmutableMessage =>
_ImmutableActivator<TMutable, TImmutable>.factory;
static class _ImmutableActivator<TMutable, TImmutable> where TMutable : IMessage, new() where TImmutable : IImmutableMessage {
public static readonly Func<TMutable, TImmutable> factory =
m => (TImmutable)Activator.CreateInstance(typeof(TImmutable), m);
}
}
#region OrderTypeVM Base Classes
abstract class OrderTypeVMBase<TMutable, TImmutable> : DisposableViewModelBase, IAlgoVM,
IObserver<SecurityMD?>,
IObserver<SecurityImmutable?>,
IObserver<OrdersModel>,
IObserver<AlgoEditingPermissions> where TMutable : class, IMessage, new() where TImmutable : struct, IImmutableMessage {
abstract class OrderTypeVMBase<TMutable, TImmutable> : DisposableViewModelBase, IAlgoVM, IObserver<SecurityMD?>, IObserver<OrdersModel> where TMutable : class, IMessage, new() where TImmutable : struct, IImmutableMessage {
/// TODO: maybe use explicit delegates rather than rely on reflection <see cref="_descriptor"/> <see cref="_create_immutable"/>.
static readonly FieldDescriptor _descriptor = OrderTypeVMUtils.find_descriptor<TMutable>();
static readonly Func<TMutable, TImmutable> _create_immutable = OrderTypeVMUtils.immutable_factory<TMutable, TImmutable>();
readonly IDictionary<string, object> _children_vm = new ExpandoObject();
protected CompositeDisposable _resources = new CompositeDisposable();
readonly ObservableKeyedCollection<(string name, Action<TMutable> assign, Action<TMutable> load), string> _auto_props = new ObservableKeyedCollection<(string name, Action<TMutable> assign, Action<TMutable> load), string>(t => t.name);
readonly ObservableKeyedCollection<AlgoSelectorData, string> _child_algo_selector_data = new ObservableKeyedCollection<AlgoSelectorData, string>(sd => sd.prop_name);
readonly ObservableAutoWrapper<IAlgoSelector, AlgoSelectorData> _child_algo_selectors;
readonly Property<bool> _is_expanded;
readonly Property<bool> _is_expanded, _can_save_values, _can_save_structure;
readonly DeferrableActionWrapper _def_change_notifier;
SecurityInput _security_input;
AlgoEditingPermissions _editing_permissions;
IDisposable _pending_load;
protected OrderTypeVMBase(AlgoKey id, string display_name, AlgoEditingPermissions editing_permissions) {
this.id = id;
this.display_name = display_name;
_editing_permissions = editing_permissions;
this.editing_permissions = editing_permissions;
_child_algo_selectors = new ObservableAutoWrapper<IAlgoSelector, AlgoSelectorData>(_child_algo_selector_data, sd => sd.selector);
_can_save_values = backing(nameof(can_save_values), true);
_can_save_structure = backing(nameof(can_save_structure), true);
_is_expanded = backing<bool>(nameof(is_expanded));
meta = OrderDataHelper.get_metadata(order_type);
_def_change_notifier = new DeferrableActionWrapper(() => definition_changed?.Invoke());
@@ -128,6 +134,10 @@ namespace mbts.spark {
switch (e.PropertyName) {
// ignore props that don't affect the resultant AlgoDef
case nameof(is_expanded):
case nameof(can_edit_values):
case nameof(can_edit_structure):
case nameof(can_save_values):
case nameof(can_save_structure):
return;
default:
_def_change_notifier.execute();
@@ -138,7 +148,10 @@ namespace mbts.spark {
CompositeDisposable def_sub = null;
sd.selector.selected_algo_changed += subscribe_new_algo;
return new CompositeDisposable(
ObservableAutoWrapper.try_dispose_on_removed(sd.selector.algos, true)) {
ObservableAutoWrapper.connect_item_hooks(sd.selector.algos, a => {
a.set_permissions(this.editing_permissions);
return a as IDisposable ?? Disposable.empty;
}, true)) {
() => {
sd.selector.selected_algo_changed -= subscribe_new_algo;
Disposable.dispose(ref def_sub);
@@ -157,7 +170,6 @@ namespace mbts.spark {
}
public event Action definition_changed;
event Action<SecurityImmutable?> _security_changed;
// core
public AlgoDefi definition => new AlgoDefi(this.created_assigned());
public AlgoType order_type => OrderDataHelper.get_order_type(new TMutable());
@@ -170,14 +182,18 @@ namespace mbts.spark {
public dynamic children_vm => _children_vm;
public ObservableCollection<IAlgoSelector> child_algo_selectors => _child_algo_selectors;
// editing permissions
public AlgoEditingPermissions editing_permissions { get => _editing_permissions; private set => Set(ref _editing_permissions, value); }
// expand
public AlgoEditingPermissions editing_permissions { get; private set; }
public bool can_edit_values => EnumHelper.includes_flag(editing_permissions, AlgoEditingPermissions.AllowEditValues);
public bool can_edit_structure => EnumHelper.includes_flag(editing_permissions, AlgoEditingPermissions.AllowEditStructure);
public bool can_save_values => _can_save_values.Value;
public bool can_save_structure => _can_save_structure.Value;
// misc
public virtual bool can_expand => !meta.NoProps;
public virtual bool is_expanded { get => _is_expanded.Value; set => _is_expanded.Value = value; }
// protected (observed props)
protected OrdersModel model { get; private set; }
protected SecurityMD? security_md => _security_input.security_md;
protected SecurityImmutable? security => _security_input.security;
protected SecurityMD? security_md { get; private set; }
protected SecurityImmutable? security => security_md?.security;
public void assign_to(AlgoDef value) {
var algo = new TMutable();
@@ -190,6 +206,12 @@ namespace mbts.spark {
}
}
public void set_permissions(AlgoEditingPermissions editing_permissions) {
using (detect_property_changes(nameof(can_edit_values), nameof(can_edit_structure), nameof(can_save_values), nameof(can_save_structure)))
this.editing_permissions = editing_permissions;
on_permissions_changed();
}
public bool key_match(KeyEventArgs key) =>
!string.IsNullOrEmpty(display_name) && key.Key == (Key.A + display_name[0] - 'A');
@@ -197,31 +219,22 @@ namespace mbts.spark {
if (value.CustomCase != order_type)
return;
var algom = _descriptor.Accessor.GetValue(value.CloneToMutable()) as TMutable ?? new TMutable();
var security_src = Observable.Create<SecurityImmutable?>(o => {
o.OnNext(security);
_security_changed += o.OnNext;
return Disposable.Create(() => _security_changed -= o.OnNext);
});
Disposable.replace(ref _pending_load,
Disposable.Create(
ObservableAutoWrapper.connect_item_hooks(
_child_algo_selector_data.AsCollection(),
sd => security_src.Subscribe(_ => ObservableAutoWrapper.connect_item_actions(_child_algo_selector_data.AsCollection(), selector_load_impl))),
new CompositeDisposable(
ObservableAutoWrapper.connect_item_actions(_child_algo_selector_data.AsCollection(), sd => {
var inner_def = sd.get_order(value);
if (inner_def.HasValue) {
if (security.HasValue) {
inner_def = OrderDataHelper.fit_to_type(inner_def.Value, security.Value.ContractCase);
if (!inner_def.HasValue)
return;
}
var vm = sd.selector.algos.GetOrAdd(OrderDataHelper.get_shallow_factory_key(inner_def.Value), k => create(k, inner_def.Value.display_name(), inner_def.Value.CustomCase));
vm.load_from(inner_def.Value);
sd.selector.selected_algo = vm;
}
}),
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load(algom))));
void selector_load_impl(AlgoSelectorData sd) {
var inner_def = sd.get_order(value);
if (inner_def.HasValue) {
if (security.HasValue) {
inner_def = OrderDataHelper.fit_to_type(inner_def.Value, security.Value.ContractCase);
if (!inner_def.HasValue)
return;
}
var vm = sd.selector.algos.GetOrAdd(OrderDataHelper.get_shallow_factory_key(inner_def.Value), k => create(k, inner_def.Value.display_name(), inner_def.Value.CustomCase));
vm.load_from(inner_def.Value);
sd.selector.selected_algo = vm;
}
}
}
public virtual async Task<AlgoDefi?> load_from_async(AlgoDefi defi) {
@@ -247,6 +260,11 @@ namespace mbts.spark {
protected virtual Task on_load_from_async(AlgoDefi defi) => Task.CompletedTask;
protected virtual void on_permissions_changed() {
foreach (var vm in child_algo_selectors.SelectMany(s => s.algos.AsCollection()))
vm.set_permissions(editing_permissions);
}
protected void raise_definition_changed() =>
_def_change_notifier.execute();
@@ -319,11 +337,12 @@ namespace mbts.spark {
protected Property<TImmutableValue> add_property<TMutableValue, TImmutableValue>(string prop_name, int target_field_number, TImmutableValue init_value = default) where TMutableValue : class, IMessage, new() where TImmutableValue : IImmutableMessage {
var prop = backing(prop_name, init_value);
var field_desc = new TMutable().Descriptor.FindFieldByNumber(target_field_number);
var immutable_factory = OrderTypeVMUtils.immutable_factory<TMutableValue, TImmutableValue>();
add_property<TMutableValue>(
prop_name,
mdef => field_desc.Accessor.GetValue(mdef) as TMutableValue,
(mdef, mval) => field_desc.Accessor.SetValue(mdef, mval),
mval => prop.set(ProtoHelper.immutable_factory<TMutableValue, TImmutableValue>(mval ?? new TMutableValue())),
mval => prop.set(immutable_factory(mval ?? new TMutableValue())),
() => {
// TODO: perhaps CloneToMutable() should be a member of IImmutableMessage.
// this method makes things so simple for the caller...but this dynamic cast is a little sketch.
@@ -362,7 +381,6 @@ namespace mbts.spark {
protected virtual void on_model_internal() { }
protected virtual void on_security_md_internal() { }
protected virtual void on_security_internal() { }
// model
void IObserver<OrdersModel>.OnNext(OrdersModel model) {
@@ -382,40 +400,14 @@ namespace mbts.spark {
void IObserver<OrdersModel>.OnCompleted() { }
// security md
void IObserver<SecurityMD?>.OnNext(SecurityMD? security_md) {
using (_def_change_notifier.defer_execution())
using (_update_sec_input())
_security_input = _security_input.with_security_md(security_md);
this.security_md = security_md;
using (_def_change_notifier.defer_execution()) {
on_security_md_internal();
_populate_selectors();
}
}
void IObserver<SecurityMD?>.OnError(Exception error) { }
void IObserver<SecurityMD?>.OnCompleted() { }
// security
void IObserver<SecurityImmutable?>.OnNext(SecurityImmutable? security) {
using (_def_change_notifier.defer_execution())
using (_update_sec_input())
_security_input = _security_input.with_security(security);
}
void IObserver<SecurityImmutable?>.OnError(Exception error) { }
void IObserver<SecurityImmutable?>.OnCompleted() { }
// editing permissions
void IObserver<AlgoEditingPermissions>.OnNext(AlgoEditingPermissions editing_permissions) => this.editing_permissions = editing_permissions;
void IObserver<AlgoEditingPermissions>.OnError(Exception error) { }
void IObserver<AlgoEditingPermissions>.OnCompleted() { }
IDisposable _update_sec_input() {
var old_sec = security;
var old_sec_md = security_md;
return Disposable.Create(() => {
if (security != old_sec) {
on_security_internal();
if (_editing_permissions.HasFlag(AlgoEditingPermissions.AllowEditStructure))
_populate_selectors();
_security_changed?.Invoke(security);
}
if (security_md != old_sec_md)
on_security_md_internal();
});
}
IDisposable _selector_data_hooks;
void _populate_selectors() {
@@ -434,40 +426,6 @@ namespace mbts.spark {
}
}
// security input may come in 2 forms:
// 1) SecurityImmutable?
// 2) SecurityMD?
// This structure resolves conflicts that may arise from the different inputs
struct SecurityInput {
readonly SecurityMD? _raw_security_md;
readonly SecurityImmutable? _raw_security;
public readonly SecurityMD? security_md;
public readonly SecurityImmutable? security;
SecurityInput(SecurityImmutable? raw_security, SecurityMD? raw_security_md, SecurityImmutable? security, SecurityMD? security_md) {
_raw_security = raw_security;
_raw_security_md = raw_security_md;
this.security_md = security_md;
this.security = security;
}
public SecurityInput with_security(SecurityImmutable? security) =>
new SecurityInput(
security,
_raw_security_md,
security,
security.HasValue && _raw_security_md.HasValue && _raw_security_md.Value.security.Id != security.Value.Id ?
null : _raw_security_md);
public SecurityInput with_security_md(SecurityMD? security_md) =>
new SecurityInput(
_raw_security,
security_md,
security_md?.security ?? _raw_security,
security_md);
}
struct AlgoSelectorData {
public readonly string prop_name;
public readonly AlgoSelector selector;
@@ -487,15 +445,14 @@ namespace mbts.spark {
IReadOnlyDictionary<AlgoKey, AlgoData> _pending_data;
AlgoKey? _pending_default;
public AlgoSelector() {
public AlgoSelector() =>
algos_view = ObservableAutoWrapper.create_sorted_wrapper(algos, AlgoCollectionHelper.algo_vm_comparer);
selected_algo_changed += (o, n) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(selected_algo)));
}
public void load_algo_data(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo) {
_pending_data = algo_data;
_pending_default = default_algo;
_sync_formats();
selected_algo_changed += (o, n) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(selected_algo)));
}
public event PropertyChange<IAlgoVM> selected_algo_changed;
@@ -556,4 +513,4 @@ namespace mbts.spark {
}
#endregion
}
}
}
@@ -1,5 +1,4 @@
using bts;
using bts.api;
using bts.api.orderman;
using mbts.ui.controls;
using mbts.ui.utility;
@@ -12,7 +11,7 @@ using SpreaderDefi = bts.api.orderman.SpreaderOrderDefinitionImmutable;
namespace mbts.spark {
static partial class OrderTypeVMFactory {
class SpreaderOrderVM : OrderTypeVMBase<SpreaderDef, SpreaderDefi>, IAlgoVM, ILazyAlgo, IObserver<OrdersModel> {
class SpreaderOrderVM : OrderTypeVMBase<SpreaderDef, SpreaderDefi>, IAlgoVM, ILazyAlgo, IObserver<SecurityMD?>, IObserver<OrdersModel> {
readonly Property<SpreaderDefi?> _template;
readonly StrategyEditorWrapper _strategy_editor_wrapper;
IDisposable _strategy_sub;
@@ -79,27 +78,16 @@ namespace mbts.spark {
set_error(nameof(template), "Waiting for security and/or strategy.", true);
else clear_error(nameof(template));
}
<<<<<<< HEAD
protected override void on_model_internal() => _reset_strategy_sub();
protected override void on_security_internal() => _reset_strategy_sub();
=======
protected override void on_security_md_internal() => _reset_strategy_sub();
protected override void on_model_internal() => _reset_strategy_sub();
>>>>>>> master
void _reset_strategy_sub() {
Disposable.dispose(ref _strategy_sub);
if (model != null) {
<<<<<<< HEAD
if (security.HasValue) {
_strategy_sub = model.spreader.security_strategy(security.Value.Id)
.Select(strategy => new SpreaderDefi?(model.factory.spreader_orderi(security.Value, strategy)))
=======
if (security_md.HasValue) {
_strategy_sub = model.spreader.security_strategy(security_md.Value.security.Id)
.Select(strategy => new SpreaderDefi?(model.factory.spreader_orderi(security_md.Value.security, strategy)))
>>>>>>> master
.OnDispatcher()
.Subscribe(s => {
is_loaded = true;
@@ -130,4 +130,4 @@ namespace mbts.spark {
}
}
}
}
}
+15 -22
View File
@@ -86,7 +86,7 @@ namespace mbts.spark {
(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);
on_order_tuples = order_secs_algo_meta_join.Cache();
on_order_tuples = order_secs_algo_meta_join;
var on_fills =
_service.subscribei_fills()
.Naggle(interval, scheduler)
@@ -101,14 +101,17 @@ namespace mbts.spark {
(f, fk, o, ok) => new FillTuple(f, o),
(f, fk, ok) => new FillTuple(f, null),
f => f.fill.FillKey);
on_fill_tuples = fill_order_tuples_join.Cache();
on_fill_tuples = fill_order_tuples_join;
_dispose = Disposable.Create(
on_orders.Select(t => t.order, o => o.Key).Subscribe(_security_orders_demuxer),
on_positions.Subscribe(_security_positions_demuxer),
order_secs_algo_meta_join.Connect(),
algo_meta_join.Connect(),
fill_order_tuples_join.Connect(),
_bus.subscribe(SparkTags.CreateOrder).Subscribe(a => send_order(a.definition, a.price, a.size).ignore()),
_bus.subscribe(SparkTags.RecreateOrder).Subscribe(a => a.ForEach(x => send_order(x.definition, x.price, x.size).ignore())),
_bus.subscribe(SparkTags.CancelOrders).Subscribe(x => cancel_orders(x).ignore()),
_bus.subscribe(SparkTags.DetachOrders).Subscribe(x => detach_orders(x).ignore()),
_bus.subscribe(SparkTags.CrackOrders).Subscribe(x => crack_orders(x).ignore()),
_bus.subscribe(SparkTags.UpdateOrder).Subscribe(x => this.update_order(x.key, x.price, x.size).ignore())
);
}
@@ -163,37 +166,27 @@ namespace mbts.spark {
return success;
}
public async Task<bool> update_order(OrderRecordImmutable order, double? new_price = null, uint? new_size = null, CustomOrderDefinitionImmutable? new_algo = null) {
if (order.working_quantity() <= 0) {
// order already filled to completion
return false;
}
var size = new_size ?? (uint)order.working_quantity();
var price = new_price ?? order.price();
if (size == order.working_quantity() && price == order.price() && !new_algo.HasValue) {
// no parameters would be changed by this update
return false;
}
_activity.update_order(order, price, size, new_algo.HasValue);
public async Task<bool> update_order(OrderRecordImmutable order, double price, uint size) {
if (order.working_quantity() <= 0) return false;
_activity.update_order(order, price, size);
var result = await _service.update_order_async(new Update() {
OrderKey = order.Key,
Price = price,
Quantity = size,
Custom = new_algo?.CloneToMutable()
Quantity = size
});
(var key, string msg) = _handle_error(result, "Failed to update order: {0}");
bool success = key.HasValue;
if (!success) {
_activity.update_order_failed(order, price, size, new_algo.HasValue);
_activity.update_order_failed(order, price, size);
}
return success;
}
public Task<bool[]> detach_orders(IEnumerable<uint> orders) =>
Task.WhenAll(orders.Select(detach_order));
public Task<bool[]> crack_orders(IEnumerable<uint> orders) =>
Task.WhenAll(orders.Select(crack_order));
public async Task<bool> detach_order(uint order) {
var result = await _service.detach_order_async(new Detach() { OrderKey = order });
public async Task<bool> crack_order(uint order) {
var result = await _service.crack_order_async(new Crack() { OrderKey = order });
return _handle_error(result, "Failed to break order: {0}").key.HasValue;
}
@@ -4,7 +4,7 @@ using System;
namespace mbts {
[ProtoContract]
class DiscreteStringPredicate : IDiscreteValuesPredicateBuilder<string>, IEquatable<DiscreteStringPredicate> {
class DiscreteStringPredicate : IDiscreteValuesPredicateData<string>, IEquatable<DiscreteStringPredicate> {
[ProtoMember(1)]
string[] _values;
[ProtoMember(2)]
@@ -4,7 +4,7 @@ using System;
namespace mbts {
[ProtoContract]
class DiscreteUIntPredicate : IDiscreteValuesPredicateBuilder<uint, uint?, uint[]>, IEquatable<DiscreteUIntPredicate> {
class DiscreteUIntPredicate : IDiscreteValuesPredicateData<uint, uint?, uint[]>, IEquatable<DiscreteUIntPredicate> {
[ProtoMember(1)]
uint[] _values;
[ProtoMember(2)]
@@ -16,7 +16,7 @@ namespace mbts {
public uint[] not_in_set { get => _get_set(true); set => _set(true, value); }
public DiscreteValuesOneofCase predicate_type_case => DiscreteValuesPredicateUtils.get_predicate_type_case(_values, _negated);
public IPredicate<uint> create(object obj) => DiscreteValuesPredicateUtils.create(this);
public IPredicate<uint> create() => DiscreteValuesPredicateUtils.create(this);
public bool Equals(DiscreteUIntPredicate other) => DiscreteValuesPredicateUtils.equals(this, other);
public override bool Equals(object obj) => Equals(obj as DiscreteUIntPredicate);
@@ -8,14 +8,14 @@ using System.Collections.Generic;
using System.Linq;
namespace mbts {
interface IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> : IPredicateBuilder<T> {
interface IDiscreteValuesPredicateData<T, TSingle, TMulti> {
TSingle is_value { get; set; }
TSingle not_value { get; set; }
TMulti in_set { get; set; }
TMulti not_in_set { get; set; }
DiscreteValuesOneofCase predicate_type_case { get; }
}
interface IDiscreteValuesPredicateBuilder<T> : IDiscreteValuesPredicateBuilder<T, T, T[]> { }
interface IDiscreteValuesPredicateData<T> : IDiscreteValuesPredicateData<T, T, T[]> { }
public enum DiscreteValuesOneofCase {
None,
@@ -32,8 +32,8 @@ namespace mbts {
namespace mbts.ui.settings {
static class DiscreteValuesPredicateUtils {
public static bool equals<T>(IDiscreteValuesPredicateBuilder<T> a, IDiscreteValuesPredicateBuilder<T> b) => a.safe_equals(b, _equals);
static bool _equals<T>(IDiscreteValuesPredicateBuilder<T> a, IDiscreteValuesPredicateBuilder<T> b) {
public static bool equals<T>(IDiscreteValuesPredicateData<T> a, IDiscreteValuesPredicateData<T> b) => a.safe_equals(b, _equals);
static bool _equals<T>(IDiscreteValuesPredicateData<T> a, IDiscreteValuesPredicateData<T> b) {
var dvoc = a.predicate_type_case;
if (dvoc != b.predicate_type_case)
return false;
@@ -47,8 +47,8 @@ namespace mbts.ui.settings {
}
}
public static bool equals<T, TSingle, TMulti>(IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> a, IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> b) => a.safe_equals(b, _equals);
public static bool _equals<T, TSingle, TMulti>(IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> a, IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> b) {
public static bool equals<T, TSingle, TMulti>(IDiscreteValuesPredicateData<T, TSingle, TMulti> a, IDiscreteValuesPredicateData<T, TSingle, TMulti> b) => a.safe_equals(b, _equals);
public static bool _equals<T, TSingle, TMulti>(IDiscreteValuesPredicateData<T, TSingle, TMulti> a, IDiscreteValuesPredicateData<T, TSingle, TMulti> b) {
var dvoc = a.predicate_type_case;
if (dvoc != b.predicate_type_case)
return false;
@@ -62,7 +62,7 @@ namespace mbts.ui.settings {
}
}
public static int hash<T>(IDiscreteValuesPredicateBuilder<T> a) {
public static int hash<T>(IDiscreteValuesPredicateData<T> a) {
switch (a.predicate_type_case) {
case DiscreteValuesOneofCase.IsValue: return a.is_value.GetHashCode() % MathUtils.Primes.P_17191;
case DiscreteValuesOneofCase.NotValue: return a.not_value.GetHashCode() % MathUtils.Primes.P_83987;
@@ -72,7 +72,7 @@ namespace mbts.ui.settings {
}
}
public static int hash<T, TSingle, TMulti>(IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> a) {
public static int hash<T, TSingle, TMulti>(IDiscreteValuesPredicateData<T, TSingle, TMulti> a) {
switch (a.predicate_type_case) {
case DiscreteValuesOneofCase.IsValue: return a.is_value.GetHashCode() % MathUtils.Primes.P_17191;
case DiscreteValuesOneofCase.NotValue: return a.not_value.GetHashCode() % MathUtils.Primes.P_83987;
@@ -88,7 +88,7 @@ namespace mbts.ui.settings {
(negated ? DiscreteValuesOneofCase.NotValue : DiscreteValuesOneofCase.IsValue) :
(negated ? DiscreteValuesOneofCase.NotInSet : DiscreteValuesOneofCase.IsInSet);
public static DiscreteValuesOneofCase get_predicate_type_case<T, TSingle, TMulti>(IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> predicate_builder) {
public static DiscreteValuesOneofCase get_predicate_type_case<T, TSingle, TMulti>(IDiscreteValuesPredicateData<T, TSingle, TMulti> predicate_builder) {
if (predicate_builder.is_value != null) return DiscreteValuesOneofCase.IsValue;
if (predicate_builder.not_value != null) return DiscreteValuesOneofCase.NotValue;
if (predicate_builder.in_set != null) return DiscreteValuesOneofCase.IsInSet;
@@ -96,7 +96,7 @@ namespace mbts.ui.settings {
return DiscreteValuesOneofCase.None;
}
public static IPredicate<T> create<T>(IDiscreteValuesPredicateBuilder<T> predicate_builder) {
public static IPredicate<T> create<T>(IDiscreteValuesPredicateData<T> predicate_builder) {
switch (predicate_builder.predicate_type_case) {
case DiscreteValuesOneofCase.IsValue: return PredicateBuilder.equals(predicate_builder.is_value);
case DiscreteValuesOneofCase.NotValue: return PredicateBuilder.not_equals(predicate_builder.not_value);
@@ -106,7 +106,7 @@ namespace mbts.ui.settings {
}
}
public static IPredicate<T> create<T>(IDiscreteValuesPredicateBuilder<T, T?, T[]> predicate_builder) where T : struct {
public static IPredicate<T> create<T>(IDiscreteValuesPredicateData<T, T?, T[]> predicate_builder) where T : struct {
switch (predicate_builder.predicate_type_case) {
case DiscreteValuesOneofCase.IsValue: return PredicateBuilder.equals(predicate_builder.is_value.Value);
case DiscreteValuesOneofCase.NotValue: return PredicateBuilder.not_equals(predicate_builder.not_value.Value);
@@ -118,13 +118,13 @@ namespace mbts.ui.settings {
}
static class DiscreteValuesPredicateVMFactory {
public static Dictionary<DiscreteValuesOneofCase, ITemplatedPropertyWrapperVM<TPredicate>> get_oneofs_value_type<T, TPredicate>(IEnumerable<T> items)
where T : struct where TPredicate : IDiscreteValuesPredicateBuilder<T, T?, T[]> =>
where T : struct where TPredicate : IDiscreteValuesPredicateData<T, T?, T[]> =>
//
get_oneofs<T, T?, T[], TPredicate>(identity_view_value_collection(items), val => val.Value, val => val, vals => vals ?? Enumerable.Empty<T>(), vals => vals?.ToArray() ?? new T[0], get_identity_view);
public static Dictionary<DiscreteValuesOneofCase, ITemplatedPropertyWrapperVM<TPredicate>> get_oneofs<T, TSingle, TMulti, TPredicate>(
IObservableCollection<IViewValue<T>> items, Func<TSingle, T> from_single, Func<T, TSingle> to_single, Func<TMulti,IEnumerable<T>> from_multi, Func<IEnumerable<T>, TMulti> to_multi, Func<T, object> get_view)
where TPredicate : IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> {
where TPredicate : IDiscreteValuesPredicateData<T, TSingle, TMulti> {
//
return new Dictionary<DiscreteValuesOneofCase, ITemplatedPropertyWrapperVM<TPredicate>> {
{ DiscreteValuesOneofCase.IsValue, PropertyWrapperVMUtils.create_selector_vm(
@@ -153,14 +153,14 @@ namespace mbts.ui.settings {
public static Dictionary<DiscreteValuesOneofCase, ITemplatedPropertyWrapperVM<TPredicate>> get_oneofs<T, TKey, TSingle, TMulti, TPredicate>(
IEnumerable<T> items, Func<T, TKey> key, Func<TSingle, TKey> from_single, Func<TKey, TSingle> to_single, Func<TMulti, IEnumerable<TKey>> from_multi, Func<IEnumerable<TKey>, TMulti> to_multi)
where TKey : struct, IEquatable<TKey>
where TPredicate : IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> =>
where TPredicate : IDiscreteValuesPredicateData<T, TSingle, TMulti> =>
//
get_oneofs<T, TKey, TSingle, TMulti, TPredicate>(RXCollection.from_enumerable(items.Select<T, IViewValue<T>>(v => new ViewValueTuple<T>(v, v))), key, from_single, to_single, from_multi, to_multi);
public static Dictionary<DiscreteValuesOneofCase, ITemplatedPropertyWrapperVM<TPredicate>> get_oneofs<T, TKey, TSingle, TMulti, TPredicate>(
IObservableCollection<IViewValue<T>> items, Func<T, TKey> key, Func<TSingle, TKey> from_single, Func<TKey, TSingle> to_single, Func<TMulti, IEnumerable<TKey>> from_multi, Func<IEnumerable<TKey>, TMulti> to_multi)
where TKey : struct, IEquatable<TKey>
where TPredicate : IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> {
where TPredicate : IDiscreteValuesPredicateData<T, TSingle, TMulti> {
//
return new Dictionary<DiscreteValuesOneofCase, ITemplatedPropertyWrapperVM<TPredicate>> {
{ DiscreteValuesOneofCase.IsValue, PropertyWrapperVMUtils.create_keyed_selector_vm<TPredicate, IViewValue<T>, TKey>(
@@ -5,7 +5,7 @@ using System;
namespace mbts {
[ProtoContract]
class ExchangePredicate : IDiscreteValuesPredicateBuilder<Venue, Venue?, Venue[]>, IEquatable<ExchangePredicate> {
class ExchangePredicate : IDiscreteValuesPredicateData<Venue, Venue?, Venue[]>, IEquatable<ExchangePredicate> {
[ProtoMember(1)]
Venue[] _values;
[ProtoMember(2)]
@@ -17,7 +17,7 @@ namespace mbts {
public Venue[] not_in_set { get => _get_set(true); set => _set(true, value); }
public DiscreteValuesOneofCase predicate_type_case => DiscreteValuesPredicateUtils.get_predicate_type_case(_values, _negated);
public IPredicate<Venue> create(object obj) => DiscreteValuesPredicateUtils.create(this);
public IPredicate<Venue> create() => DiscreteValuesPredicateUtils.create(this);
public bool Equals(ExchangePredicate other) => DiscreteValuesPredicateUtils.equals(this, other);
public override bool Equals(object obj) => Equals(obj as ExchangePredicate);
@@ -5,7 +5,7 @@ using System;
namespace mbts {
[ProtoContract]
class OrderStatusPredicate : IDiscreteValuesPredicateBuilder<OrderStatus, OrderStatus?, OrderStatus[]>, IEquatable<OrderStatusPredicate> {
class OrderStatusPredicate : IDiscreteValuesPredicateData<OrderStatus, OrderStatus?, OrderStatus[]>, IEquatable<OrderStatusPredicate> {
[ProtoMember(1)]
OrderStatus[] _values;
[ProtoMember(2)]
@@ -17,7 +17,7 @@ namespace mbts {
public OrderStatus[] not_in_set { get => _get_set(true); set => _set(true, value); }
public DiscreteValuesOneofCase predicate_type_case => DiscreteValuesPredicateUtils.get_predicate_type_case(_values, _negated);
public IPredicate<OrderStatus> create(object obj) => DiscreteValuesPredicateUtils.create(this);
public IPredicate<OrderStatus> create() => DiscreteValuesPredicateUtils.create(this);
public bool Equals(OrderStatusPredicate other) => DiscreteValuesPredicateUtils.equals(this, other);
public override bool Equals(object obj) => Equals(obj as OrderStatusPredicate);
@@ -5,7 +5,7 @@ using System;
namespace mbts {
[ProtoContract]
class OrderSubTypePredicate : IDiscreteValuesPredicateBuilder<OrderSubType, OrderSubType?, OrderSubType[]>, IEquatable<OrderSubTypePredicate> {
class OrderSubTypePredicate : IDiscreteValuesPredicateData<OrderSubType, OrderSubType?, OrderSubType[]>, IEquatable<OrderSubTypePredicate> {
[ProtoMember(1)]
OrderSubType[] _values;
[ProtoMember(2)]
@@ -17,7 +17,7 @@ namespace mbts {
public OrderSubType[] not_in_set { get => _get_set(true); set => _set(true, value); }
public DiscreteValuesOneofCase predicate_type_case => DiscreteValuesPredicateUtils.get_predicate_type_case(_values, _negated);
public IPredicate<OrderSubType> create(object obj) => DiscreteValuesPredicateUtils.create(this);
public IPredicate<OrderSubType> create() => DiscreteValuesPredicateUtils.create(this);
public bool Equals(OrderSubTypePredicate other) => DiscreteValuesPredicateUtils.equals(this, other);
public override bool Equals(object obj) => Equals(obj as OrderSubTypePredicate);
@@ -5,7 +5,7 @@ using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
namespace mbts {
[ProtoContract]
class OrderTypePredicate : IDiscreteValuesPredicateBuilder<OrderType, OrderType?, OrderType[]>, IEquatable<OrderTypePredicate> {
class OrderTypePredicate : IDiscreteValuesPredicateData<OrderType, OrderType?, OrderType[]>, IEquatable<OrderTypePredicate> {
[ProtoMember(1)]
OrderType[] _values;
[ProtoMember(2)]
@@ -17,7 +17,7 @@ namespace mbts {
public OrderType[] not_in_set { get => _get_set(true); set => _set(true, value); }
public DiscreteValuesOneofCase predicate_type_case => DiscreteValuesPredicateUtils.get_predicate_type_case(_values, _negated);
public IPredicate<OrderType> create(object obj) => DiscreteValuesPredicateUtils.create(this);
public IPredicate<OrderType> create() => DiscreteValuesPredicateUtils.create(this);
public bool Equals(OrderTypePredicate other) => DiscreteValuesPredicateUtils.equals(this, other);
public override bool Equals(object obj) => Equals(obj as OrderTypePredicate);
@@ -5,7 +5,7 @@ using System;
namespace mbts {
[ProtoContract]
class TimeInForcePredicate : IDiscreteValuesPredicateBuilder<TimeInForce, TimeInForce?, TimeInForce[]>, IEquatable<TimeInForcePredicate> {
class TimeInForcePredicate : IDiscreteValuesPredicateData<TimeInForce, TimeInForce?, TimeInForce[]>, IEquatable<TimeInForcePredicate> {
[ProtoMember(1)]
TimeInForce[] _values;
[ProtoMember(2)]
@@ -17,7 +17,7 @@ namespace mbts {
public TimeInForce[] not_in_set { get => _get_set(true); set => _set(true, value); }
public DiscreteValuesOneofCase predicate_type_case => DiscreteValuesPredicateUtils.get_predicate_type_case(_values, _negate);
public IPredicate<TimeInForce> create(object obj) => DiscreteValuesPredicateUtils.create(this);
public IPredicate<TimeInForce> create() => DiscreteValuesPredicateUtils.create(this);
public bool Equals(TimeInForcePredicate other) => DiscreteValuesPredicateUtils.equals(this, other);
public override bool Equals(object obj) => Equals(obj as TimeInForcePredicate);
@@ -4,7 +4,6 @@ using bts.shell;
using bts.utils;
using mbts.spark;
using mbts.ui.settings;
using mbts.utils;
using ProtoBuf;
using System;
using System.Collections.Generic;
@@ -14,7 +13,7 @@ namespace mbts {
static class OrderCanceledEventPredicateVMFactory {
public static Dictionary<OrderCanceledEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderCanceledEventPredicate>> get_oneofs(IBus bus) =>
new Dictionary<OrderCanceledEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderCanceledEventPredicate>> {
{ OrderCanceledEventPredicate.PredicateTypeOneofCase.AnyOrder, PredicateBuilderVMWrapper.create<OrderCanceledEventPredicate>(p => p.security = null) },
{ OrderCanceledEventPredicate.PredicateTypeOneofCase.AnyOrder, PredicateVMWrapper.create<OrderCanceledEventPredicate>(p => p.security = null) },
{ OrderCanceledEventPredicate.PredicateTypeOneofCase.SecurityOrder, SecurityCriteriaVMFactory.create<OrderCanceledEventPredicate>(bus, o => o.security?.is_value ?? new SecurityCriteria(),
(p, s) => {
if (p.security == null)
@@ -26,7 +25,7 @@ namespace mbts {
}
[ProtoContract]
class OrderCanceledEventPredicate : IPredicateBuilder<OrderSecurity, INotificationServiceDataCache>, IEquatable<OrderCanceledEventPredicate> {
class OrderCanceledEventPredicate : IEquatable<OrderCanceledEventPredicate> {
public enum PredicateTypeOneofCase {
[Description("(any order)")]
AnyOrder,
@@ -52,7 +51,7 @@ namespace mbts {
OrderEventPredicateHelper.create_status_transition_predicate(OrderStatus.Canceled, security, data_cache);
public INotificationPredicate<OrderSecurity> create_toast(INotificationServiceDataCache data_cache) =>
new MyToaster(security?.create(data_cache), data_cache);
new MyToaster(security?.create(), data_cache);
public bool Equals(OrderCanceledEventPredicate other) => other != null && other.security.safe_equals(other.security);
public override bool Equals(object obj) => Equals(obj as OrderCanceledEventPredicate);
@@ -4,7 +4,6 @@ using bts.shell;
using bts.utils;
using mbts.spark;
using mbts.ui.settings;
using mbts.utils;
using ProtoBuf;
using System;
using System.Collections.Generic;
@@ -14,7 +13,7 @@ namespace mbts {
static class OrderCompletedEventPredicateVMFactory {
public static Dictionary<OrderCompletedEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderCompletedEventPredicate>> get_oneofs(IBus bus) =>
new Dictionary<OrderCompletedEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderCompletedEventPredicate>> {
{ OrderCompletedEventPredicate.PredicateTypeOneofCase.AnyOrder, PredicateBuilderVMWrapper.create<OrderCompletedEventPredicate>(p => p.security = null) },
{ OrderCompletedEventPredicate.PredicateTypeOneofCase.AnyOrder, PredicateVMWrapper.create<OrderCompletedEventPredicate>(p => p.security = null) },
{ OrderCompletedEventPredicate.PredicateTypeOneofCase.SecurityOrder, SecurityCriteriaVMFactory.create<OrderCompletedEventPredicate>(bus, o => o.security?.is_value ?? new SecurityCriteria(),
(p, s) => {
if (p.security == null)
@@ -26,7 +25,7 @@ namespace mbts {
}
[ProtoContract]
class OrderCompletedEventPredicate : IPredicateBuilder<OrderSecurity, INotificationServiceDataCache>, IEquatable<OrderCompletedEventPredicate> {
class OrderCompletedEventPredicate : IEquatable<OrderCompletedEventPredicate> {
public enum PredicateTypeOneofCase {
[Description("(any order)")]
AnyOrder,
@@ -52,7 +51,7 @@ namespace mbts {
OrderEventPredicateHelper.create_status_transition_predicate(OrderStatus.Completed, security, data_cache);
public INotificationPredicate<OrderSecurity> create_toast(INotificationServiceDataCache data_cache) =>
new MyToaster(security?.create(data_cache), data_cache);
new MyToaster(security?.create(), data_cache);
public bool Equals(OrderCompletedEventPredicate other) => other != null && other.security.safe_equals(other.security);
public override bool Equals(object obj) => Equals(obj as OrderCompletedEventPredicate);
@@ -2,8 +2,6 @@
using bts.api.orderman;
using bts.shell;
using bts.utils;
using mbts.spark;
using mbts.utils;
using System;
namespace mbts {
@@ -39,7 +37,7 @@ namespace mbts {
}
public static IPredicate<OrderSecurity> create_status_transition_predicate(OrderStatus status, SecurityPredicate security_predicate, INotificationServiceDataCache data_cache) =>
new OrderStatusTransitionPredicate(status, security_predicate?.create(data_cache), data_cache);
new OrderStatusTransitionPredicate(status, security_predicate?.create(), data_cache);
class OrderStatusTransitionPredicate : IPredicate<OrderSecurity>, IEquatable<OrderStatusTransitionPredicate> {
readonly IPredicate<SecurityImmutable> _security_predicate;
@@ -11,7 +11,7 @@ namespace mbts {
static class OrderFilledEventPredicateVMFactory {
public static Dictionary<OrderFilledEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderFilledEventPredicate>> get_oneofs(IBus bus) =>
new Dictionary<OrderFilledEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderFilledEventPredicate>> {
{ OrderFilledEventPredicate.PredicateTypeOneofCase.AnyOrder, PredicateBuilderVMWrapper.create<OrderFilledEventPredicate>(p => p.security = null) },
{ OrderFilledEventPredicate.PredicateTypeOneofCase.AnyOrder, PredicateVMWrapper.create<OrderFilledEventPredicate>(p => p.security = null) },
{ OrderFilledEventPredicate.PredicateTypeOneofCase.SecurityOrder, SecurityCriteriaVMFactory.create<OrderFilledEventPredicate>(bus, o => o.security?.is_value ?? new SecurityCriteria(),
(p, s) => {
if (p.security == null) {
@@ -24,7 +24,7 @@ namespace mbts {
}
[ProtoContract]
class OrderFilledEventPredicate : IPredicateBuilder<OrderSecurity, INotificationServiceDataCache>, IEquatable<OrderFilledEventPredicate> {
class OrderFilledEventPredicate : IEquatable<OrderFilledEventPredicate> {
public enum PredicateTypeOneofCase {
[PredicateCase("(any order)")]
AnyOrder,
@@ -59,7 +59,7 @@ namespace mbts {
public override int GetHashCode() => security == null ? 0 : security.GetHashCode();
FillTriggerModel _create_model(INotificationServiceDataCache nsdc) =>
new FillTriggerModel(nsdc, security?.create(nsdc));
new FillTriggerModel(nsdc, security?.create());
class FillTriggerModel {
readonly IPredicate<SecurityImmutable> _security_predicate;
@@ -56,16 +56,16 @@ namespace mbts {
Func<TChildPredicate, TChildPredicateOneof> key,
Dictionary<TChildPredicateOneof, ITemplatedPropertyWrapperVM<TChildPredicate>> oneofs) where TChildPredicateOneof : struct where TChildPredicate : new() =>
//
PredicateBuilderVMWrapper.wrap_child_oneof<OrderRecordTuplePredicate, TChildPredicate, TChildPredicateOneof>(get, set, key, oneofs);
PredicateVMWrapper.wrap_child_oneof<OrderRecordTuplePredicate, TChildPredicate, TChildPredicateOneof>(get, set, key, oneofs);
}
}
[ProtoContract]
class OrderRecordTuplePredicate : INotificationPredicateBuilder<OrderSecurity, INotificationServiceDataCache>, IEquatable<OrderRecordTuplePredicate> {
class OrderRecordTuplePredicate : IEquatable<OrderRecordTuplePredicate> {
SecurityPredicate _security;
SidePredicate _side;
DoublePredicate _price, _last_price, _avg_price;
UIntPredicate _quantity, _filled_quantity, _security_key, _order_key, _parent_key, _attached_target_order_key;
UIntPredicate _quantity, _filled_quantity, _security_key, _order_key, _parent_key;
OrderStatusPredicate _order_status;
OrderTypePredicate _order_type;
OrderSubTypePredicate _order_sub_type;
@@ -125,9 +125,7 @@ namespace mbts {
[PredicateCase("When order is completed", PredicateCategory.Event)]
CompletedEvent,
[PredicateCase("When order is canceled", PredicateCategory.Event)]
CanceledEvent,
[PredicateCase(is_toastable:false)]
AttachedTargetOrderKey
CanceledEvent
}
[ProtoMember(1)]
@@ -178,8 +176,6 @@ namespace mbts {
public OrderCanceledEventPredicate canceled_event { get => _canceled_event; set => _set(ref _canceled_event, value); }
[ProtoMember(24)]
public bool is_toast_enabled { get; set; }
[ProtoMember(25)]
public UIntPredicate attached_order_target_key { get => _attached_target_order_key; set => _set(ref _attached_target_order_key, value); }
public PTOC predicate_type_case {
get {
@@ -206,47 +202,42 @@ namespace mbts {
if (_filled_event != null) return PTOC.FilledEvent;
if (_completed_event != null) return PTOC.CompletedEvent;
if (_canceled_event != null) return PTOC.CanceledEvent;
if (_attached_target_order_key != null) return PTOC.AttachedTargetOrderKey;
return PTOC.None;
}
}
public IPredicate<OrderSecurity> create_predicate(INotificationServiceDataCache data_cache) {
switch (predicate_type_case) {
case PTOC.Security: return wrap(_security, r => r.security ?? SecurityDataHelper.Defaults.SecurityImmutable);
case PTOC.Time: return wrap(_time_created, r => r.order.created());
case PTOC.LastChange: return wrap(_last_change, r => r.order.last_change());
case PTOC.User: return wrap(_user, r => r.order.user_id());
case PTOC.Account: return wrap(_account, r => r.order.account_id());
case PTOC.Exchange: return wrap(_exchange, r => r.security.exchange_or_default());
case PTOC.TimeInForce: return wrap(_time_in_force, r => r.order.time_in_force());
case PTOC.OrderType: return wrap(_order_type, r => r.order.order_type());
case PTOC.SubType: return wrap(_order_sub_type, r => r.order.sub_type());
case PTOC.Side: return wrap(_side, r => r.order.side());
case PTOC.Size: return wrap(_quantity, r => r.order.quantity());
case PTOC.Price: return wrap(_price, r => r.order.price());
case PTOC.Filled: return wrap(_filled_quantity, r => r.order.filled());
case PTOC.AvgFilled: return wrap(_avg_price, r => r.order.avg_price());
case PTOC.LastPrice: return wrap(_last_price, r => r.order.last_price());
case PTOC.Status: return wrap(_order_status, r => r.order.status());
case PTOC.LastMessage: return wrap(_last_message, r => r.order.last_msg());
case PTOC.SecurityKey: return wrap(_security_key, r => r.order.security_key());
case PTOC.OrderKey: return wrap(_order_key, r => r.order.Key);
case PTOC.ParentKey: return wrap(_parent_key, r => r.order.ParentKey);
case PTOC.Security: return wrap(_security.create(), r => r.security ?? SecurityDataHelper.Defaults.SecurityImmutable);
case PTOC.Time: return wrap(_time_created.create(), r => r.order.created());
case PTOC.LastChange: return wrap(_last_change.create(), r => r.order.last_change());
case PTOC.User: return wrap(_user.create(), r => r.order.user_id());
case PTOC.Account: return wrap(_account.create(), r => r.order.account_id());
case PTOC.Exchange: return wrap(_exchange.create(), r => r.security.exchange_or_default());
case PTOC.TimeInForce: return wrap(_time_in_force.create(), r => r.order.time_in_force());
case PTOC.OrderType: return wrap(_order_type.create(), r => r.order.order_type());
case PTOC.SubType: return wrap(_order_sub_type.create(), r => r.order.sub_type());
case PTOC.Side: return wrap(_side.create(), r => r.order.side());
case PTOC.Size: return wrap(_quantity.create(), r => r.order.quantity());
case PTOC.Price: return wrap(_price.create(), r => r.order.price());
case PTOC.Filled: return wrap(_filled_quantity.create(), r => r.order.filled());
case PTOC.AvgFilled: return wrap(_avg_price.create(), r => r.order.avg_price());
case PTOC.LastPrice: return wrap(_last_price.create(), r => r.order.last_price());
case PTOC.Status: return wrap(_order_status.create(), r => r.order.status());
case PTOC.LastMessage: return wrap(_last_message.create(), r => r.order.last_msg());
case PTOC.SecurityKey: return wrap(_security_key.create(), r => r.order.security_key());
case PTOC.OrderKey: return wrap(_order_key.create(), r => r.order.Key);
case PTOC.ParentKey: return wrap(_parent_key.create(), r => r.order.ParentKey);
case PTOC.FilledEvent: return _filled_event.create_predicate(data_cache);
case PTOC.CompletedEvent: return _completed_event.create_predicate(data_cache);
case PTOC.CanceledEvent: return _canceled_event.create(data_cache);
case PTOC.AttachedTargetOrderKey: return wrap(_attached_target_order_key, r => r.order.attached_target_order_key());
default: return null;
}
//
IPredicate<OrderSecurity> wrap<T>(IPredicateBuilder<T> trigger, Func<OrderSecurity, T> select) =>
trigger.create(data_cache).wrap<OrderSecurity,T>(select);
IPredicate<OrderSecurity> wrap<T>(IPredicate<T> trigger, Func<OrderSecurity, T> select) =>
trigger.wrap<OrderSecurity, T>(select);
}
IPredicate<OrderSecurity> IPredicateBuilder<OrderSecurity>.create(object obj) =>
create_predicate((INotificationServiceDataCache)obj);
public INotificationPredicate<OrderSecurity> create_notification_predicate(INotificationServiceDataCache notification_service_data_cache) {
if (!is_toast_enabled)
return null;
@@ -258,9 +249,6 @@ namespace mbts {
}
}
INotificationPredicate<OrderSecurity> INotificationPredicateBuilder<OrderSecurity>.create_notification_predicate(object obj) =>
create_notification_predicate((INotificationServiceDataCache)obj);
public bool Equals(OrderRecordTuplePredicate other) {
if (other == null)
return false;
@@ -291,7 +279,6 @@ namespace mbts {
case PTOC.FilledEvent: return _filled_event.Equals(other._filled_event);
case PTOC.CompletedEvent: return _completed_event.Equals(other._completed_event);
case PTOC.CanceledEvent: return _canceled_event.Equals(other._canceled_event);
case PTOC.AttachedTargetOrderKey: return _attached_target_order_key.Equals(other._attached_target_order_key);
case PTOC.None: return true;
default: throw ExceptionHelper.unexpected_oneof_case(ptoc);
}
@@ -326,7 +313,6 @@ namespace mbts {
case PTOC.FilledEvent: raw_hash = _filled_event.GetHashCode(); break;
case PTOC.CompletedEvent: raw_hash = _completed_event.GetHashCode(); break;
case PTOC.CanceledEvent: raw_hash = _canceled_event.GetHashCode(); break;
case PTOC.AttachedTargetOrderKey: raw_hash = _attached_target_order_key.GetHashCode(); break;
case PTOC.None: raw_hash = 0; break;
default: throw ExceptionHelper.unexpected_oneof_case(ptoc);
}
@@ -361,7 +347,6 @@ namespace mbts {
_filled_event = null;
_completed_event = null;
_canceled_event = null;
_attached_target_order_key = null;
}
field = value;
}
@@ -387,9 +372,9 @@ namespace mbts {
_sound_animations = new List<SoundNotification> ( sound_animations );
}
public OrderRecordTuplePredicate predicate_builder => _predicate;
public OrderRecordTuplePredicate predicate_data => _predicate;
public IReadOnlyList<CellAnimation> cell_animations => _cell_animations ?? (_cell_animations = new List<CellAnimation>());
public IReadOnlyList<RowAnimation> row_animations => _row_animations ?? (_row_animations = new List<RowAnimation>());
public IReadOnlyList<SoundNotification> sound_notifications => _sound_animations ?? (_sound_animations = new List<SoundNotification>());
}
}
}
@@ -1,7 +1,6 @@
using bts.proto;
using bts.utils;
using mbts.spark;
using mbts.spark.utilities;
using mbts.ui.spark;
using ProtoBuf;
using System;
@@ -24,7 +23,7 @@ namespace mbts {
}
[ProtoContract]
class DoublePredicate : IPredicateBuilder<double>, IEquatable<DoublePredicate> {
class DoublePredicate : IEquatable<DoublePredicate> {
// not using a 'negate' flag here bc its meaning is not consistent for all value-types
// greater than vs less than for specific values, or inside vs outside for value-ranges
// trading a little more code for more clarity
@@ -62,7 +61,7 @@ namespace mbts {
}
}
public IPredicate<double> create(object obj) {
public IPredicate<double> create() {
switch (predicate_type_case) {
case PredicateTypeOneofCase.GreaterThan: return PredicateBuilder.greater_than(_greater_than.Value);
case PredicateTypeOneofCase.LessThan: return PredicateBuilder.less_than(_less_than.Value);
@@ -1,7 +1,6 @@
using bts.api;
using bts.utils;
using mbts.spark;
using mbts.spark.utilities;
using mbts.ui.spark;
using mbts.ui.utility;
using ProtoBuf;
@@ -23,7 +22,7 @@ namespace mbts {
}
[ProtoContract]
class SidePredicate : IPredicateBuilder<Side>, IEquatable<SidePredicate> {
class SidePredicate : IEquatable<SidePredicate> {
Side? _is_value;
Side? _not_value;
@@ -48,7 +47,7 @@ namespace mbts {
}
}
public IPredicate<Side> create(object obj) {
public IPredicate<Side> create() {
switch (predicate_type_case) {
case PredicateTypeOneofCase.IsValue: return PredicateBuilder.equals(_is_value.Value);
case PredicateTypeOneofCase.NotValue: return PredicateBuilder.not_equals(_not_value.Value);
@@ -1,9 +1,7 @@
using bts.proto;
using bts.utils;
using mbts.spark;
using mbts.spark.utilities;
using mbts.ui.spark;
using mbts.utils;
using ProtoBuf;
using System;
using System.Collections.Generic;
@@ -27,7 +25,7 @@ namespace mbts {
}
[ProtoContract]
class StringPredicate : IPredicateBuilder<string>, IEquatable<StringPredicate> {
class StringPredicate : IEquatable<StringPredicate> {
StringIgnoreCase? _is_string, _not_string, _contains_string, _not_contains_string;
public enum PredicateTypeOneofCase {
@@ -61,7 +59,7 @@ namespace mbts {
}
}
public IPredicate<string> create(object obj) {
public IPredicate<string> create() {
switch (predicate_type_case) {
case PTOC.IsMatch: return PredicateBuilder.equals(_is_string.Value.text, _get_eq_comparer(_is_string));
case PTOC.NotMatch: return PredicateBuilder.not_equals(_not_string.Value.text, _get_eq_comparer(_not_string));
@@ -1,7 +1,6 @@
using bts.proto;
using bts.utils;
using mbts.spark;
using mbts.spark.utilities;
using mbts.ui.spark;
using ProtoBuf;
using System;
@@ -21,7 +20,7 @@ namespace mbts {
}
[ProtoContract]
class TimePredicate : IPredicateBuilder<DateTime>, IEquatable<TimePredicate> {
class TimePredicate : IEquatable<TimePredicate> {
// not using a 'negate' flag here bc its meaning is not consistent for all value-types
// before vs after for specific times, or inside vs outside for time-ranges
// trading a little more code for clarity
@@ -59,7 +58,7 @@ namespace mbts {
}
}
public IPredicate<DateTime> create(object obj) {
public IPredicate<DateTime> create() {
switch (predicate_type_case) {
case PTOC.Before: return PredicateBuilder.less_than(_before.Value.get_date_time());
case PTOC.After: return PredicateBuilder.greater_than(_after.Value.get_date_time());
@@ -1,7 +1,6 @@
using bts.proto;
using bts.utils;
using mbts.spark;
using mbts.spark.utilities;
using mbts.ui.spark;
using ProtoBuf;
using System;
@@ -24,7 +23,7 @@ namespace mbts {
}
[ProtoContract]
class UIntPredicate : IPredicateBuilder<uint>, IEquatable<UIntPredicate> {
class UIntPredicate : IEquatable<UIntPredicate> {
// not using a 'negate' flag here bc its meaning is not consistent for all value-types
// greater than vs less than for specific values, or inside vs outside for value-ranges
// trading a little more code for more clarity
@@ -62,7 +61,7 @@ namespace mbts {
}
}
public IPredicate<uint> create(object obj) {
public IPredicate<uint> create() {
switch (predicate_type_case) {
case PredicateTypeOneofCase.GreaterThan: return PredicateBuilder.greater_than(_greater_than.Value);
case PredicateTypeOneofCase.LessThan: return PredicateBuilder.less_than(_less_than.Value);
@@ -1,7 +1,6 @@
using bts.proto;
using bts.utils;
using mbts.spark;
using mbts.spark.utilities;
using mbts.ui.spark;
using ProtoBuf;
using System;
@@ -24,7 +23,7 @@ namespace mbts {
}
[ProtoContract]
class UShortPredicate : IPredicateBuilder<ushort>, IEquatable<UShortPredicate> {
class UShortPredicate : IEquatable<UShortPredicate> {
// not using a 'negate' flag here bc its meaning is not consistent for all value-types
// greater than vs less than for specific values, or inside vs outside for value-ranges
// trading a little more code for more clarity
@@ -23,7 +23,7 @@ namespace mbts {
}
[ProtoContract]
class SecurityPredicate : IPredicateBuilder<SecurityImmutable>, IEquatable<SecurityPredicate> {
class SecurityPredicate : IEquatable<SecurityPredicate> {
[ProtoMember(1)]
SecurityCriteria _criteria;
[ProtoMember(2)]
@@ -43,7 +43,8 @@ namespace mbts {
_criteria == null ? PTOC.None :
_negate ? PTOC.NotInCriteria : PTOC.InCriteria;
public IPredicate<SecurityImmutable> create(object obj) => _criteria.is_defined() ? new MyPredicate(_criteria, _negate) : null;
public IPredicate<SecurityImmutable> create() => _criteria.is_defined() ? new MyPredicate(_criteria, _negate) : null;
public bool Equals(SecurityPredicate other) =>
other != null &&
_negate == other._negate &&
+1 -20
View File
@@ -1,6 +1,4 @@
using bts.shell;
using bts.utils;
using mbts.utils;
using bts.utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@@ -14,23 +12,6 @@ namespace mbts {
bool trigger(T value);
}
interface IPredicateBuilder<T> {
IPredicate<T> create(object obj);
}
interface INotificationPredicateBuilder<T> : IPredicateBuilder<T> {
bool is_toast_enabled { get; set; }
INotificationPredicate<T> create_notification_predicate(object obj);
}
interface IPredicateBuilder<T, TArg> : IPredicateBuilder<T> {
IPredicate<T> create_predicate(TArg arg);
}
interface INotificationPredicateBuilder<T, TArg> : IPredicateBuilder<T, TArg>, INotificationPredicateBuilder<T> {
INotificationPredicate<T> create_notification_predicate(TArg arg);
}
public enum PredicateCategory {
Normal,
Event
@@ -13,25 +13,23 @@ using System.Reactive.Linq;
using System.Windows.Data;
namespace mbts.ui.settings {
interface IPredicateBuilderVM<TRowData, TPredicateBuilder, TPredicateBuilderOneof> : ITemplatedPropertyWrapperVM<TPredicateBuilder>
where TPredicateBuilder : IPredicateBuilder<TRowData>
where TPredicateBuilderOneof : struct {
IReadOnlyCollection<TPredicateBuilderOneof> predicate_types { get; }
interface IPredicateVM<TRowData, TPredicateData, TPredicateDataOneof> : ITemplatedPropertyWrapperVM<TPredicateData> where TPredicateDataOneof : struct {
IReadOnlyCollection<TPredicateDataOneof> predicate_types { get; }
ICollectionView predicate_types_view { get; }
TPredicateBuilderOneof selected_predicate_type { get; set; }
ITemplatedPropertyWrapperVM<TPredicateBuilder> selected_predicate { get; }
TPredicateDataOneof selected_predicate_type { get; set; }
ITemplatedPropertyWrapperVM<TPredicateData> selected_predicate { get; }
bool can_enable_toast_notifications { get; }
bool toast_notifications_enabled { get; }
}
interface IChildPredicateBuilderVM<TSource, TChildPredicateBuilder, TChildPredicateBuilderOneof> : ITemplatedPropertyWrapperVM<TSource> where TChildPredicateBuilderOneof : struct {
IReadOnlyCollection<TChildPredicateBuilderOneof> predicate_types { get; }
interface IChildPredicateVM<TSource, TChildPredicate, TChildPredicateOneof> : ITemplatedPropertyWrapperVM<TSource> where TChildPredicateOneof : struct {
IReadOnlyCollection<TChildPredicateOneof> predicate_types { get; }
ICollectionView predicate_types_view { get; }
TChildPredicateBuilderOneof selected_predicate_type { get; set; }
ITemplatedPropertyWrapperVM<TChildPredicateBuilder> selected_predicate { get; }
TChildPredicateOneof selected_predicate_type { get; set; }
ITemplatedPropertyWrapperVM<TChildPredicate> selected_predicate { get; }
}
static class PredicateBuilderVMWrapper {
static class PredicateVMWrapper {
static readonly bool _toast_notifications_enabled_global = Configuration.get<bts.config.toggles.ToastNotifications>().enabled;
public static ITemplatedPropertyWrapperVM<TSource> wrap_child_oneof<TSource, TChild, TChildOneof>(
@@ -39,14 +37,17 @@ namespace mbts.ui.settings {
where TChildOneof : struct
where TChild : new() =>
//
new PredicateChildWrapperVM<TSource, TChild, TChildOneof>(get, set, get_child_oneof, oneof_vms);
new ChildImpl<TSource, TChild, TChildOneof>(get, set, get_child_oneof, oneof_vms);
public static IPredicateBuilderVM<TRowData, TPredicate, TPredicateOneof> create<TRowData, TPredicate, TPredicateOneof>(
Dictionary<TPredicateOneof, ITemplatedPropertyWrapperVM<TPredicate>> oneofs, Func<TPredicate, TPredicateOneof> key)
where TPredicateOneof : struct
where TPredicate : IPredicateBuilder<TRowData>, new() =>
public static IPredicateVM<TRowData, TPredicate, TPredicateOneof> create<TRowData, TPredicate, TPredicateOneof>(
Dictionary<TPredicateOneof, ITemplatedPropertyWrapperVM<TPredicate>> oneofs, Func<TPredicate, TPredicateOneof> key) where TPredicateOneof : struct =>
//
new PredicateWrapperVM<TRowData, TPredicate, TPredicateOneof>(oneofs, key);
new Impl<TRowData, TPredicate, TPredicateOneof>(oneofs, key, _ => false, (p, b) => { });
public static IPredicateVM<TRowData, TPredicate, TPredicateOneof> create<TRowData, TPredicate, TPredicateOneof>(
Dictionary<TPredicateOneof, ITemplatedPropertyWrapperVM<TPredicate>> oneofs, Func<TPredicate, TPredicateOneof> key, Func<TPredicate, bool> get_is_toast_enabled, Action<TPredicate, bool> set_is_toast_enabled) where TPredicateOneof : struct =>
//
new Impl<TRowData, TPredicate, TPredicateOneof>(oneofs, key, get_is_toast_enabled, set_is_toast_enabled);
public static ITemplatedPropertyWrapperVM<TSource> create<TSource>(Action<TSource> set = null) =>
new PredicatePlaceholderVM<TSource>(set);
@@ -63,35 +64,30 @@ namespace mbts.ui.settings {
return cv;
}
class PredicateWrapperVM<TRowData, TPredicate, TPredicateOneof> : DisposableViewModelBase, IPredicateBuilderVM<TRowData, TPredicate, TPredicateOneof>, ITemplatedPropertyWrapperVM<TPredicate>
where TPredicateOneof : struct
where TPredicate : IPredicateBuilder<TRowData>, new() {
class Impl<TRowData, TPredicate, TPredicateOneof> : DisposableViewModelBase, IPredicateVM<TRowData, TPredicate, TPredicateOneof>, ITemplatedPropertyWrapperVM<TPredicate>
where TPredicateOneof : struct {
readonly Dictionary<TPredicateOneof, ITemplatedPropertyWrapperVM<TPredicate>> _oneofs;
readonly Property<TPredicateOneof> _selected_predicate_type;
readonly Property<ITemplatedPropertyWrapperVM<TPredicate>> _selected_predicate;
readonly DelegateProperty<bool> _can_enable_toast_notifications;
readonly Property<bool> _toast_notifications_enabled;
readonly Func<TPredicate, TPredicateOneof> _key_selector;
readonly Func<TPredicate, bool> _get_is_toast_enabled;
readonly Action<TPredicate, bool> _set_is_toast_enabeled;
public PredicateWrapperVM(Dictionary<TPredicateOneof, ITemplatedPropertyWrapperVM<TPredicate>> oneofs, Func<TPredicate, TPredicateOneof> key_selector) {
public Impl(Dictionary<TPredicateOneof, ITemplatedPropertyWrapperVM<TPredicate>> oneofs, Func<TPredicate, TPredicateOneof> key_selector, Func<TPredicate, bool> get_is_toast_enabled, Action<TPredicate, bool> set_is_toast_enabled) {
_oneofs = oneofs;
_key_selector = key_selector;
_get_is_toast_enabled = get_is_toast_enabled;
_set_is_toast_enabeled = set_is_toast_enabled;
_selected_predicate = backing<ITemplatedPropertyWrapperVM<TPredicate>>(nameof(selected_predicate));
_selected_predicate_type = backing<TPredicateOneof>(nameof(selected_predicate_type));
predicate_types_view = _create_predicate_types_view(predicate_types);
if (typeof(TPredicate).GetInterfaces().Contains(typeof(INotificationPredicateBuilder<TRowData>))) {
// simple_predicates are only referred to when TPredicate is an INotificationPredicateBuilder, but does not support INotificationPredicates for every predicate-case
// (this way we can gradually add notifications as-needed beasis)
// for ex: we may release a cut that supports fill notifications without having to create a notification-predicate for every predicate-case in OrderRecordTuplePredicate.
// Note: it is assumed that all TPredicateOneof cases DO SUPPORT notification-predicates UNLESS THEY SPECIFY OTHERWISE IN A PREDICATECASEATTRIBUTE.
var simple_predicates = new HashSet<TPredicateOneof>(oneofs.Keys.Where(k => {
var pca = EnumHelper.get_attribute<TPredicateOneof, PredicateCaseAttribute>(k);
return pca != null && !pca.is_toastable;
}));
_can_enable_toast_notifications = backing(nameof(can_enable_toast_notifications), () => !simple_predicates.Contains(selected_predicate_type), nameof(selected_predicate_type));
} else {
_can_enable_toast_notifications = backing(nameof(can_enable_toast_notifications), () => false);
}
var toastable_predicates = new HashSet<TPredicateOneof>(oneofs.Keys.Where(k => {
var pca = EnumHelper.get_attribute<TPredicateOneof, PredicateCaseAttribute>(k);
return pca != null && pca.is_toastable;
}));
_can_enable_toast_notifications = backing(nameof(can_enable_toast_notifications), () => toastable_predicates.Contains(selected_predicate_type), nameof(selected_predicate_type));
_toast_notifications_enabled = backing<bool>(nameof(toast_notifications_enabled));
selected_predicate_type = predicate_types.FirstOrDefault();
}
@@ -118,19 +114,18 @@ namespace mbts.ui.settings {
public void load_from(TPredicate value) {
selected_predicate_type = _key_selector(value);
selected_predicate?.load_from(value);
if (can_enable_toast_notifications) {
_toast_notifications_enabled.set(((INotificationPredicateBuilder<TRowData>)value).is_toast_enabled);
}
if (can_enable_toast_notifications)
_toast_notifications_enabled.set(_get_is_toast_enabled(value));
}
public void assign_to(TPredicate value) {
selected_predicate?.assign_to(value);
if (toast_notifications_enabled && value is INotificationPredicateBuilder<TRowData> tp)
tp.is_toast_enabled = true;
if (can_enable_toast_notifications)
_set_is_toast_enabeled(value, toast_notifications_enabled);
}
}
class PredicateChildWrapperVM<TPredicate, TChild, TChildKey> : DisposableViewModelBase, IChildPredicateBuilderVM<TPredicate, TChild, TChildKey> where TChildKey : struct where TChild : new() {
class ChildImpl<TPredicate, TChild, TChildKey> : DisposableViewModelBase, IChildPredicateVM<TPredicate, TChild, TChildKey> where TChildKey : struct where TChild : new() {
readonly Property<ITemplatedPropertyWrapperVM<TChild>> _selected_predicate;
readonly Property<TChildKey> _selected_predicate_type;
readonly Dictionary<TChildKey, ITemplatedPropertyWrapperVM<TChild>> _children;
@@ -139,7 +134,7 @@ namespace mbts.ui.settings {
readonly Func<TChild, TChildKey> _key;
readonly string _template_key;
public PredicateChildWrapperVM(Func<TPredicate, TChild> get, Action<TPredicate, TChild> set, Func<TChild, TChildKey> key,
public ChildImpl(Func<TPredicate, TChild> get, Action<TPredicate, TChild> set, Func<TChild, TChildKey> key,
Dictionary<TChildKey, ITemplatedPropertyWrapperVM<TChild>> oneofs, string template = PredicateTemplateKeys.Recursive) {
_get = get;
_set = set;
+6 -16
View File
@@ -1,10 +1,7 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:bv="clr-namespace:bts.view"
xmlns:mbts="clr-namespace:mbts.ui"
xmlns:control="clr-namespace:mbts.ui.controls"
>
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mbts="clr-namespace:mbts.ui"
xmlns:control="clr-namespace:mbts.ui.controls">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Images/Vector/Icons.xaml" />
<ResourceDictionary Source="/view/Resources/Converters.xaml" />
@@ -519,16 +516,9 @@
<Setter.Value>
<DataTemplate>
<control:SparkMenuItem
Command="{Binding command}" Style="{StaticResource SparkMenuButton}" Icon="{Binding icon}"
Content="{Binding title}" Command="{Binding command}" Style="{StaticResource SparkMenuButton}" Icon="{Binding icon}"
Collapsed="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type control:SparkMenu}}, Path=ShowText, Converter={StaticResource NotConverter}}"
>
<control:SparkMenuItem.Content>
<MultiBinding Converter="{x:Static bv:SparkWindowView.HeaderConverter}">
<Binding Path="title"/>
<Binding Path="key_gesture"/>
</MultiBinding>
</control:SparkMenuItem.Content>
</control:SparkMenuItem>
/>
</DataTemplate>
</Setter.Value>
</Setter>
+17 -11
View File
@@ -2,7 +2,6 @@
using mbts;
using mbts.ui;
using System;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
@@ -12,15 +11,13 @@ using static mbts.spark.SparkTags;
namespace bts.spark {
public abstract class SparkCommand {
public readonly KeyGesture key_gesture;
string _title;
bool _show_in_toolbar, _show_in_context_menu;
public SparkCommand(string title, ImageSource icon, bool show_in_toolbar = true, bool show_in_context_menu = true, KeyGesture key_gesture = null) {
public SparkCommand(string title, ImageSource icon, bool show_in_toolbar = true, bool show_in_context_menu = true) {
_title = title;
this.icon = icon;
_show_in_toolbar = show_in_toolbar;
_show_in_context_menu = show_in_context_menu;
this.key_gesture = key_gesture;
}
public event Action title_changed, show_in_toolbar_changed, show_in_context_menu_changed;
@@ -81,11 +78,15 @@ namespace bts.spark {
public static SparkCommandParamObserver<T> create<T>(string title, ImageSource icon, ITag<T> tag, IObservable<T> parameter, Func<T, bool> can_execute) =>
create(title, icon, tag, parameter, parameter.Select(can_execute));
public static SparkCommandParamObserver<T> create<T>(string title, ImageSource icon, ITag<T> tag, IObservable<T> parameter, Func<T, bool> can_execute, Func<bool> is_confirmation_affirmed) =>
create(title, icon, tag, parameter, parameter.Select(can_execute), is_confirmation_affirmed);
public static SparkCommandParamObserver<T> create<T, TSource>(string title, ImageSource icon, ITag<T> tag, IObservable<TSource> parameter_source, Func<TSource, bool> parameter_source_where, Func<TSource, T> parameter_selector) =>
create(title, icon, tag, parameter_source.Select(parameter_selector), parameter_source.Select(parameter_source_where));
public static SparkCommandParamObserver<T> create<T>(string title, ImageSource icon, ITag<T> tag, IObservable<T> parameter, IObservable<bool> can_execute) =>
new SparkCommandParamObserver<T>(title, icon, tag, parameter, can_execute);
public static SparkCommandParamObserver<T> create<T>(string title, ImageSource icon, ITag<T> tag, IObservable<T> parameter, IObservable<bool> can_execute, Func<bool> is_confirmation_affirmed = null) =>
new SparkCommandParamObserver<T>(title, icon, tag, parameter, can_execute, is_confirmation_affirmed);
}
public static class SparkCommandExtensions {
@@ -135,11 +136,15 @@ namespace bts.spark {
public SparkCommandParamObserver<T> complete(IObservable<T> value, Func<T, bool> can_execute) =>
SparkCommand.create(title, icon, tag, value, can_execute);
public SparkCommandParamObserver<T> complete(IObservable<T> value, Func<T, bool> can_execute, Func<bool> is_confirmation_affirmed) =>
SparkCommand.create(title, icon, tag, value, can_execute, is_confirmation_affirmed);
public SparkCommandParamObserver<T> complete<TSource>(IObservable<TSource> value_source, Func<TSource, bool> value_source_where, Func<TSource, T> value_selector) =>
SparkCommand.create(title, icon, tag, value_source, value_source_where, value_selector);
public SparkCommandParamObserver<T> complete(IObservable<T> parameter, IObservable<bool> can_execute) =>
SparkCommand.create(title, icon, tag, parameter, can_execute);
}
public class SparkCommandWrapper : SparkCommand, IDisposable {
@@ -202,8 +207,7 @@ namespace bts.spark {
public class SparkCommandCommand : SparkCommand {
readonly ICommand _a;
public SparkCommandCommand(string title, ImageSource icon, ICommand a, KeyGesture key_gesture = null)
: base(title, icon, key_gesture: key_gesture) {
public SparkCommandCommand(string title, ImageSource icon, ICommand a) : base(title, icon) {
_a = a;
}
@@ -226,8 +230,9 @@ namespace bts.spark {
}
public class SparkCommandParamObserver<T> : SparkCommand, IDisposable {
public SparkCommandParamObserver(string title, ImageSource icon, ITag<T> tag, IObservable<T> parameter, IObservable<bool> can_execute) : base(title, icon) {
public SparkCommandParamObserver(string title, ImageSource icon, ITag<T> tag, IObservable<T> parameter, IObservable<bool> can_execute, Func<bool> is_confirmation_affirmed = null) : base(title, icon) {
_tag = tag;
_is_confirmation_affirmed = is_confirmation_affirmed;
_subs = Disposable.Create(
parameter.OnDispatcher().Subscribe(p => _parameter = p),
can_execute.OnDispatcher().Subscribe(ce => {
@@ -241,10 +246,12 @@ namespace bts.spark {
IDisposable _subs;
T _parameter;
bool _can_execute;
Func<bool> _is_confirmation_affirmed;
public override bool canExecute() => _can_execute;
public override void execute(IBus bus) {
if (_can_execute) {
if (_can_execute && (_is_confirmation_affirmed == null || _is_confirmation_affirmed())) {
try {
bus.post(_tag, _parameter);
} catch (Exception e) {
@@ -318,7 +325,6 @@ namespace bts.spark {
}
public string title => _cmd.title;
public KeyGesture key_gesture => _cmd.key_gesture;
public ImageSource icon => _cmd.icon;
public bool show_in_toolbar => _cmd.show_in_toolbar;
public bool show_in_context_menu => _cmd.show_in_context_menu;
+1 -1
View File
@@ -22,7 +22,7 @@ namespace mbts.spark
public static ITag<IEnumerable<(OrderDefinitionImmutable definition, double price, uint size)>> RecreateOrder { get; } = new SparkTag<IEnumerable<(OrderDefinitionImmutable, double, uint)>>();
public static ITag<IEnumerable<uint>> CancelOrders { get; } = new SparkTag<IEnumerable<uint>>();
public static ITag<IEnumerable<uint>> DetachOrders { get; } = new SparkTag<IEnumerable<uint>>();
public static ITag<IEnumerable<uint>> CrackOrders { get; } = new SparkTag<IEnumerable<uint>>();
public static ITag<(uint key, double price, uint size)> UpdateOrder { get; } = new SparkTag<(uint, double, uint)>();
public static ITag<uint> ShowModifyOrderTag { get; } = new SparkTag<uint>(nameof(ShowModifyOrderTag));
@@ -11,7 +11,7 @@ namespace mbts.ui.controls {
var rv = new ObservableAggregateCollection<TInner>();
var aggregator = new Aggregator<TInner>(rv);
foreach (var o in source)
aggregator.Add(select_inner(o));
aggregator.add_collection(select_inner(o));
source.CollectionChanged += _mk_outter_collection_changed_handler(aggregator, select_inner);
return rv;
}
@@ -20,7 +20,7 @@ namespace mbts.ui.controls {
aggregate.Clear();
var aggregator = new Aggregator<TInner>(aggregate);
foreach (var o in source)
aggregator.Add(select_inner(o));
aggregator.add_collection(select_inner(o));
var handler = _mk_outter_collection_changed_handler(aggregator, select_inner);
source.CollectionChanged += handler;
return Disposable.Create(() => source.CollectionChanged -= handler);
@@ -37,23 +37,23 @@ namespace mbts.ui.controls {
add_collections(e.NewStartingIndex, e.NewItems);
break;
case NotifyCollectionChangedAction.Reset:
aggregator.Clear();
aggregator.clear_collections();
foreach (TOutter o in (IEnumerable<TOutter>)s)
aggregator.Add(select_inner(o));
aggregator.add_collection(select_inner(o));
break;
}
};
void add_collections(int new_item_index, IList new_items) {
foreach (TOutter o in new_items)
aggregator.Insert(new_item_index++, select_inner(o));
aggregator.insert_collection(new_item_index++, select_inner(o));
}
void remove_collections(int old_item_index, IList old_items) {
foreach (TOutter o in old_items)
aggregator.RemoveAt(old_item_index);
aggregator.remove_collection_at(old_item_index);
}
}
internal class Aggregator<T> : IDisposable, IList<ObservableCollection<T>>, IReadOnlyList<ObservableCollection<T>> {
internal class Aggregator<T> : IDisposable {
readonly ObservableCollection<T> _target;
readonly List<ObservableCollection<T>> _collections = new List<ObservableCollection<T>>();
bool _is_disposed;
@@ -61,39 +61,28 @@ namespace mbts.ui.controls {
public Aggregator(ObservableCollection<T> target, params ObservableCollection<T>[] collections) {
_target = target;
foreach (var c in collections)
Add(c);
add_collection(c);
}
public ObservableCollection<T> this[int index] {
get => _collections[index];
set => Insert(index, value);
}
public int Count => _collections.Count;
public bool IsReadOnly => false;
public void Add(ObservableCollection<T> collection) {
public void add_collection(ObservableCollection<T> collection) {
_check_disposed();
_target.AddRange(collection);
_collections.Add(collection);
collection.CollectionChanged += _on_collection_changed;
}
public bool Remove(ObservableCollection<T> collection) {
int num_before = _collections.Count;
public void remove_collection(ObservableCollection<T> collection) {
_check_disposed();
_remove_collection_items(collection, out int _, out int collection_index);
_collections.RemoveAt(collection_index);
collection.CollectionChanged -= _on_collection_changed;
return _collections.Count < num_before;
}
public void Insert(int index, ObservableCollection<T> collection) {
public void insert_collection(int index, ObservableCollection<T> collection) {
_check_disposed();
if (index < 0 || index > _collections.Count) throw new ArgumentOutOfRangeException(nameof(index));
if (index == _collections.Count)
Add(collection);
add_collection(collection);
else {
_collections.Insert(index, collection);
_on_collection_changed(collection, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection, 0));
@@ -101,12 +90,12 @@ namespace mbts.ui.controls {
}
}
public void RemoveAt(int index) {
public void remove_collection_at(int index) {
_check_disposed();
Remove(_collections[index]);
remove_collection(_collections[index]);
}
public void Clear() {
public void clear_collections() {
_check_disposed();
foreach (var c in _collections)
c.CollectionChanged -= _on_collection_changed;
@@ -114,15 +103,6 @@ namespace mbts.ui.controls {
_target.Clear();
}
public bool Contains(ObservableCollection<T> item) =>
_collections.Contains(item);
public void CopyTo(ObservableCollection<T>[] array, int arrayIndex) =>
_collections.CopyTo(array, arrayIndex);
public int IndexOf(ObservableCollection<T> item) =>
_collections.IndexOf(item);
public void Dispose() {
if (!_is_disposed) {
foreach (var c in _collections)
@@ -131,9 +111,6 @@ namespace mbts.ui.controls {
}
}
public IEnumerator<ObservableCollection<T>> GetEnumerator() => _collections.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _collections.GetEnumerator();
void _on_collection_changed(object sender, NotifyCollectionChangedEventArgs e) {
_check_disposed();
int offset = _get_collection_start(sender);
@@ -189,12 +166,10 @@ namespace mbts.ui.controls {
public ObservableAggregateCollection(params ObservableCollection<T>[] collections) =>
_aggregator = new ObservableAggregateCollection.Aggregator<T>(this, collections);
public IReadOnlyList<ObservableCollection<T>> source_collections => _aggregator;
public void add_collection(ObservableCollection<T> collection) => _aggregator.Add(collection);
public void remove_collection(ObservableCollection<T> collection) => _aggregator.Remove(collection);
public void insert_collection(int index, ObservableCollection<T> collection) => _aggregator.Insert(index, collection);
public void remove_collection_at(int index) => _aggregator.RemoveAt(index);
public void clear_collections() => _aggregator.Clear();
public void add_collection(ObservableCollection<T> collection) => _aggregator.add_collection(collection);
public void remove_collection(ObservableCollection<T> collection) => _aggregator.remove_collection(collection);
public void insert_collection(int index, ObservableCollection<T> collection) => _aggregator.insert_collection(index, collection);
public void remove_collection_at(int index) => _aggregator.remove_collection_at(index);
public void clear_collections() => _aggregator.clear_collections();
}
}
@@ -192,16 +192,16 @@ namespace mbts.ui.utility {
}
}
public static IDisposable connect_item_actions<T>(IReadOnlyObservableCollection<T> source, Action<T> action) =>
connect_item_actions(source, source, action);
public static IDisposable connect_item_actions<T>(ObservableCollection<T> source, Action<T> action) =>
connect_item_actions(source, source, action);
public static IDisposable connect_item_actions<T>(INotifyCollectionChanged notifier, IEnumerable<T> collection, Action<T> action) {
foreach (T v in collection)
public static IDisposable connect_item_actions<T>(IReadOnlyObservableCollection<T> source, Action<T> action) {
foreach (T v in source)
action(v);
return notifier.Subscribe((s, e) => CollectionHelper.process_new_items(collection, action, e));
return source.Subscribe((s, e) => CollectionHelper.process_new_items(source, action, e));
}
public static IDisposable connect_item_actions<T>(ObservableCollection<T> source, Action<T> action) {
foreach (T v in source)
action(v);
return source.Subscribe((s, e) => CollectionHelper.process_new_items(source, action, e));
}
public static IDisposable connect_item_hooks<T>(IReadOnlyObservableCollection<T> source, Func<T, IDisposable> get_item_hook) {
@@ -218,18 +218,6 @@ namespace mbts.ui.utility {
source.Subscribe((s, e) => CollectionHelper.reflect_change(item_hooks, source, get_item_hook, d => d.Dispose(), e)));
}
public static IDisposable dispose_on_removed<T>(IReadOnlyObservableCollection<T> source) where T : IDisposable =>
connect_item_hooks(source, v => v);
public static IDisposable dispose_on_removed<T>(ObservableCollection<T> source, bool clear_source_on_dispose) where T : IDisposable =>
connect_item_hooks(source, v => v, clear_source_on_dispose);
public static IDisposable try_dispose_on_removed<T>(IReadOnlyObservableCollection<T> source) =>
connect_item_hooks(source, v => v as IDisposable ?? Disposable.empty);
public static IDisposable try_dispose_on_removed<T>(ObservableCollection<T> source, bool clear_source_on_dispose = false) =>
connect_item_hooks(source, v => v as IDisposable ?? Disposable.empty, clear_source_on_dispose);
class SortedWrapper<T> : IReadOnlyObservableCollection<T> {
const int _min_capacity = 4;
readonly IComparer<T> _comparer;
@@ -27,7 +27,7 @@ namespace mbts.ui.utilities
readonly IEqualityComparer<T> _comparer;
readonly TSourceCollection _source;
readonly IList<T> _target;
IDictionary<T, int> _cache;
IDictionary<T, int> _cache = new Dictionary<T, int>();
IDisposable _sub;
public DistinctCollectionResolver(TSourceCollection source, IList<T> target, Func<TSource, T> get_value)
@@ -38,7 +38,6 @@ namespace mbts.ui.utilities
_target = target;
_get_value = get_value;
_comparer = comparer;
_cache = new Dictionary<T, int>(comparer);
_sync();
_sub = _source.Subscribe(_source_CollectionChanged);
}
@@ -87,11 +87,7 @@ namespace mbts.ui.utility {
foreach (var value in source_collection.OfType<INotifyPropertyChanged>())
value.PropertyChanged += on_item_PropertyChanged;
}
if (source_collection.Count == 0) {
filtered_collection.Clear();
} else {
CollectionHelper.sync_with_never_duplicate(filtered_collection, source_collection.Where(filter));
}
CollectionHelper.sync_with_never_duplicate(filtered_collection, source_collection.Where(filter));
}
}
}
@@ -217,4 +213,4 @@ namespace mbts.ui.utility {
return i;
}
}
}
}
@@ -24,8 +24,5 @@ namespace mbts {
return (uint)value;
}
}
public static double? nan_nullable(double value) =>
double.IsNaN(value) ? (double?)null : value;
}
}
@@ -54,17 +54,6 @@ namespace System.Collections.Generic {
// yield return item;
//}
public static T SafeSingleOrDefault<T>(this IEnumerable<T> source) {
using (var en = source.GetEnumerator()) {
if (en.MoveNext()) {
var rv_candidate = en.Current;
if (!en.MoveNext())
return rv_candidate;
}
}
return default;
}
public static IEnumerable<T> TakeWhile<T, TAccumulate>(this IEnumerable<T> source, TAccumulate seed, Func<TAccumulate, T, TAccumulate> accumulate, Func<TAccumulate, T, bool> predicate) {
TAccumulate a = seed;
foreach (T el in source) {
@@ -97,28 +86,6 @@ namespace System.Collections.Generic {
return false;
}
public static bool TryGetSingle<T>(this IEnumerable<T> source, out T result) {
if (source == null) throw new ArgumentNullException(nameof(source));
using (var en = source.GetEnumerator()) {
if (en.MoveNext()) {
result = en.Current;
if (!en.MoveNext())
return true;
}
}
result = default;
return false;
}
public static bool TryGetSingle<T, TResult>(this IEnumerable<T> source, Func<T, bool> predicate, Func<T, TResult> convert, out TResult result) {
if (TryGetSingle(source.Where(predicate), out T v)) {
result = convert(v);
return true;
}
result = default;
return false;
}
public static T FirstOrDefault<T>(this IEnumerable<T> source, Func<T, bool> predicate, T default_rv) {
if (source == null) throw new ArgumentNullException(nameof(source));
foreach (T element in source)
@@ -1,18 +1,25 @@
using bts;
using mbts;
using mbts.utils;
using System;
using System.Collections;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Text;
using System.Runtime.InteropServices;
using System.Collections.ObjectModel;
using System.Threading;
using System.Reactive.Linq;
using System.Collections;
using mbts.utils;
using System.Threading.Tasks;
using mbts;
using System.Reactive.Subjects;
using System.Windows.Threading;
using System.Collections.Specialized;
using mbts.spark;
using bts.api;
using bts;
using bts.utils;
namespace mbts {
namespace mbts
{
public static class GlobalExtensions
{
#region AddRange
@@ -98,8 +105,8 @@ namespace mbts {
return items.GroupBy(key_selector).ToDictionary(g => g.Key, g => g.AsEnumerable());
}
public static IDictionary<K, V> MapAggregate<K, V, T>(this IEnumerable<T> items, Func<T, K> key_selector, Func<T, V> value_selector, V seed, Func<V, V, V> aggregate, IEqualityComparer<K> key_comparer) {
return items.GroupBy(key_selector, key_comparer).ToDictionary(g => g.Key, g => g.Select(value_selector).Aggregate(seed, aggregate), key_comparer);
public static IDictionary<K, V> MapAggregate<K, V, T>(this IEnumerable<T> items, Func<T, K> key_selector, Func<T, V> value_selector, V seed, Func<V, V, V> aggregate, IEqualityComparer<K> key_equality_comparer) {
return items.GroupBy(key_selector, key_equality_comparer).ToDictionary(g => g.Key, g => g.Select(value_selector).Aggregate(seed, aggregate));
}
public static int aggregate_hash<T>(this IEnumerable<T> series) where T : class {
@@ -231,7 +238,8 @@ namespace mbts {
}
}
namespace System.Collections.Specialized {
namespace System.Collections.Specialized
{
public static class NotifyCollectionChangedExtensions
{
public static IDisposable SuspendHandler(this INotifyCollectionChanged source, NotifyCollectionChangedEventHandler handler) {
@@ -250,7 +258,8 @@ namespace System.Collections.Specialized {
}
}
namespace System.Reactive.Linq {
namespace System.Reactive.Linq
{
public static class ObservableExtensions
{
/// <summary>
@@ -388,4 +397,4 @@ namespace System.Reactive.Linq {
};
}
}
}
}
@@ -1,5 +1,4 @@
using bts.utils;
using mbts.ui.shell;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.IO;
@@ -99,12 +98,8 @@ namespace mbts.ui {
}
}
public static string diagnostic_name(this IShellWindow window) {
var sb = new StringBuilderEx { "[", window.window_type, "]", window.Title };
if (window is ISharedWindow shared_win)
sb.Add("(thread: ", shared_win.thread_id, ")");
return sb.ToString();
}
public static string diagnostic_name(this IShellWindow window) =>
$"[{window.window_type}]{window.Title}";
public static IObservable<string> observe_title(this IShellWindow window) {
var src = Observable.FromEvent<Action, string>(cb => () => cb(window.Title), cb => window.TitleChanged += cb, cb => window.TitleChanged -= cb);
@@ -90,25 +90,20 @@ namespace mbts.utils {
}
// Note: this is not optimized, be wary of use with large collections
public static void sync_with_never_duplicate<T>(IList<T> dest, IEnumerable<T> src) =>
public static void sync_with_never_duplicate<T>(ObservableCollection<T> dest, IEnumerable<T> src) =>
sync_with_never_duplicate(dest, src, EqualityComparer<T>.Default);
// Note: this is not optimized, be wary of use with large collections
public static void sync_with_never_duplicate<T>(IList<T> dest, IEnumerable<T> src, IEqualityComparer<T> comp) {
var src_list = src as IList<T> ?? new List<T>(src);
var src_set = src as ISet<T> ?? new HashSet<T>(src_list);
for (int i = 0; i < dest.Count; i++) {
if (!src_set.Contains(dest[i]))
dest.RemoveAt(i);
}
for (int i = 0; i < src_list.Count; i++) {
int curr_index = dest.IndexOf(src_list[i]);
public static void sync_with_never_duplicate<T>(ObservableCollection<T> dest, IEnumerable<T> src, IEqualityComparer<T> comp) {
int i = 0;
foreach (var val in src.empty_if_null()) {
int curr_index = dest.IndexOf(val);
if (curr_index == i) {
continue;
} else if (curr_index >= 0 && curr_index < dest.Count) {
} else if (curr_index > 0 && curr_index < dest.Count) {
dest.RemoveAt(curr_index);
}
dest.Insert(i, src_list[i]);
dest.Insert(i++, val);
}
}
@@ -117,16 +112,11 @@ namespace mbts.utils {
// Note: this is not optimized, be wary of use with large collections
public static void sync_with_never_duplicate<T, TSource, TKey>(ObservableCollection<T> dest, IEnumerable<TSource> src, Func<TSource, T> create, Func<T, TKey> key_selector, Func<TSource, TKey> src_key_selector, IEqualityComparer<TKey> key_comparer) {
var src_list = src as IList<TSource> ?? new List<TSource>(src);
var src_map = src_list.ToDictionary(src_key_selector);
for (int i = 0; i < dest.Count; i++) {
var k = key_selector(dest[i]);
if (!src_map.ContainsKey(k))
dest.RemoveAt(i);
}
int i = 0;
var src_map = src.ToDictionary(src_key_selector);
var dest_map = dest.ToDictionary(key_selector);
for (int i = 0; i < src_list.Count; i++) {
TKey key = src_key_selector(src_list[i]);
foreach (var val in src.empty_if_null()) {
TKey key = src_key_selector(val);
if (dest_map.TryGetValue(key, out T dval)) {
int curr_index = dest.IndexOf(dval);
if (curr_index != i) {
@@ -134,8 +124,9 @@ namespace mbts.utils {
dest.Insert(i, dval);
}
} else {
dest.Insert(i, create(src_list[i]));
dest.Insert(i, create(val));
}
++i;
}
}
@@ -145,5 +136,13 @@ namespace mbts.utils {
rv[i] = selector(source[i]);
return rv;
}
public static IDisposable dispose_on_removed<T>(ObservableCollection<T> collection) =>
collection.Subscribe((s, e) => {
if (e.OldItems != null) {
foreach (T item in e.OldItems)
(item as IDisposable)?.Dispose();
}
});
}
}
}
@@ -1,162 +0,0 @@
using mbts.collections;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
namespace bts.utils {
class CommonValueMonitor<T> {
readonly RefCountMap<T> _values;
T _default_value;
public CommonValueMonitor()
: this(default, EqualityComparer<T>.Default) { }
public CommonValueMonitor(T default_value)
: this(default_value, EqualityComparer<T>.Default) { }
public CommonValueMonitor(T default_value, IEqualityComparer<T> comparer) {
_default_value = default_value;
_values = new RefCountMap<T>(comparer);
}
public event Action<T> common_value_changed;
public ICollection<T> distinct_values => _values.keys;
public T common_value => _values.keys.SafeSingleOrDefault();
public void add(T value) {
if (_values.increment_adds_key(value)) {
switch (_values.Count) {
case 1: common_value_changed?.Invoke(value); break;
case 2: common_value_changed?.Invoke(_default_value); break;
}
}
}
public void remove(T value) {
if (_values.decrement_removes_key(value)) {
switch (_values.Count) {
case 0: common_value_changed?.Invoke(_default_value); break;
case 1: common_value_changed?.Invoke(_values.keys.First()); break;
}
}
}
}
class CommonValueMonitorMultiplexor<TSource> {
int _member_id_counter;
readonly Dictionary<int, (Action<TSource> add, Action<TSource> remove)> _members = new Dictionary<int, (Action<TSource> add, Action<TSource> remove)>();
public void add(TSource value) {
foreach ((var add, var remove) in _members.Values)
add(value);
}
public void remove(TSource value) {
foreach ((var add, var remove) in _members.Values)
remove(value);
}
public IDisposable add_member_mon<T>(CommonValueMonitor<T> member_mon, Func<TSource, T> get_value) {
int id = _member_id_counter++;
_members.Add(id, (
v => member_mon.add(get_value(v)),
v => member_mon.remove(get_value(v))));
return Disposable.Create(() => _members.Remove(id));
}
public IDisposable add_member_mon<T>(CommonValueMonitor<T> member_mon, Func<TSource, T> get_value, Func<T, bool> where) {
int id = _member_id_counter++;
_members.Add(id, (
v => {
var res = get_value(v);
if (where(res))
member_mon.add(res);
},
v => {
var res = get_value(v);
if (where(res))
member_mon.remove(res);
}));
return Disposable.Create(() => _members.Remove(id));
}
}
static class CommonValueMonitorExtensions {
public static bool try_get_common_value<T>(this CommonValueMonitor<T> monitor, out T result) =>
monitor.distinct_values.TryGetSingle(out result);
public static T? common_value_nullable<T>(this CommonValueMonitor<T> monitor) where T : struct =>
monitor.try_get_common_value(out T result) ? result : (T?)null;
public static IObservable<T> SubscribeThrottled<T>(this CommonValueMonitor<T> monitor, TimeSpan throttle) =>
Observable.Create<T>(o => {
bool busy = false, dirty = false;
o.OnNext(monitor.common_value);
monitor.common_value_changed += on_changed;
return Disposable.Create(() => monitor.common_value_changed -= on_changed);
async void on_changed(T _) {
if (busy) {
dirty = true;
return;
}
dirty = false;
do {
o.OnNext(monitor.common_value);
await Task.Delay(throttle);
} while (dirty);
busy = false;
}
});
public static IObservable<T> SubscribeThrottled<T>(this CommonValueMonitor<T> monitor, TimeSpan throttle, T default_value) =>
Observable.Create<T>(o => {
bool busy = false, dirty = false;
notify();
monitor.common_value_changed += on_changed;
return Disposable.Create(() => monitor.common_value_changed -= on_changed);
async void on_changed(T _) {
if (busy) {
dirty = true;
return;
}
dirty = false;
do {
notify();
await Task.Delay(throttle);
} while (dirty);
busy = false;
}
void notify() =>
o.OnNext(monitor.try_get_common_value(out T result) ? result : default_value);
});
public static IObservable<TResult> SubscribeThrottled<T, TResult>(this CommonValueMonitor<T> monitor, TimeSpan throttle, Func<T, TResult> selector, TResult default_value) =>
Observable.Create<TResult>(o => {
bool busy = false, dirty = false;
notify();
monitor.common_value_changed += on_changed;
return Disposable.Create(() => monitor.common_value_changed -= on_changed);
async void on_changed(T _) {
if (busy) {
dirty = true;
return;
}
dirty = false;
do {
notify();
await Task.Delay(throttle);
} while (dirty);
busy = false;
}
void notify() =>
o.OnNext(monitor.try_get_common_value(out T result) ? selector(result) : default_value);
});
public static IObservable<T?> SubscribeThrottledNullable<T>(this CommonValueMonitor<T> monitor, TimeSpan throttle) where T : struct =>
monitor.SubscribeThrottled<T, T?>(throttle, val => val, null);
}
}
@@ -1,49 +0,0 @@
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace bts.utils {
class PropertyUpdateThrottle<T> {
readonly TimeSpan _throttle;
readonly IEqualityComparer<T> _comparer;
bool _busy, _dirty;
T _value, _last_fired;
public PropertyUpdateThrottle(TimeSpan throttle)
: this(throttle, EqualityComparer<T>.Default) { }
public PropertyUpdateThrottle(TimeSpan throttle, IEqualityComparer<T> comparer) {
_throttle = throttle;
_comparer = comparer;
}
public T value {
get => _value;
set {
if (!_comparer.Equals(_value, value)) {
_value = value;
_fire();
}
}
}
public event PropertyChange<T> changed;
async void _fire() {
if (!_busy) {
_busy = true;
do {
var old = _last_fired;
_last_fired = _value;
changed?.Invoke(old, _value);
_dirty = false;
await Task.Delay(_throttle);
} while (_dirty);
_busy = false;
} else {
_dirty = true;
}
}
}
}
+91 -115
View File
@@ -1,115 +1,91 @@
using mbts;
using Google.Protobuf;
using Google.Protobuf.Reflection;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Linq;
using mbts.utils;
using bts.api;
using bts.api.orderman;
using System.Reflection;
namespace bts.utils {
static class ProtoHelper {
const string
_case_suffix = "Case",
_oneof_case_suffix = "OneofCase";
public static MessageDescriptor Descriptor<T>() where T : IMessage, new() =>
_DescriptorImpl<T>.value;
public static OneofDescriptor get_oneof_descriptor<T>(string prop_name) where T : IMessage, new() {
prop_name = prop_name.iremove_suffix(_case_suffix);
return Descriptor<T>().Oneofs.FirstOrDefault(d => d.Name.iequals(prop_name));
}
public static IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)> get_oneof_descriptor_index<T, TOneofCase>(string prop_name)
where T : IMessage, new()
where TOneofCase : struct =>
_DescriptorImpl<T>.OneofImpl<TOneofCase>.get_descriptor_case_index(prop_name);
public static FieldDescriptor get_oneof_field_descriptor<T, TOneofCase>(TOneofCase oneof_case) where T : IMessage, new() where TOneofCase : struct {
string oneof_name = oneof_case.ToString();
return Descriptor<T>().Fields.InFieldNumberOrder().FirstOrDefault(fd => fd.Name.Replace("_", "").iequals(oneof_name));
}
public static TImmutable immutable_factory<TMutable, TImmutable>(TMutable m) where TMutable : IMessage, new() where TImmutable : IImmutableMessage =>
(TImmutable)Activator.CreateInstance(typeof(TImmutable), m);
public static IMessage mutable_factory(Type type) =>
MutableFactoryStore.get(type)();
static class MutableFactoryStore {
[ThreadStatic]
static readonly Dictionary<Type, Func<IMessage>> _factories = new Dictionary<Type, Func<IMessage>>();
public static Func<IMessage> get(Type type) =>
_factories.GetOrAdd(type,
t => typeof(MutableFactory<>).MakeGenericType(t)
.GetField(nameof(MutableFactory<LimitOrderDefinition>.anonymous_factory), BindingFlags.Static | BindingFlags.Public)
.GetValue(null) as Func<IMessage>);
}
static class MutableFactory<T> where T : IMessage<T>, new() {
public static readonly Func<IMessage> anonymous_factory = () => new T();
}
public static Type get_field_type(FieldDescriptor field_descriptor) {
switch (field_descriptor.FieldType) {
case FieldType.Bool: return typeof(bool);
case FieldType.Bytes: return typeof(ByteString);
case FieldType.Double: return typeof(double);
case FieldType.Float: return typeof(float);
case FieldType.UInt32: return typeof(uint);
case FieldType.UInt64: return typeof(ulong);
// int
case FieldType.SFixed32:
case FieldType.Fixed32:
case FieldType.SInt32:
case FieldType.Int32: return typeof(int);
// long
case FieldType.SFixed64:
case FieldType.Fixed64:
case FieldType.SInt64:
case FieldType.Int64: return typeof(long);
//
case FieldType.String: return typeof(string);
case FieldType.Enum: return field_descriptor.EnumType.ClrType;
case FieldType.Message: return field_descriptor.MessageType.ClrType;
default: throw ExceptionBuilder.Argument.unexpected_case(field_descriptor.FieldType, nameof(field_descriptor));
}
}
public static IReadOnlyDictionary<TOneofCase, FieldDescriptor> get_oneof_descriptor_index<TOneofCase>(OneofDescriptor descriptor) where TOneofCase : struct =>
descriptor.Fields.ToDictionary(fd => EnumHelper.parse<TOneofCase>(fd.Name.Replace("_", "")));
static class _DescriptorImpl<T> where T : IMessage, new() {
public static readonly MessageDescriptor value = new T().Descriptor;
public static class OneofImpl<TOneofCase> where TOneofCase : struct {
static Dictionary<string, IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)>> _descriptor_case_indices =
new Dictionary<string, IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)>>();
public static IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)> get_descriptor_case_index(string prop_name) {
/// Use local copy because <see cref="_descriptor_case_indices"/> is updated atomically.
var dci = _descriptor_case_indices;
if (!dci.TryGetValue(prop_name, out IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)> rv)) {
/// Update <see cref="_descriptor_case_indices"/> atomically (lock-free).
/// 1) clone the local copy.
/// 2) add the new KeyValue to the clone.
/// 3) assign the clone back to <see cref="_descriptor_case_indices"/>.
rv = get_oneof_descriptor_index<TOneofCase>(get_oneof_descriptor<T>(prop_name))
.ToDictionary(kv => kv.Key, kv => (kv.Value, get_field_type(kv.Value)));
dci = dci.ToDictionary();
dci.Add(prop_name, rv);
/// It is possible for add ops to be overwritten.
/// This is perfectly safe, just means that the 'clone-add-assign' will be repeated the next time a caller requests this key.
_descriptor_case_indices = dci;
}
return rv;
}
}
}
}
}
using mbts;
using Google.Protobuf;
using Google.Protobuf.Reflection;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Linq;
using mbts.utils;
namespace bts.utils {
static class ProtoHelper {
const string
_case_suffix = "Case",
_oneof_case_suffix = "OneofCase";
public static MessageDescriptor Descriptor<T>() where T : IMessage, new() =>
_DescriptorImpl<T>.value;
public static OneofDescriptor get_oneof_descriptor<T>(string prop_name) where T : IMessage, new() {
prop_name = prop_name.iremove_suffix(_case_suffix);
return Descriptor<T>().Oneofs.FirstOrDefault(d => d.Name.iequals(prop_name));
}
public static IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)> get_oneof_descriptor_index<T, TOneofCase>(string prop_name)
where T : IMessage, new()
where TOneofCase : struct =>
_DescriptorImpl<T>.OneofImpl<TOneofCase>.get_descriptor_case_index(prop_name);
public static FieldDescriptor get_oneof_field_descriptor<T, TOneofCase>(TOneofCase oneof_case) where T : IMessage, new() where TOneofCase : struct {
string oneof_name = oneof_case.ToString();
return Descriptor<T>().Fields.InFieldNumberOrder().FirstOrDefault(fd => fd.Name.Replace("_", "").iequals(oneof_name));
}
public static Type get_field_type(FieldDescriptor field_descriptor) {
switch (field_descriptor.FieldType) {
case FieldType.Bool: return typeof(bool);
case FieldType.Bytes: return typeof(ByteString);
case FieldType.Double: return typeof(double);
case FieldType.Float: return typeof(float);
case FieldType.UInt32: return typeof(uint);
case FieldType.UInt64: return typeof(ulong);
// int
case FieldType.SFixed32:
case FieldType.Fixed32:
case FieldType.SInt32:
case FieldType.Int32: return typeof(int);
// long
case FieldType.SFixed64:
case FieldType.Fixed64:
case FieldType.SInt64:
case FieldType.Int64: return typeof(long);
//
case FieldType.String: return typeof(string);
case FieldType.Enum: return field_descriptor.EnumType.ClrType;
case FieldType.Message: return field_descriptor.MessageType.ClrType;
default: throw ExceptionBuilder.Argument.unexpected_case(field_descriptor.FieldType, nameof(field_descriptor));
}
}
public static IReadOnlyDictionary<TOneofCase, FieldDescriptor> get_oneof_descriptor_index<TOneofCase>(OneofDescriptor descriptor) where TOneofCase : struct =>
descriptor.Fields.ToDictionary(fd => EnumHelper.parse<TOneofCase>(fd.Name.Replace("_", "")));
static class _DescriptorImpl<T> where T : IMessage, new() {
public static readonly MessageDescriptor value = new T().Descriptor;
public static class OneofImpl<TOneofCase> where TOneofCase : struct {
static Dictionary<string, IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)>> _descriptor_case_indices =
new Dictionary<string, IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)>>();
public static IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)> get_descriptor_case_index(string prop_name) {
/// Use local copy because <see cref="_descriptor_case_indices"/> is updated atomically.
var dci = _descriptor_case_indices;
if (!dci.TryGetValue(prop_name, out IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)> rv)) {
/// Update <see cref="_descriptor_case_indices"/> atomically (lock-free).
/// 1) clone the local copy.
/// 2) add the new KeyValue to the clone.
/// 3) assign the clone back to <see cref="_descriptor_case_indices"/>.
rv = get_oneof_descriptor_index<TOneofCase>(get_oneof_descriptor<T>(prop_name))
.ToDictionary(kv => kv.Key, kv => (kv.Value, get_field_type(kv.Value)));
dci = dci.ToDictionary();
dci.Add(prop_name, rv);
/// It is possible for add ops to be overwritten.
/// This is perfectly safe, just means that the 'clone-add-assign' will be repeated the next time a caller requests this key.
_descriptor_case_indices = dci;
}
return rv;
}
}
}
}
}
+42 -45
View File
@@ -4,11 +4,8 @@
xmlns:mbts="clr-namespace:mbts.ui"
xmlns:ui="clr-namespace:mbts.ui"
>
<!--Control Base Colors-->
<Color x:Key="Control.MediumGrayColor">#B3B3B3</Color>
<Color x:Key="Text.Prompt.Foreground.Color">#FF999999</Color>
<SolidColorBrush x:Key="Control.MediumGrayBrush" Color="{StaticResource Control.MediumGrayColor}"/>
<SolidColorBrush x:Key="CommonBackground" Color="{ui:BTSColor common_background}" />
<SolidColorBrush x:Key="ControlBackground" Color="{x:Static mbts:BTSColors.common_background}" />
<SolidColorBrush x:Key="AccentColor1" Color="{x:Static mbts:BTSColors.accent1}" />
@@ -52,48 +49,48 @@
<SolidColorBrush x:Key="CheckBox.Pressed.BorderBrush" Color="{StaticResource CheckBox.Pressed.BorderColor}"/>
<!--ComboBox-->
<LinearGradientBrush x:Key="ComboBox.Static.Background" EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFF0F0F0" Offset="0.0"/>
<GradientStop Color="#FFE5E5E5" Offset="1.0"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ComboBox.Static.Border" Color="#FFACACAC"/>
<SolidColorBrush x:Key="ComboBox.Static.Editable.Background" Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="ComboBox.Static.Editable.Border" Color="#FFABADB3"/>
<SolidColorBrush x:Key="ComboBox.Static.Editable.Button.Background" Color="Transparent"/>
<SolidColorBrush x:Key="ComboBox.Static.Editable.Button.Border" Color="Transparent"/>
<SolidColorBrush x:Key="ComboBox.MouseOver.Glyph" Color="#FF000000"/>
<LinearGradientBrush x:Key="ComboBox.MouseOver.Background" EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFECF4FC" Offset="0.0"/>
<GradientStop Color="#FFDCECFC" Offset="1.0"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ComboBox.MouseOver.Border" Color="#FF7EB4EA"/>
<SolidColorBrush x:Key="ComboBox.MouseOver.Editable.Background" Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="ComboBox.MouseOver.Editable.Border" Color="#FF7EB4EA"/>
<LinearGradientBrush x:Key="ComboBox.MouseOver.Editable.Button.Background" EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFEBF4FC" Offset="0.0"/>
<GradientStop Color="#FFDCECFC" Offset="1.0"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ComboBox.MouseOver.Editable.Button.Border" Color="#FF7EB4EA"/>
<SolidColorBrush x:Key="ComboBox.Pressed.Glyph" Color="#FF000000"/>
<LinearGradientBrush x:Key="ComboBox.Pressed.Background" EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFDAECFC" Offset="0.0"/>
<GradientStop Color="#FFC4E0FC" Offset="1.0"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ComboBox.Pressed.Border" Color="#FF569DE5"/>
<SolidColorBrush x:Key="ComboBox.Pressed.Editable.Background" Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="ComboBox.Pressed.Editable.Border" Color="#FF569DE5"/>
<LinearGradientBrush x:Key="ComboBox.Pressed.Editable.Button.Background" EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFDAEBFC" Offset="0.0"/>
<GradientStop Color="#FFC4E0FC" Offset="1.0"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ComboBox.Pressed.Editable.Button.Border" Color="#FF569DE5"/>
<SolidColorBrush x:Key="ComboBox.Disabled.Glyph" Color="#FFBFBFBF"/>
<SolidColorBrush x:Key="ComboBox.Disabled.Background" Color="#FFF0F0F0"/>
<SolidColorBrush x:Key="ComboBox.Disabled.Border" Color="#FFD9D9D9"/>
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Background" Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Border" Color="#FFBFBFBF"/>
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Button.Background" Color="Transparent"/>
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Button.Border" Color="Transparent"/>
<LinearGradientBrush x:Key="ComboBox.Static.Background" EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFF0F0F0" Offset="0.0"/>
<GradientStop Color="#FFE5E5E5" Offset="1.0"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ComboBox.Static.Border" Color="#FFACACAC"/>
<SolidColorBrush x:Key="ComboBox.Static.Editable.Background" Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="ComboBox.Static.Editable.Border" Color="#FFABADB3"/>
<SolidColorBrush x:Key="ComboBox.Static.Editable.Button.Background" Color="Transparent"/>
<SolidColorBrush x:Key="ComboBox.Static.Editable.Button.Border" Color="Transparent"/>
<SolidColorBrush x:Key="ComboBox.MouseOver.Glyph" Color="#FF000000"/>
<LinearGradientBrush x:Key="ComboBox.MouseOver.Background" EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFECF4FC" Offset="0.0"/>
<GradientStop Color="#FFDCECFC" Offset="1.0"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ComboBox.MouseOver.Border" Color="#FF7EB4EA"/>
<SolidColorBrush x:Key="ComboBox.MouseOver.Editable.Background" Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="ComboBox.MouseOver.Editable.Border" Color="#FF7EB4EA"/>
<LinearGradientBrush x:Key="ComboBox.MouseOver.Editable.Button.Background" EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFEBF4FC" Offset="0.0"/>
<GradientStop Color="#FFDCECFC" Offset="1.0"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ComboBox.MouseOver.Editable.Button.Border" Color="#FF7EB4EA"/>
<SolidColorBrush x:Key="ComboBox.Pressed.Glyph" Color="#FF000000"/>
<LinearGradientBrush x:Key="ComboBox.Pressed.Background" EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFDAECFC" Offset="0.0"/>
<GradientStop Color="#FFC4E0FC" Offset="1.0"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ComboBox.Pressed.Border" Color="#FF569DE5"/>
<SolidColorBrush x:Key="ComboBox.Pressed.Editable.Background" Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="ComboBox.Pressed.Editable.Border" Color="#FF569DE5"/>
<LinearGradientBrush x:Key="ComboBox.Pressed.Editable.Button.Background" EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFDAEBFC" Offset="0.0"/>
<GradientStop Color="#FFC4E0FC" Offset="1.0"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ComboBox.Pressed.Editable.Button.Border" Color="#FF569DE5"/>
<SolidColorBrush x:Key="ComboBox.Disabled.Glyph" Color="#FFBFBFBF"/>
<SolidColorBrush x:Key="ComboBox.Disabled.Background" Color="#FFF0F0F0"/>
<SolidColorBrush x:Key="ComboBox.Disabled.Border" Color="#FFD9D9D9"/>
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Background" Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Border" Color="#FFBFBFBF"/>
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Button.Background" Color="Transparent"/>
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Button.Border" Color="Transparent"/>
<SolidColorBrush x:Key="ComboBox.Static.Glyph" Color="#FF606060"/>
<!--ListBox-->
@@ -1,11 +0,0 @@
using System.Windows;
using System.Windows.Documents;
namespace mbts.ui.controls.adorners {
class Adorner<T> : Adorner where T : UIElement {
public Adorner(T adorned_element) : base(adorned_element) =>
AdornedElement = adorned_element;
public new T AdornedElement { get; }
}
}
@@ -1,87 +0,0 @@
using bts.utils;
using mbts.ui.extensions;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace mbts.ui.controls.adorners {
/// <summary>
/// This Adorner draws a rectangle over rows when the mouse hovers over them.
/// </summary>
class DIYGridRowHighlighterAdorner : Adorner<DIYGrid> {
DIYGridRowsPresenter _rows_presenter_backing;
ScrollContentPresenter _content_presenter_backing;
object _target_row;
Brush _fill;
Pen _stroke;
#region Fill
public static readonly DependencyProperty FillProperty =
WPFHelper.create_dp<SolidColorBrush, DIYGridRowHighlighterAdorner>(
nameof(Fill),
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._fill = v,
DIYGrid.DefaultSelectionHighlight);
public SolidColorBrush Fill {
get => GetValue(FillProperty) as SolidColorBrush;
set => SetValue(FillProperty, value);
}
#endregion
#region Stroke
public static readonly DependencyProperty StrokeProperty =
WPFHelper.create_dp<Pen, DIYGridRowHighlighterAdorner>(
nameof(Stroke),
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._stroke = v,
DIYGrid.DefaultSelectionStroke);
public Pen Stroke {
get => GetValue(StrokeProperty) as Pen;
set => SetValue(StrokeProperty, value);
}
#endregion
public DIYGridRowHighlighterAdorner(DIYGrid grid) : base(grid) {
_target_row = TargetRow;
_fill = Fill;
_stroke = Stroke;
IsHitTestVisible = false;
grid.SubscribeMouseEnter(on_mouse_enter);
grid.SubscribeMouseLeave(on_mouse_leave);
grid.SubscribePreviewMouseMove(on_mouse_move);
void on_mouse_enter(object sender, MouseEventArgs e) => InvalidateVisual();
void on_mouse_move(object sender, MouseEventArgs e) => InvalidateVisual();
void on_mouse_leave(object sender, MouseEventArgs e) => InvalidateVisual();
}
DIYGridRowsPresenter _rows_presenter => _rows_presenter_backing ?? (_rows_presenter_backing = AdornedElement.descendants_of_type<DIYGridRowsPresenter>().FirstOrDefault());
ScrollContentPresenter _content_presenter => _content_presenter_backing ?? (_content_presenter_backing = _rows_presenter?.descendants_of_type<ScrollContentPresenter>().empty_if_null().FirstOrDefault());
public static readonly DependencyProperty TargetRowProperty =
WPFHelper.create_dp<object, DIYGridRowHighlighterAdorner>(
nameof(TargetRow),
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._target_row = v);
public object TargetRow { get => GetValue(TargetRowProperty); set => SetValue(TargetRowProperty, value); }
protected override void OnRender(DrawingContext drawingContext) {
if (_content_presenter == null || _target_row == null) {
base.OnRender(drawingContext);
}
var pt = Mouse.GetPosition(AdornedElement);
if (AdornedElement.RowContainerGenerator.ContainerFromItem(_target_row) is FrameworkElement rview) {
var top_left = rview.TranslatePoint(new Point(0, 0), _content_presenter);
double x_adj = MathUtils.within_range(0.0, _content_presenter.ActualWidth, top_left.X);
double y_adj = MathUtils.within_range(0.0, _content_presenter.ActualHeight, top_left.Y);
top_left = _content_presenter.TranslatePoint(new Point(x_adj, y_adj), AdornedElement);
drawingContext.DrawRectangle(_fill, _stroke, new Rect(top_left, new Point(top_left.X + _content_presenter.ActualWidth, top_left.Y + rview.ActualHeight)));
}
}
}
}
@@ -48,14 +48,12 @@ namespace mbts.ui.controls {
PnL_Generic = 80,
Working = 72,
LastMessage = 100,
ParentKey = 100,
AttachedTargetOrderKey = 100;
ParentKey = 100;
}
public static class DefaultColumnHeaders {
public const string
Account = "Account",
AttachedTargetOrderKey = "Attached Target Key",
Exchange = "Exchange",
FillKey = "Fill Key",
FillTime = "Fill Time",
@@ -98,7 +96,15 @@ namespace mbts.ui.controls {
TotalNetPos = "Total Net Pos",
TotalPnL = "Total PnL",
PnLPrice = "PnL Price",
Cancel = "Cancel";
Cancel = "Cancel",
MarketDescription = "Market Description",
ClearedAlias = "Cleared Alias",
ProductDescription = "Product Description",
HubAlias = "Hub Alias",
StripName = "Strip Name",
MicCode = "Mic Code"
;
}
public interface IKeyedColumn<TColKey> {
@@ -19,7 +19,6 @@ using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using mbts.utils;
using System.Reactive.Linq;
namespace mbts.ui.controls {
interface IHandleGridSorting {
@@ -324,9 +323,6 @@ namespace mbts.ui.controls {
public event DIYGridSortingEventHandler Sorting;
public ItemContainerGenerator RowContainerGenerator =>
_row_presenter.ItemContainerGenerator;
private void OnCopy(object sender, ExecutedRoutedEventArgs e) {
if (_row_presenter != null) {
IEnumerable<RowBase> selected_rows = _row_presenter.SelectedItems.Cast<object>()
@@ -708,8 +704,8 @@ namespace mbts.ui.controls {
(o, v) => {
if (DoubleHelper.IsNaNOrInfinity(v))
v = 0;
if (!o._visible_columns.is_null_or_empty()) {
double max_font_size = o._visible_columns.Max(c => c.font_size);
if (!o._visible_columns.is_null_or_empty() && o.Columns != null) {
double max_font_size = o.Columns.Max(c => c.font_size);
double min_row_height = o.RowPadding.Top + max_font_size + o.RowPadding.Bottom;
if (v < min_row_height)
return min_row_height;
@@ -1029,25 +1025,5 @@ namespace mbts.ui.controls {
callback(map);
}
}
public static IObservable<T> SubscribeMouseHoverRow<T>(this DIYGrid grid) where T : class =>
SubscribeMouseHoverRow(grid, EqualityComparer<T>.Default);
public static IObservable<T> SubscribeMouseHoverRow<T>(this DIYGrid grid, IEqualityComparer<T> comparer) where T : class =>
Observable.Create<T>(o => {
T curr_row = null;
return Disposable.Create(
grid.SubscribeMouseEnter(on_mouse_enter),
grid.SubscribeMouseLeave(on_mouse_leave),
grid.SubscribePreviewMouseMove(on_mouse_move));
void on_mouse_enter(object sender, MouseEventArgs e) => refresh();
void on_mouse_move(object sender, MouseEventArgs e) => refresh();
void on_mouse_leave(object sender, MouseEventArgs e) => refresh();
void refresh() {
T row = grid.ChildAtPoint<ListBoxItem>(Mouse.GetPosition(grid)) is var rview && rview != null ? grid.RowContainerGenerator.ItemFromContainer(rview) as T : null;
if (!comparer.Equals(curr_row, row))
o.OnNext(curr_row = row);
}
});
}
}
@@ -11,26 +11,26 @@ namespace mbts.ui {
IReadOnlyList<SoundNotification> sound_notifications { get; }
}
interface IPredicateAnimationsData<T, TPredicateBuilder> where TPredicateBuilder : IPredicateBuilder<T> {
TPredicateBuilder predicate_builder { get; }
interface IPredicateAnimationsData<T, TPredicateData> {
TPredicateData predicate_data { get; }
IReadOnlyList<CellAnimation> cell_animations { get; }
IReadOnlyList<RowAnimation> row_animations { get; }
IReadOnlyList<SoundNotification> sound_notifications { get; }
}
static class PredicateAnimations {
public static IPredicateAnimationsData<T, TPredicateBuilder> create_data<T, TPredicateBuilder>(TPredicateBuilder predicate_builder, IReadOnlyList<CellAnimation> cell_animations, IReadOnlyList<RowAnimation> row_animations, IReadOnlyList<SoundNotification> sounds)
where TPredicateBuilder : IPredicateBuilder<T> => new PredicateAnimationsData<T, TPredicateBuilder>(predicate_builder, cell_animations, row_animations, sounds);
public static IPredicateAnimationsData<T, TPredicateData> create_data<T, TPredicateData>(TPredicateData predicate_data, IReadOnlyList<CellAnimation> cell_animations, IReadOnlyList<RowAnimation> row_animations, IReadOnlyList<SoundNotification> sounds)
=> new PredicateAnimationsData<T, TPredicateData>(predicate_data, cell_animations, row_animations, sounds);
class PredicateAnimationsData<TRowData, TPredicateBuilder> : IPredicateAnimationsData<TRowData, TPredicateBuilder> where TPredicateBuilder : IPredicateBuilder<TRowData> {
public PredicateAnimationsData(TPredicateBuilder predicate_builder, IReadOnlyList<CellAnimation> cell_animations, IReadOnlyList<RowAnimation> row_animations, IReadOnlyList<SoundNotification> sound_notifications) {
this.predicate_builder = predicate_builder;
class PredicateAnimationsData<TRowData, TPredicateData> : IPredicateAnimationsData<TRowData, TPredicateData> {
public PredicateAnimationsData(TPredicateData predicate_data, IReadOnlyList<CellAnimation> cell_animations, IReadOnlyList<RowAnimation> row_animations, IReadOnlyList<SoundNotification> sound_notifications) {
this.predicate_data = predicate_data;
this.cell_animations = cell_animations;
this.row_animations = row_animations;
this.sound_notifications = sound_notifications;
}
public TPredicateBuilder predicate_builder { get; }
public TPredicateData predicate_data { get; }
public IReadOnlyList<CellAnimation> cell_animations { get; }
public IReadOnlyList<RowAnimation> row_animations { get; }
public IReadOnlyList<SoundNotification> sound_notifications { get; }
@@ -27,15 +27,15 @@ namespace mbts.spark {
ImmutableFontWeight
}
partial class GridSettingsVM<TRowData, TPredicateBuilder, TSettingsData> {
public interface IColumnVM : ISimpleColumnVM, IValueFactory<(GridColumnSettings settings, ILookup<TPredicateBuilder, CellAnimation> animations)>, IDisposable {
partial class GridSettingsVM<TRowData, TPredicateData, TSettingsData> {
public interface IColumnVM : ISimpleColumnVM, IValueFactory<(GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations)>, IDisposable {
ObservableEditableCollection<PredicateCellAnimationVM> predicate_animations { get; }
new (GridColumnSettings settings, ILookup<TPredicateBuilder, CellAnimation> animations) create();
new void load_from((GridColumnSettings settings, ILookup<TPredicateBuilder, CellAnimation> animations) args);
new (GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations) create();
new void load_from((GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations) args);
}
public class ColumnVM : DisposableViewModelBase, IColumnVM {
public readonly GridSettingsVM<TRowData, TPredicateBuilder, TSettingsData> owner;
public readonly GridSettingsVM<TRowData, TPredicateData, TSettingsData> owner;
readonly Property<string> _id, _display_name;
readonly Property<double> _width;
@@ -44,7 +44,7 @@ namespace mbts.spark {
readonly Property<FontWeight> _font_weight_override;
GridColumnSettings _settings;
public ColumnVM(GridSettingsVM<TRowData, TPredicateBuilder, TSettingsData> owner) {
public ColumnVM(GridSettingsVM<TRowData, TPredicateData, TSettingsData> owner) {
this.owner = owner;
_id = backing(nameof(id), string.Empty);
_display_name = backing(nameof(display_name), string.Empty);
@@ -60,7 +60,7 @@ namespace mbts.spark {
_font_weight_override = backing(nameof(font_weight_override), FontWeights.Normal);
// animations
predicate_animations = new ObservableEditableCollection<PredicateCellAnimationVM>(() =>
new PredicateCellAnimationVM(id, owner._create_column_predicate_vm(id)), true);
new PredicateCellAnimationVM(id, owner._create_column_predicate_vm(id), owner._predicate_data_factory), true);
predicate_animations.CollectionChanged += WPFHelper.create_collection_changed_cb(on_items_added: (_, __) => owner.show_cell_predicate_animations = true);
_dispose = Disposable.Create(() => predicate_animations.OfType<IDisposable>().DisposeAll());
}
@@ -81,17 +81,17 @@ namespace mbts.spark {
// animations
public ObservableEditableCollection<PredicateCellAnimationVM> predicate_animations { get; }
public (GridColumnSettings settings, ILookup<TPredicateBuilder, CellAnimation> animations) create() {
public (GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations) create() {
var settings = _settings.with(display_name, is_visible, null,
is_font_weight_overridden ? new NullableFontWeightProto(font_weight_override) : default,
is_font_color_overridden ? new NullableColorProto(font_color_override.color) : default,
width,
horizontal_alignment);
var animations_lookup = predicate_animations.ToFlatLookup(pa => pa.predicate.create_assigned(), pa => pa.animations.Select(a => a.create()));
var animations_lookup = predicate_animations.ToFlatLookup(pa => pa.predicate.create_assigned(owner._predicate_data_factory), pa => pa.animations.Select(a => a.create()));
return (settings, animations_lookup);
}
public void load_from((GridColumnSettings settings, ILookup<TPredicateBuilder, CellAnimation> animations) value) {
public void load_from((GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations) value) {
_settings = value.settings;
_id.set(_settings.id);
_display_name.set(_settings.display_name);
@@ -107,7 +107,7 @@ namespace mbts.spark {
font_color_override.load_from(new SolidColorBrush(_settings.font_color_override.Value));
}
predicate_animations.SetItems(value.animations.empty_if_null().Select(g => {
var vm = new PredicateCellAnimationVM(id, owner._create_column_predicate_vm(id));
var vm = new PredicateCellAnimationVM(id, owner._create_column_predicate_vm(id), owner._predicate_data_factory);
vm.load_from((g.Key, g));
return vm;
}));
@@ -131,17 +131,19 @@ namespace mbts.spark {
create().settings;
void IValueFactory<GridColumnSettings>.load_from(GridColumnSettings value) =>
load_from((value, new Dictionary<TPredicateBuilder, CellAnimation>().ToLookup(kv => kv.Key, kv => kv.Value)));
load_from((value, new Dictionary<TPredicateData, CellAnimation>().ToLookup(kv => kv.Key, kv => kv.Value)));
}
public class PredicateCellAnimationVM : DisposableViewModelBase, IValueFactory<(TPredicateBuilder predicate, IEnumerable<CellAnimation> animations)> {
public class PredicateCellAnimationVM : DisposableViewModelBase, IValueFactory<(TPredicateData predicate, IEnumerable<CellAnimation> animations)> {
//
readonly string _col_id;
readonly Property<bool> _show_animations;
readonly Func<TPredicateData> _predicate_data_factory;
public PredicateCellAnimationVM(string col_id, IPropertyWrapperVM<TPredicateBuilder> predicate) {
public PredicateCellAnimationVM(string col_id, IPropertyWrapperVM<TPredicateData> predicate, Func<TPredicateData> predicate_data_factory) {
_col_id = col_id;
this.predicate = predicate;
_predicate_data_factory = predicate_data_factory;
_show_animations = backing(nameof(show_animations), true);
animations = new ObservableEditableCollection<CellAnimationVM>(() => {
var vm = new CellAnimationVM(col_id);
@@ -152,15 +154,15 @@ namespace mbts.spark {
_dispose = predicate as IDisposable;
}
public IPropertyWrapperVM<TPredicateBuilder> predicate { get; }
public IPropertyWrapperVM<TPredicateData> predicate { get; }
public ObservableEditableCollection<CellAnimationVM> animations { get; }
public IEnumerable<CellAnimation.AnimationTypeOneofCase> animation_types { get; } = EnumHelper.get_values_except(CellAnimation.AnimationTypeOneofCase.None);
public bool show_animations { get => _show_animations.Value; set => _show_animations.set(value); }
public (TPredicateBuilder predicate, IEnumerable<CellAnimation> animations) create() =>
(predicate.create_assigned(), animations.Select(a => a.create()));
public (TPredicateData predicate, IEnumerable<CellAnimation> animations) create() =>
(predicate.create_assigned(_predicate_data_factory), animations.Select(a => a.create()));
public void load_from((TPredicateBuilder predicate, IEnumerable<CellAnimation> animations) args) {
public void load_from((TPredicateData predicate, IEnumerable<CellAnimation> animations) args) {
predicate.load_from(args.predicate);
animations.SetItems(args.animations.Select(a => {
var vm = new CellAnimationVM(_col_id);
Loaded 100 of 331 files, more files were not shown because too many files have changed in this diff. Show more