Compare commits
237
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
189157b1c7 | ||
|
|
64fd8addb2 | ||
|
|
4490ac303e | ||
|
|
666bd07411 | ||
|
|
0ff4624778 | ||
|
|
ab04b4afab | ||
|
|
ac226ddd3b | ||
|
|
0223e43aa2 | ||
|
|
73e5cfdada | ||
|
|
33b4b88af1 | ||
|
|
12f4b4b25b | ||
|
|
96cacb8bb9 | ||
|
|
300a1ed17f | ||
|
|
1a0836ca6d | ||
|
|
88908543e1 | ||
|
|
80c9b6a534 | ||
|
|
f6282c0676 | ||
|
|
02fe83f20b | ||
|
|
84d666c29c | ||
|
|
147e43b464 | ||
|
|
b909a6062e | ||
|
|
421b3af6a7 | ||
|
|
46a340d493 | ||
|
|
3cc7d76a1b | ||
|
|
5123b928f2 | ||
|
|
c2631e3257 | ||
|
|
685181ae28 | ||
|
|
929492c2f0 | ||
|
|
be023ff6cb | ||
|
|
a4d63a1421 | ||
|
|
2fd8c39888 | ||
|
|
eb97fd045a | ||
|
|
a07bc152d1 | ||
|
|
aef8e2dbb1 | ||
|
|
baac6a893d | ||
|
|
df681ca209 | ||
|
|
21b34434ef | ||
|
|
0c17dbb55b | ||
|
|
fd6c6a3055 | ||
|
|
259355d629 | ||
|
|
c6c2d3448d | ||
|
|
f860649996 | ||
|
|
05c3c97713 | ||
|
|
f3be277484 | ||
|
|
0ba311788c | ||
|
|
341eda5962 | ||
|
|
3398bb5b86 | ||
|
|
dc7cacc126 | ||
|
|
6d3d4e1f56 | ||
|
|
433b1abecd | ||
|
|
4544180c5f | ||
|
|
5722fcffc3 | ||
|
|
f1880615b1 | ||
|
|
e60f5678ec | ||
|
|
e9a70e51e4 | ||
|
|
e98d18a5aa | ||
|
|
513cc248b8 | ||
|
|
c024095f75 | ||
|
|
3e5eb80a7c | ||
|
|
942dc1640e | ||
|
|
b5c3abb782 | ||
|
|
3478d1ad52 | ||
|
|
d54e9233d1 | ||
|
|
e94759cd89 | ||
|
|
b58da4a20e | ||
|
|
8426399a23 | ||
|
|
9b2f41c805 | ||
|
|
15f96dda3e | ||
|
|
a9d5c17d63 | ||
|
|
715b629778 | ||
|
|
dd82d127ea | ||
|
|
f97e22d6eb | ||
|
|
f9e04ee28c | ||
|
|
1ce00ca13d | ||
|
|
3b9c2f5d70 | ||
|
|
5cd2dfec18 | ||
|
|
31b2ba9165 | ||
|
|
2b1b1f1681 | ||
|
|
4427b02d8d | ||
|
|
dcd3921379 | ||
|
|
bd156ecbed | ||
|
|
02f5731e1d | ||
|
|
7e75a71898 | ||
|
|
798c4dcd49 | ||
|
|
7e47d7f8a5 | ||
|
|
260ca06cde | ||
|
|
8615adb564 | ||
|
|
ab15d9d348 | ||
|
|
9540a15585 | ||
|
|
e8a467d5f1 | ||
|
|
567c18f325 | ||
|
|
270ab9c3f1 | ||
|
|
9a9024c2c0 | ||
|
|
45a5049a2d | ||
|
|
8b17911da0 | ||
|
|
61556eb534 | ||
|
|
b691dbae80 | ||
|
|
f6be20ce8d | ||
|
|
68428ab89d | ||
|
|
12d54fdb31 | ||
|
|
366e13ef05 | ||
|
|
e94ef6a602 | ||
|
|
3e7f145290 | ||
|
|
748a99d4ef | ||
|
|
35b8c1e53f | ||
|
|
123b690c6c | ||
|
|
38a65bd0e3 | ||
|
|
1cb8c54225 | ||
|
|
414833c0c5 | ||
|
|
6cc4064cd3 | ||
|
|
0fcdc9c493 | ||
|
|
0f967ae4cd | ||
|
|
a232d738a0 | ||
|
|
3d3ac4e49f | ||
|
|
15a5abf1ad | ||
|
|
76218fd35a | ||
|
|
d0370c5dc7 | ||
|
|
b07700cdc7 | ||
|
|
15de3bbfe9 | ||
|
|
8d2fa40dcc | ||
|
|
3758ac6101 | ||
|
|
ac45e9bdb2 | ||
|
|
769bad89a7 | ||
|
|
7dc272506a | ||
|
|
86c787efaf | ||
|
|
f917f581c3 | ||
|
|
bb0171a2e4 | ||
|
|
88bf448e68 | ||
|
|
05a615038d | ||
|
|
51cfd0e5bb | ||
|
|
d2ee575f19 | ||
|
|
0b76ffb4f3 | ||
|
|
0f9b93115a | ||
|
|
8cfc6a7591 | ||
|
|
9de2dd30bc | ||
|
|
38005e3266 | ||
|
|
26ae70f432 | ||
|
|
e7cea797ba | ||
|
|
f16545dfaa | ||
|
|
dcd62409f1 | ||
|
|
11252b4300 | ||
|
|
90526a8bfd | ||
|
|
e815dade3a | ||
|
|
576be7e961 | ||
|
|
407de320f6 | ||
|
|
bc0234a5e9 | ||
|
|
e1229186de | ||
|
|
a3e00217a1 | ||
|
|
16f0beb3d8 | ||
|
|
d244c62c26 | ||
|
|
44730204a5 | ||
|
|
bd47816d16 | ||
|
|
2f12f2609a | ||
|
|
9c489c03c6 | ||
|
|
5ae8a66558 | ||
|
|
03882718a0 | ||
|
|
0f21d9783b | ||
|
|
2c547f37fb | ||
|
|
a7f4944bd6 | ||
|
|
a3a33ffb6a | ||
|
|
b3db0fc4ca | ||
|
|
0f066fdeb9 | ||
|
|
22f89490a3 | ||
|
|
2137d37cbd | ||
|
|
082a241bb9 | ||
|
|
0057f8f080 | ||
|
|
4768c1cd87 | ||
|
|
57b70684ea | ||
|
|
af64592f52 | ||
|
|
a1b89ccb34 | ||
|
|
44f2a9daf7 | ||
|
|
0e5833b275 | ||
|
|
bded6d334c | ||
|
|
893c6702a2 | ||
|
|
627b8742c4 | ||
|
|
1f7e0a672e | ||
|
|
29e8fd1050 | ||
|
|
6ce741bcaf | ||
|
|
8dd070961a | ||
|
|
f6a3a276d9 | ||
|
|
68c8828d4f | ||
|
|
0d68ebbbea | ||
|
|
cc04e79f8b | ||
|
|
ac76462f7e | ||
|
|
d20b30557f | ||
|
|
a6fa5f9852 | ||
|
|
0db165bf76 | ||
|
|
705fd293ba | ||
|
|
2172f007a8 | ||
|
|
9d762e0777 | ||
|
|
ef53c70c7e | ||
|
|
8cf4e37310 | ||
|
|
04e7180b69 | ||
|
|
d40d3615b2 | ||
|
|
4efec939aa | ||
|
|
a0f24d8227 | ||
|
|
a2278423f6 | ||
|
|
7321d5d8f7 | ||
|
|
464426537e | ||
|
|
ebb2d5618c | ||
|
|
7b9c10f47c | ||
|
|
478640e808 | ||
|
|
ab993423a6 | ||
|
|
ab8802c3f1 | ||
|
|
42906f5a37 | ||
|
|
931ed76673 | ||
|
|
ebc4e202fe | ||
|
|
a7dda587ba | ||
|
|
1ace2b7a3f | ||
|
|
af2875e0ef | ||
|
|
4138ab0730 | ||
|
|
86467b2be1 | ||
|
|
7b61b4bd5b | ||
|
|
1bcb7ac87f | ||
|
|
52305d69f2 | ||
|
|
bd70e7c09e | ||
|
|
d04f9f4e96 | ||
|
|
c0d097cb40 | ||
|
|
533e688d1a | ||
|
|
36d40dec70 | ||
|
|
a3f5de099f | ||
|
|
9b497e323f | ||
|
|
9a5b5595c3 | ||
|
|
24f23adf24 | ||
|
|
cc26d0bd8b | ||
|
|
96bb63ba6d | ||
|
|
0ea72b017e | ||
|
|
698612bb3d | ||
|
|
2f23292836 | ||
|
|
085e6ae95a | ||
|
|
3be2aedfe3 | ||
|
|
1f01d974e3 | ||
|
|
910c04544b | ||
|
|
10674b8930 | ||
|
|
8c9522e22b | ||
|
|
f60afe1196 | ||
|
|
0cc30a7934 |
No files matched your search
Vendored
+1
-1
@@ -249,7 +249,7 @@
|
||||
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
|
||||
"args": [
|
||||
"test",
|
||||
"[mbo]"
|
||||
"SingleWriterConcurrentVector"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/server/",
|
||||
|
||||
@@ -4,6 +4,62 @@ weight = 3
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
## 2.2.0
|
||||
### User Visible Changes
|
||||
- Made our orders easier to spot in MBO display
|
||||
- Changed middle-click on center column to recenter ladder and introduced Alt middle-click on center to bring up the MBO popup
|
||||
- Fixed Net/Absolute radio button in risk admin UI
|
||||
- Fixed double counting of spread legs in limits
|
||||
- Removed expired fills from the system
|
||||
- Improved handling of orders with undiscovered securities
|
||||
- Added feedback to the ladder when the user attempts to middle click on bid/ask columns using an order that does not have parameters
|
||||
- Fixed display of algo studio algo types in order book
|
||||
- Hid net change setting in synthetic ladders
|
||||
- Fixed a number of problems with filtering on the Fills form
|
||||
- On the Lean algo, OnToward and Away are now calculated with respect to the opposite inside market at time of the order launch
|
||||
- Iceberg Spreaders with queue holders are not erroneously cancelled after the first fill
|
||||
## 2.1.4
|
||||
### User Visible Changes
|
||||
- Fixed handling of cancel button in security picker
|
||||
|
||||
## 2.1.3
|
||||
### User Visible Changes
|
||||
- Fixed a simulator bug in the handling of 0 sized orders
|
||||
|
||||
## 2.2.1
|
||||
### User Visible Changes
|
||||
- Fixed order book form exception when display a spreader order for whichs the legs were not present yet
|
||||
|
||||
## 2.1.1
|
||||
### User Visible Changes
|
||||
- Added PIQ to GLBX Iceberg orders
|
||||
- Corrected handling of stop limit orders
|
||||
|
||||
## 2.1.0
|
||||
### User Visible Changes
|
||||
- Added support for the GLBX Iceberg order type
|
||||
- Allowed quick algo param edits by middle-clicking on either the bid/ask columns or a "favorite" button
|
||||
- Added support for manual fills, either entered manually or imported from file. Manual fills are used only for P&L purposes, they have no impact on limits
|
||||
- Allowed the LTQ and VAP values to be overlayed on the same ladder column
|
||||
- Added day position information to the ladder
|
||||
- Added PIQ to the mini order book
|
||||
- Fixed a rare bug that could lead to server failure when requesting market data
|
||||
- Fixed a bug that could cause incorrect price displays for certain types of synthetic securities
|
||||
|
||||
## 2.0.5
|
||||
### User Visible Changes
|
||||
- Fixed a bug in the handling of rejected QHO orders
|
||||
- Implemented stricter position limit enforcement
|
||||
- Fixed a bug that could cause server failure when trying to attach to already attached orders
|
||||
|
||||
## 2.0.4
|
||||
### User Visible Changes
|
||||
- Fixed performance regression in order manager
|
||||
|
||||
### Internal changes
|
||||
- Improved market data diagnostics for bad configurations
|
||||
- Changed market data default buffer size
|
||||
|
||||
## 2.0.3
|
||||
### Internal changes
|
||||
- Improved backwards compatibility in UI settings
|
||||
|
||||
@@ -73,6 +73,7 @@ namespace bts {
|
||||
window.Close();
|
||||
message.complete(select_result(vm)).execute(_bus);
|
||||
};
|
||||
vm.cancel_action = () => window.Close();
|
||||
window.move_to_mouse();
|
||||
window.Show();
|
||||
}
|
||||
|
||||
@@ -24,10 +24,12 @@ namespace mbts.ui {
|
||||
AddFileThinGeometryKey = nameof(AddFileThinGeometryKey),
|
||||
AttachAlgosKey = nameof(AttachAlgosKey),
|
||||
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
|
||||
DataEditGeometryKey = nameof(DataEditGeometryKey),
|
||||
DetachAlgosKey = nameof(DetachAlgosKey),
|
||||
DiskGeometryKey = nameof(DiskGeometryKey),
|
||||
DiskFileGeometryKey = nameof(DiskFileGeometryKey),
|
||||
DocumentGeometryKey = nameof(DocumentGeometryKey),
|
||||
DocumentAddGeometryKey = nameof(DocumentAddGeometryKey),
|
||||
DocumentBrokenGeometryKey = nameof(DocumentBrokenGeometryKey),
|
||||
DocumentCopyGeometryKey = nameof(DocumentCopyGeometryKey),
|
||||
DocumentEmptyGeometryKey = nameof(DocumentEmptyGeometryKey),
|
||||
@@ -145,11 +147,13 @@ namespace mbts.ui {
|
||||
public static ImageSource ModifyOrder { get { return load_resource_and_freeze("ModifyOrder"); } }
|
||||
public static ImageSource CheckMark => create_icon(CheckMarkGeometryKey);
|
||||
public static ImageSource Clock { get { return load_resource_and_freeze("Clock"); } }
|
||||
public static ImageSource DataEdit => create_icon(DataEditGeometryKey, Brushes.White, new Pen(Brushes.Black, 1).frozen());
|
||||
public static ImageSource Defrag { get { return load_resource_and_freeze("Defrag"); } }
|
||||
public static ImageSource DetachAlgos => create_icon_and_rotate(DetachAlgosKey, Brushes.Red);
|
||||
public static ImageSource Disk => create_icon(DiskGeometryKey);
|
||||
public static ImageSource DiskFile => create_icon(DiskFileGeometryKey);
|
||||
public static ImageSource Display { get { return load_resource_and_freeze("Display"); } }
|
||||
public static ImageSource DocumentAdd => create_icon(DocumentAddGeometryKey);
|
||||
public static ImageSource DocumentBroken => create_icon(DocumentBrokenGeometryKey);
|
||||
public static ImageSource DocumentCopy => create_icon(DocumentCopyGeometryKey);
|
||||
public static ImageSource DocumentEmpty => create_icon(DocumentEmptyGeometryKey);
|
||||
|
||||
@@ -48,6 +48,10 @@
|
||||
</sys:String>
|
||||
</x:Array>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.DocumentAddGeometryKey}">
|
||||
M10.830011,12.05L13.334007,12.05 13.334007,16.535997 17.819998,16.535997 17.819998,19.03902 13.334007,19.03902 13.334007,23.525016 10.830988,23.525016 10.830988,19.03902 6.3460046,19.03902 6.3449975,16.53499 10.830988,16.535997z M2.2799975,2.4149805L2.2799975,29.580015 22.121998,29.580015 22.121998,8.500008 15.83599,8.500008 15.83599,2.4149805z M0,0L15.83599,0 17.125998,0 22.681996,5.439977 24.407,7.125007 24.407,32.000001 0,32.000001z
|
||||
</sys:String>
|
||||
|
||||
<!--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
|
||||
@@ -107,6 +111,9 @@
|
||||
s1-0.448,1-1v-5h5c0.552,0,1-0.448,1-1S52.052,46,51.5,46z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.DataEditGeometryKey}">
|
||||
M17.997977,17.396268C18.197928,17.396268,18.297904,17.496276,18.497975,17.596284L19.497972,18.295283C19.99797,18.595292,20.697919,19.195308,21.197917,19.495299L22.196937,20.195323C22.696935,20.495315,22.596959,20.995322,22.096961,21.195338L20.997966,21.695346C20.497968,21.89434,19.597947,22.294355,19.097949,22.494355L17.997977,22.994363C17.497979,23.194362,17.097957,22.894355,17.197932,22.394347L17.297908,21.195338C17.398005,20.595323,17.497979,19.695315,17.497979,19.195308L17.597955,17.995277C17.497979,17.695283,17.69793,17.396268,17.997977,17.396268z M1.799981,13.597213L19.697923,13.597213 17.997977,15.896244C17.797906,16.096259 17.69793,16.396253 17.69793,16.69626 17.197932,16.796268 16.79791,17.296275 16.697934,17.895284L16.597959,19.195308 1.799981,19.195308C0.79998477,19.195308,0,18.395291,0,17.396268L0,15.396237C0,14.396221,0.79998477,13.597213,1.799981,13.597213z M1.799981,6.7981106L21.796914,6.7981106C22.696935,6.7981106,23.49698,7.498118,23.596955,8.3981257L20.697919,12.397197 1.799981,12.397197C0.79998477,12.397197,0,11.597181,0,10.597165L0,8.5981418C0,7.598126,0.79998477,6.7981106,1.799981,6.7981106z M27.796891,4.3990708C27.996963,4.3990708,28.096938,4.3990708,28.296889,4.5990711L31.695924,7.0981179C31.995851,7.3981105,32.095948,7.7981258,31.895875,8.0981341L23.296908,19.795308C23.096957,20.095315,22.596959,20.195323,22.196937,19.895314L18.797902,17.396268C18.497975,17.096274,18.398002,16.69626,18.597951,16.396253L27.196918,4.6990786C27.396869,4.4990784,27.59694,4.3990708,27.796891,4.3990708z M1.799981,0L21.796914,0C22.79691,4.9549271E-08,23.596955,0.8000156,23.596955,1.800031L23.596955,3.7990554C23.596955,4.7990709,22.79691,5.5990864,21.796914,5.5990864L1.799981,5.5990864C0.79998477,5.4990941,0,4.6990786,0,3.6990629L0,1.7000236C0,0.8000156,0.79998477,4.9549271E-08,1.799981,0z
|
||||
</sys:String>
|
||||
<sys:String x:Key="{x:Static mbts:Icons.DiskGeometryKey}">
|
||||
M403.213,20H0v472h512V128.787L403.213,20z M131,50h180v100h10h20h10V50h30v130H131V50z M381,462H131V315
|
||||
c0-8.271,6.729-15,15-15h220c8.271,0,15,6.729,15,15V462z M482,462h-71V315c0-24.813-20.187-45-45-45H146
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.persist;
|
||||
@@ -117,8 +118,8 @@ namespace mbts.spark {
|
||||
// show fills
|
||||
var show_fills_cmd = new DelegateCommand(() => {
|
||||
var sc = _selected_security_criteria;
|
||||
_bus.post(SparkTags.ShowFillsTag, new FillsWindowFactory.StartupData(
|
||||
new FillsWindowFactory.FilterStates {
|
||||
_bus.post(SparkTags.ShowFillsTag, new FillsSettings(
|
||||
new FillsFilterStates {
|
||||
security = sc.is_defined() ? sc : _state.security_criteria
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using mbts.co2;
|
||||
using mbts.persist;
|
||||
@@ -92,8 +93,8 @@ namespace mbts.spark {
|
||||
_initialize();
|
||||
|
||||
bool always_true<T>(T _) => true;
|
||||
FillsWindowFactory.StartupData get_fills_startup_data(IReadOnlyList<SecurityImmutable> secs) =>
|
||||
new FillsWindowFactory.StartupData(new FillsWindowFactory.FilterStates {
|
||||
FillsSettings get_fills_startup_data(IReadOnlyList<SecurityImmutable> secs) =>
|
||||
new FillsSettings(new FillsFilterStates {
|
||||
security = secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria
|
||||
});
|
||||
void update_criteria(SecurityCriteria c) {
|
||||
|
||||
@@ -321,9 +321,12 @@ namespace mbts.spark {
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
static IEnumerable<LegSpreaderParamsTuple> _get_legs(Security synthetic, ExecutionStrategyImmutable strategy) =>
|
||||
synthetic.Synthetic.Legs
|
||||
.Select(l => new LegSpreaderParamsTuple(l, strategy.LegParamsMap[l.Security.Id].Clone()));
|
||||
static IEnumerable<LegSpreaderParamsTuple> _get_legs(Security synthetic, ExecutionStrategyImmutable strategy) {
|
||||
foreach (var l in synthetic.Synthetic.Legs) {
|
||||
if (strategy.LegParamsMap.TryGetValue(l.Security.Id, out var p))
|
||||
yield return new LegSpreaderParamsTuple(l, p);
|
||||
}
|
||||
}
|
||||
|
||||
void _close_new_leg_popup() {
|
||||
is_new_leg_popup_open = false;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
@@ -18,7 +19,6 @@ using System;
|
||||
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;
|
||||
@@ -34,22 +34,19 @@ using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
|
||||
namespace mbts.spark {
|
||||
class FillsVM : DisposableViewModelBase, IHandleGridSorting {
|
||||
readonly OrdersModel _model;
|
||||
readonly OperableCollection<Row, FillTuple, string> _rows;
|
||||
readonly DIYGridSortManager<Columns> _sort_manager;
|
||||
readonly IDIYGridFiltersManager<FillTuple, Columns, FilterStates> _filters_manager;
|
||||
readonly DIYGridRowsManager<FillTuple, Columns, FillsFilterStates> _rows_manager;
|
||||
readonly ObservableKeyedCollection<ColumnBase, Columns> _columns = new ObservableKeyedCollection<ColumnBase, Columns>(_col_key);
|
||||
List<(ColumnBase col, Columns key, ListSortDirection direction)> _active_comparers = new List<(ColumnBase col, Columns key, ListSortDirection direction)>();
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
int _total_row_count;
|
||||
|
||||
public FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
public FillsVM(IBus bus, OrdersModel model, IObservable<FillsSettings> 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) {
|
||||
public FillsVM(IBus bus, OrdersModel model, IObservable<FillsSettings> state_obs, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
|
||||
_model = model;
|
||||
_settings_manager = settings_manager;
|
||||
/// Columns
|
||||
var col_tups = new ObservableKeyedCollection<DIYGridColumnData<FillTuple, Columns, FilterStates>, Columns>(c => c.key);
|
||||
var col_tups = new ObservableKeyedCollection<DIYGridColumnData<FillTuple, Columns, FillsFilterStates>, Columns>(c => c.key);
|
||||
// security
|
||||
add_col(Columns.Security, DefaultColumnHeaders.Security, r => r.security.ToString(),
|
||||
DiscreteValuesFilterVM.Create<FillTuple, SecurityImmutable, SecurityCriteria>(
|
||||
@@ -86,29 +83,29 @@ namespace mbts.spark {
|
||||
add_col(Columns.FillKey, DefaultColumnHeaders.FillKey, r => r.fill_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.FillKey), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.FillKey);
|
||||
add_col(Columns.OrderKey, DefaultColumnHeaders.OrderKey, r => r.order_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.OrderKey.ToString()), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.OrderKey);
|
||||
add_col(Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, r => r.security_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.SecurityKey.ToString()), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.SecurityKey);
|
||||
/// Rows
|
||||
_rows = new OperableCollection<Row, FillTuple, string>(
|
||||
(ft, fk) => new Row(ft, model),
|
||||
ft => ft.fill.FillKey,
|
||||
new ComparerBuilder<FillTuple>(new[] {
|
||||
_get_column_comparer(Columns.FillTime),
|
||||
_get_column_comparer(Columns.FillKey)
|
||||
}).to_comparer());
|
||||
// row sort/filter managers
|
||||
_sort_manager = new DIYGridSortManager<Columns>(col_tups.WrapSelector(t => (t.col, t.key)), Columns.FillKey);
|
||||
_filters_manager = DIYGridFiltersManager.create(col_tups);
|
||||
var rows_manager = new DIYGridRowsManager<FillTuple, Columns, FilterStates>(_filters_manager, _sort_manager, _get_column_comparer, f => _rows.filter = f, c => _rows.comparer = c, _rows.reset, FillsWindow.window_type_name);
|
||||
// rows
|
||||
_rows_manager = DIYGridRowsManager.create(FillsWindow.window_type_name, Columns.FillKey, col_tups, col_tups.WrapSelector(t => (t.col, t.key)), _get_column_comparer, fs => fs.build_predicate());
|
||||
// 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());
|
||||
//
|
||||
var filter_src = Observable.FromEvent<Action<Func<FillTuple, bool>>, Func<FillTuple, bool>>(cb => _rows_manager.filter_changed += cb, cb => _rows_manager.filter_changed -= cb);
|
||||
var comp_src = Observable.FromEvent<Action<IComparer<FillTuple>>, IComparer<FillTuple>>(cb => _rows_manager.comparer_changed += cb, cb => _rows_manager.comparer_changed -= cb);
|
||||
TableUpdate<FillTuple, string> last_unfiltered_fills = TableUpdate<FillTuple, string>.mk_empty(FillTuple.key_fn);
|
||||
var fills_buffer = Observable.Create<TableUpdate<FillTuple, string>>(o => {
|
||||
// this is what .Cache() does, but is a little cheaper, since we know subscriptions and callbacks occur on the same thread (i.e., on this Dispatcher).
|
||||
o.OnNext(last_unfiltered_fills.as_snapshot());
|
||||
_unfiltered_fills_changed += o.OnNext;
|
||||
return Disposable.Create(() => _unfiltered_fills_changed -= o.OnNext);
|
||||
});
|
||||
_dispose = new CompositeDisposable(
|
||||
ObservableAutoWrapper.connect(col_tups, columns, ct => ct.col),
|
||||
RXCollection.connect(rows, fills_buffer.Filter(filter_src, FillTuple.key_fn), comp_src, ft => new Row(ft, model), r => r.fill_tuple),
|
||||
fills_source.OnDispatcher().Subscribe(t => {
|
||||
_rows.update(t);
|
||||
last_unfiltered_fills = t;
|
||||
Set(ref _total_row_count, t.state.Count, nameof(total_row_count));
|
||||
_unfiltered_fills_changed?.Invoke(t);
|
||||
}),
|
||||
state_obs.OnDispatcher().Subscribe(s => {
|
||||
if (s.grid != null && _settings_manager != null) {
|
||||
@@ -116,13 +113,13 @@ namespace mbts.spark {
|
||||
// Note: settings_manager in turn notifies grid_settings_manager of this update
|
||||
_settings_manager.instance_settings = s.grid;
|
||||
}
|
||||
var fs = s.filters ?? new FilterStates {
|
||||
var fs = s.filters ?? new FillsFilterStates {
|
||||
fill_time = new TimeFilterTuple(
|
||||
TimeFilterMode.CMEOpen,
|
||||
TimeTextTuple.FromDateTime(DateTime.Today),
|
||||
TimeTextTuple.FromDateTime(DateTime.Today.AddDays(1)))
|
||||
};
|
||||
rows_manager.on_settings(fs, s.sorting_criterion);
|
||||
_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
|
||||
@@ -135,14 +132,17 @@ namespace mbts.spark {
|
||||
Width = width
|
||||
}, filter);
|
||||
void add_col_expl<TFilter>(Columns col_key, ColumnBase col, IDataGridColumnPersistentFilter<FillTuple, TFilter> filter) =>
|
||||
col_tups.Add(col_key, DIYGridColumnData.create<FillTuple, Columns, FilterStates, TFilter>(col, filter, _get_column_comparer(col_key)));
|
||||
col_tups.Add(col_key, DIYGridColumnData.create<FillTuple, Columns, FillsFilterStates, TFilter>(col, filter, _get_column_comparer(col_key)));
|
||||
// discrete-values-filter factories
|
||||
IDataGridColumnPersistentFilter<FillTuple, T[]> dv_filter<T>(IReadOnlyCollection<T> items, Func<FillTuple, T> get_row_val) => DiscreteValuesFilterVM.Create<FillTuple, T>(items, get_row_val);
|
||||
IDataGridColumnPersistentFilter<FillTuple, T[]> dv_filter_rx<T>(IObservable<IEnumerable<T>> items, Func<FillTuple, T> get_row_val) => DiscreteValuesFilterVM.Create<FillTuple, T>(items, get_row_val);
|
||||
}
|
||||
|
||||
// this event is used to forward updates from the main data to the filtered data that drives the rows, after resetting the total row count.
|
||||
event Action<TableUpdate<FillTuple, string>> _unfiltered_fills_changed;
|
||||
|
||||
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
|
||||
public ObservableCollection<Row> rows => _rows;
|
||||
public ObservableCollection<Row> rows { get; } = new ObservableCollection<Row>();
|
||||
public int total_row_count => _total_row_count;
|
||||
|
||||
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
|
||||
@@ -154,26 +154,26 @@ namespace mbts.spark {
|
||||
return grid_settings_manager.create();
|
||||
}
|
||||
|
||||
public FillsFilterStates get_filters() =>
|
||||
_rows_manager.get_filters();
|
||||
|
||||
public IEnumerable<SortingCriteria> get_sorting_criterion() =>
|
||||
_sort_manager.create();
|
||||
_rows_manager.get_sorting_criterion();
|
||||
|
||||
public void on_sorting(object sender, DIYGridSortingEventArgs e) =>
|
||||
_sort_manager.on_sorting(e);
|
||||
_rows_manager.on_sorting(e);
|
||||
|
||||
static Columns _col_key(ColumnBase column) =>
|
||||
((IKeyedColumn<Columns>)column).key;
|
||||
|
||||
public FilterStates get_filters() =>
|
||||
_filters_manager.create();
|
||||
|
||||
static IObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, StartupData state) {
|
||||
static IObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, FillsSettings state) {
|
||||
if (!state.orders.is_null_or_empty()) {
|
||||
var orders_hash = new HashSet<uint>(state.orders);
|
||||
return model.on_fill_tuples.Filter(f => orders_hash.Contains(f.fill.OrderKey));
|
||||
} else return model.on_fill_tuples;
|
||||
}
|
||||
|
||||
static IConnectableObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, IObservable<StartupData> state) =>
|
||||
static IConnectableObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, IObservable<FillsSettings> state) =>
|
||||
state.SelectManyInSeries(s => _get_observable(bus, model, s));
|
||||
|
||||
static IComparer<FillTuple> _get_column_comparer(Columns col) {
|
||||
@@ -212,8 +212,9 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public class Row : SecurityRow, IEquatable<Row>, IRowVM {
|
||||
public readonly FillTuple fill_tuple;
|
||||
|
||||
readonly OrdersModel _model;
|
||||
readonly FillTuple _fill_tuple;
|
||||
readonly FillImmutable _fill;
|
||||
readonly OrderRecordImmutable _order;
|
||||
Brush _badge_fill;
|
||||
@@ -221,7 +222,7 @@ namespace mbts.spark {
|
||||
|
||||
public Row(FillTuple fill_tuple, OrdersModel model) :
|
||||
base(fill_tuple.security ?? SecurityDataHelper.Defaults.SecurityImmutable) {
|
||||
_fill_tuple = fill_tuple;
|
||||
this.fill_tuple = fill_tuple;
|
||||
_fill = fill_tuple.fill;
|
||||
_order = fill_tuple.order ?? OrderDataHelper.Defaults.OrderRecordImmutable;
|
||||
_model = model;
|
||||
@@ -231,9 +232,9 @@ namespace mbts.spark {
|
||||
public DateTime fill_time => _fill.Timestamp.to_local_time();
|
||||
public TimeInForce time_in_force => _order.time_in_force();
|
||||
public OrderType order_type => _order.order_type();
|
||||
public string display_name => OrderUtils.display_name(_order, _fill_tuple.algo);
|
||||
public string display_name => OrderUtils.display_name(_order, fill_tuple.algo);
|
||||
public OrderMetadata order_metadata => _order_metadata ?? (_order_metadata = _model.factory.get_metadata(_order.Def.CustomDef.CustomCase));
|
||||
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(_fill_tuple.algo_meta, order_metadata));
|
||||
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(fill_tuple.algo_meta, order_metadata));
|
||||
public string account { get; private set; }
|
||||
public string user => _order.user();
|
||||
public Venue exchange => security.Exchange;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
@@ -13,21 +14,20 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using StartupData = bts.shell.FillsWindowFactory.StartupData;
|
||||
|
||||
namespace mbts.spark {
|
||||
class FillsWindow : SparkWindow<StartupData>, ISharedWindow {
|
||||
class FillsWindow : SparkWindow<FillsSettings>, ISharedWindow {
|
||||
public const string window_type_name = "Fills";
|
||||
readonly AllowOnce _init_guard = new AllowOnce("Fills");
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
FillsVM _vm;
|
||||
FillsView _view;
|
||||
StartupData _state;
|
||||
FillsSettings _state;
|
||||
IReadOnlyList<SparkCommand> _commands;
|
||||
IDisposable _subscriptions;
|
||||
|
||||
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state, WindowSettingsManager<GridSettings> settings_manager)
|
||||
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, FillsSettings state, WindowSettingsManager<GridSettings> settings_manager)
|
||||
: base("Fills", bus, factory, SparkCommands.ShowFills().icon) {
|
||||
_orders_model = orders_model;
|
||||
_state = state;
|
||||
@@ -35,15 +35,15 @@ namespace mbts.spark {
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().fills ? "Fills" : null;
|
||||
}
|
||||
|
||||
event Action<FillsWindowFactory.StartupData> _state_changed;
|
||||
event Action<FillsSettings> _state_changed;
|
||||
|
||||
public override string window_type => window_type_name;
|
||||
public override UIElement Content => _view;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
|
||||
public override StartupData SaveState => _state = new StartupData(_state.orders, _vm.get_sorting_criterion().ToList(), _vm.get_filters(), _vm.get_grid_save_state());
|
||||
public override FillsSettings SaveState => _state = new FillsSettings(_state.orders, _vm.get_sorting_criterion().ToList(), _vm.get_filters(), _vm.get_grid_save_state());
|
||||
public override double MinWidth =>
|
||||
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
||||
FillsSettings.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
@@ -53,7 +53,7 @@ namespace mbts.spark {
|
||||
public void thread_init() {
|
||||
_init_guard.check_access();
|
||||
Log.notnull(_state);
|
||||
var state_src = Observable.FromEvent<Action<FillsWindowFactory.StartupData>, FillsWindowFactory.StartupData>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
|
||||
var state_src = Observable.FromEvent<Action<FillsSettings>, FillsSettings>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
|
||||
_vm = new FillsVM(_bus, _orders_model, state_src, _settings_manager);
|
||||
_view = new FillsView { DataContext = _vm };
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace mbts.spark {
|
||||
};
|
||||
_subscriptions = Disposable.Create(() =>
|
||||
_state_changed?.Invoke(
|
||||
_state = new StartupData(_state.orders,
|
||||
_state = new FillsSettings(_state.orders,
|
||||
_vm.get_sorting_criterion().ToList(),
|
||||
_vm.get_filters(),
|
||||
_vm.grid_settings_manager.create()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.shell.fills;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
@@ -16,7 +17,7 @@ using static mbts.ui.controls.DIYGrid;
|
||||
using static mbts.ui.spark.FillsView;
|
||||
|
||||
namespace bts.shell {
|
||||
class FillsWindowFactory : ShellWindowFactory<FillsWindowFactory.StartupData> {
|
||||
class FillsWindowFactory : ShellWindowFactory<FillsSettings> {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
@@ -52,13 +53,13 @@ namespace bts.shell {
|
||||
|
||||
public static string col_id(Columns col) => col.ToString();
|
||||
|
||||
public override Task<IShellWindow> load(StartupData state) =>
|
||||
public override Task<IShellWindow> load(FillsSettings state) =>
|
||||
Task.FromResult(_create(state));
|
||||
|
||||
public async void show(StartupData state) =>
|
||||
public async void show(FillsSettings state) =>
|
||||
_bus.post(SparkTags.ShowWindow, await load(state));
|
||||
|
||||
IShellWindow _create(StartupData state) {
|
||||
IShellWindow _create(FillsSettings state) {
|
||||
GridSettingsHelper.normalize_sorting_criterion(state.sorting_criterion, _sort_member_to_id);
|
||||
var settings_manager = new WindowSettingsManager<GridSettings>(_settings_hub,
|
||||
state.grid,
|
||||
@@ -66,162 +67,5 @@ namespace bts.shell {
|
||||
set: (us, gs) => us.with(provided_fills_gridsettings: gs));
|
||||
return new FillsWindow(this, _bus, _orders_model, state, settings_manager);
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public class StartupData {
|
||||
public const int Version = 1;
|
||||
|
||||
[ProtoMember(1)]
|
||||
public uint[] orders;
|
||||
[ProtoMember(2)]
|
||||
List<SortingCriteria> _sorting_criteria;
|
||||
[ProtoMember(3)]
|
||||
public FilterStates filters;
|
||||
[ProtoMember(4)]
|
||||
public GridSettings grid;
|
||||
|
||||
public StartupData() { }
|
||||
public StartupData(uint[] orders) : this(orders, null, null) { }
|
||||
public StartupData(FilterStates filters) : this(null, null, filters) { }
|
||||
public StartupData(uint[] orders, List<SortingCriteria> sorting_criteria, FilterStates filters, GridSettings grid = default) {
|
||||
this.orders = orders;
|
||||
this._sorting_criteria = sorting_criteria;
|
||||
this.filters = filters;
|
||||
this.grid = grid;
|
||||
}
|
||||
|
||||
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
|
||||
public static GridSettings MiniGridSettings => GridSettings.Default.with(DefaultColumns.Mini.all, DefaultColumns.Mini.sorting_criterion);
|
||||
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criteria ?? (_sorting_criteria = DefaultColumns.default_sorting_criterion.ToList());
|
||||
public static StartupData Default() =>
|
||||
new StartupData(null, null, new FilterStates(), null);
|
||||
|
||||
static class DefaultColumns {
|
||||
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings fill_time => new GridColumnSettings(col_id(Columns.FillTime), DefaultColumnHeaders.FillTime, width: DefaultColumnWidths.Time);
|
||||
public static GridColumnSettings user => new GridColumnSettings(col_id(Columns.User), DefaultColumnHeaders.User, width: DefaultColumnWidths.User);
|
||||
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 time_in_force => new GridColumnSettings(col_id(Columns.TimeInForce), DefaultColumnHeaders.TimeInForce, width: DefaultColumnWidths.TimeInForce);
|
||||
public static GridColumnSettings order_type => new GridColumnSettings(col_id(Columns.OrderType), DefaultColumnHeaders.OrderType, width: DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings side => new GridColumnSettings(col_id(Columns.Side), DefaultColumnHeaders.Side, width: DefaultColumnWidths.Side);
|
||||
public static GridColumnSettings quantity => new GridColumnSettings(col_id(Columns.Size), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings price => new GridColumnSettings(col_id(Columns.Price), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings fill_kind => new GridColumnSettings(col_id(Columns.FillKind), DefaultColumnHeaders.FillKind, width: DefaultColumnWidths.FillKind);
|
||||
public static GridColumnSettings fill_key => new GridColumnSettings(col_id(Columns.FillKey), DefaultColumnHeaders.FillKey, width: DefaultColumnWidths.FillKey);
|
||||
public static GridColumnSettings order_key => new GridColumnSettings(col_id(Columns.OrderKey), DefaultColumnHeaders.OrderKey, width: DefaultColumnWidths.OrderKey);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
||||
public static IEnumerable<SortingCriteria> default_sorting_criterion {
|
||||
get {
|
||||
yield return new SortingCriteria(fill_time.id, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all {
|
||||
get {
|
||||
yield return security;
|
||||
yield return fill_time;
|
||||
yield return user;
|
||||
yield return account;
|
||||
yield return exchange;
|
||||
yield return time_in_force;
|
||||
yield return order_type;
|
||||
yield return side;
|
||||
yield return quantity;
|
||||
yield return price;
|
||||
yield return fill_kind;
|
||||
yield return fill_key;
|
||||
yield return order_key;
|
||||
yield return security_key;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Mini {
|
||||
static readonly IReadOnlyDictionary<Columns, (bool is_visible, double? width_override)> _mini_cols_data =
|
||||
new Dictionary<Columns, (bool is_visible, double? width_override)> {
|
||||
{ Columns.Security, (true, null) },
|
||||
{ Columns.FillTime, (true, ShortColumnWidths.Time) },
|
||||
{ Columns.OrderType, (true, ShortColumnWidths.OrderType) },
|
||||
{ Columns.Side, (true, ShortColumnWidths.Side) },
|
||||
{ Columns.Size, (true, ShortColumnWidths.Size) },
|
||||
{ Columns.Price, (true, ShortColumnWidths.Price) },
|
||||
};
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all => DefaultColumns.all.Select(_convert);
|
||||
public static IEnumerable<SortingCriteria> sorting_criterion => default_sorting_criterion.Where(c => _try_get_data(c.col_name, out (bool is_visible, double? _) d) && d.is_visible);
|
||||
|
||||
static bool _try_get_data(string id_str, out (bool is_visible, double? width_override) data) {
|
||||
if (Enum.TryParse(id_str, out Columns k) && _mini_cols_data.TryGetValue(k, out data))
|
||||
return true;
|
||||
data = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
static GridColumnSettings _convert(GridColumnSettings default_col) =>
|
||||
_try_get_data(default_col.id, out (bool is_visible, double? width_override) d) ?
|
||||
default_col.with(is_visible: d.is_visible, width: d.width_override) :
|
||||
default_col.with(is_visible: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public class FilterStates {
|
||||
[ProtoMember(1)]
|
||||
[DIYGridColumnID(nameof(Columns.FillTime))]
|
||||
public TimeFilterTuple fill_time { get; set; }
|
||||
|
||||
[ProtoMember(2)]
|
||||
[DIYGridColumnID(nameof(Columns.TimeInForce))]
|
||||
public TimeInForce[] time_in_force { get; set; }
|
||||
|
||||
[ProtoMember(3)]
|
||||
[DIYGridColumnID(nameof(Columns.OrderType))]
|
||||
public CustomOrderDefinition.CustomOneofCase[] order_type { get; set; }
|
||||
|
||||
[ProtoMember(4)]
|
||||
[DIYGridColumnID(nameof(Columns.Account))]
|
||||
public string[] account { get; set; }
|
||||
|
||||
[ProtoMember(5)]
|
||||
[DIYGridColumnID(nameof(Columns.User))]
|
||||
public string[] user { get; set; }
|
||||
|
||||
[ProtoMember(6)]
|
||||
[DIYGridColumnID(nameof(Columns.Exchange))]
|
||||
public Venue[] exchange { get; set; }
|
||||
|
||||
[ProtoMember(7)]
|
||||
[DIYGridColumnID(nameof(Columns.Security))]
|
||||
public SecurityCriteria security { get; set; }
|
||||
|
||||
[ProtoMember(8)]
|
||||
[DIYGridColumnID(nameof(Columns.Side))]
|
||||
public Side[] side { get; set; }
|
||||
|
||||
[ProtoMember(9)]
|
||||
[DIYGridColumnID(nameof(Columns.Price))]
|
||||
public DoubleMinMaxState price { get; set; }
|
||||
|
||||
[ProtoMember(10)]
|
||||
[DIYGridColumnID(nameof(Columns.Size))]
|
||||
public DoubleMinMaxState size { get; set; }
|
||||
|
||||
[ProtoMember(11)]
|
||||
[DIYGridColumnID(nameof(Columns.FillKey))]
|
||||
public string fill_key { get; set; }
|
||||
|
||||
[ProtoMember(12)]
|
||||
[DIYGridColumnID(nameof(Columns.OrderKey))]
|
||||
public string order_key { get; set; }
|
||||
|
||||
[ProtoMember(13)]
|
||||
[DIYGridColumnID(nameof(Columns.SecurityKey))]
|
||||
public string security_key { get; set; }
|
||||
|
||||
[ProtoMember(14)]
|
||||
[DIYGridColumnID(nameof(Columns.FillKind))]
|
||||
public Fill.Types.FillKind[] fill_kind { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using Columns = mbts.ui.spark.FillsView.Columns;
|
||||
|
||||
namespace bts.shell.fills {
|
||||
[ProtoContract]
|
||||
class FillsFilterStates {
|
||||
[ProtoMember(1)]
|
||||
[DIYGridColumnID(nameof(Columns.FillTime))]
|
||||
public TimeFilterTuple fill_time { get; set; }
|
||||
|
||||
[ProtoMember(2)]
|
||||
[DIYGridColumnID(nameof(Columns.TimeInForce))]
|
||||
public TimeInForce[] time_in_force { get; set; }
|
||||
|
||||
[ProtoMember(3)]
|
||||
[DIYGridColumnID(nameof(Columns.OrderType))]
|
||||
public CustomOrderDefinition.CustomOneofCase[] order_type { get; set; }
|
||||
|
||||
[ProtoMember(4)]
|
||||
[DIYGridColumnID(nameof(Columns.Account))]
|
||||
public string[] account { get; set; }
|
||||
|
||||
[ProtoMember(5)]
|
||||
[DIYGridColumnID(nameof(Columns.User))]
|
||||
public string[] user { get; set; }
|
||||
|
||||
[ProtoMember(6)]
|
||||
[DIYGridColumnID(nameof(Columns.Exchange))]
|
||||
public Venue[] exchange { get; set; }
|
||||
|
||||
[ProtoMember(7)]
|
||||
[DIYGridColumnID(nameof(Columns.Security))]
|
||||
public SecurityCriteria security { get; set; }
|
||||
|
||||
[ProtoMember(8)]
|
||||
[DIYGridColumnID(nameof(Columns.Side))]
|
||||
public Side[] side { get; set; }
|
||||
|
||||
[ProtoMember(9)]
|
||||
[DIYGridColumnID(nameof(Columns.Price))]
|
||||
public DoubleMinMaxState price { get; set; }
|
||||
|
||||
[ProtoMember(10)]
|
||||
[DIYGridColumnID(nameof(Columns.Size))]
|
||||
public DoubleMinMaxState size { get; set; }
|
||||
|
||||
[ProtoMember(11)]
|
||||
[DIYGridColumnID(nameof(Columns.FillKey))]
|
||||
public string fill_key { get; set; }
|
||||
|
||||
[ProtoMember(12)]
|
||||
[DIYGridColumnID(nameof(Columns.OrderKey))]
|
||||
public string order_key { get; set; }
|
||||
|
||||
[ProtoMember(13)]
|
||||
[DIYGridColumnID(nameof(Columns.SecurityKey))]
|
||||
public string security_key { get; set; }
|
||||
|
||||
[ProtoMember(14)]
|
||||
[DIYGridColumnID(nameof(Columns.FillKind))]
|
||||
public Fill.Types.FillKind[] fill_kind { get; set; }
|
||||
}
|
||||
|
||||
static class FillsFilterStatesExtensions {
|
||||
public static Func<FillTuple, bool> build_predicate(this FillsFilterStates fs) {
|
||||
var pb = new ColumnPredicateBuilder<FillTuple>();
|
||||
// fill time
|
||||
pb.add_time_filter(fs.fill_time, ft => ft.fill_time());
|
||||
// security
|
||||
pb.add_security_criteria(fs.security, ft => ft.security);
|
||||
// regex
|
||||
pb.add_regex(fs.fill_key, ft => ft.fill.FillKey);
|
||||
pb.add_regex(fs.order_key, ft => ft.order_key().ToString());
|
||||
// discrete values
|
||||
pb.add_discrete_values(fs.user, ft => ft.user());
|
||||
pb.add_discrete_values(fs.account, ft => ft.account);
|
||||
pb.add_discrete_values(fs.exchange, ft => ft.exchange());
|
||||
pb.add_discrete_values(fs.time_in_force, ft => ft.time_in_force());
|
||||
pb.add_discrete_values(fs.order_type, ft => ft.order_type());
|
||||
pb.add_discrete_values(fs.side, ft => ft.fill.Side);
|
||||
pb.add_discrete_values(fs.fill_kind, ft => ft.fill.FillKind);
|
||||
// numeric
|
||||
pb.add_numeric(fs.size, ft => ft.fill.LastShares);
|
||||
pb.add_numeric(fs.price, ft => ft.fill.LastPrice);
|
||||
return pb.to_predicate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static bts.shell.FillsWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using Columns = mbts.ui.spark.FillsView.Columns;
|
||||
|
||||
namespace bts.shell.fills {
|
||||
[ProtoContract]
|
||||
class FillsSettings {
|
||||
public const int Version = 1;
|
||||
|
||||
[ProtoMember(1)]
|
||||
public uint[] orders;
|
||||
[ProtoMember(2)]
|
||||
List<SortingCriteria> _sorting_criteria;
|
||||
[ProtoMember(3)]
|
||||
public FillsFilterStates filters;
|
||||
[ProtoMember(4)]
|
||||
public GridSettings grid;
|
||||
|
||||
public FillsSettings() { }
|
||||
public FillsSettings(uint[] orders) : this(orders, null, null) { }
|
||||
public FillsSettings(FillsFilterStates filters) : this(null, null, filters) { }
|
||||
public FillsSettings(uint[] orders, List<SortingCriteria> sorting_criteria, FillsFilterStates filters, GridSettings grid = default) {
|
||||
this.orders = orders;
|
||||
this._sorting_criteria = sorting_criteria;
|
||||
this.filters = filters;
|
||||
this.grid = grid;
|
||||
}
|
||||
|
||||
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
|
||||
public static GridSettings MiniGridSettings => GridSettings.Default.with(DefaultColumns.Mini.all, DefaultColumns.Mini.sorting_criterion);
|
||||
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criteria ?? (_sorting_criteria = DefaultColumns.default_sorting_criterion.ToList());
|
||||
public static FillsSettings Default() =>
|
||||
new FillsSettings(null, null, new FillsFilterStates(), null);
|
||||
|
||||
static class DefaultColumns {
|
||||
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings fill_time => new GridColumnSettings(col_id(Columns.FillTime), DefaultColumnHeaders.FillTime, width: DefaultColumnWidths.Time);
|
||||
public static GridColumnSettings user => new GridColumnSettings(col_id(Columns.User), DefaultColumnHeaders.User, width: DefaultColumnWidths.User);
|
||||
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 time_in_force => new GridColumnSettings(col_id(Columns.TimeInForce), DefaultColumnHeaders.TimeInForce, width: DefaultColumnWidths.TimeInForce);
|
||||
public static GridColumnSettings order_type => new GridColumnSettings(col_id(Columns.OrderType), DefaultColumnHeaders.OrderType, width: DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings side => new GridColumnSettings(col_id(Columns.Side), DefaultColumnHeaders.Side, width: DefaultColumnWidths.Side);
|
||||
public static GridColumnSettings quantity => new GridColumnSettings(col_id(Columns.Size), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings price => new GridColumnSettings(col_id(Columns.Price), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings fill_kind => new GridColumnSettings(col_id(Columns.FillKind), DefaultColumnHeaders.FillKind, width: DefaultColumnWidths.FillKind);
|
||||
public static GridColumnSettings fill_key => new GridColumnSettings(col_id(Columns.FillKey), DefaultColumnHeaders.FillKey, width: DefaultColumnWidths.FillKey);
|
||||
public static GridColumnSettings order_key => new GridColumnSettings(col_id(Columns.OrderKey), DefaultColumnHeaders.OrderKey, width: DefaultColumnWidths.OrderKey);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
||||
public static IEnumerable<SortingCriteria> default_sorting_criterion {
|
||||
get {
|
||||
yield return new SortingCriteria(fill_time.id, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all {
|
||||
get {
|
||||
yield return security;
|
||||
yield return fill_time;
|
||||
yield return user;
|
||||
yield return account;
|
||||
yield return exchange;
|
||||
yield return time_in_force;
|
||||
yield return order_type;
|
||||
yield return side;
|
||||
yield return quantity;
|
||||
yield return price;
|
||||
yield return fill_kind;
|
||||
yield return fill_key;
|
||||
yield return order_key;
|
||||
yield return security_key;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Mini {
|
||||
static readonly IReadOnlyDictionary<Columns, (bool is_visible, double? width_override)> _mini_cols_data =
|
||||
new Dictionary<Columns, (bool is_visible, double? width_override)> {
|
||||
{ Columns.Security, (true, null) },
|
||||
{ Columns.FillTime, (true, ShortColumnWidths.Time) },
|
||||
{ Columns.OrderType, (true, ShortColumnWidths.OrderType) },
|
||||
{ Columns.Side, (true, ShortColumnWidths.Side) },
|
||||
{ Columns.Size, (true, ShortColumnWidths.Size) },
|
||||
{ Columns.Price, (true, ShortColumnWidths.Price) },
|
||||
};
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all => DefaultColumns.all.Select(_convert);
|
||||
public static IEnumerable<SortingCriteria> sorting_criterion => default_sorting_criterion.Where(c => _try_get_data(c.col_name, out (bool is_visible, double? _) d) && d.is_visible);
|
||||
|
||||
static bool _try_get_data(string id_str, out (bool is_visible, double? width_override) data) {
|
||||
if (Enum.TryParse(id_str, out Columns k) && _mini_cols_data.TryGetValue(k, out data))
|
||||
return true;
|
||||
data = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
static GridColumnSettings _convert(GridColumnSettings default_col) =>
|
||||
_try_get_data(default_col.id, out (bool is_visible, double? width_override) d) ?
|
||||
default_col.with(is_visible: d.is_visible, width: d.width_override) :
|
||||
default_col.with(is_visible: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,7 +119,8 @@ namespace mbts.spark {
|
||||
buy = new HistogramBarAnimation { bars = new ColorShowProto((uint)BTSColors.MarketData.bid, false) },
|
||||
sell = new HistogramBarAnimation { bars = new ColorShowProto((uint)BTSColors.MarketData.ask, false) },
|
||||
passive = new HistogramBarAnimation { bars = new ColorShowProto((uint)BTSColors.LadderSpecial.passive_volume_bar, false) },
|
||||
total = new HistogramBarAnimation { bars = new ColorShowProto((uint)BTSColors.LadderSpecial.total_volume_bar), show_numbers = true }
|
||||
total = new HistogramBarAnimation { bars = new ColorShowProto((uint)BTSColors.LadderSpecial.total_volume_bar), show_numbers = true },
|
||||
show_ltq = false
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -134,6 +135,8 @@ namespace mbts.spark {
|
||||
public HistogramBarAnimation passive { get; set; }
|
||||
[ProtoMember(5)]
|
||||
public HistogramBarAnimation total{ get; set; }
|
||||
[ProtoMember(6)]
|
||||
public bool show_ltq { get; set; }
|
||||
|
||||
public bool Equals(VolumeColumnSettings other) =>
|
||||
other != null &&
|
||||
@@ -141,7 +144,8 @@ namespace mbts.spark {
|
||||
buy.safe_equals(other.buy) &&
|
||||
sell.safe_equals(other.sell) &&
|
||||
passive.safe_equals(other.passive) &&
|
||||
total.safe_equals(other.total);
|
||||
total.safe_equals(other.total) &&
|
||||
show_ltq.Equals(other.show_ltq);
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as VolumeColumnSettings);
|
||||
|
||||
@@ -151,7 +155,8 @@ namespace mbts.spark {
|
||||
buy.GetHashCode(),
|
||||
sell.GetHashCode(),
|
||||
passive.GetHashCode(),
|
||||
total.GetHashCode());
|
||||
total.GetHashCode(),
|
||||
show_ltq.GetHashCode());
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
@@ -227,6 +232,7 @@ namespace mbts.spark {
|
||||
// Default proto constructor
|
||||
// Added this so that when clients upgrade to a version with new settings,
|
||||
// the default values are applied correctly; thx to John for this tip
|
||||
/// TODO: all default values should be consts, because they must be referenced in two places (in the default constructor, and the <see cref="DefaultValueAttribute"/>)
|
||||
public LadderSettings() {
|
||||
day_visible = true;
|
||||
high_visible = true;
|
||||
@@ -238,6 +244,7 @@ namespace mbts.spark {
|
||||
tif_visible = true;
|
||||
order_type_visible = true;
|
||||
account_position_visible = true;
|
||||
day_position_visible = true;
|
||||
size_presets_visible = true;
|
||||
cancel_buys_visible = true;
|
||||
cancel_sells_visible = true;
|
||||
@@ -301,6 +308,7 @@ namespace mbts.spark {
|
||||
tif_visible = true,
|
||||
order_type_visible = true,
|
||||
account_position_visible = true,
|
||||
day_position_visible = true,
|
||||
size_presets_visible = true,
|
||||
cancel_buys_visible = true,
|
||||
cancel_sells_visible = true,
|
||||
@@ -397,6 +405,8 @@ namespace mbts.spark {
|
||||
public bool order_type_visible { get; set; }
|
||||
[ProtoMember(36), DefaultValue(true)]
|
||||
public bool account_position_visible { get; set; }
|
||||
[ProtoMember(50), DefaultValue(true)]
|
||||
public bool day_position_visible { get; set; }
|
||||
[ProtoMember(37), DefaultValue(true)]
|
||||
public bool size_presets_visible { get; set; }
|
||||
[ProtoMember(38), DefaultValue(true)]
|
||||
@@ -473,6 +483,7 @@ namespace mbts.spark {
|
||||
tif_visible == other.tif_visible &&
|
||||
order_type_visible == other.order_type_visible &&
|
||||
account_position_visible == other.account_position_visible &&
|
||||
day_position_visible == other.day_position_visible &&
|
||||
size_presets_visible == other.size_presets_visible &&
|
||||
cancel_buys_visible == other.cancel_buys_visible &&
|
||||
cancel_sells_visible == other.cancel_sells_visible &&
|
||||
@@ -488,6 +499,7 @@ namespace mbts.spark {
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as LadderSettings);
|
||||
|
||||
// TODO: remove mutable props from hash logic (or conversely, make them immutable).
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
row_height.GetHashCode(),
|
||||
|
||||
@@ -52,6 +52,7 @@ namespace mbts.spark {
|
||||
_tif_visibility = new OrderEntryPanelItemVisibilityVM("Time In Force"),
|
||||
_order_type_visibility = new OrderEntryPanelItemVisibilityVM("Order Type"),
|
||||
_account_position_visibility = new OrderEntryPanelItemVisibilityVM("Account Position"),
|
||||
_day_position_visibility = new OrderEntryPanelItemVisibilityVM("Day Position"),
|
||||
_size_preset_visibility = new OrderEntryPanelItemVisibilityVM("Size Presets"),
|
||||
_auto_clear_visibility = new OrderEntryPanelItemVisibilityVM("Auto Clear"),
|
||||
_clear_visibility = new OrderEntryPanelItemVisibilityVM("Clear"),
|
||||
@@ -164,6 +165,7 @@ namespace mbts.spark {
|
||||
yield return _tif_visibility;
|
||||
yield return _order_type_visibility;
|
||||
yield return _account_position_visibility;
|
||||
yield return _day_position_visibility;
|
||||
yield return _size_preset_visibility;
|
||||
yield return _clear_visibility;
|
||||
yield return _auto_clear_visibility;
|
||||
@@ -232,6 +234,7 @@ namespace mbts.spark {
|
||||
_tif_visibility.show = settings.tif_visible;
|
||||
_order_type_visibility.show = settings.order_type_visible;
|
||||
_account_position_visibility.show = settings.account_position_visible;
|
||||
_day_position_visibility.show = settings.day_position_visible;
|
||||
_size_preset_visibility.show = settings.size_presets_visible;
|
||||
_clear_visibility.show = settings.clear_visible;
|
||||
_auto_clear_visibility.show = settings.auto_clear_visible;
|
||||
@@ -278,6 +281,7 @@ namespace mbts.spark {
|
||||
clone.tif_visible = _tif_visibility.show;
|
||||
clone.order_type_visible = _order_type_visibility.show;
|
||||
clone.account_position_visible = _account_position_visibility.show;
|
||||
clone.day_position_visible = _day_position_visibility.show;
|
||||
clone.size_presets_visible = _size_preset_visibility.show;
|
||||
clone.auto_clear_visible = _auto_clear_visibility.show;
|
||||
clone.clear_visible = _clear_visibility.show;
|
||||
@@ -449,6 +453,7 @@ namespace mbts.spark {
|
||||
passive = new VolumeAtPriceAnimationVM("Passive", new VolumeAtPriceSampleData(this, 72, 127, 198));
|
||||
total = new VolumeAtPriceAnimationVM("Total", new VolumeAtPriceSampleData(this, 81, 100, 144));
|
||||
_any_visible = backing(nameof(any_visible), () => volume_components.Any(vc => vc.show_numbers || vc.bars.enabled));
|
||||
_show_last_trade = backing(nameof(show_last_trade), false);
|
||||
foreach (var vc in volume_components) {
|
||||
vc.show_numbers_changed += _any_visible.refresh;
|
||||
vc.bars.enabled_changed += _any_visible.refresh;
|
||||
@@ -463,6 +468,9 @@ namespace mbts.spark {
|
||||
public VolumeAtPriceAnimationVM sell { get; }
|
||||
public VolumeAtPriceAnimationVM passive { get; }
|
||||
public VolumeAtPriceAnimationVM total { get; }
|
||||
|
||||
readonly Property<bool> _show_last_trade;
|
||||
public bool show_last_trade { get => _show_last_trade.Value; set => _show_last_trade.Value = value; }
|
||||
public bool any_visible => volume_components.Any(vc => vc.show_numbers || vc.bars.enabled);
|
||||
|
||||
public IEnumerable<VolumeAtPriceAnimationVM> volume_components {
|
||||
@@ -481,6 +489,7 @@ namespace mbts.spark {
|
||||
sell.assign_from(settings.sell);
|
||||
passive.assign_from(settings.passive);
|
||||
total.assign_from(settings.total);
|
||||
show_last_trade = settings.show_ltq;
|
||||
}
|
||||
|
||||
public VolumeColumnSettings get_settings() =>
|
||||
@@ -490,6 +499,7 @@ namespace mbts.spark {
|
||||
sell = sell.get_settings(),
|
||||
passive = passive.get_settings(),
|
||||
total = total.get_settings(),
|
||||
show_ltq = show_last_trade
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -25,14 +25,17 @@ namespace mbts.spark {
|
||||
|
||||
readonly IBus _bus;
|
||||
readonly LadderModel _model;
|
||||
readonly Property<double> _high, _low, _change, _open, _settlement, _position, _left_panel_width, _zoom_factor;
|
||||
readonly Property<double> _high, _low, _change, _open, _settlement, _left_panel_width, _zoom_factor;
|
||||
//readonly Property<double> _window_width, _window_height;
|
||||
readonly Property<uint> _orders_column_width, _bid_column_width, _price_column_width, _ask_column_width, _last_column_width, _volume_column_width, _piq_column_width;
|
||||
readonly Property<ulong> _volume;
|
||||
readonly Property<bool> _auto_clear, _show_day, _show_high, _show_low, _show_open, _show_settle, _show_volume, _show_account,
|
||||
_show_tif, _show_order_type, _show_account_position, _show_size_preset, _show_auto_clear, _show_clear, _show_cancel_buys,
|
||||
readonly Property<bool> _auto_clear;
|
||||
readonly PropertyAutoWrapper<LadderSettings, bool> _show_day, _show_high, _show_low, _show_open, _show_settle, _show_volume, _show_account,
|
||||
_show_tif, _show_order_type, _show_size_preset, _show_auto_clear, _show_clear, _show_cancel_buys,
|
||||
_show_cancel_sells, _show_cancel_all, _show_zoom;
|
||||
readonly Property<Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
|
||||
readonly PropertyAutoWrapper<LadderSettings, Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
|
||||
double _position, _day_position;
|
||||
bool _show_account_position, _show_day_position;
|
||||
IWindowSettingsManager<LadderSettings> _settings_manager;
|
||||
ulong _misc_time = 0;
|
||||
ulong _last_trade_time = 0;
|
||||
@@ -77,45 +80,50 @@ namespace mbts.spark {
|
||||
_left_panel_width = backing(nameof(left_panel_width), ws.order_entry_panel_width);
|
||||
_zoom_factor = backing(nameof(zoom_factor), ws.zoom_factor);
|
||||
// remember: account_positions needs to subscribe to selected_account; both the sub and account_positions need to be disposed
|
||||
var account_positions = new KeyDrivenObservableSeries<string, double>(a => _model.order_gateway.security_total_position(security.Id, a));
|
||||
_position = backing(nameof(position), account_positions.OnDispatcher());
|
||||
_volume = backing(nameof(volume), 0ul);
|
||||
_auto_clear = backing(nameof(auto_clear), false);
|
||||
_status = backing(nameof(status_message), "");
|
||||
_status_brush = backing(nameof(status_background), _transparent_status_brush);
|
||||
// brush props
|
||||
_bid_background = brush_prop(nameof(bid_background), s => s.bid_column.background.rgb_brush());
|
||||
_bid_foreground = brush_prop(nameof(bid_foreground), s => s.bid_column.foreground.rgb_brush());
|
||||
_ask_background = brush_prop(nameof(ask_background), s => s.ask_column.background.rgb_brush());
|
||||
_ask_foreground = brush_prop(nameof(ask_foreground), s => s.ask_column.foreground.rgb_brush());
|
||||
Property<Brush> brush_prop(string name, Func<LadderSettings, Brush> get_brush) =>
|
||||
backing(name, settings_manager.subscribe_current().OnDispatcher(), get_brush);
|
||||
// bool visibility props
|
||||
_show_day = bool_prop(nameof(show_day), s => s.day_visible);
|
||||
_show_high = bool_prop(nameof(show_high), s => s.high_visible);
|
||||
_show_low = bool_prop(nameof(show_low), s => s.low_visible);
|
||||
_show_open = bool_prop(nameof(show_open), s => s.open_visible);
|
||||
_show_settle = bool_prop(nameof(show_settle), s => s.settle_visible);
|
||||
_show_volume = bool_prop(nameof(show_volume), s => s.volume_visible);
|
||||
_show_account = bool_prop(nameof(show_account), s => s.account_visible);
|
||||
_show_tif = bool_prop(nameof(show_tif), s => s.tif_visible);
|
||||
_show_order_type = bool_prop(nameof(show_order_type), s => s.order_type_visible);
|
||||
_show_account_position = bool_prop(nameof(show_account_position), s => s.account_position_visible);
|
||||
_show_size_preset = bool_prop(nameof(show_size_preset), s => s.size_presets_visible);
|
||||
_show_auto_clear = bool_prop(nameof(show_auto_clear), s => s.auto_clear_visible);
|
||||
_show_clear = bool_prop(nameof(show_clear), s => s.clear_visible);
|
||||
_show_cancel_buys = bool_prop(nameof(show_cancel_buys), s => s.cancel_buys_visible);
|
||||
_show_cancel_sells = bool_prop(nameof(show_cancel_sells), s => s.cancel_sells_visible);
|
||||
_show_cancel_all = bool_prop(nameof(show_cancel_all), s => s.cancel_all_visible);
|
||||
_show_zoom = bool_prop(nameof(show_zoom), s => s.zoom_slider_visible);
|
||||
|
||||
Property<bool> bool_prop(string name, Func<LadderSettings, bool> get_bool) =>
|
||||
backing(name, settings_manager.subscribe_current().OnDispatcher(), get_bool);
|
||||
// All settings observers should be aggregated together, so they can all share the same subscription to the settings
|
||||
// (these subscriptions are not horribly expensive, but they're not free either - even more so with the .OnDispatcher() extension).
|
||||
var settings_observer = new CompositeObserver<LadderSettings> {
|
||||
// brush props
|
||||
(_bid_background = brush_prop(nameof(bid_background), s => s.bid_column.background)).set,
|
||||
(_bid_foreground = brush_prop(nameof(bid_foreground), s => s.bid_column.foreground)).set,
|
||||
(_ask_background = brush_prop(nameof(ask_background), s => s.ask_column.background)).set,
|
||||
(_ask_foreground = brush_prop(nameof(ask_foreground), s => s.ask_column.foreground)).set,
|
||||
// bool visibility props
|
||||
(_show_day = bool_prop(nameof(show_day), s => s.day_visible)).set,
|
||||
(_show_high = bool_prop(nameof(show_high), s => s.high_visible)).set,
|
||||
(_show_low = bool_prop(nameof(show_low), s => s.low_visible)).set,
|
||||
(_show_open = bool_prop(nameof(show_open), s => s.open_visible)).set,
|
||||
(_show_settle = bool_prop(nameof(show_settle), s => s.settle_visible)).set,
|
||||
(_show_volume = bool_prop(nameof(show_volume), s => s.volume_visible)).set,
|
||||
(_show_account = bool_prop(nameof(show_account), s => s.account_visible)).set,
|
||||
(_show_tif = bool_prop(nameof(show_tif), s => s.tif_visible)).set,
|
||||
(_show_order_type = bool_prop(nameof(show_order_type), s => s.order_type_visible)).set,
|
||||
(_show_size_preset = bool_prop(nameof(show_size_preset), s => s.size_presets_visible)).set,
|
||||
(_show_auto_clear = bool_prop(nameof(show_auto_clear), s => s.auto_clear_visible)).set,
|
||||
(_show_clear = bool_prop(nameof(show_clear), s => s.clear_visible)).set,
|
||||
(_show_cancel_buys = bool_prop(nameof(show_cancel_buys), s => s.cancel_buys_visible)).set,
|
||||
(_show_cancel_sells = bool_prop(nameof(show_cancel_sells), s => s.cancel_sells_visible)).set,
|
||||
(_show_cancel_all = bool_prop(nameof(show_cancel_all), s => s.cancel_all_visible)).set,
|
||||
(_show_zoom = bool_prop(nameof(show_zoom), s => s.zoom_slider_visible)).set,
|
||||
s => {
|
||||
if (Set(ref _show_account_position, s.account_position_visible, nameof(show_account_position)) | Set(ref _show_day_position, s.day_position_visible, nameof(show_day_position))) {
|
||||
RaisePropertyChanged(nameof(position_display_text));
|
||||
}
|
||||
}
|
||||
};
|
||||
PropertyAutoWrapper<LadderSettings, bool> bool_prop(string name, Func<LadderSettings, bool> get_bool) =>
|
||||
backing(name, init, get_bool, EqualityComparer.Create(get_bool));
|
||||
|
||||
PropertyAutoWrapper<LadderSettings, Brush> brush_prop(string name, Func<LadderSettings, uint> get_color_uint) =>
|
||||
backing(name, init, s => get_color_uint(s).rgb_brush(), EqualityComparer.Create(get_color_uint));
|
||||
//
|
||||
//_window_height = backing(nameof(window_height), settings_manager.subscribe_current().OnDispatcher().Select(s => s.zoom_factor), (scale) => scale * 600);
|
||||
//_window_width = backing(nameof(window_width), settings_manager.subscribe_current().OnDispatcher().Select(s => s.zoom_factor), (scale) => scale * 420);
|
||||
position_click_command = new DelegateCommand(() => size_calculator.value = DoubleHelper.safe_convert_to_uint(Math.Abs(position)));
|
||||
position_click_command = new DelegateCommand(() => size_calculator.value = DoubleHelper.safe_convert_to_uint(Math.Abs(_show_day_position ? day_position : position)));
|
||||
send_order_command = new DelegateCommand<(double price, Side? side, MouseButton button)>(send_order);
|
||||
cancel_command = new AsyncCommand<LadderModel.CancelType>(_model.cancel_orders, true);
|
||||
show_book_command = new DelegateCommand(_show_book);
|
||||
@@ -139,10 +147,10 @@ namespace mbts.spark {
|
||||
orders = new ObservableCollection<LadderOrderVM>();
|
||||
|
||||
_dispose = Disposable.Create(
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(settings_observer),
|
||||
settings_command,
|
||||
non_shared_wsm,
|
||||
get_order_properties_editor_hooks(),
|
||||
account_positions,
|
||||
size_calculator = new CalculatorLogic(0, _settings_manager.subscribe_current().Select(s => s.calculator_buttons)),
|
||||
bus.subscribe(SparkTags.LogActivity).OnDispatcher().Subscribe(status_update),
|
||||
// we need to hook this up to model.orders which is filtered for just the security
|
||||
@@ -215,13 +223,29 @@ namespace mbts.spark {
|
||||
WorkLoop.Start($"Ladder '{security}'", _tick),
|
||||
Disposable.Create(() => _on_disposed_impl?.Invoke(true))
|
||||
);
|
||||
IDisposable get_order_properties_editor_hooks() =>
|
||||
order_properties_vm == null ?
|
||||
Disposable.empty :
|
||||
Disposable.Create(
|
||||
settings.OnDispatcher().Subscribe(s => order_properties_vm.on_favorite_algos(s.favorite_algos)),
|
||||
order_properties_vm,
|
||||
order_properties_vm.observe_account(true).Select(a => a?.name ?? string.Empty).Subscribe(account_positions));
|
||||
IDisposable get_order_properties_editor_hooks() {
|
||||
if (order_properties_vm == null)
|
||||
return Disposable.empty;
|
||||
var position_src = order_properties_vm.observe_account(true)
|
||||
.Select(a => a?.name ?? string.Empty)
|
||||
.SelectManyInSeries(a => _model.order_gateway.on_security_positions(security.Id)
|
||||
.Select(table => table.state.Values.FirstOrDefault(pos => pos.Item2.Account == a, tup => tup.Item2, (bts.api.position.PositionUnitImmutable?)null)));
|
||||
return Disposable.Create(
|
||||
settings.OnDispatcher().Subscribe(s => order_properties_vm.on_favorite_algos(s.favorite_algos)),
|
||||
order_properties_vm,
|
||||
position_src.OnDispatcher().Subscribe(pos => {
|
||||
double total, day;
|
||||
if (pos.HasValue) {
|
||||
total = pos.Value.total_pos();
|
||||
day = pos.Value.day_pos();
|
||||
} else {
|
||||
total = day = double.NaN;
|
||||
}
|
||||
if ((Set(ref _position, total, nameof(position)) | Set(ref _day_position, day, nameof(day_position))) && (show_day_position || show_account_position))
|
||||
RaisePropertyChanged(nameof(position_display_text));
|
||||
}),
|
||||
position_src.Connect());
|
||||
}
|
||||
}
|
||||
|
||||
public IBus bus => _bus;
|
||||
@@ -266,7 +290,8 @@ namespace mbts.spark {
|
||||
public double change => _change.Value;
|
||||
public double open => _open.Value;
|
||||
public double settlement => _settlement.Value;
|
||||
public double position => _position.Value;
|
||||
public double position => _position;
|
||||
public double day_position => _day_position;
|
||||
public ulong volume => _volume.Value;
|
||||
public bool auto_clear { get => _auto_clear.Value; set => _auto_clear.set(value); }
|
||||
public CalculatorLogic size_calculator { get; }
|
||||
@@ -277,30 +302,41 @@ namespace mbts.spark {
|
||||
public ICommand send_order_command { get; }
|
||||
public ICommand show_book_command { get; }
|
||||
public ICommand show_ticket_command { get; }
|
||||
public Brush bid_background => _bid_background.Value;
|
||||
public Brush bid_foreground => _bid_foreground.Value;
|
||||
public Brush ask_background => _ask_background.Value;
|
||||
public Brush ask_foreground => _ask_foreground.Value;
|
||||
public Brush bid_background => _bid_background.view;
|
||||
public Brush bid_foreground => _bid_foreground.view;
|
||||
public Brush ask_background => _ask_background.view;
|
||||
public Brush ask_foreground => _ask_foreground.view;
|
||||
public DialogCommand settings_command { get; }
|
||||
public bool show_day { get => _show_day.Value; set => _show_day.set(value); }
|
||||
public bool show_high { get => _show_high.Value; set => _show_high.set(value); }
|
||||
public bool show_low { get => _show_low.Value; set => _show_low.set(value); }
|
||||
public bool show_open { get => _show_open.Value; set => _show_open.set(value); }
|
||||
public bool show_settle { get => _show_settle.Value; set => _show_settle.set(value); }
|
||||
public bool show_volume { get => _show_volume.Value; set => _show_volume.set(value); }
|
||||
public bool show_account { get => _show_account.Value; set => _show_account.set(value); }
|
||||
public bool show_tif { get => _show_tif.Value; set => _show_tif.set(value); }
|
||||
public bool show_order_type { get => _show_order_type.Value; set => _show_order_type.set(value); }
|
||||
public bool show_account_position { get => _show_account_position.Value; set => _show_account_position.set(value); }
|
||||
public bool show_size_preset { get => _show_size_preset.Value; set => _show_size_preset.set(value); }
|
||||
public bool show_auto_clear { get => _show_auto_clear.Value; set => _show_auto_clear.set(value); }
|
||||
public bool show_clear { get => _show_clear.Value; set => _show_clear.set(value); }
|
||||
public bool show_cancel_buys { get => _show_cancel_buys.Value; set => _show_cancel_buys.set(value); }
|
||||
public bool show_cancel_sells { get => _show_cancel_sells.Value; set => _show_cancel_sells.set(value); }
|
||||
public bool show_cancel_all { get => _show_cancel_all.Value; set => _show_cancel_all.set(value); }
|
||||
public bool show_zoom { get => _show_zoom.Value; set => _show_zoom.set(value); }
|
||||
public bool show_day => _show_day.view;
|
||||
public bool show_high => _show_high.view;
|
||||
public bool show_low => _show_low.view;
|
||||
public bool show_open => _show_open.view;
|
||||
public bool show_settle => _show_settle.view;
|
||||
public bool show_volume => _show_volume.view;
|
||||
public bool show_account => _show_account.view;
|
||||
public bool show_tif => _show_tif.view;
|
||||
public bool show_order_type => _show_order_type.view;
|
||||
public bool show_account_position => _show_account_position;
|
||||
public bool show_day_position => _show_day_position;
|
||||
public bool show_size_preset => _show_size_preset.view;
|
||||
public bool show_auto_clear => _show_auto_clear.view;
|
||||
public bool show_clear => _show_clear.view;
|
||||
public bool show_cancel_buys => _show_cancel_buys.view;
|
||||
public bool show_cancel_sells => _show_cancel_sells.view;
|
||||
public bool show_cancel_all => _show_cancel_all.view;
|
||||
public bool show_zoom => _show_zoom.view;
|
||||
public double left_panel_width { get => _left_panel_width.Value; set => _left_panel_width.set(value); }
|
||||
public double zoom_factor { get => _zoom_factor.Value; set => _zoom_factor.set(value); }
|
||||
public string position_display_text {
|
||||
get {
|
||||
if (show_day_position) {
|
||||
string day_pos_text = SparkUtils.fmt_position(day_position);
|
||||
return show_account_position ? $"{day_pos_text} ({SparkUtils.fmt_position(position)})" : day_pos_text;
|
||||
} else if (show_account_position) {
|
||||
return SparkUtils.fmt_position(position);
|
||||
} else return string.Empty;
|
||||
}
|
||||
}
|
||||
//public double window_height { get => _window_height.Value; set => _window_height.set(value); }
|
||||
//public double window_width { get => _window_width.Value; set => _window_width.set(value); }
|
||||
public uint orders_column_width { get => _orders_column_width.Value; set => _orders_column_width.set(value); }
|
||||
@@ -348,36 +384,41 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public async void send_order((double price, Side? side, MouseButton buton) args) {
|
||||
public void send_order((double price, Side? side, MouseButton buton) args) {
|
||||
if (args.buton == MouseButton.Middle) return;
|
||||
var size_from_alternate = args.buton == MouseButton.Right;
|
||||
if (order_properties_vm == null)
|
||||
return;
|
||||
var s = _settings_manager.current_settings();
|
||||
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && s.alternate_order_size == 0))
|
||||
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && s.alternate_order_size == 0)) {
|
||||
return;
|
||||
else if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
|
||||
size_calculator.flash_error();
|
||||
} else {
|
||||
var order = order_properties_vm.create(args.side.Value);
|
||||
if (order.CustomDef != null) {
|
||||
if (order.CustomDef.Stop != null) {
|
||||
order.CustomDef.Stop.StopPrice = args.price;
|
||||
} else if (order.CustomDef.StopLimit != null) {
|
||||
// swap the prices here so that the level clicked
|
||||
// in the ladder is the StopPrice (trigger) and the Price
|
||||
// is what was specificed in the extra parameter box
|
||||
double temp_price = order.CustomDef.StopLimit.StopPrice;
|
||||
order.CustomDef.StopLimit.StopPrice = args.price;
|
||||
args.price = temp_price;
|
||||
}
|
||||
}
|
||||
|
||||
uint size = size_from_alternate ? s.alternate_order_size : (uint)Math.Abs(size_calculator.value.Value);
|
||||
await _model.order_gateway.send_order(order, args.price, size);
|
||||
if (auto_clear)
|
||||
size_calculator.value = 0;
|
||||
}
|
||||
if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
|
||||
size_calculator.flash_error();
|
||||
}
|
||||
else {
|
||||
uint size = size_from_alternate ? s.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
|
||||
send_order_async(args.side.Value, size, args.price).ignore();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task send_order_async(Side side, uint size, double price) {
|
||||
var order = order_properties_vm.create(side);
|
||||
if (order.CustomDef != null) {
|
||||
if (order.CustomDef.Stop != null) {
|
||||
order.CustomDef.Stop.StopPrice = price;
|
||||
} else if (order.CustomDef.StopLimit != null) {
|
||||
// swap the prices here so that the level clicked
|
||||
// in the ladder is the StopPrice (trigger) and the Price
|
||||
// is what was specificed in the extra parameter box
|
||||
double temp_price = order.CustomDef.StopLimit.StopPrice;
|
||||
order.CustomDef.StopLimit.StopPrice = price;
|
||||
price = temp_price;
|
||||
}
|
||||
}
|
||||
await _model.order_gateway.send_order(order, price, size);
|
||||
if (auto_clear)
|
||||
size_calculator.value = 0;
|
||||
}
|
||||
|
||||
void _update_header() {
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.md.auxiliary;
|
||||
using bts.md;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using bts.config;
|
||||
using mbts.co2;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.spark.algo;
|
||||
using mbts.ui.utility.text;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
@@ -22,10 +21,10 @@ using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using mbts.config;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class LadderGridHost : Control {
|
||||
@@ -67,6 +66,11 @@ namespace mbts.spark {
|
||||
MarketBreakLines _mkt_break_lines;
|
||||
NativePriceSize _last_trade;
|
||||
Func<NativePriceSize, string> _fmt_size = MDDisplayHelper.get_formatter_nps(MarketSizeDisplay.get_default());
|
||||
int _old_tooltip_row = int.MaxValue;
|
||||
double _old_last_trade_price = 0.0;
|
||||
bool _tooltips_are_hooked_up = false;
|
||||
ToolTip tooltip_view = new ToolTip();
|
||||
IDisposable _parameterized_order_popup;
|
||||
|
||||
public LadderGridHost() {
|
||||
AllowDrop = true;
|
||||
@@ -320,30 +324,113 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
protected override async void OnMouseDown(MouseButtonEventArgs e) {
|
||||
if (_settings == null || e.Handled)
|
||||
return;
|
||||
Keyboard.ClearFocus();
|
||||
Focus();
|
||||
var point = e.GetPosition(this);
|
||||
var column = _column_for_point(point);
|
||||
if (column.column_type == ColumnType.Price && e.ChangedButton == MouseButton.Middle) {
|
||||
show_mbo_popup(point).ignore();
|
||||
}
|
||||
else if (_settings != null) {
|
||||
var price = price_for_point(point);
|
||||
Side? bs = null;
|
||||
if (column.column_type == ColumnType.Bid) bs = Side.Buy;
|
||||
if (column.column_type == ColumnType.Ask) bs = Side.Sell;
|
||||
var pbs = (price, bs, e.ChangedButton);
|
||||
if (Command != null && Command.CanExecute(pbs)) {
|
||||
if (bs.HasValue) {
|
||||
using (_tactile_adorner.show_busy(_row_for_point(point), bs.Value == Side.Buy ? ColumnType.Bid : ColumnType.Ask))
|
||||
await Command.ExecuteAsync(pbs);
|
||||
}
|
||||
else {
|
||||
Command.Execute(pbs);
|
||||
var price = price_for_point(point);
|
||||
Side? bs = null;
|
||||
if (column.column_type == ColumnType.Bid) bs = Side.Buy;
|
||||
if (column.column_type == ColumnType.Ask) bs = Side.Sell;
|
||||
if (e.ChangedButton == MouseButton.Middle) {
|
||||
if (bs.HasValue) {
|
||||
_send_parameterized_order(point, bs.Value);
|
||||
} else if (column.column_type == ColumnType.Price) {
|
||||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown (Key.RightShift) ||
|
||||
Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt)) {
|
||||
show_mbo_popup(point).ignore();
|
||||
} else {
|
||||
_scroll_to_center();
|
||||
}
|
||||
}
|
||||
}
|
||||
base.OnMouseDown(e);
|
||||
var pbs = (price, bs, e.ChangedButton);
|
||||
if (Command is ICommand cmd && cmd.CanExecute(pbs)) {
|
||||
if (bs.HasValue) {
|
||||
using (_tactile_adorner.show_busy(_row_for_point(point), bs.Value == Side.Buy ? ColumnType.Bid : ColumnType.Ask))
|
||||
await cmd.ExecuteAsync(pbs);
|
||||
} else {
|
||||
cmd.Execute(pbs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _send_parameterized_order(Point point, Side side) {
|
||||
Disposable.dispose(ref _parameterized_order_popup);
|
||||
if (side == Side.Undefined)
|
||||
throw new ArgumentException("Side is undefined.", nameof(side));
|
||||
if (!(_vm.size_calculator.value is uint size) || size == 0 || !(_vm.order_properties_vm?.selected_algo_definition?.algo_root?.can_expand ?? false))
|
||||
return;
|
||||
double price = _price_for_row(_row_for_point(point));
|
||||
var grid = new Grid { DataContext = _vm };
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
var close_cmd = new DelegateCommand(() => Disposable.dispose(ref _parameterized_order_popup));
|
||||
var send_order_cmd = new DelegateCommand(send_order);
|
||||
LadderColumnSettings lcs = side == Side.Buy ? _settings.bid_column : _settings.ask_column;
|
||||
grid.Children.Add(new AlgoParamsView { DataContext = _vm });
|
||||
// dialog skirt
|
||||
var dialog_skirt = new DialogSkirt {
|
||||
RightItems = new[] {
|
||||
new Button {
|
||||
Background = lcs.background.rgb_brush(),
|
||||
Command = send_order_cmd,
|
||||
Content = $"{side} @ {security.spec_or_default().format(price)}",
|
||||
Foreground = lcs.foreground.rgb_brush(),
|
||||
Margin = new Thickness(4, 0, 4, 0),
|
||||
Height = 22
|
||||
},
|
||||
new Button {
|
||||
Content = "Cancel",
|
||||
Command = close_cmd,
|
||||
Margin = new Thickness(4, 0, 4, 0),
|
||||
Height = 22
|
||||
}
|
||||
}
|
||||
};
|
||||
Grid.SetRow(dialog_skirt, 1);
|
||||
grid.Children.Add(dialog_skirt);
|
||||
// root
|
||||
var root = new Border {
|
||||
Background = BTSColors.common_background_brush,
|
||||
BorderBrush = Brushes.DarkGray,
|
||||
BorderThickness = new Thickness(1),
|
||||
Child = grid
|
||||
};
|
||||
// popup
|
||||
var popup = new Popup {
|
||||
Child = root,
|
||||
PlacementTarget = this,
|
||||
PlacementRectangle = new Rect(point, point),
|
||||
StaysOpen = false
|
||||
};
|
||||
popup.Closed += on_closed;
|
||||
root.Loaded += (s, e) => {
|
||||
if (_parameterized_order_popup == null)
|
||||
_parameterized_order_popup = Disposable.Create(() => popup.IsOpen = false);
|
||||
};
|
||||
popup.PreviewMouseDown += (s, e) => {
|
||||
if (e.ChangedButton == MouseButton.Middle) {
|
||||
e.Handled = true;
|
||||
Disposable.dispose(ref _parameterized_order_popup);
|
||||
}
|
||||
};
|
||||
popup.IsOpen = true;
|
||||
// local methods
|
||||
void send_order() {
|
||||
// Double check that size hasn't changed (if it has, do NOT send anything).
|
||||
// While this should be impossible, sending an order with an unexpected size is dangerous enough to warrant redunant checks.
|
||||
if (_vm.size_calculator.value == size) {
|
||||
_vm.send_order_async(side, size, price).ignore();
|
||||
} else {
|
||||
Log.Ui.warn($"Parameterized algo popup '{side}' order was deliberately ignored by the ui: the 'quantity' box value changed during the lifetime of the popup (was {size} when the popup was first opened; currently is {_vm.size_calculator.value}).");
|
||||
}
|
||||
Disposable.dispose(ref _parameterized_order_popup);
|
||||
}
|
||||
void on_closed(object sender, EventArgs e) =>
|
||||
_vm.order_properties_vm.algo_param_rows.revert_all();
|
||||
}
|
||||
|
||||
private async Task show_mbo_popup(Point point) {
|
||||
@@ -410,6 +497,18 @@ namespace mbts.spark {
|
||||
uint? _ask_size_from_row(int row) =>
|
||||
_size_from_row(row, _price_list, _asks, _vm.security.ContractSpec);
|
||||
|
||||
uint _volume_from_row(int row) {
|
||||
if (_price_list != null) {
|
||||
var price = _price_list[row];
|
||||
var vapls = _volume_container.read_new_value();
|
||||
if (vapls != null) {
|
||||
return vapls.SelectFirstOrDefault(v => MathUtils.epsilon_equals(v.Price, price), v => v.Volume);
|
||||
}
|
||||
}
|
||||
return 0u;
|
||||
}
|
||||
|
||||
|
||||
int _row_for_price(double price) => LadderRender.index_of(_price_list, price, _price_comparer);
|
||||
#endregion
|
||||
|
||||
@@ -535,6 +634,70 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
#region tooltip handling
|
||||
void _toggle_tooltips() {
|
||||
if (_tooltips_are_hooked_up && !_settings.volume_column.show_ltq) {
|
||||
this.MouseMove -= _mouse_move_handler;
|
||||
_tooltips_are_hooked_up = false;
|
||||
_hide_tool_tip();
|
||||
// somehow even after hiding an unhooking the event handler, after toggling the setting off and
|
||||
// then hovering over the Volume column, the tooltip will still display, so we explicitly set it
|
||||
// to null here.
|
||||
this.ToolTip = null;
|
||||
}
|
||||
|
||||
if (_settings.volume_column.show_ltq && !_tooltips_are_hooked_up) {
|
||||
this.MouseMove += _mouse_move_handler;
|
||||
_tooltips_are_hooked_up = true;
|
||||
}
|
||||
}
|
||||
|
||||
void _mouse_move_handler(object sender, MouseEventArgs e) {
|
||||
var pt = e.GetPosition(this);
|
||||
if (_column_for_point(pt)?.column_type == ColumnType.Volume) {
|
||||
var row = _row_for_point(pt);
|
||||
// mark dirty so that we'll always get a value on our read
|
||||
_volume_container.mark_dirty();
|
||||
var volume = _volume_from_row(row);
|
||||
if (volume > uint.MinValue && row != _old_tooltip_row) {
|
||||
if (tooltip_view.IsOpen) {
|
||||
_old_tooltip_row = row;
|
||||
// we've changed rows, so hide the old tooltip
|
||||
//System.Diagnostics.Debug.WriteLine(" Tooltip - Previous tooltip is visible, but we've changed rows");
|
||||
_hide_tool_tip();
|
||||
}
|
||||
|
||||
tooltip_view.Content = $"{volume}";
|
||||
tooltip_view.IsOpen = true;
|
||||
this.ToolTip = tooltip_view;
|
||||
} else {
|
||||
// got 0 volume or we're over the same row
|
||||
if (volume == uint.MinValue) {
|
||||
// The latest volume query resulted in a 0 volume
|
||||
// Perhaps we read it too quickly, or perhaps we are on a row where there is no volume
|
||||
// At any rate, we need this part hiding the tooltip so that we don't leave random tooltips around
|
||||
// and we have to reset to old row so that when we go back to what could be our preivous row, the tooltip gets drawn again
|
||||
//System.Diagnostics.Debug.WriteLine(" Tooltip - Volume is 0");
|
||||
_hide_tool_tip();
|
||||
//_old_tooltip_row = Int32.MaxValue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//System.Diagnostics.Debug.WriteLine(" Tooltip - not over Volume column");
|
||||
_hide_tool_tip();
|
||||
}
|
||||
}
|
||||
void _hide_tool_tip() {
|
||||
// I am leaving this here for posterity
|
||||
// Apparently even when setting IsOpen to false first and then setting Content to null afterward,
|
||||
// the tooltip will *still* display as empty for a brief moment, which looks odd. So, we just hide it now.
|
||||
tooltip_view.IsOpen = false;
|
||||
//tooltip_view.Content = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
void _on_vm_disposed(bool obj) =>
|
||||
_dispose_resources();
|
||||
|
||||
@@ -686,7 +849,18 @@ namespace mbts.spark {
|
||||
var vapls = _volume_container.read_new_value();
|
||||
if (vapls != null) {
|
||||
Log.assert_d(_price_list != null);
|
||||
LadderRender.render_volumes(_volume_vis_tup.visual, _volume_vis_tup.glyph_context, _price_list, vapls, _settings.row_height, security.ContractSpec, _settings.volume_column);
|
||||
var ltp = _last_trade.price;
|
||||
var last_index = LadderRender.index_of(_price_list, ltp, _price_comparer);
|
||||
string direction_arrow = string.Empty;
|
||||
if (MathUtils.epsilon_equals(ltp, _old_last_trade_price)) {
|
||||
// do nothing if in here b/c direction_arrow already string.empty
|
||||
} else if (ltp > _old_last_trade_price) {
|
||||
direction_arrow = "▲";
|
||||
} else {
|
||||
direction_arrow = "▼";
|
||||
}
|
||||
LadderRender.render_volumes(_volume_vis_tup.visual, _volume_vis_tup.glyph_context, _price_list, vapls, _settings.row_height, security.ContractSpec, _settings.volume_column, last_index, _last_trade, direction_arrow);
|
||||
_old_last_trade_price = ltp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -869,6 +1043,7 @@ namespace mbts.spark {
|
||||
case ColumnType.PIQ: SetCurrentValue(PIQColumnWidthProperty, col.settings.width); break;
|
||||
case ColumnType.Volume:
|
||||
SetCurrentValue(VolumeColumnWidthProperty, col.settings.width);
|
||||
_toggle_tooltips();
|
||||
_volume_container.mark_dirty();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -194,7 +194,10 @@ namespace mbts.spark {
|
||||
IEnumerable<VolumeAtPriceLevelImmutable> data,
|
||||
double row_height,
|
||||
ContractSpecificationImmutable spec,
|
||||
VolumeColumnSettings settings
|
||||
VolumeColumnSettings settings,
|
||||
int last_index,
|
||||
NativePriceSize last_trade,
|
||||
string direction_arrow
|
||||
) {
|
||||
if (!data.Any()) {
|
||||
_clear_visual(dv);
|
||||
@@ -217,28 +220,47 @@ namespace mbts.spark {
|
||||
_clear_visual(dv);
|
||||
return;
|
||||
}
|
||||
double max = data.Max(v => v.Volume);
|
||||
// inflate the max so that the graph never fills the available space
|
||||
double max = data.Max(v => v.Volume) * 1.1;
|
||||
Brush text_brush = settings.general.foreground.rgb_brush();
|
||||
_render_cells(dv, row_height, data.Select(d => (d.Price, d)), settings.general, prices, spec, render_cell);
|
||||
/// local methods
|
||||
void render_cell(DrawingContext dc, int r, VolumeAtPriceLevelImmutable vap) {
|
||||
double y = row_height * r;
|
||||
var last_trade_here = r == last_index;
|
||||
|
||||
// render the total first; each component (buy/sell/passive) will be rendered on top of the total
|
||||
if (total_brush != null) {
|
||||
// if we have to change the brush to indicate this is the last, save brush, draw, then restore brush
|
||||
var temp = total_brush;
|
||||
if (settings.show_ltq && last_trade_here) {
|
||||
total_brush = BTSColors.yellow_brush;
|
||||
}
|
||||
render_bar(dc, 0, y, vap.Volume, total_brush, out double _);
|
||||
total_brush = temp;
|
||||
}
|
||||
List<string> numbers_text = new List<string>();
|
||||
double x_bar = 0;
|
||||
foreach ((var get_volume, Brush bars, bool show_numbers) in components) {
|
||||
uint v = get_volume(vap);
|
||||
render_bar(dc, x_bar, y, v, bars, out double width);
|
||||
double width;
|
||||
if (settings.show_ltq && last_trade_here) {
|
||||
render_bar(dc, x_bar, y, v, BTSColors.yellow_brush, out width);
|
||||
} else {
|
||||
render_bar(dc, x_bar, y, v, bars, out width);
|
||||
}
|
||||
x_bar += width;
|
||||
if (show_numbers)
|
||||
numbers_text.Add(v.ToString());
|
||||
}
|
||||
|
||||
if (settings.total.show_numbers) {
|
||||
numbers_text.Add(vap.Volume.ToString());
|
||||
}
|
||||
if (settings.show_ltq && last_trade_here) {
|
||||
numbers_text.Clear();
|
||||
numbers_text.Add($"{direction_arrow} {last_trade.size} ");
|
||||
}
|
||||
if (numbers_text.Count > 0) {
|
||||
string str = string.Join("/", numbers_text);
|
||||
GlyphRun gr = glyph_context.get_glyph_run(str, settings.general.font_size, settings.general.width, row_height, HorizontalAlignment.Center, 0, VerticalAlignment.Center, y, true);
|
||||
@@ -253,7 +275,9 @@ namespace mbts.spark {
|
||||
}
|
||||
double pct = v / max;
|
||||
width = pct * settings.general.width;
|
||||
dc.DrawRectangle(brush, null, new Rect(x, y, width, row_height - 1));
|
||||
// 4 seems like a good number to move the y pos down by and similarly
|
||||
// to shorten the total y coponent by (-4 for the shift of +4, and another -4 to give it some space);
|
||||
dc.DrawRectangle(brush, null, new Rect(x, y + 4, width, row_height - 2 * 4));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
TextElement.FontSize="{x:Static u:FontSizes.Small}"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<cv:BooleanToVisibilityConverter x:Key="NetChangeGroupBoxIsVisibleVisibilityConverter" VisibilityForTrue="Hidden" VisibilityForFalse="Visible"/>
|
||||
<sys:Double x:Key="HorizontalPush">4</sys:Double>
|
||||
<sys:Double x:Key="HorizontalSplit">8</sys:Double>
|
||||
<sys:Double x:Key="IndentChild">24</sys:Double>
|
||||
@@ -382,7 +383,8 @@
|
||||
FontStyle="Italic"
|
||||
Foreground="{Binding can_choose_alternate_implied_format, Converter={StaticResource IsEnabledForegroundConverter}}"
|
||||
/>
|
||||
<GroupBox Grid.Row="3" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,10,0,0" Grid.ColumnSpan="3">
|
||||
<GroupBox Grid.Row="3" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,10,0,0" Grid.ColumnSpan="3"
|
||||
Visibility="{Binding DataContext.is_synthetic, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}, Converter={StaticResource NetChangeGroupBoxIsVisibleVisibilityConverter}}">
|
||||
<GroupBox.Header>
|
||||
<CheckBox
|
||||
x:Name="show_name_change_chk"
|
||||
@@ -478,6 +480,7 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid x:Name="volume_headers" TextElement.Foreground="{u:BTSColor header_text}" Background="{u:BTSColor header}" Margin="8,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -586,6 +589,22 @@
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</WrapPanel>
|
||||
<StackPanel Grid.Row="4" Margin="8,0" VerticalAlignment="Top">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Margin="0,5,5,0">
|
||||
<CheckBox Content="Show Last on Volume Column" FlowDirection="RightToLeft" DataContext="{Binding volume}" IsChecked="{Binding show_last_trade}">
|
||||
<CheckBox.Resources>
|
||||
<Style TargetType="{x:Type Path}">
|
||||
<Setter Property="FlowDirection" Value="LeftToRight" />
|
||||
</Style>
|
||||
</CheckBox.Resources>
|
||||
</CheckBox>
|
||||
</Border>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<!-- Size Presets Tab-->
|
||||
|
||||
@@ -149,11 +149,33 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
void _host_PreviewMouseDown(object sender, MouseButtonEventArgs e) {
|
||||
// When middle click over bid or ask columns:
|
||||
// Non-parameterizalbe order: change cursor to No indicating sending an order is not possible
|
||||
// Parameterizable order: if qty is null/0, flash and No cursor
|
||||
var pos = e.GetPosition(_host);
|
||||
var column_type = _host._column_for_point(pos).column_type;
|
||||
var selected_algo_can_expand = _host._vm.order_properties_vm?.selected_algo_definition?.algo_root?.can_expand ?? true;
|
||||
if (e.MiddleButton == MouseButtonState.Pressed &&
|
||||
(column_type == LadderGridHost.ColumnType.Bid || column_type == LadderGridHost.ColumnType.Ask)) {
|
||||
if (!selected_algo_can_expand) {
|
||||
_host.Cursor = Cursors.No;
|
||||
}
|
||||
if (selected_algo_can_expand && (_host._vm.size_calculator.value ?? 0) == 0) {
|
||||
_host._vm.size_calculator.flash_error();
|
||||
_host.Cursor = Cursors.No;
|
||||
}
|
||||
}
|
||||
|
||||
_is_mouse_down = true;
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
void _host_MouseUp(object sender, MouseButtonEventArgs e) {
|
||||
// This will reset the cursor appropriately if the middle mouse button
|
||||
// has been pressed while over the bid or ask columns
|
||||
var point = e.GetPosition(_host);
|
||||
_set_current_row_and_column(point);
|
||||
|
||||
_is_mouse_down = false;
|
||||
_is_mouse_dragging = false;
|
||||
InvalidateVisual();
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/Order/OrderDetailsPopupControl.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoParamsView.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoTemplateFavoriteButtonsView.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<converters:BooleanToVisibilityConverter x:Key="BoolToInVisibility" VisibilityForTrue="Collapsed" VisibilityForFalse="Visible" />
|
||||
@@ -31,7 +32,6 @@
|
||||
</converters:BooleanToObjectConverter.ValueForFalse>
|
||||
</converters:BooleanToObjectConverter>
|
||||
<converters:EnumCharacterCasingConverter x:Key="casing_converter"/>
|
||||
<converters:DoubleTextConverter x:Key="PositionTextConverter" Format="0" NaNText="-"/>
|
||||
<converters:GridLengthConverter x:Key="GridLengthPixelConverter" UnitType="Pixel" />
|
||||
<converters:ResponsiveTextConverter x:Key="DayTextConverter" LongText="Day:" ShortText="D:" Threshold="99" />
|
||||
<converters:ResponsiveTextConverter x:Key="HighTextConverter" LongText="High:" ShortText="H:" Threshold="99" />
|
||||
@@ -455,13 +455,33 @@
|
||||
<Button
|
||||
Margin="2"
|
||||
Command="{Binding position_click_command}"
|
||||
Content="{Binding position, Converter={StaticResource PositionTextConverter}}"
|
||||
Content="{Binding position_display_text}"
|
||||
FontWeight="Bold"
|
||||
Foreground="{Binding position, Converter={StaticResource NumericSignBrushConverter}}"
|
||||
Visibility="{Binding show_account_position, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
>
|
||||
<Button.Visibility>
|
||||
<MultiBinding>
|
||||
<MultiBinding.Converter>
|
||||
<converters:MultiValueConverterPipe>
|
||||
<converters:BooleanOrConverter/>
|
||||
<converters:BooleanToVisibilityConverter/>
|
||||
</converters:MultiValueConverterPipe>
|
||||
</MultiBinding.Converter>
|
||||
<Binding Path="show_day_position"/>
|
||||
<Binding Path="show_account_position"/>
|
||||
</MultiBinding>
|
||||
</Button.Visibility>
|
||||
<Button.ToolTip>
|
||||
<TextBlock Text="{Binding order_properties_vm.selected_account, StringFormat='{}{0} total position (click to copy to size)'}"/>
|
||||
<!--<TextBlock Text="{Binding order_properties_vm.selected_account, StringFormat='{}{0} total position (click to copy to size)'}"/>-->
|
||||
<TextBlock TextAlignment="Center">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{x:Static spark:LadderView.PositionToolTipConverter}">
|
||||
<Binding Path="order_properties_vm.selected_account"/>
|
||||
<Binding Path="show_day_position"/>
|
||||
<Binding Path="show_account_position"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Button.ToolTip>
|
||||
</Button>
|
||||
<!-- Qty textbox -->
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using bts.collections;
|
||||
using bts;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.ui.converters;
|
||||
using mbts.utils;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
@@ -8,6 +11,35 @@ using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class LadderView : UserControl {
|
||||
public static IMultiValueConverter PositionToolTipConverter =
|
||||
ValueConverter.create<AccountTuple?, bool, bool, string>((account, show_day_pos, show_total_pos) => {
|
||||
const string day_pos_text = "day", total_pos_text = "total";
|
||||
if (!account.HasValue)
|
||||
return string.Empty;
|
||||
var sb = new StringBuilderEx(account.Value.name);
|
||||
string primary_pos;
|
||||
if (show_day_pos) {
|
||||
primary_pos = day_pos_text;
|
||||
if (show_total_pos) {
|
||||
sb.Add(" '", primary_pos, " ");
|
||||
sb.Add("(", total_pos_text, ")' ");
|
||||
} else {
|
||||
sb.Add(" ", primary_pos, " ");
|
||||
}
|
||||
} else if (show_total_pos) {
|
||||
primary_pos = total_pos_text;
|
||||
sb.Add(" ", primary_pos, " ");
|
||||
} else {
|
||||
// unexpected code path (the button for which this tooltip is destined should be invisible in this case), but no need to throw an exception.
|
||||
return string.Empty;
|
||||
}
|
||||
sb.Add("position\n(click to copy ");
|
||||
if (show_day_pos && show_total_pos)
|
||||
sb.Add(primary_pos, " position ");
|
||||
sb.Add("to size)");
|
||||
return sb.ToString();
|
||||
});
|
||||
|
||||
readonly double _max_zoom, _min_zoom;
|
||||
|
||||
internal LadderView(LadderVM vm) {
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
using bts.api;
|
||||
using bts.api.position;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace bts.shell.manual_fills {
|
||||
class ManualFillRow : DisposableViewModelBase, IEquatable<ManualFillRow> {
|
||||
public const int _uncommitted_record_id = -1;
|
||||
static readonly PropertyInfo[] _snapshot_props =
|
||||
Array.ConvertAll(new[] {
|
||||
nameof(security),
|
||||
nameof(account),
|
||||
nameof(user),
|
||||
nameof(side),
|
||||
nameof(size),
|
||||
nameof(price)
|
||||
}, typeof(ManualFillRow).GetProperty);
|
||||
readonly Property<bool> _is_dirty;
|
||||
readonly Property<EditableItemState> _editable_item_state;
|
||||
readonly DelegateProperty<bool> _is_valid;
|
||||
SecurityImmutable? _security;
|
||||
AccountTuple? _account;
|
||||
UserTuple? _user;
|
||||
Side _side;
|
||||
uint _size;
|
||||
double _price;
|
||||
bool _is_selected;
|
||||
ManualFillData? _data;
|
||||
IDisposable _committed_snapshot;
|
||||
|
||||
public ManualFillRow(ManualFillData data) {
|
||||
_data = data;
|
||||
key = data.key;
|
||||
_security = data.value.security;
|
||||
_account = data.value.account;
|
||||
_user = data.value.user;
|
||||
_side = data.value.manual_fill.Side;
|
||||
_size = data.value.manual_fill.Quantity;
|
||||
_price = data.value.manual_fill.Price;
|
||||
if (placeholder_key.HasValue) {
|
||||
_is_dirty = backing(nameof(is_dirty), false);
|
||||
_editable_item_state = backing(nameof(editable_item_state), EditableItemState.Uncommitted);
|
||||
_committed_snapshot = ObjectSnapshot.create(this, _snapshot_props);
|
||||
} else {
|
||||
_is_dirty = backing(nameof(is_dirty), ObjectSnapshot.dirty_monitor(this, out _committed_snapshot, _snapshot_props));
|
||||
_editable_item_state = backing(nameof(editable_item_state), _is_dirty, ch => ch.newval ? EditableItemState.Modified : EditableItemState.None);
|
||||
}
|
||||
_is_valid = backing(nameof(is_valid), () => _validate(this));
|
||||
}
|
||||
|
||||
public event Action<bool> is_selected_changed;
|
||||
// key/id
|
||||
public ManualFillKey key { get; }
|
||||
public long? placeholder_key => key.unpersisted;
|
||||
public int record_id => key.persisted ?? _uncommitted_record_id;
|
||||
public string record_id_display_text => key.persisted?.ToString();
|
||||
// security/spec
|
||||
public SecurityImmutable? security {
|
||||
get => _security;
|
||||
set {
|
||||
if (_set(ref _security, value))
|
||||
RaisePropertyChanged(nameof(spec));
|
||||
}
|
||||
}
|
||||
public ContractSpecificationImmutable? spec => security.spec_or_default();
|
||||
// account/user
|
||||
public AccountTuple? account { get => _account; set => _set(ref _account, value); }
|
||||
public UserTuple? user { get => _user; set => _set(ref _user, value); }
|
||||
// side/size/price
|
||||
public Side side { get => _side; set => _set(ref _side, value); }
|
||||
public uint size { get => _size; set => _set(ref _size, value); }
|
||||
public double price { get => _price; set => _set(ref _price, value); }
|
||||
// is selected
|
||||
public bool is_selected {
|
||||
get => _is_selected;
|
||||
set {
|
||||
if (_set(ref _is_selected, value))
|
||||
is_selected_changed?.Invoke(value);
|
||||
}
|
||||
}
|
||||
// dirty/valid/editable-state
|
||||
public bool is_dirty => _is_dirty.Value;
|
||||
public bool is_valid => _is_valid.Value;
|
||||
public EditableItemState editable_item_state => _editable_item_state.Value;
|
||||
|
||||
public ManualFillData data => _data ??
|
||||
(_data = new ManualFillData(key, new ManualFillTuple(new ManualFillImmutable(to_man_fill()), security, account, user))).Value;
|
||||
|
||||
public void revert() =>
|
||||
_committed_snapshot.Dispose();
|
||||
|
||||
public IObservable<bool> subscribe_is_dirty() =>
|
||||
_is_dirty.observe_current(true);
|
||||
|
||||
public IObservable<bool> subscribe_is_selected() =>
|
||||
Observable.Create<bool>(o => {
|
||||
o.OnNext(is_selected);
|
||||
is_selected_changed += o.OnNext;
|
||||
return Disposable.Create(() => is_selected_changed -= o.OnNext);
|
||||
});
|
||||
|
||||
public ManualFill to_man_fill() =>
|
||||
new ManualFill {
|
||||
RecordId = record_id,
|
||||
AccountId = account?.id ?? 0,
|
||||
AccountName = account?.name ?? string.Empty,
|
||||
UserId = user?.id ?? 0,
|
||||
Price = price,
|
||||
Side = side,
|
||||
Quantity = size,
|
||||
SecurityKey = security?.Id ?? 0
|
||||
};
|
||||
|
||||
public ManualFillTuple to_tuple() =>
|
||||
new ManualFillTuple(new ManualFillImmutable(to_man_fill()), security, account, user);
|
||||
|
||||
public bool Equals(ManualFillRow other) =>
|
||||
data == other?.data;
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as ManualFillRow);
|
||||
public override int GetHashCode() => record_id;
|
||||
|
||||
static bool _validate(ManualFillRow row) =>
|
||||
row.security.HasValue &&
|
||||
row.account.HasValue &&
|
||||
row.user.HasValue &&
|
||||
row.side != Side.Undefined &&
|
||||
!double.IsNaN(row._price);
|
||||
|
||||
bool _set<T>(ref T backing, T value, [CallerMemberName] string prop_name = "") =>
|
||||
_set(ref backing, value, EqualityComparer<T>.Default, prop_name);
|
||||
|
||||
bool _set<T>(ref T backing, T value, IEqualityComparer<T> comparer, [CallerMemberName] string prop_name = "") {
|
||||
if (Set(ref backing, value, comparer, prop_name)) {
|
||||
_is_valid.refresh();
|
||||
_data = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<UserControl
|
||||
x:Class="mbts.ui.spark.ManualFillRowEditor"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
FocusVisualStyle="{x:Null}"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<sys:Double x:Key="HorizontalPush">4</sys:Double>
|
||||
<sys:Double x:Key="VerticalPush3">8</sys:Double>
|
||||
<Style x:Key="HeaderText" TargetType="TextBlock">
|
||||
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="Foreground" Value="{x:Static u:BTSColors.header_text_brush}"/>
|
||||
<Setter Property="Margin" Value="4"/>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Margin="6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle Grid.ColumnSpan="11" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{x:Static u:BTSColors.header_brush}"/>
|
||||
<TextBlock Grid.Column="0" Text="Security" Style="{StaticResource HeaderText}"/>
|
||||
<TextBlock Grid.Column="2" Text="Account" Style="{StaticResource HeaderText}"/>
|
||||
<TextBlock Grid.Column="4" Text="User" Style="{StaticResource HeaderText}"/>
|
||||
<TextBlock Grid.Column="6" Text="Side" Style="{StaticResource HeaderText}"/>
|
||||
<TextBlock Grid.Column="8" Text="Qty" Style="{StaticResource HeaderText}"/>
|
||||
<TextBlock Grid.Column="10" Text="Price" Style="{StaticResource HeaderText}"/>
|
||||
|
||||
<c:ContractPickerPopup
|
||||
Grid.Row="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
CloseOnCancel="True"
|
||||
CloseOnOK="True"
|
||||
Header="{Binding security}"
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
SecurityMap="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.SecuritiesKey}}"
|
||||
SelectedSecurity="{Binding security}"
|
||||
ShowDialogSkirt="True"
|
||||
/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="2" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.AccountsKey}}" SelectedItem="{Binding account}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="4" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.UsersKey}}" SelectedItem="{Binding user}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="6" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.SidesKey}}" SelectedItem="{Binding side}" Width="48"/>
|
||||
<c:TickUpDownUInt Grid.Row="2" Grid.Column="8" MinWidth="40" Value="{Binding size}"/>
|
||||
<usp:PriceEntryControl Grid.Row="2" Grid.Column="10" MinWidth="55" Value="{Binding price}" ContractSpec="{Binding spec}"/>
|
||||
</Grid>
|
||||
<c:DialogSkirt Grid.Row="1" Margin="0,6,0,0">
|
||||
<c:DialogSkirt.RightItems>
|
||||
<x:Array Type="{x:Type Button}">
|
||||
<Button Content="OK" Command="{x:Static usp:ManualFillRowEditor.CommitCommand}" CommandParameter="{Binding}" IsDefault="True"/>
|
||||
<Button Content="Cancel" Command="{x:Static usp:ManualFillRowEditor.CancelCommand}" IsCancel="True"/>
|
||||
</x:Array>
|
||||
</c:DialogSkirt.RightItems>
|
||||
</c:DialogSkirt>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for ManualFillRowEditor.xaml
|
||||
/// </summary>
|
||||
partial class ManualFillRowEditor : UserControl {
|
||||
public const string
|
||||
SecuritiesKey = nameof(SecuritiesKey),
|
||||
AccountsKey = nameof(AccountsKey),
|
||||
UsersKey = nameof(UsersKey),
|
||||
SidesKey = nameof(SidesKey);
|
||||
|
||||
public static readonly RoutedCommand
|
||||
CommitCommand = new RoutedCommand(),
|
||||
CancelCommand = new RoutedCommand();
|
||||
|
||||
public ManualFillRowEditor() =>
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<Window
|
||||
x:Class="mbts.ui.spark.ManualFillsImportConflictsDialog"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
u:WindowStyle.AllowResize="True"
|
||||
u:WindowStyle.SizeToContent="WidthAndHeight"
|
||||
u:WindowStyle.TitleContent="Import Fills Conflicts"
|
||||
Style="{StaticResource BTSDialogTitleTemplate}"
|
||||
>
|
||||
<u:WindowStyle.TitleContentTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<Image Height="12" Margin="5,2" Source="{x:Static u:Icons.Import}"/>
|
||||
<TextBlock Margin="4,0,0,0" Text="{Binding}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</u:WindowStyle.TitleContentTemplate>
|
||||
<Window.Content>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<usp:ManualFillsImportConflictsView Margin="12,10,12,8"/>
|
||||
<c:DialogSkirt Grid.Row="1">
|
||||
<c:DialogSkirt.RightItems>
|
||||
<x:Array Type="{x:Type Button}">
|
||||
<Button Command="{Binding ok_cmd}">Continue</Button>
|
||||
<Button Command="{Binding cancel_cmd}" IsCancel="True">Cancel</Button>
|
||||
</x:Array>
|
||||
</c:DialogSkirt.RightItems>
|
||||
</c:DialogSkirt>
|
||||
</Grid>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for ManualFillsImportConflictsDialog.xaml
|
||||
/// </summary>
|
||||
partial class ManualFillsImportConflictsDialog : Window {
|
||||
public ManualFillsImportConflictsDialog() =>
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using mbts;
|
||||
using mbts.collections;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using Component = mbts.ui.spark.ManualFillsImportConflictsView.ManualFillComponent;
|
||||
|
||||
namespace bts.shell.manual_fills {
|
||||
class ManualFillsImportVM : DisposableViewModelBase, IRequestViewClose {
|
||||
readonly RefCountMap<ParsedManualFill> _conflicts = new RefCountMap<ParsedManualFill>();
|
||||
IReadOnlyDictionary<uint, SecurityMD> _security_map = DictionaryHelper.empty<uint, SecurityMD>();
|
||||
|
||||
public ManualFillsImportVM(int num_fills) {
|
||||
rows_view = CollectionViewSource.GetDefaultView(rows);
|
||||
ok_cmd = new DelegateCommand(() => import_request?.Invoke());
|
||||
cancel_cmd = new DelegateCommand(() => RequestClose?.Invoke(this, new EventArgs()));
|
||||
this.num_fills = num_fills;
|
||||
_dispose = ObservableAutoWrapper.connect_item_hooks(rows, r =>
|
||||
r.subscribe_has_error().Subscribe(has_error => {
|
||||
if (has_error) {
|
||||
if (r.affected_fills.Count(_conflicts.increment_adds_key) == 0)
|
||||
return;
|
||||
} else if (r.affected_fills.Count(_conflicts.decrement_removes_key) == 0)
|
||||
return;
|
||||
RaisePropertyChanged(nameof(num_successfully_parsed_fills));
|
||||
}));
|
||||
}
|
||||
|
||||
public event Action import_request;
|
||||
public event EventHandler RequestClose;
|
||||
|
||||
public ObservableCollection<Row> rows { get; } = new ObservableCollection<Row>();
|
||||
public ICollectionView rows_view { get; }
|
||||
public int num_fills { get; }
|
||||
public int num_successfully_parsed_fills => num_fills - _conflicts.Count;
|
||||
// misc items sources
|
||||
public IEnumerable<Side> sides => new[] { Side.Buy, Side.Sell };
|
||||
public ObservableCollection<AccountTuple?> accounts { get; } = new ObservableCollection<AccountTuple?>();
|
||||
public ObservableCollection<UserTuple?> users { get; } = new ObservableCollection<UserTuple?>();
|
||||
public IReadOnlyDictionary<uint, SecurityMD> security_map {
|
||||
get => _security_map;
|
||||
set {
|
||||
if (!ReferenceEquals(_security_map, value)) {
|
||||
_security_map = value;
|
||||
_resolve_securities();
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
// commands
|
||||
public ICommand ok_cmd { get; }
|
||||
public ICommand cancel_cmd { get; }
|
||||
|
||||
void _resolve_securities() {
|
||||
if (_security_map == null)
|
||||
return;
|
||||
var reverse_lookup = ManualFillsParser.reverse_lookup(MappedDictionary.create(_security_map, v => v.security.ToString()));
|
||||
foreach (var r in rows) {
|
||||
if (r.component == Component.Security && r.has_error) {
|
||||
var id = reverse_lookup(r.text);
|
||||
if (id != 0 && _security_map.TryGetValue(id, out var smd))
|
||||
r.value = smd.security;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Row : ViewModelBase, ITemplateSelectorKey {
|
||||
readonly DelegateProperty<bool> _has_error;
|
||||
object _value;
|
||||
string _text;
|
||||
|
||||
public Row(Component component, string text, IReadOnlyCollection<ParsedManualFill> affected_fills, object error_value, object value) {
|
||||
this.component = component;
|
||||
this.text = text;
|
||||
this.affected_fills = affected_fills;
|
||||
_value = value;
|
||||
_has_error = backing(nameof(has_error), () => Equals(this.value, error_value));
|
||||
}
|
||||
|
||||
public Component component { get; }
|
||||
public string text { get => _text; set => Set(ref _text, value); }
|
||||
public IReadOnlyCollection<ParsedManualFill> affected_fills { get; }
|
||||
public object value {
|
||||
get => _value;
|
||||
set {
|
||||
if (Set(ref _value, value))
|
||||
_has_error.refresh();
|
||||
}
|
||||
}
|
||||
public bool has_error => _has_error.Value;
|
||||
string ITemplateSelectorKey.key => component.ToString();
|
||||
|
||||
public IObservable<bool> subscribe_has_error() => _has_error.observe_current(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<UserControl
|
||||
x:Class="mbts.ui.spark.ManualFillsImportConflictsView"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
MinWidth="400"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<Style x:Key="VerticallyCenteredText" TargetType="TextBlock">
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
<!--Value Cell Template Selector-->
|
||||
<u:KeyedTemplateSelector x:Key="ValueCellTemplateSelector">
|
||||
<u:KeyedTemplateSelector.Default>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding value}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</u:KeyedTemplateSelector.Default>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static Price, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding value, Converter={StaticResource DoubleTextConverter}}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
<!--Value Cell Editing Template Selector-->
|
||||
<u:KeyedTemplateSelector x:Key="ValueCellEditingTemplateSelector">
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static Security, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
|
||||
<DataTemplate>
|
||||
<c:ContractPickerPopup
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
CloseOnCancel="True"
|
||||
CloseOnOK="True"
|
||||
Header="{Binding value}"
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
SecurityMap="{Binding DataContext.security_map, RelativeSource={RelativeSource AncestorType=DataGrid}, Mode=OneWay}"
|
||||
SelectedSecurity="{Binding value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
ShowDialogSkirt="True"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static Account, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
|
||||
<DataTemplate>
|
||||
<ComboBox ItemsSource="{Binding DataContext.accounts, RelativeSource={RelativeSource AncestorType=DataGrid}}" SelectedItem="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static User, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
|
||||
<DataTemplate>
|
||||
<ComboBox ItemsSource="{Binding DataContext.users, RelativeSource={RelativeSource AncestorType=DataGrid}}" SelectedItem="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static Side, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
|
||||
<DataTemplate>
|
||||
<ComboBox ItemsSource="{Binding DataContext.sides, RelativeSource={RelativeSource AncestorType=DataGrid}}" SelectedItem="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static Size, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
|
||||
<DataTemplate>
|
||||
<c:TickUpDownUInt Value="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static Price, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
|
||||
<DataTemplate>
|
||||
<usp:PriceEntryControl Value="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
</UserControl.Resources>
|
||||
<!--Content-->
|
||||
<Grid x:Name="content_root">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image VerticalAlignment="Center" Margin="0,0,8,0">
|
||||
<Image.Source>
|
||||
<Binding Source="{x:Static MessageBoxImage.Exclamation}">
|
||||
<Binding.Converter>
|
||||
<cv:SystemIconConverter/>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
<Canvas x:Name="text_canvas" Grid.Column="1" HorizontalAlignment="Stretch"/>
|
||||
<TextBlock Grid.Column="1" TextWrapping="Wrap" Width="{Binding ActualWidth, ElementName=text_canvas}">
|
||||
<Run Text="{Binding num_successfully_parsed_fills, Mode=OneWay}"/>
|
||||
<Run Text="/"/>
|
||||
<Run Text="{Binding num_fills, Mode=OneWay}"/>
|
||||
<Run Text=" fills have been successfully converted."/>
|
||||
<Run Text="Resolve conflicts below by selecting a value to assign each affected fill,"/>
|
||||
<Run Text="or modify individual fills in the main window."/>
|
||||
<LineBreak/>
|
||||
<LineBreak/>
|
||||
<Run Text="Click 'Continue' to proceed to the main window."/>
|
||||
</TextBlock>
|
||||
<DataGrid
|
||||
x:Name="data_grid"
|
||||
u:DataGridBehavior.SingleClickEdit="True"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserAddRows="False"
|
||||
CanUserDeleteRows="False"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserSortColumns="True"
|
||||
HeadersVisibility="All"
|
||||
ItemsSource="{Binding rows_view}"
|
||||
Margin="0,8,0,0"
|
||||
MinRowHeight="17"
|
||||
RowHeaderWidth="22"
|
||||
>
|
||||
<DataGrid.CellStyle>
|
||||
<Style TargetType="DataGridCell" BasedOn="{StaticResource DataGridCellBaseStyle}">
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
</Style>
|
||||
</DataGrid.CellStyle>
|
||||
<DataGrid.RowStyle>
|
||||
<Style TargetType="DataGridRow">
|
||||
<Setter Property="IsSelected" Value="{Binding is_selected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource SelectedRowBackground}"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</DataGrid.RowStyle>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Component" Binding="{Binding component}" ElementStyle="{StaticResource VerticallyCenteredText}" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Text" Binding="{Binding text}" ElementStyle="{StaticResource VerticallyCenteredText}" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Affected Fills" Binding="{Binding affected_fills.Count}" ElementStyle="{StaticResource VerticallyCenteredText}" IsReadOnly="True"/>
|
||||
<DataGridTemplateColumn Header="Value" CellTemplateSelector="{StaticResource ValueCellTemplateSelector}" CellEditingTemplateSelector="{StaticResource ValueCellEditingTemplateSelector}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for ManualFillsImportConflictsView.xaml
|
||||
/// </summary>
|
||||
partial class ManualFillsImportConflictsView : UserControl {
|
||||
public ManualFillsImportConflictsView() =>
|
||||
InitializeComponent();
|
||||
|
||||
public enum ManualFillComponent {
|
||||
Security,
|
||||
Account,
|
||||
User,
|
||||
Side,
|
||||
Size,
|
||||
Price
|
||||
}
|
||||
|
||||
public static class ManualFillComponentNames {
|
||||
public static readonly string
|
||||
Security = ManualFillComponent.Security.ToString(),
|
||||
Account = ManualFillComponent.Account.ToString(),
|
||||
User = ManualFillComponent.User.ToString(),
|
||||
Side = ManualFillComponent.Side.ToString(),
|
||||
Size = ManualFillComponent.Size.ToString(),
|
||||
Price = ManualFillComponent.Price.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
using bts.api.core;
|
||||
using bts.api.position;
|
||||
using bts.collections;
|
||||
using mbts.spark;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.shell.manual_fills {
|
||||
struct ManualFillKey : IEquatable<ManualFillKey>, IComparable<ManualFillKey> {
|
||||
public readonly int? persisted;
|
||||
public readonly long? unpersisted;
|
||||
|
||||
public ManualFillKey(int persisted) {
|
||||
this.persisted = persisted;
|
||||
unpersisted = null;
|
||||
}
|
||||
|
||||
public ManualFillKey(long unpersisted) {
|
||||
this.unpersisted = unpersisted;
|
||||
persisted = null;
|
||||
}
|
||||
|
||||
public int CompareTo(ManualFillKey other) {
|
||||
if (persisted.HasValue) {
|
||||
if (other.persisted.HasValue) {
|
||||
return persisted.Value.CompareTo(other.persisted.Value);
|
||||
} else if (other.unpersisted.HasValue) {
|
||||
return -1;
|
||||
} else return 1;
|
||||
} else if (unpersisted.HasValue) {
|
||||
return other.unpersisted.HasValue ? unpersisted.Value.CompareTo(other.unpersisted.Value) : 1;
|
||||
} else return !other.persisted.HasValue && !other.unpersisted.HasValue ? 0 : -1;
|
||||
}
|
||||
|
||||
public override string ToString() => persisted.HasValue ? persisted.Value.ToString() : unpersisted.HasValue ? unpersisted.Value.ToString() : "(N/A)";
|
||||
|
||||
public bool Equals(ManualFillKey other) =>
|
||||
persisted == other.persisted &&
|
||||
unpersisted == other.unpersisted;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is ManualFillKey other && Equals(other);
|
||||
|
||||
public override int GetHashCode() => persisted ?? unpersisted?.GetHashCode() ?? 0;
|
||||
public static bool operator ==(ManualFillKey a, ManualFillKey b) => a.Equals(b);
|
||||
public static bool operator !=(ManualFillKey a, ManualFillKey b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
struct ManualFillData : IEquatable<ManualFillData> {
|
||||
public static readonly Func<ManualFillData, ManualFillKey> key_fn = mfd => mfd.key;
|
||||
|
||||
public readonly ManualFillKey key;
|
||||
public readonly ManualFillTuple value;
|
||||
|
||||
public ManualFillData(ManualFillKey key, ManualFillTuple value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public bool Equals(ManualFillData other) =>
|
||||
key == other.key &&
|
||||
value.Equals(other.value);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is ManualFillData other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
key.GetHashCode();
|
||||
|
||||
public static bool operator ==(ManualFillData a, ManualFillData b) => a.Equals(b);
|
||||
public static bool operator !=(ManualFillData a, ManualFillData b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
class ManualFillsModel {
|
||||
readonly OrdersModel _model;
|
||||
|
||||
public ManualFillsModel(OrdersModel model)
|
||||
: this(model, model.on_manual_fill_tuples,
|
||||
model.factory.subscribe_accounts().Select(d => MappedDictionary.create(d, kv => new AccountTuple(kv.Key, kv.Value))).Table(u => u.id),
|
||||
model.factory.subscribe_users().Select(d => MappedDictionary.create(d, kv => new UserTuple(kv.Key, kv.Value))).Table(a => a.id)) { }
|
||||
|
||||
public ManualFillsModel(OrdersModel model, IObservable<TableUpdate<ManualFillTuple, int>> manual_fills, IObservable<TableUpdate<AccountTuple, uint>> accounts, IObservable<TableUpdate<UserTuple, uint>> users) {
|
||||
_model = model;
|
||||
this.manual_fills = manual_fills.SelectKeyValue(
|
||||
mft => new ManualFillData(new ManualFillKey(mft.manual_fill.RecordId), mft),
|
||||
ManualFillData.key_fn);
|
||||
this.accounts = accounts;
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
public IObservable<TableUpdate<ManualFillData, ManualFillKey>> manual_fills { get; }
|
||||
public IObservable<TableUpdate<AccountTuple, uint>> accounts { get; }
|
||||
public IObservable<TableUpdate<UserTuple, uint>> users { get; }
|
||||
|
||||
public async Task delete_fills(IEnumerable<ManualFill> fills) =>
|
||||
await _model.delete_manual_fills(fills);
|
||||
|
||||
public async Task<bool> create_fills(IEnumerable<ManualFill> fills) =>
|
||||
await _model.create_manual_fills(fills);
|
||||
|
||||
public async Task<bool> update_fills(IEnumerable<ManualFill> fills) =>
|
||||
await _model.update_manual_fills(fills);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
using bts.api;
|
||||
using bts.api.position;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using ManualFillComponent = mbts.ui.spark.ManualFillsImportConflictsView.ManualFillComponent;
|
||||
|
||||
namespace bts.shell.manual_fills {
|
||||
class ParsedManualFill {
|
||||
public readonly Guid unpersisted_id = Guid.NewGuid();
|
||||
public readonly ManualFill result = new ManualFill();
|
||||
|
||||
public (string text, bool success) security { get; set; }
|
||||
public (string text, bool success) account { get; set; }
|
||||
public (string text, bool success) user { get; set; }
|
||||
public (string text, bool success) side { get; set; }
|
||||
public (string text, bool success) quantity { get; set; }
|
||||
public (string text, bool success) price { get; set; }
|
||||
|
||||
public bool Equals(ParsedManualFill other) => other != null && unpersisted_id == other.unpersisted_id;
|
||||
public override bool Equals(object obj) => Equals(obj as ParsedManualFill);
|
||||
public override int GetHashCode() => unpersisted_id.GetHashCode();
|
||||
}
|
||||
|
||||
static class ManualFillsParser {
|
||||
public const string file_extension = "csv";
|
||||
|
||||
public static Action<ManualFill, object> assign_component(ManualFillComponent component) {
|
||||
switch (component) {
|
||||
case ManualFillComponent.Security: return (mf, val) => mf.SecurityKey = ConvertUtils.try_convert<SecurityImmutable?>(val)?.Id ?? 0;
|
||||
case ManualFillComponent.Account: return (mf, val) => mf.AccountId = ConvertUtils.try_convert<AccountTuple?>(val)?.id ?? 0;
|
||||
case ManualFillComponent.User: return (mf, val) => mf.UserId = ConvertUtils.try_convert<UserTuple?>(val)?.id ?? 0;
|
||||
case ManualFillComponent.Side: return (mf, val) => mf.Side = ConvertUtils.try_convert<Side>(val, Side.Undefined);
|
||||
case ManualFillComponent.Size: return (mf, val) => mf.Quantity = ConvertUtils.try_convert<uint>(val);
|
||||
case ManualFillComponent.Price: return (mf, val) => mf.Price = ConvertUtils.try_convert(val, double.NaN);
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// Note: the returned dictionary will have entries for each <see cref="ManualFillComponent"/>, regardless of whether or not that component has any conflicts.
|
||||
public static Dictionary<ManualFillComponent, Dictionary<string, List<ParsedManualFill>>> get_conflicts(IEnumerable<ParsedManualFill> parsed_fills) {
|
||||
var missing_components = EnumHelper.get_values<ManualFillComponent>().ToDictionary(c => c, c => new Dictionary<string, List<ParsedManualFill>>());
|
||||
var mk_list = new Func<string, List<ParsedManualFill>>(_ => new List<ParsedManualFill>());
|
||||
foreach (var pmf in parsed_fills) {
|
||||
if (!pmf.security.success) {
|
||||
add_missing(ManualFillComponent.Security, pmf.security.text, pmf);
|
||||
}
|
||||
if (!pmf.account.success) {
|
||||
add_missing(ManualFillComponent.Account, pmf.account.text, pmf);
|
||||
}
|
||||
if (!pmf.user.success) {
|
||||
add_missing(ManualFillComponent.User, pmf.user.text, pmf);
|
||||
}
|
||||
if (!pmf.quantity.success) {
|
||||
add_missing(ManualFillComponent.Size, pmf.quantity.text, pmf);
|
||||
}
|
||||
if (!pmf.side.success) {
|
||||
add_missing(ManualFillComponent.Side, pmf.side.text, pmf);
|
||||
}
|
||||
if (!pmf.price.success) {
|
||||
add_missing(ManualFillComponent.Price, pmf.price.text, pmf);
|
||||
}
|
||||
}
|
||||
return missing_components;
|
||||
void add_missing(ManualFillComponent component, string text, ParsedManualFill result) =>
|
||||
missing_components[component].GetOrAdd(text ?? string.Empty, mk_list).Add(result);
|
||||
}
|
||||
|
||||
// This implementation prioritizes simplicity over speed.
|
||||
// The security desc lookup could be better optimized, but the speed at which fills are imported is probably not a paramount concern.
|
||||
public static IEnumerable<ParsedManualFill> parse_rows(IEnumerable<string> rows, IReadOnlyDictionary<uint, SecurityImmutable> security_map, IReadOnlyDictionary<uint, string> account_map, IReadOnlyDictionary<uint, string> user_map) =>
|
||||
parse_rows(rows,
|
||||
reverse_lookup(MappedDictionary.create(security_map, kv => kv.Value.ToString())),
|
||||
reverse_lookup(account_map),
|
||||
reverse_lookup(user_map));
|
||||
|
||||
public static IEnumerable<ParsedManualFill> parse_rows(IEnumerable<string> rows, Func<string, uint> security_id_lookup, Func<string, uint> account_id_lookup, Func<string, uint> user_id_lookup) {
|
||||
var columns = new[] {
|
||||
(Headers.Security, Cells.security(security_id_lookup)),
|
||||
(Headers.Account, Cells.account(account_id_lookup)),
|
||||
(Headers.User, Cells.user(user_id_lookup)),
|
||||
(Headers.Side, Cells.side),
|
||||
(Headers.Quantity, Cells.quantity),
|
||||
(Headers.Price, Cells.price)
|
||||
}.ToDictionary(col => col.Item1, col => col.Item2.with_null_check());
|
||||
return RowParser.parse_rows(rows, columns, Cells.delimiter);
|
||||
}
|
||||
|
||||
public static Func<string, TKey> reverse_lookup<TKey>(IReadOnlyDictionary<TKey, string> map) {
|
||||
var lookup = map.ToLookup(kv => kv.Value, kv => kv.Key);
|
||||
return cell_text => lookup.Contains(cell_text) ? lookup[cell_text].First() : default;
|
||||
}
|
||||
|
||||
static class Headers {
|
||||
public const string
|
||||
Security = "Security",
|
||||
Account = "Account",
|
||||
User = "User",
|
||||
Side = "Side",
|
||||
Quantity = "Quantity",
|
||||
Price = "Price";
|
||||
}
|
||||
|
||||
static class Cells {
|
||||
public const char delimiter = ',';
|
||||
|
||||
public static RowParser.AssignColumnValue<ParsedManualFill> security(Func<string, uint> reverse_id_lookup) {
|
||||
return impl;
|
||||
void impl(ref ParsedManualFill pmf, string text) {
|
||||
pmf.result.SecurityKey = reverse_id_lookup(text);
|
||||
pmf.security = (text, pmf.result.SecurityKey != 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static void side(ref ParsedManualFill pmf, string cell_text) {
|
||||
switch (cell_text[0]) {
|
||||
case 'B': pmf.result.Side = Side.Buy; break;
|
||||
case 'S': pmf.result.Side = Side.Sell; break;
|
||||
default: pmf.side = (cell_text, false); break;
|
||||
}
|
||||
pmf.side = (cell_text, true);
|
||||
}
|
||||
|
||||
public static void quantity(ref ParsedManualFill pmf, string cell_text) {
|
||||
if (uint.TryParse(cell_text, out uint v)) {
|
||||
pmf.result.Quantity = v;
|
||||
pmf.quantity = (cell_text, true);
|
||||
} else {
|
||||
pmf.quantity = (cell_text, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void price(ref ParsedManualFill pmf, string cell_text) {
|
||||
if (double.TryParse(cell_text, out var v)) {
|
||||
pmf.result.Price = v;
|
||||
pmf.price = (cell_text, true);
|
||||
} else {
|
||||
pmf.price = (cell_text, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static RowParser.AssignColumnValue<ParsedManualFill> account(Func<string, uint> reverse_id_lookup) {
|
||||
return impl;
|
||||
void impl(ref ParsedManualFill pmf, string text) {
|
||||
pmf.result.AccountId = reverse_id_lookup(text);
|
||||
pmf.account = (text, pmf.result.AccountId != 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static RowParser.AssignColumnValue<ParsedManualFill> user(Func<string, uint> reverse_id_lookup) {
|
||||
return impl;
|
||||
void impl(ref ParsedManualFill pmf, string text) {
|
||||
pmf.result.UserId = reverse_id_lookup(text);
|
||||
pmf.user = (text, pmf.result.UserId != 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using mbts.ui;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace bts.shell.manual_fills {
|
||||
[ProtoContract]
|
||||
class ManualFillsSettings {
|
||||
[ProtoMember(1)]
|
||||
readonly SortingCriteria[] _sorting_criterion;
|
||||
|
||||
public static ManualFillsSettings Default =>
|
||||
new ManualFillsSettings(new[] { new SortingCriteria(nameof(mbts.ui.spark.ManualFillsView.Columns.Security), true) });
|
||||
|
||||
public ManualFillsSettings() { }
|
||||
|
||||
public ManualFillsSettings(IEnumerable<SortingCriteria> sorting_criterion) =>
|
||||
_sorting_criterion = sorting_criterion.ToArray();
|
||||
|
||||
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criterion ?? Array.Empty<SortingCriteria>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.position;
|
||||
using bts.collections;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using Disposable = mbts.Disposable;
|
||||
using ManualFillComponent = mbts.ui.spark.ManualFillsImportConflictsView.ManualFillComponent;
|
||||
|
||||
namespace bts.shell.manual_fills {
|
||||
enum EditableItemState {
|
||||
None, // Committed and not modified
|
||||
Modified, // Committed and modified
|
||||
Uncommitted // Uncommitted (modified n/a)
|
||||
}
|
||||
|
||||
class ManualFillsVM : DisposableViewModelBase {
|
||||
const int _max_unconfirmed_removes = 16;
|
||||
static readonly IComparer<ManualFillRow> _row_key_comparer = Comparer<ManualFillRow>.Create((a, b) => a.key.CompareTo(b.key));
|
||||
readonly IBus _bus;
|
||||
readonly ManualFillsModel _model;
|
||||
readonly ObservableCollection<ManualFillRow>
|
||||
_persisted_rows = new ObservableCollection<ManualFillRow>(),
|
||||
_placeholder_rows = new ObservableCollection<ManualFillRow>();
|
||||
long _placeholder_id_counter = long.MinValue;
|
||||
IDisposable _import_fills_hooks;
|
||||
|
||||
public static ManualFillsVM create(IBus bus, OrdersModel model) =>
|
||||
new ManualFillsVM(bus, new ManualFillsModel(model));
|
||||
|
||||
public ManualFillsVM(IBus bus, ManualFillsModel model) {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
var merged_rows = new ObservableAggregateCollection<ManualFillRow>(_persisted_rows, _placeholder_rows);
|
||||
rows_view = CollectionViewSource.GetDefaultView(merged_rows);
|
||||
// mons
|
||||
var dirty_mon = new ReactiveThresholdMonitor<ManualFillRow>(r => r.subscribe_is_dirty());
|
||||
var selection_mon = new ReactiveThresholdMonitor<ManualFillRow>(r => r.subscribe_is_selected());
|
||||
var placeholder_mon = new ReactiveThresholdMonitor<ManualFillRow>();
|
||||
// commands
|
||||
commit_all_fills_cmd = ObservantAsyncCommand.create(() => _commit_async(merged_rows), out var commit_all_fils_observer);
|
||||
commit_selected_fills_cmd = ObservantAsyncCommand.create(() => _commit_async(selection_mon.filtered_items), out var commit_selected_fills_observer);
|
||||
revert_all_cmd = ObservantCommand.create(() => revert(dirty_mon.filtered_items.ToList()), out var revert_all_observer);
|
||||
revert_selected_fills_cmd = ObservantCommand.create(() => revert(selection_mon.filtered_items.ToList()), out var revert_selected_fills_observer);
|
||||
remove_selected_fills_cmd = ObservantAsyncCommand.create(remove_selected_fills_async, out var remove_selected_fills_observer);
|
||||
add_fill_cmd = new DelegateCommand<ManualFillRow>(_add_fill);
|
||||
import_fills_cmd = FileDialogCommand.open_file(_import_fills, extensions: ManualFillsParser.file_extension);
|
||||
//
|
||||
var dirty_or_new_src = dirty_mon.CombineLatest(placeholder_mon, LogicalDelegates.Or);
|
||||
_dispose = Disposable.Create(
|
||||
RXCollection.connect(_persisted_rows,
|
||||
_model.manual_fills.OnDispatcher(),
|
||||
Observable.Return(Comparer<ManualFillData>.Create((a, b) => a.key.CompareTo(b.key))),
|
||||
mfd => new ManualFillRow(mfd),
|
||||
mfr => mfr.data),
|
||||
_bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m => {
|
||||
security_map = m;
|
||||
RaisePropertyChanged(nameof(security_map));
|
||||
}),
|
||||
ObservableAutoWrapper.connect_item_hooks(_persisted_rows, r => Disposable.Create(dirty_mon.join(r), selection_mon.join(r))),
|
||||
ObservableAutoWrapper.connect_item_hooks(_placeholder_rows, r => Disposable.Create(selection_mon.join(r), placeholder_mon.join(r, Observable.Return(true)))),
|
||||
// command param subs
|
||||
dirty_mon.Subscribe(revert_all_observer),
|
||||
dirty_or_new_src.OnDispatcher().Subscribe(commit_all_fils_observer),
|
||||
selection_mon.Subscribe(remove_selected_fills_observer),
|
||||
selection_mon.CombineLatest(dirty_mon, LogicalDelegates.And).OnDispatcher().Subscribe(revert_selected_fills_observer),
|
||||
selection_mon.CombineLatest(dirty_or_new_src, LogicalDelegates.And).OnDispatcher().Subscribe(commit_selected_fills_observer),
|
||||
// accounts/users subs
|
||||
RXCollection.connect(accounts, model.accounts.OnDispatcher(), a => new AccountTuple?(a), a => a.Value, Comparer<AccountTuple>.Default),
|
||||
RXCollection.connect(users, model.users.OnDispatcher(), u => new UserTuple?(u), u => u.Value, Comparer<UserTuple>.Default),
|
||||
Disposable.Create(() => Disposable.dispose(ref _import_fills_hooks))
|
||||
);
|
||||
// local methods
|
||||
void revert(IReadOnlyList<ManualFillRow> rows) {
|
||||
foreach (var r in rows)
|
||||
r.revert();
|
||||
}
|
||||
async Task remove_selected_fills_async() {
|
||||
if (selection_mon.filtered_items.Count < _max_unconfirmed_removes || BTSMessageBox.Confirm($"Are you sure you want to delete {selection_mon.filtered_items.Count} fills?", "Delete Fills")) {
|
||||
var persisted_fills = new List<ManualFill>();
|
||||
foreach (var r in selection_mon.filtered_items.ToList()) {
|
||||
if (r.placeholder_key.HasValue)
|
||||
_placeholder_rows.RemoveSorted(r, _row_key_comparer);
|
||||
else persisted_fills.Add(r.to_man_fill());
|
||||
}
|
||||
await _model.delete_fills(persisted_fills);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ICollectionView rows_view { get; }
|
||||
// misc items sources
|
||||
public IEnumerable<Side> sides => new[] { Side.Buy, Side.Sell };
|
||||
public ObservableCollection<AccountTuple?> accounts { get; } = new ObservableCollection<AccountTuple?>();
|
||||
public ObservableCollection<UserTuple?> users { get; } = new ObservableCollection<UserTuple?>();
|
||||
public IReadOnlyDictionary<uint, SecurityMD> security_map { get; private set; } = DictionaryHelper.empty<uint, SecurityMD>();
|
||||
// commands
|
||||
public ICommand add_fill_cmd { get; }
|
||||
public ICommand commit_all_fills_cmd { get; }
|
||||
public ICommand commit_selected_fills_cmd { get; }
|
||||
public ICommand import_fills_cmd { get; }
|
||||
public ICommand remove_selected_fills_cmd { get; }
|
||||
public ICommand revert_all_cmd { get; }
|
||||
public ICommand revert_selected_fills_cmd { get; }
|
||||
|
||||
public ManualFillRow row_factory() =>
|
||||
new ManualFillRow(
|
||||
new ManualFillData(
|
||||
new ManualFillKey(_placeholder_id_counter++),
|
||||
new ManualFillTuple(new ManualFillImmutable(new ManualFill {
|
||||
Side = Side.Buy
|
||||
}), null, accounts.FirstOrDefault(), users.FirstOrDefault())));
|
||||
|
||||
void _add_fill(ManualFillRow new_row) =>
|
||||
_placeholder_rows.InsertSorted((new_row ?? (new_row = row_factory())).placeholder_key.HasValue ?
|
||||
new_row : throw new InvalidOperationException("New row is already persisted."),
|
||||
_row_key_comparer);
|
||||
|
||||
async Task _commit_async(IEnumerable<ManualFillRow> rows) {
|
||||
var updates = new List<ManualFill>();
|
||||
var adds = new List<(ManualFill man_fill, long pl_key)>();
|
||||
foreach (var r in rows) {
|
||||
if (r.is_dirty) {
|
||||
updates.Add(r.to_man_fill());
|
||||
} else if (r.placeholder_key.HasValue) {
|
||||
adds.Add((r.to_man_fill(), r.placeholder_key.Value));
|
||||
}
|
||||
}
|
||||
if (updates.Count > 0)
|
||||
await _model.update_fills(updates);
|
||||
if (adds.Count > 0) {
|
||||
await _model.create_fills(adds.WrapSelector(r => r.man_fill, null));
|
||||
_placeholder_rows
|
||||
.WrapSelector(r => r.placeholder_key.Value, null)
|
||||
.RemoveSorted(adds.Select(r => r.pl_key));
|
||||
}
|
||||
}
|
||||
|
||||
void _import_fills(string file_name) {
|
||||
try {
|
||||
var lines = File.ReadAllLines(file_name);
|
||||
var parsed_fills = ManualFillsParser.parse_rows(
|
||||
lines,
|
||||
MappedDictionary.create(security_map, kv => kv.Value.security),
|
||||
accounts.ToDictionary(a => a.Value.id, a => a.Value.name),
|
||||
users.ToDictionary(u => u.Value.id, u => u.Value.name)
|
||||
).ToList();
|
||||
var conflicts = ManualFillsParser.get_conflicts(parsed_fills);
|
||||
if (conflicts.Values.Any(c => c.Count > 0)) {
|
||||
// init vm
|
||||
var vm = new ManualFillsImportVM(parsed_fills.Count) {
|
||||
security_map = security_map
|
||||
};
|
||||
// add rows
|
||||
foreach (var kv in conflicts) {
|
||||
var assign_del = ManualFillsParser.assign_component(kv.Key);
|
||||
foreach (var text_groups in kv.Value) {
|
||||
var eval = get_error_value(kv.Key);
|
||||
var row = new ManualFillsImportVM.Row(kv.Key, text_groups.Key, text_groups.Value, eval, eval);
|
||||
vm.rows.Add(row);
|
||||
vm.import_request += () => {
|
||||
foreach (var pmf in text_groups.Value)
|
||||
assign_del(pmf.result, row.value);
|
||||
};
|
||||
}
|
||||
}
|
||||
var win = new mbts.ui.spark.ManualFillsImportConflictsDialog {
|
||||
DataContext = vm,
|
||||
Title = ManualFillsWindowFactory.window_name,
|
||||
Topmost = true
|
||||
};
|
||||
vm.RequestClose += (s, e) => Disposable.dispose(ref _import_fills_hooks);
|
||||
vm.import_request += () => {
|
||||
add_imported_fills();
|
||||
Disposable.dispose(ref _import_fills_hooks);
|
||||
};
|
||||
Disposable.replace(ref _import_fills_hooks, Disposable.Create(
|
||||
ObservableAutoWrapper.connect(accounts, vm.accounts),
|
||||
ObservableAutoWrapper.connect(users, vm.users),
|
||||
vm,
|
||||
Disposable.Create(close)
|
||||
));
|
||||
win.move_to_mouse(-200, -75);
|
||||
win.Show();
|
||||
void close() => WindowUtilities.force_close(win);
|
||||
object get_error_value(ManualFillComponent component) {
|
||||
switch (component) {
|
||||
case ManualFillComponent.Price: return double.NaN;
|
||||
case ManualFillComponent.Size: return 0;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
add_imported_fills();
|
||||
}
|
||||
void add_imported_fills() =>
|
||||
_placeholder_rows.InsertSorted(parsed_fills.Select(pmf => new ManualFillRow(new ManualFillData(new ManualFillKey(_placeholder_id_counter++), new ManualFillTuple(new ManualFillImmutable(pmf.result),
|
||||
pmf.result.SecurityKey != 0 && security_map.TryGetValue(pmf.result.SecurityKey, out var smd) ? smd.security : (SecurityImmutable?)null,
|
||||
accounts.FirstOrDefault(a => a?.id == pmf.result.AccountId),
|
||||
users.FirstOrDefault(a => a?.id == pmf.result.UserId))))), _row_key_comparer);
|
||||
} catch (Exception ex) {
|
||||
BTSMessageBox.Error($"Error importing fills: {ex}.", ManualFillsWindowFactory.window_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
<UserControl
|
||||
x:Class="mbts.ui.spark.ManualFillsView"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
>
|
||||
<AdornerDecorator>
|
||||
<Grid>
|
||||
<DataGrid
|
||||
x:Name="data_grid"
|
||||
u:DataGridBehavior.SingleClickEdit="True"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserAddRows="False"
|
||||
CanUserDeleteRows="False"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserSortColumns="True"
|
||||
HeadersVisibility="All"
|
||||
ItemsSource="{Binding rows_view}"
|
||||
MinRowHeight="17"
|
||||
RowHeaderWidth="22"
|
||||
>
|
||||
<!--Row Header Style-->
|
||||
<DataGrid.RowHeaderStyle>
|
||||
<Style TargetType="DataGridRowHeader">
|
||||
<Setter Property="Content" Value="{Binding}"/>
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<Border x:Name="Root">
|
||||
<u:StateManager.VisualState>
|
||||
<MultiBinding Converter="{x:Static usp:ManualFillsView.RowHeaderVisualStateConverter}">
|
||||
<Binding Path="editable_item_state"/>
|
||||
<Binding Path="is_valid"/>
|
||||
</MultiBinding>
|
||||
</u:StateManager.VisualState>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup>
|
||||
<VisualState Name="None"/>
|
||||
<VisualState Name="Modified">
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="pencil" Storyboard.TargetProperty="(UIElement.Opacity)" BeginTime="0" Duration="0" To="0.7"/>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Fill has modified properties."/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="Uncommitted">
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetName="new_item" Storyboard.TargetProperty="(usp:ManualFillsView.ImageBrush).(SolidColorBrush.Color)" BeginTime="0" Duration="0" To="#B3006000"/>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="New Fill."/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="Error">
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="exclamation" Storyboard.TargetProperty="(UIElement.Opacity)" BeginTime="0" Duration="0" To="1"/>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Fill has invalid properties."/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="ErrorUncommitted">
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetName="new_item" Storyboard.TargetProperty="(usp:ManualFillsView.ImageBrush).(SolidColorBrush.Color)" BeginTime="0" Duration="0" To="#CCCD4848"/>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="New Fill has invalid properties."/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid>
|
||||
<Image x:Name="new_item" usp:ManualFillsView.ImageBrush="Transparent" Height="14">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<GeometryDrawing Brush="{Binding Path='(usp:ManualFillsView.ImageBrush)', ElementName=new_item}" Geometry="{Binding Source={StaticResource {x:Static u:Icons.DocumentAddGeometryKey}}}"/>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
<Image x:Name="exclamation" Height="14" Opacity="0">
|
||||
<Image.Source>
|
||||
<Binding Source="{x:Static MessageBoxImage.Exclamation}">
|
||||
<Binding.Converter>
|
||||
<cv:SystemIconConverter/>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
<Image x:Name="pencil" Height="14" Source="{x:Static u:Icons.Pencil}" Opacity="0">
|
||||
<Image.RenderTransform>
|
||||
<RotateTransform Angle="45" CenterX="0" CenterY="7"/>
|
||||
</Image.RenderTransform>
|
||||
</Image>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</DataGrid.RowHeaderStyle>
|
||||
<!--Cell Style-->
|
||||
<DataGrid.CellStyle>
|
||||
<Style TargetType="DataGridCell" BasedOn="{StaticResource DataGridCellBaseStyle}">
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
</Style>
|
||||
</DataGrid.CellStyle>
|
||||
<!--Row Style-->
|
||||
<DataGrid.RowStyle>
|
||||
<Style TargetType="DataGridRow">
|
||||
<Setter Property="IsSelected" Value="{Binding is_selected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource SelectedRowBackground}"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</DataGrid.RowStyle>
|
||||
<DataGrid.Columns>
|
||||
<!--Record ID-->
|
||||
<DataGridTextColumn c:DataGridEx.CellKey="{x:Static RecordID, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="ID" Binding="{Binding record_id_display_text}" SortMemberPath="record_id" IsReadOnly="True"/>
|
||||
<!---Security-->
|
||||
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static Security, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="Security" SortMemberPath="security">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding security}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<c:ContractPickerPopup
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
CloseOnCancel="True"
|
||||
CloseOnOK="True"
|
||||
Header="{Binding security}"
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
SecurityMap="{Binding DataContext.security_map, RelativeSource={RelativeSource AncestorType=DataGrid}, Mode=OneWay}"
|
||||
SelectedSecurity="{Binding security, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
ShowDialogSkirt="True"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<!--Account-->
|
||||
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static Account, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="Account" SortMemberPath="account">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding account}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<ComboBox
|
||||
x:Name="mutable_view"
|
||||
ItemsSource="{Binding DataContext.accounts, RelativeSource={RelativeSource AncestorType=DataGrid}}"
|
||||
SelectedItem="{Binding account, UpdateSourceTrigger=PropertyChanged}"
|
||||
Visibility="Visible"
|
||||
/>
|
||||
<TextBlock
|
||||
x:Name="readonly_view"
|
||||
Text="{Binding account}"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="Collapsed"
|
||||
/>
|
||||
</Grid>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding placeholder_key}" Value="{x:Null}">
|
||||
<Setter TargetName="mutable_view" Property="Visibility" Value="Collapsed"/>
|
||||
<Setter TargetName="readonly_view" Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<!--User-->
|
||||
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static User, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="User" SortMemberPath="user">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding user}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<ComboBox ItemsSource="{Binding DataContext.users, RelativeSource={RelativeSource AncestorType=DataGrid}}" SelectedItem="{Binding user, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<!--Side-->
|
||||
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static Side, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="Side" SortMemberPath="side">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding side}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<ComboBox ItemsSource="{Binding DataContext.sides, RelativeSource={RelativeSource AncestorType=DataGrid}}" SelectedItem="{Binding side, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<!--Size-->
|
||||
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static Size, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="Qty" SortMemberPath="side">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding size}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<c:TickUpDownUInt MinWidth="40" Value="{Binding size, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<!--Price-->
|
||||
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static Price, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="Price" SortMemberPath="price">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock VerticalAlignment="Center">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
|
||||
<Binding Path="price"/>
|
||||
<Binding Path="spec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<usp:PriceEntryControl ContractSpec="{Binding spec}" Value="{Binding price, UpdateSourceTrigger=PropertyChanged}" AlwaysReformatOnSpecChange="True"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,97 @@
|
||||
using bts.shell.manual_fills;
|
||||
using mbts.ui.controls.adorners;
|
||||
using mbts.ui.converters;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for ManualFillsView.xaml
|
||||
/// </summary>
|
||||
partial class ManualFillsView : UserControl {
|
||||
public static readonly IMultiValueConverter
|
||||
RowHeaderVisualStateConverter = ValueConverter.create<EditableItemState, bool, string>(
|
||||
(edit_state, is_valid) => is_valid ? edit_state.ToString() : edit_state == EditableItemState.Uncommitted ? "ErrorUncommitted" : "Error");
|
||||
|
||||
public enum Columns {
|
||||
RecordID,
|
||||
Security,
|
||||
Account,
|
||||
User,
|
||||
Side,
|
||||
Size,
|
||||
Price
|
||||
}
|
||||
|
||||
IDisposable _transaction;
|
||||
|
||||
public ManualFillsView() =>
|
||||
InitializeComponent();
|
||||
|
||||
public static readonly DependencyProperty ImageBrushProperty =
|
||||
DependencyProperty.RegisterAttached("ImageBrush", typeof(Brush), typeof(ManualFillsView));
|
||||
|
||||
public static Brush GetImageBrush(DependencyObject d) => d.GetValue(ImageBrushProperty) as Brush;
|
||||
public static void SetImageBrush(DependencyObject d, Brush value) => d.SetValue(ImageBrushProperty, value);
|
||||
|
||||
public void begin_add_fill() {
|
||||
if (!(DataContext is ManualFillsVM vm))
|
||||
return;
|
||||
Disposable.dispose(ref _transaction);
|
||||
var row = vm.row_factory();
|
||||
var row_editor = new ManualFillRowEditor { DataContext = row };
|
||||
row_editor.CommandBindings.Add(new CommandBinding(ManualFillRowEditor.CommitCommand, commit_executed));
|
||||
row_editor.CommandBindings.Add(new CommandBinding(ManualFillRowEditor.CancelCommand, cancel_executed));
|
||||
// dynamic resources
|
||||
row_editor.Resources[ManualFillRowEditor.AccountsKey] = vm.accounts;
|
||||
row_editor.Resources[ManualFillRowEditor.UsersKey] = vm.users;
|
||||
row_editor.Resources[ManualFillRowEditor.SidesKey] = vm.sides;
|
||||
FocusManager.SetIsFocusScope(row_editor, true);
|
||||
var sec_hook = vm.as_demuxer().subscribe(nameof(ManualFillsVM.security_map)).OnDispatcher().Subscribe(v => row_editor.Resources[ManualFillRowEditor.SecuritiesKey] = v);
|
||||
// popup adorner
|
||||
var popup_adorner = new PopupAdorner(
|
||||
data_grid, data_grid,
|
||||
new Border {
|
||||
Child = row_editor,
|
||||
Background = BTSColors.common_background_brush,
|
||||
BorderBrush = Brushes.Black,
|
||||
BorderThickness = new Thickness(1),
|
||||
Padding = new Thickness(0, 6, 0, 0),
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
VerticalAlignment = VerticalAlignment.Top
|
||||
},
|
||||
HorizontalPosition.Middle,
|
||||
VerticalPosition.Middle,
|
||||
0,
|
||||
22) {
|
||||
OpenRelativeToMouse = true,
|
||||
EnableDragMove = true,
|
||||
ClipToBounds = true
|
||||
};
|
||||
popup_adorner.attach();
|
||||
popup_adorner.IsOpen = true;
|
||||
row_editor.Focus();
|
||||
_transaction = new CompositeDisposable(sec_hook) { destroy_popup };
|
||||
// local methods
|
||||
void commit_executed(object sender, ExecutedRoutedEventArgs e) {
|
||||
vm.add_fill_cmd?.TryExecuteIfCan(row);
|
||||
Disposable.dispose(ref _transaction);
|
||||
}
|
||||
void cancel_executed(object sender, ExecutedRoutedEventArgs e) {
|
||||
row.revert();
|
||||
Disposable.dispose(ref _transaction);
|
||||
}
|
||||
void destroy_popup() {
|
||||
popup_adorner.IsOpen = false;
|
||||
popup_adorner.detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
using bts.api;
|
||||
using bts.shell.manual_fills;
|
||||
using bts.spark;
|
||||
using mbts;
|
||||
using mbts.collections;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.spark;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace bts.shell {
|
||||
class ManualFillsWindowFactory : ShellWindowFactory<ManualFillsSettings> {
|
||||
public const string
|
||||
window_type = "ManualFills",
|
||||
window_name = "Manual Fills";
|
||||
const double _window_min_width = 466;
|
||||
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly ManualFillsConfig _manual_fills_conf = ManualFillsConfig.read();
|
||||
|
||||
public ManualFillsWindowFactory(IShellModule owner, IBus bus, OrdersModel orders_model) : base(owner, window_name) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_dispose = bus.subscribe(SparkTags.ShowManualFillsTag).Subscribe(open_window);
|
||||
//
|
||||
async void open_window(ManualFillsSettings state) =>
|
||||
_bus.post(SparkTags.ShowWindow, await load(state));
|
||||
}
|
||||
|
||||
public override Task<IShellWindow> load(ManualFillsSettings settings) {
|
||||
return Task.FromResult<IShellWindow>(SparkWindow<ManualFillsSettings>.create(
|
||||
window_type, window_name, _bus, this, Icons.DataEdit, _manual_fills_conf.thread_id, thread_init, _window_min_width));
|
||||
//
|
||||
(UIElement, IEnumerable<SparkCommand>, Func<ManualFillsSettings>, IDisposable) thread_init() {
|
||||
var vm = ManualFillsVM.create(_bus, _orders_model);
|
||||
var view = new ManualFillsView { DataContext = vm };
|
||||
view.data_grid.Sorting += on_sorting;
|
||||
IReadOnlyList<SortingCriteria> last_sorting_criterion = Array.Empty<SortingCriteria>();
|
||||
using (apply_sort()) {
|
||||
var col_sort_tuples =
|
||||
from sc in settings.sorting_criterion
|
||||
join col in view.data_grid.Columns on sc.col_name equals _col_id(col)
|
||||
select (col, sc);
|
||||
foreach (var (col, sc) in col_sort_tuples) {
|
||||
col.SortDirection = sc.direction();
|
||||
vm.rows_view.SortDescriptions.Add(new SortDescription(col.SortMemberPath, sc.direction()));
|
||||
}
|
||||
}
|
||||
var cmds = new[] {
|
||||
new SparkCommandCommand("Add New", Icons.AddBoxes, _manual_fills_conf.modal_transactions ? new DelegateCommand(() => view.begin_add_fill()) : vm.add_fill_cmd) { group = CommandGroups.Add },
|
||||
new SparkCommandCommand("Import Fills", Icons.Import, vm.import_fills_cmd) { group = CommandGroups.Add },
|
||||
new SparkCommandCommand("Revert Selected", Icons.UndoCircular,vm.revert_selected_fills_cmd) { group = CommandGroups.Revert, show_in_toolbar = false },
|
||||
new SparkCommandCommand("Revert All", Icons.UndoCircular, vm.revert_all_cmd) { group = CommandGroups.Revert },
|
||||
new SparkCommandCommand("Save Selected", Icons.Disk, vm.commit_selected_fills_cmd) { group = CommandGroups.Save, show_in_toolbar = false },
|
||||
new SparkCommandCommand("Save All", Icons.Disk, vm.commit_all_fills_cmd) { group = CommandGroups.Save },
|
||||
new SparkCommandCommand("Delete Selected", Icons.Trash, vm.remove_selected_fills_cmd)
|
||||
};
|
||||
return (view, cmds, get_settings, Disposable.Create(
|
||||
vm.rows_view.subscribe_sorting_criterion(sm => view.data_grid.Columns.TryGetFirst(dc => dc.SortMemberPath == sm, out DataGridColumn match) ? _col_id(match) : null)
|
||||
.Subscribe(sc => last_sorting_criterion = sc),
|
||||
vm));
|
||||
//
|
||||
void on_sorting(object sender, DataGridSortingEventArgs e) {
|
||||
e.Handled = true;
|
||||
using (apply_sort()) {
|
||||
var new_sort_desc = new SortDescription(e.Column.SortMemberPath, e.Column.SortDirection == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending);
|
||||
e.Column.SortDirection = new_sort_desc.Direction;
|
||||
if (KeyboardExtensions.IsShiftDown()) {
|
||||
if (vm.rows_view.SortDescriptions.TryGetFirst(sd => sd.PropertyName == e.Column.SortMemberPath, out var match, out int index)) {
|
||||
vm.rows_view.SortDescriptions[index] = new_sort_desc;
|
||||
} else {
|
||||
vm.rows_view.SortDescriptions.Add(new_sort_desc);
|
||||
}
|
||||
} else {
|
||||
vm.rows_view.SortDescriptions.Clear();
|
||||
foreach (var col in view.data_grid.Columns.Except(new[] { e.Column }))
|
||||
col.SortDirection = null;
|
||||
vm.rows_view.SortDescriptions.Add(new_sort_desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Ensures that the primary sort description property (i.e., at index 0) is the placeholder_key.
|
||||
/// This behavior has the effect of grouping placeholder rows at the bottom, without using <see cref="ICollectionView.GroupDescriptions"/> (which is incompatible with row virtualization).
|
||||
IDisposable apply_sort() =>
|
||||
Disposable.Create(() => {
|
||||
var pk_sort_desc = new SortDescription(nameof(ManualFillRow.placeholder_key), ListSortDirection.Ascending);
|
||||
if (vm.rows_view.SortDescriptions.Count > 0) {
|
||||
if (vm.rows_view.SortDescriptions[0].Equals(pk_sort_desc))
|
||||
return;
|
||||
vm.rows_view.SortDescriptions.removeFirst(sd => sd.PropertyName == pk_sort_desc.PropertyName);
|
||||
}
|
||||
vm.rows_view.SortDescriptions.Insert(0, pk_sort_desc);
|
||||
});
|
||||
|
||||
ManualFillsSettings get_settings() =>
|
||||
new ManualFillsSettings(last_sorting_criterion);
|
||||
}
|
||||
}
|
||||
|
||||
static string _col_id(DataGridColumn col) =>
|
||||
DataGridEx.GetCellKey(col)?.ToString();
|
||||
|
||||
readonly struct ManualFillsConfig {
|
||||
public readonly bool modal_transactions;
|
||||
public readonly string thread_id;
|
||||
|
||||
ManualFillsConfig(string thread_id, bool modal_transactions) {
|
||||
this.thread_id = thread_id;
|
||||
this.modal_transactions = modal_transactions;
|
||||
}
|
||||
|
||||
public static ManualFillsConfig read() => new ManualFillsConfig(
|
||||
mbts.config.Configuration.get<bts.config.MultiThreadedWindows>().manual_fills ? window_type : null,
|
||||
mbts.config.Configuration.get<bts.config.ManualFillsOptions>().modal_transactions);
|
||||
}
|
||||
|
||||
static class CommandGroups {
|
||||
public const string
|
||||
Add = "Add",
|
||||
Save = "Save",
|
||||
Revert = "Revert";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ 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 {
|
||||
ListSortDirection? SortDirection { get; set; }
|
||||
IDataGridColumnFilter<OrderSecurityAlgoMeta> filter { get; }
|
||||
IDataGridColumnFilter<OrderSecurity> filter { get; }
|
||||
IComparer<OrderBookRowVM> comparer { get; }
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace mbts.spark {
|
||||
public OrderBookColumnCollection(IBus bus, OrdersModel model, IObservable<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)> order_securities) {
|
||||
// security
|
||||
Add(security_column = new SecurityColumn(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, SecurityImmutable, SecurityCriteria>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, SecurityImmutable, SecurityCriteria>(
|
||||
order_securities,
|
||||
ort => ort.security ?? SecurityDataHelper.Defaults.SecurityImmutable,
|
||||
SecurityCriteria.from_securities,
|
||||
@@ -85,7 +85,7 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.Symbol,
|
||||
r => r.symbol,
|
||||
DefaultColumnWidths.Symbol,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, string>(
|
||||
mbts.co2.UserSymbol.get_all().Select(u => u.symbol).Distinct().ToList(),
|
||||
ort => ort.security?.Symbol ?? string.Empty,
|
||||
nameof(OrderBookRowVM.symbol)
|
||||
@@ -98,7 +98,7 @@ namespace mbts.spark {
|
||||
"Time",
|
||||
r => r.time_created_text,
|
||||
DefaultColumnWidths.Time,
|
||||
new TimeFilterVM<OrderSecurityAlgoMeta>(
|
||||
new TimeFilterVM<OrderSecurity>(
|
||||
ort => ort.order.created(),
|
||||
nameof(OrderBookRowVM.time_created)),
|
||||
DataGridFilterKnownTemplates.TimeFilter,
|
||||
@@ -109,7 +109,7 @@ namespace mbts.spark {
|
||||
"Last Change",
|
||||
r => r.last_change_text,
|
||||
DefaultColumnWidths.Time,
|
||||
new TimeFilterVM<OrderSecurityAlgoMeta>(
|
||||
new TimeFilterVM<OrderSecurity>(
|
||||
ort => ort.order.last_change(),
|
||||
nameof(OrderBookRowVM.last_change)),
|
||||
DataGridFilterKnownTemplates.TimeFilter,
|
||||
@@ -120,7 +120,7 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.User,
|
||||
r => r.user,
|
||||
DefaultColumnWidths.User,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, string>(
|
||||
model.factory.subscribe_users().Select(d => d.Values),
|
||||
ort => ort.order.user(),
|
||||
nameof(OrderBookRowVM.user)),
|
||||
@@ -132,7 +132,7 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.Account,
|
||||
r => r.account,
|
||||
DefaultColumnWidths.Account,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string, string[]>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, string, string[]>(
|
||||
model.factory.subscribe_accounts().Select(d => d.Values),
|
||||
ort => ort.order.account(),
|
||||
vals => vals.ToArray(),
|
||||
@@ -146,7 +146,7 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.Exchange,
|
||||
r => r.exchange.ToString(),
|
||||
DefaultColumnWidths.Exchange,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, Venue>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, Venue>(
|
||||
EnumHelper.get_values_except(Venue.Unset).ToList(),
|
||||
ort => ort.security.exchange_or_default(),
|
||||
nameof(OrderBookRowVM.exchange)),
|
||||
@@ -158,7 +158,7 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.TimeInForce,
|
||||
r => r.time_in_force.ToString(),
|
||||
DefaultColumnWidths.TimeInForce,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, TimeInForce>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, TimeInForce>(
|
||||
EnumHelper.get_values<TimeInForce>().ToList(),
|
||||
ort => ort.order.time_in_force(),
|
||||
nameof(OrderBookRowVM.time_in_force)),
|
||||
@@ -174,9 +174,8 @@ namespace mbts.spark {
|
||||
nameof(OrderBookRowVM.badge_fill),
|
||||
nameof(OrderBookRowVM.display_name),
|
||||
_wrap_comparer(r => r.order_type),
|
||||
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurityAlgoMeta, AlgoDataVM, AlgoID>(
|
||||
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurity, AlgoDataVM, AlgoID>(
|
||||
o => new AlgoDataVM(o.algo.GetValueOrDefault()),
|
||||
o => AlgoID.from_order_def(o.order.Def),
|
||||
vm => AlgoID.from_algo_key(vm.data.key), out var order_type_filter_observer, out var order_type_items_view)));
|
||||
_resources.Add(algo_items_source.Subscribe(order_type_filter_observer));
|
||||
order_type_items_view.CustomSort = algo_items_comp;
|
||||
@@ -187,9 +186,8 @@ namespace mbts.spark {
|
||||
nameof(OrderBookRowVM.root_algo_badge_fill),
|
||||
nameof(OrderBookRowVM.root_algo_display_name),
|
||||
_wrap_comparer(r => r.order_tuple.root_algo.value.CustomCase),
|
||||
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurityAlgoMeta, AlgoDataVM, AlgoID>(
|
||||
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurity, AlgoDataVM, AlgoID>(
|
||||
o => new AlgoDataVM(o.root_algo),
|
||||
o => AlgoID.from_algo_key(o.root_algo.key),
|
||||
vm => AlgoID.from_algo_key(vm.data.key), out var root_algo_type_observer, out var root_algo_type_items_view),
|
||||
DefaultColumnHeaders.RootAlgoType));
|
||||
_resources.Add(algo_items_source.Subscribe(root_algo_type_observer));
|
||||
@@ -201,7 +199,7 @@ namespace mbts.spark {
|
||||
"Sub Type",
|
||||
r => r.order_sub_type_text,
|
||||
80,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, OrderSubType>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, OrderSubType>(
|
||||
EnumHelper.get_values<OrderSubType>().ToList(),
|
||||
ort => ort.order.sub_type(),
|
||||
nameof(OrderBookRowVM.order_sub_type)),
|
||||
@@ -209,27 +207,27 @@ namespace mbts.spark {
|
||||
_wrap_comparer(r => r.order_sub_type)));
|
||||
// side
|
||||
Add(side_column = new SideColumn(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, Side>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, Side>(
|
||||
new[] { Side.Buy, Side.Sell },
|
||||
ort=> ort.order.side(),
|
||||
ort => ort.order.side(),
|
||||
nameof(OrderBookRowVM.side))));
|
||||
// quantity
|
||||
Add(qty_column = new SizeColumn(
|
||||
new UIntMinMax<OrderSecurityAlgoMeta>(ort => ort.order.quantity(), nameof(OrderBookRowVM.quantity))));
|
||||
new UIntMinMax<OrderSecurity>(ort => ort.order.quantity(), nameof(OrderBookRowVM.quantity))));
|
||||
// edit quantity column
|
||||
// not usually part of the main order book, but it's useful to add it
|
||||
// rather than debugging the popup window
|
||||
//Add(edit_qty_column = new EditableQtyColumn());
|
||||
// filled quantity
|
||||
Add(filled_qty_column = new FilledColumn(
|
||||
new UIntMinMax<OrderSecurityAlgoMeta>(ort => ort.order.filled(), nameof(OrderBookRowVM.filled_quantity))));
|
||||
new UIntMinMax<OrderSecurity>(ort => ort.order.filled(), nameof(OrderBookRowVM.filled_quantity))));
|
||||
// price
|
||||
Add(price_column = new SimpleTextColumn<DoubleMinMaxState>(
|
||||
Columns.Price,
|
||||
"Price",
|
||||
r => r.price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new PriceMinMax<OrderSecurityAlgoMeta>(
|
||||
new PriceMinMax<OrderSecurity>(
|
||||
ort => ort.order.price(),
|
||||
nameof(OrderBookRowVM.price)),
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
@@ -245,7 +243,7 @@ namespace mbts.spark {
|
||||
"Avg Price",
|
||||
r => r.avg_price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new PriceMinMax<OrderSecurityAlgoMeta>(
|
||||
new PriceMinMax<OrderSecurity>(
|
||||
ort => ort.order.avg_price(),
|
||||
nameof(OrderBookRowVM.avg_price)),
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
@@ -256,18 +254,21 @@ namespace mbts.spark {
|
||||
"Last Filled",
|
||||
r => r.last_price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new PriceMinMax<OrderSecurityAlgoMeta>(
|
||||
new PriceMinMax<OrderSecurity>(
|
||||
ort => ort.order.last_price(),
|
||||
nameof(OrderBookRowVM.last_price)),
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
_wrap_comparer(r => r.last_price)));
|
||||
// piq
|
||||
Add(piq_column = new PiqColumn(
|
||||
new UIntMinMax<OrderSecurity>(ort => ort.piq ?? uint.MaxValue, nameof(OrderBookRowVM.piq))));
|
||||
// status
|
||||
Add(status_column = new SimpleTextColumn<OrderStatus[]>(
|
||||
Columns.Status,
|
||||
"Status",
|
||||
r => r.status.ToString(),
|
||||
80,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, OrderStatus>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, OrderStatus>(
|
||||
EnumHelper.get_values<OrderStatus>().ToList(),
|
||||
ort => ort.order.status(),
|
||||
nameof(OrderBookRowVM.status)),
|
||||
@@ -275,7 +276,7 @@ namespace mbts.spark {
|
||||
_wrap_comparer(r => r.status)));
|
||||
// last message
|
||||
Add(last_message_column = new OrderBookLastMessageColumn(
|
||||
new RegexFilterVM<OrderSecurityAlgoMeta>(
|
||||
new RegexFilterVM<OrderSecurity>(
|
||||
ort => ort.order.last_msg(),
|
||||
nameof(OrderBookRowVM.last_msg))));
|
||||
// security key
|
||||
@@ -284,7 +285,7 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.SecurityKey,
|
||||
r => r.security_key.ToString(),
|
||||
DefaultColumnWidths.SecurityKey,
|
||||
new RegexFilterVM<OrderSecurityAlgoMeta>(
|
||||
new RegexFilterVM<OrderSecurity>(
|
||||
ort => ort.order.security_key().ToString(),
|
||||
nameof(OrderBookRowVM.security_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
@@ -295,7 +296,7 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.OrderKey,
|
||||
r => r.order_key.ToString(),
|
||||
DefaultColumnWidths.OrderKey,
|
||||
new RegexFilterVM<OrderSecurityAlgoMeta>(
|
||||
new RegexFilterVM<OrderSecurity>(
|
||||
ort => ort.order.Key.ToString(),
|
||||
nameof(OrderBookRowVM.order_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
@@ -306,7 +307,7 @@ namespace mbts.spark {
|
||||
"Parent Key",
|
||||
r => r.parent_order_key.ToString(),
|
||||
DefaultColumnWidths.ParentKey,
|
||||
new RegexFilterVM<OrderSecurityAlgoMeta>(
|
||||
new RegexFilterVM<OrderSecurity>(
|
||||
ort => ort.order.ParentKey.ToString(),
|
||||
nameof(OrderBookRowVM.parent_order_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
@@ -317,7 +318,7 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.AttachedTargetOrderKey,
|
||||
r => r.order.attached_target_order_key().ToString(),
|
||||
DefaultColumnWidths.AttachedTargetOrderKey,
|
||||
new RegexFilterVM<OrderSecurityAlgoMeta>(
|
||||
new RegexFilterVM<OrderSecurity>(
|
||||
ort => ort.order.attached_target_order_key().ToString(),
|
||||
nameof(OrderBookRowVM.parent_order_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
@@ -350,6 +351,7 @@ namespace mbts.spark {
|
||||
public SimpleTextColumn<string> order_key_column { get; }
|
||||
public SimpleTextColumn<string> parent_key_column { get; }
|
||||
public SimpleTextColumn<string> attached_target_order_key_column { get; }
|
||||
public PiqColumn piq_column { get; }
|
||||
|
||||
public FilterStates create_filter_states() =>
|
||||
new FilterStates {
|
||||
@@ -374,7 +376,8 @@ namespace mbts.spark {
|
||||
exchange_message_regex = last_message_column.filter.get_state(),
|
||||
last_change = last_change_column.filter.get_state(),
|
||||
security_criteria = security_column.filter.get_state(),
|
||||
symbols = symbol_column.filter.get_state()
|
||||
symbols = symbol_column.filter.get_state(),
|
||||
piq = piq_column.filter.get_state()
|
||||
};
|
||||
|
||||
public (FilterStates filters, IReadOnlyList<SortingCriteria> sorting) create() =>
|
||||
@@ -404,6 +407,7 @@ namespace mbts.spark {
|
||||
status_column.filter.load_state(fs.statuses);
|
||||
last_message_column.filter.load_state(fs.exchange_message_regex);
|
||||
last_change_column.filter.load_state(fs.last_change);
|
||||
piq_column.filter.load_state(fs.piq);
|
||||
}
|
||||
_active_comparers.Clear();
|
||||
foreach (var col in this)
|
||||
@@ -424,9 +428,9 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public Func<OrderSecurityAlgoMeta, bool> get_filter() {
|
||||
public Func<OrderSecurity, bool> get_filter() {
|
||||
var active_filters = this.OfType<ICVOrderBookColumn>().Where(c => c.filter.active.GetValueOrDefault()).Select(c => c.filter).ToArray();
|
||||
return active_filters.Length == 0 ? null : new Func<OrderSecurityAlgoMeta, bool>(r => active_filters.All(f => f.filter(r)));
|
||||
return active_filters.Length == 0 ? null : new Func<OrderSecurity, bool>(r => active_filters.All(f => f.filter(r)));
|
||||
}
|
||||
|
||||
public OrderBookSortingData get_sorting_data() {
|
||||
@@ -527,7 +531,7 @@ namespace mbts.spark {
|
||||
GlyphContext _root_glyph_context, _child_glyph_context;
|
||||
|
||||
protected OrderbookColumnBase(Columns col_key, string header, Func<OrderBookRowVM, string> get_text, double width,
|
||||
IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, T> filter, string filter_template, IComparer<OrderBookRowVM> comparer) : base(col_key) {
|
||||
IDataGridColumnPersistentFilter<OrderSecurity, T> filter, string filter_template, IComparer<OrderBookRowVM> comparer) : base(col_key) {
|
||||
id = key.ToString();
|
||||
Width = width;
|
||||
Header = header;
|
||||
@@ -544,10 +548,10 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, T> filter { get; }
|
||||
public IDataGridColumnPersistentFilter<OrderSecurity, T> filter { get; }
|
||||
public IComparer<OrderBookRowVM> comparer { get; }
|
||||
|
||||
IDataGridColumnFilter<OrderSecurityAlgoMeta> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter<OrderSecurity> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter IFilterableColumn.filter => filter;
|
||||
string IFilterableColumn.filter_template => _filter_template;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
|
||||
@@ -561,7 +565,7 @@ namespace mbts.spark {
|
||||
|
||||
public class SimpleTextColumn<TFilterState> : OrderbookColumnBase<TFilterState>, IRenderColumn<OrderBookRowVM> {
|
||||
public SimpleTextColumn(Columns key, string header, Func<OrderBookRowVM, string> get_text, double width,
|
||||
IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, TFilterState> filter, string filter_template, IComparer<OrderBookRowVM> comparer)
|
||||
IDataGridColumnPersistentFilter<OrderSecurity, TFilterState> filter, string filter_template, IComparer<OrderBookRowVM> comparer)
|
||||
: base(key, header, get_text, width, filter, filter_template, comparer) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
@@ -571,7 +575,7 @@ namespace mbts.spark {
|
||||
|
||||
#region Explicit Column Defs
|
||||
public class SideColumn : OrderbookColumnBase<Side[]>, IRenderColumn<OrderBookRowVM> {
|
||||
public SideColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, Side[]> filter)
|
||||
public SideColumn(IDataGridColumnPersistentFilter<OrderSecurity, Side[]> filter)
|
||||
: base(Columns.Side, "B/S", r => r.side.ToString(), DefaultColumnWidths.Side, filter, DataGridFilterKnownTemplates.EnumValues, _wrap_comparer(r => r.side)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
@@ -580,7 +584,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public class SizeColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public SizeColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, UIntMinMaxState> filter)
|
||||
public SizeColumn(IDataGridColumnPersistentFilter<OrderSecurity, UIntMinMaxState> filter)
|
||||
: base(Columns.Size, "Qty", r => r.quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.quantity)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
@@ -589,7 +593,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public class FilledColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public FilledColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, UIntMinMaxState> filter)
|
||||
public FilledColumn(IDataGridColumnPersistentFilter<OrderSecurity, UIntMinMaxState> filter)
|
||||
: base(Columns.Filled, "Filled Qty", r => r.filled_quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.filled_quantity)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
@@ -601,7 +605,7 @@ namespace mbts.spark {
|
||||
readonly BindingBase _foreground_binding;
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
|
||||
public OrderBookOrderTypeColumn(Columns key, string badge_fill_binding_path, string display_name_binding_path, IComparer<OrderBookRowVM> row_comparer, IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, AlgoID[]> filter, string header = DefaultColumnHeaders.OrderType)
|
||||
public OrderBookOrderTypeColumn(Columns key, string badge_fill_binding_path, string display_name_binding_path, IComparer<OrderBookRowVM> row_comparer, IDataGridColumnPersistentFilter<OrderSecurity, AlgoID[]> filter, string header = DefaultColumnHeaders.OrderType)
|
||||
: base(key, new Binding(badge_fill_binding_path), new Binding(display_name_binding_path), filter, header) {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
@@ -611,11 +615,11 @@ namespace mbts.spark {
|
||||
this.comparer = row_comparer;
|
||||
}
|
||||
|
||||
public new IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, AlgoID[]> filter { get; }
|
||||
public new IDataGridColumnPersistentFilter<OrderSecurity, AlgoID[]> filter { get; }
|
||||
public override string filter_template => DataGridFilterKnownTemplates.AlgoType;
|
||||
public IComparer<OrderBookRowVM> comparer { get; }
|
||||
|
||||
IDataGridColumnFilter<OrderSecurityAlgoMeta> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter<OrderSecurity> ICVOrderBookColumn.filter => filter;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
|
||||
|
||||
public override FrameworkElement view_factory() {
|
||||
@@ -633,7 +637,7 @@ namespace mbts.spark {
|
||||
public class OrderBookLastMessageColumn : TextColumn<Columns>, ICVOrderBookColumn {
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
|
||||
public OrderBookLastMessageColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, string> filter)
|
||||
public OrderBookLastMessageColumn(IDataGridColumnPersistentFilter<OrderSecurity, string> filter)
|
||||
: base(Columns.LastMessage, new Binding(nameof(OrderBookRowVM.last_msg)), filter, DataGridFilterKnownTemplates.Regex) {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
@@ -642,10 +646,10 @@ namespace mbts.spark {
|
||||
tooltip_binding = new Binding(nameof(OrderBookRowVM.last_msg));
|
||||
}
|
||||
|
||||
public new IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, string> filter { get; }
|
||||
public new IDataGridColumnPersistentFilter<OrderSecurity, string> filter { get; }
|
||||
public IComparer<OrderBookRowVM> comparer => _wrap_comparer(r => r.last_msg);
|
||||
|
||||
IDataGridColumnFilter<OrderSecurityAlgoMeta> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter<OrderSecurity> ICVOrderBookColumn.filter => filter;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
|
||||
|
||||
protected override void OnSortDirectionChanged(ListSortDirection? new_value) {
|
||||
@@ -654,6 +658,15 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public class PiqColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public PiqColumn(IDataGridColumnPersistentFilter<OrderSecurity, UIntMinMaxState> filter)
|
||||
: base(Columns.Piq, "PIQ", r => r.piq_text, DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.piq)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.piq_text, position, row_height,
|
||||
RenderUtils.get_foreground(row, font_brush), null, HorizontalAlignment.Right, VerticalAlignment.Center, DefaultCellPadding);
|
||||
}
|
||||
|
||||
public class SecurityColumn : FrameworkElementColumnBase<Columns>, ICVOrderBookColumn, IFilterableColumn {
|
||||
readonly BindingBase _font_weight_binding = RenderUtils.get_font_weight_binding(), _foreground_binding;
|
||||
readonly IValueConverter _expander_visiblilty_converter = new ui.converters.BooleanToVisibilityConverter { VisibilityForFalse = Visibility.Hidden };
|
||||
@@ -661,7 +674,7 @@ namespace mbts.spark {
|
||||
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
|
||||
public SecurityColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, SecurityCriteria> filter) : base(Columns.Security) {
|
||||
public SecurityColumn(IDataGridColumnPersistentFilter<OrderSecurity, SecurityCriteria> filter) : base(Columns.Security) {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
_foreground_binding = RenderUtils.get_foreground_binding(this);
|
||||
@@ -670,10 +683,10 @@ namespace mbts.spark {
|
||||
_cached_sort_direction = SortDirection;
|
||||
}
|
||||
|
||||
public IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, SecurityCriteria> filter { get; }
|
||||
public IDataGridColumnPersistentFilter<OrderSecurity, SecurityCriteria> filter { get; }
|
||||
public IComparer<OrderBookRowVM> comparer => _wrap_comparer(r => r.security, SecurityHelper.Comparer.InstanceImmutable.to_nullable());
|
||||
|
||||
IDataGridColumnFilter<OrderSecurityAlgoMeta> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter<OrderSecurity> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter IFilterableColumn.filter => filter;
|
||||
string IFilterableColumn.filter_template => DataGridFilterKnownTemplates.DiscreteValues;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
|
||||
|
||||
@@ -14,7 +14,7 @@ using System.Windows.Media;
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class OrderBookRowVM : IEquatable<OrderBookRowVM>, DIYGrid.IRowVM, IHierarchicalRow<OrderSecurityAlgoMeta>, INotifyPropertyChanged {
|
||||
partial class OrderBookRowVM : IEquatable<OrderBookRowVM>, DIYGrid.IRowVM, IHierarchicalRow<OrderSecurity>, INotifyPropertyChanged {
|
||||
const string _unknown_security_price_format_string = "0.#########";
|
||||
const int _unknown_security_price_num_fractional_digits = 9;
|
||||
readonly OrderBookVM _owner;
|
||||
@@ -28,7 +28,7 @@ namespace mbts.spark {
|
||||
uint _prospective_quantity;
|
||||
double _prospective_price;
|
||||
|
||||
public OrderBookRowVM(OrderBookVM owner, OrderSecurityAlgoMeta order_tuple, bool is_expanded) {
|
||||
public OrderBookRowVM(OrderBookVM owner, OrderSecurity order_tuple, bool is_expanded) {
|
||||
_owner = owner;
|
||||
this.order_tuple = order_tuple;
|
||||
is_visible = !is_child;
|
||||
@@ -43,19 +43,19 @@ namespace mbts.spark {
|
||||
//// 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>(
|
||||
public IObservable<OrderSecurity> subscribe_data() {
|
||||
return Observable.FromEvent<Action, OrderSecurity>(
|
||||
cb => () => cb(order_tuple),
|
||||
cb => invalidated += cb,
|
||||
cb => invalidated -= cb);
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
public event Action<(OrderSecurityAlgoMeta oldval, OrderSecurityAlgoMeta newval)> updated;
|
||||
public event Action<(OrderSecurity oldval, OrderSecurity newval)> updated;
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
// Core
|
||||
public OrderSecurityAlgoMeta order_tuple { get; private set; }
|
||||
public OrderSecurity order_tuple { get; private set; }
|
||||
public OrderRecordImmutable order => order_tuple.order;
|
||||
public OrderMetadata order_metadata => _owner.get_metadata(this);
|
||||
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(order_tuple.meta, order_metadata));
|
||||
@@ -91,6 +91,8 @@ namespace mbts.spark {
|
||||
public uint quantity => order.quantity();
|
||||
public uint filled_quantity => order.filled();
|
||||
public string last_msg => order.last_msg();
|
||||
public uint piq => order_tuple.piq ?? uint.MaxValue;
|
||||
public string piq_text => piq == uint.MaxValue ? string.Empty : piq.ToString();
|
||||
public bool is_non_terminal => order.is_non_terminal();
|
||||
public bool is_attachable { get => _is_attachable; set => SetVisual(ref _is_attachable, value); }
|
||||
public bool is_detachable { get => _is_detachable; set => SetVisual(ref _is_detachable, value); }
|
||||
@@ -134,14 +136,14 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool is_qty_dirty => this.prospective_quantity != this.quantity;
|
||||
public bool is_qty_dirty => this.prospective_quantity != this.quantity;
|
||||
public bool is_price_dirty => this.prospective_price != this.price;
|
||||
|
||||
// Commands
|
||||
public ICommand toggle_command => _toggle_expanded_cmd ?? (
|
||||
_toggle_expanded_cmd = new DelegateCommand(() => is_expanded = !is_expanded));
|
||||
|
||||
public ICommand show_order_modify_cmd =>
|
||||
public ICommand show_order_modify_cmd =>
|
||||
_show_order_modify_cmd ?? (_show_order_modify_cmd = new DelegateCommand(() => _owner.show_order_modify(this), () => status == OrderStatus.Working));
|
||||
|
||||
public ICommand cancel_command => _cancel_cmd ??
|
||||
@@ -195,7 +197,7 @@ namespace mbts.spark {
|
||||
|
||||
public ICommand update_command => _update_command ?? (
|
||||
_update_command = new DelegateCommand(() => {
|
||||
_select_me();
|
||||
_select_me();
|
||||
})
|
||||
);
|
||||
|
||||
@@ -214,7 +216,7 @@ namespace mbts.spark {
|
||||
bool IHierarchicalRow.is_parent => is_parent;
|
||||
bool IHierarchicalRow.is_expanded => is_expanded;
|
||||
int IHierarchicalRow.depth_level => depth_level;
|
||||
OrderSecurityAlgoMeta IHierarchicalRow<OrderSecurityAlgoMeta>.value => order_tuple;
|
||||
OrderSecurity IHierarchicalRow<OrderSecurity>.value => order_tuple;
|
||||
|
||||
void IHierarchicalRow.on_is_parent_changed(bool is_parent) => this.is_parent = is_parent;
|
||||
/// Do NOT actually call the <see cref="is_expanded"/> setter (it has side effects) - the row collection calls this method when the row is expanded/collapsed by some **external means, outside of this class.
|
||||
@@ -226,7 +228,7 @@ namespace mbts.spark {
|
||||
/// <see cref="OperableHierarchicalCollection{TRow, T, TKey}.collapse_row(TKey)"/>
|
||||
void IHierarchicalRow.on_is_expanded_changed(bool is_expanded) => Set(ref _is_expanded, is_expanded, nameof(is_expanded));
|
||||
void IHierarchicalRow.on_depth_level_changed(int depth_level) => this.depth_level = depth_level;
|
||||
void IHierarchicalRow<OrderSecurityAlgoMeta>.on_value_changed(OrderSecurityAlgoMeta value) => update(value);
|
||||
void IHierarchicalRow<OrderSecurity>.on_value_changed(OrderSecurity value) => update(value);
|
||||
#endregion
|
||||
|
||||
private void _unselect_me() {
|
||||
@@ -244,7 +246,7 @@ namespace mbts.spark {
|
||||
internal void expand_internal() => Set(ref _is_expanded, true, nameof(is_expanded));
|
||||
internal void collapse_internal() => Set(ref _is_expanded, false, nameof(is_expanded));
|
||||
|
||||
public void update(OrderSecurityAlgoMeta order_tuple) {
|
||||
public void update(OrderSecurity order_tuple) {
|
||||
Log.assert(order_tuple.order.Key == this.order.Key, "OrderBookRowVM: update_order was called with an order with the wrong key.");
|
||||
// _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);
|
||||
@@ -257,11 +259,11 @@ namespace mbts.spark {
|
||||
// quantity
|
||||
if ((Columns.Size & dirty) != 0)
|
||||
RaisePropertyChanged(nameof(quantity), nameof(is_qty_dirty));
|
||||
// we need to update the prospective_quantity here b/c an update could have come in while the row was in edit mode
|
||||
// via the Mini OB update column mechanism (vs the EditableQty column)
|
||||
// prospecive price does not need to be updated in the same manner b/c if the price is changed, the whole
|
||||
// row will no longer match the filter
|
||||
prospective_quantity = quantity;
|
||||
// we need to update the prospective_quantity here b/c an update could have come in while the row was in edit mode
|
||||
// via the Mini OB update column mechanism (vs the EditableQty column)
|
||||
// prospecive price does not need to be updated in the same manner b/c if the price is changed, the whole
|
||||
// row will no longer match the filter
|
||||
prospective_quantity = quantity;
|
||||
// price
|
||||
if ((Columns.Price & dirty) != 0) {
|
||||
RaisePropertyChanged(nameof(price));
|
||||
@@ -314,6 +316,9 @@ namespace mbts.spark {
|
||||
// this will update the cancel column in the mini ob
|
||||
cancel_command.TryRaiseCanExecuteChanged();
|
||||
}
|
||||
if ((Columns.Piq & dirty) != 0) {
|
||||
RaisePropertyChanged(nameof(piq), nameof(piq_text));
|
||||
}
|
||||
// always refresh attach and detach b/c
|
||||
// conditionally raise invalidated
|
||||
if ((dirty & ViewRefresh.invalidate_mask) != 0)
|
||||
@@ -374,13 +379,14 @@ namespace mbts.spark {
|
||||
Columns.Status |
|
||||
Columns.ParentKey |
|
||||
Columns.AttachedTargetOrderKey |
|
||||
Columns.LastMessage,
|
||||
Columns.LastMessage |
|
||||
Columns.Piq,
|
||||
filled_status_desc_mask =
|
||||
Columns.Filled |
|
||||
Columns.AvgFilled |
|
||||
Columns.Status;
|
||||
|
||||
public static Columns get_dirty_cells(OrderSecurityAlgoMeta oldval, OrderSecurityAlgoMeta newval) {
|
||||
public static Columns get_dirty_cells(OrderSecurity oldval, OrderSecurity newval) {
|
||||
var dirty = (Columns)0;
|
||||
if (oldval.security != newval.security)
|
||||
dirty |= Columns.Security;
|
||||
@@ -404,8 +410,11 @@ namespace mbts.spark {
|
||||
dirty |= Columns.Status;
|
||||
if (oldval.order.last_msg() != newval.order.last_msg())
|
||||
dirty |= Columns.LastMessage;
|
||||
if (oldval.piq != newval.piq) {
|
||||
dirty |= Columns.Piq;
|
||||
}
|
||||
return dirty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,11 @@ using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.md;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.shell.orderbook;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.persist;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
@@ -42,10 +41,10 @@ namespace mbts.spark {
|
||||
readonly OrdersModel _model;
|
||||
readonly Property<OrderBookRowVM> _current_row;
|
||||
readonly DelegateProperty<bool> _show_order_params, _show_ladder, _show_fills, _can_attach_selected_orders, _can_detach_selected_orders;
|
||||
readonly OperableHierarchicalCollection<OrderBookRowVM, OrderSecurityAlgoMeta, uint> _row_collection;
|
||||
readonly OperableHierarchicalCollection<OrderBookRowVM, OrderSecurity, uint> _row_collection;
|
||||
readonly OrderBookColumnCollection _columns;
|
||||
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
|
||||
readonly IWindowSettingsManager<GridSettings> _fills_settings_manager = new WindowSettingsManager<GridSettings>.DisconnectedImpl(FillsWindowFactory.StartupData.MiniGridSettings, FillsWindowFactory.StartupData.MiniGridSettings);
|
||||
readonly IWindowSettingsManager<GridSettings> _fills_settings_manager = new WindowSettingsManager<GridSettings>.DisconnectedImpl(FillsSettings.MiniGridSettings, FillsSettings.MiniGridSettings);
|
||||
readonly SelectedOrderParamsMon _selected_params_mon;
|
||||
readonly RightPanelModel _right_panel = new RightPanelModel();
|
||||
OrderBookFillsGridSettings _cached_fills_settings = OrderBookStartupData.DefaultFillsGrid;
|
||||
@@ -58,7 +57,7 @@ namespace mbts.spark {
|
||||
_show_right_panel = OrderBookStartupData.Default.show_right_panel,
|
||||
_is_attaching_orders,
|
||||
_is_row_collection_populated;
|
||||
IReadOnlyDictionary<uint, OrderSecurityAlgoMeta> _cached_orders = DictionaryHelper.empty<uint, OrderSecurityAlgoMeta>();
|
||||
IReadOnlyDictionary<uint, OrderSecurity> _cached_orders = DictionaryHelper.empty<uint, OrderSecurity>();
|
||||
IReadOnlyDictionary<uint, bool> _cached_attachable = DictionaryHelper.empty<uint, bool>(), _cached_detachable = DictionaryHelper.empty<uint, bool>();
|
||||
HashSet<uint>
|
||||
_attachable_selected_orders = new HashSet<uint>(),
|
||||
@@ -98,7 +97,7 @@ namespace mbts.spark {
|
||||
_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 OperableHierarchicalCollection<OrderBookRowVM, OrderSecurityAlgoMeta, uint>(
|
||||
_row_collection = new OperableHierarchicalCollection<OrderBookRowVM, OrderSecurity, uint>(
|
||||
o => new OrderBookRowVM(this, o, rows_are_initially_expanded) {
|
||||
is_attachable = _cached_attachable.TryGetValue(o.order.Key, out bool ia) && ia,
|
||||
is_detachable = _cached_detachable.TryGetValue(o.order.Key, out bool id)
|
||||
@@ -147,6 +146,8 @@ namespace mbts.spark {
|
||||
IDisposable attach_vm_hooks = null;
|
||||
begin_attaching += on_begin_attaching;
|
||||
end_attaching += on_end_attaching;
|
||||
// switch between orders and/or orders w/ piq
|
||||
var orders_source = OrdersModel.piq_enabled ? model.on_order_tuples_with_root_and_piq : model.on_order_tuples_with_root;
|
||||
concat_disposable(
|
||||
connect_order_mod_vm(),
|
||||
_can_attach_selected_orders.observe_current().Subscribe(send_attached_orders_async_cmd, begin_attach_cmd),
|
||||
@@ -168,7 +169,7 @@ namespace mbts.spark {
|
||||
security_plurality = p;
|
||||
RaisePropertyChanged(nameof(security_plurality));
|
||||
}),
|
||||
model.on_order_tuples_with_root.OnDispatcher().Subscribe(t => {
|
||||
orders_source.OnDispatcher().Subscribe(t => {
|
||||
_cached_orders = t.state;
|
||||
_row_collection.update(t.added, t.changed, t.removed.Select(o => o.order.Key));
|
||||
}),
|
||||
@@ -543,7 +544,7 @@ namespace mbts.spark {
|
||||
|
||||
public void begin_attach_algos() => is_attaching_orders = true;
|
||||
|
||||
public bool try_get_parent(OrderBookRowVM row, out OrderSecurityAlgoMeta parent) =>
|
||||
public bool try_get_parent(OrderBookRowVM row, out OrderSecurity parent) =>
|
||||
_cached_orders.TryGetValue(row.parent_order_key, out parent);
|
||||
|
||||
public OrderRecordImmutable get_root(OrderRecordImmutable r) {
|
||||
@@ -556,14 +557,14 @@ namespace mbts.spark {
|
||||
internal void detach_orders(OrderBookRowVM order) =>
|
||||
_detach_orders_async(new[] { order }).ignore();
|
||||
|
||||
public OrderSecurityAlgoMeta get_ordersecurityalgometa(OrderRecordImmutable order) {
|
||||
public OrderSecurity get_ordersecurityalgometa(OrderRecordImmutable order) {
|
||||
_cached_orders.TryGetValue(order.Key, out var osam);
|
||||
return osam;
|
||||
}
|
||||
|
||||
public OrderMetadata get_metadata(CustomOrderDefinition.CustomOneofCase order_type) => _model.factory.get_metadata(order_type);
|
||||
|
||||
public OrderMetadata get_metadata(OrderBookRowVM row) => _model.factory.get_metadata(row.order_type);
|
||||
public OrderMetadata get_metadata(OrderBookRowVM row) => _model.factory.get_metadata(row.order.actual_order_type());
|
||||
|
||||
internal OrderBookRowVM get_parent(OrderBookRowVM row) => _row_collection.get_parent(row.order_key);
|
||||
|
||||
@@ -907,7 +908,7 @@ namespace mbts.spark {
|
||||
this.column_filters = column_filters;
|
||||
}
|
||||
|
||||
public Func<OrderSecurityAlgoMeta, bool> build_predicate() {
|
||||
public Func<OrderSecurity, bool> build_predicate() {
|
||||
var pred = column_filters.build_predicate();
|
||||
if (algo_tree_display_mode == AlgoTreeDisplayMode.RootAndLeafNodes) {
|
||||
if (pred == null) {
|
||||
@@ -916,7 +917,7 @@ namespace mbts.spark {
|
||||
var col_pred = pred;
|
||||
pred = o => tree_disp_pred(o) && col_pred(o);
|
||||
}
|
||||
bool tree_disp_pred(OrderSecurityAlgoMeta o) =>
|
||||
bool tree_disp_pred(OrderSecurity o) =>
|
||||
!o.order.has_parent() ||
|
||||
o.order.is_exchange_order() ||
|
||||
o.order.order_type() == CustomOrderDefinition.CustomOneofCase.Spreader;
|
||||
@@ -951,7 +952,7 @@ namespace mbts.spark {
|
||||
readonly CommonValueMonitor<uint> _size_mon = new CommonValueMonitor<uint>();
|
||||
readonly CommonValueMonitor<AccountTuple> _account_mon = new CommonValueMonitor<AccountTuple>();
|
||||
readonly CommonValueMonitor<bool> _all_active_mon = new CommonValueMonitor<bool>();
|
||||
readonly CommonValueMonitorMultiplexor<OrderSecurityAlgoMeta> _group_mon = new CommonValueMonitorMultiplexor<OrderSecurityAlgoMeta>();
|
||||
readonly CommonValueMonitorMultiplexor<OrderSecurity> _group_mon = new CommonValueMonitorMultiplexor<OrderSecurity>();
|
||||
readonly DynamicOrderHelper _dyn_helper;
|
||||
IReadOnlyDictionary<uint, SecurityMD> _security_md_map = DictionaryHelper.empty<uint, SecurityMD>();
|
||||
|
||||
@@ -1020,22 +1021,22 @@ namespace mbts.spark {
|
||||
_security_md_map_changed?.Invoke();
|
||||
}
|
||||
|
||||
void _on_row_updated((OrderSecurityAlgoMeta oldval, OrderSecurityAlgoMeta newval) ch) {
|
||||
void _on_row_updated((OrderSecurity oldval, OrderSecurity newval) ch) {
|
||||
using (begin_udpate()) {
|
||||
_group_mon.remove(ch.oldval);
|
||||
_group_mon.add(ch.newval);
|
||||
}
|
||||
}
|
||||
|
||||
static SecurityImmutable? _get_spreader_sec(OrderSecurityAlgoMeta osam) =>
|
||||
static SecurityImmutable? _get_spreader_sec(OrderSecurity osam) =>
|
||||
OrderDataHelper.enumerate_algo_hierarchy(osam.order.Def.CustomDef.CloneToMutable())
|
||||
.Any(algo => algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Spreader) ?
|
||||
osam.security : null;
|
||||
|
||||
AlgoSecurityKey _algo_key(OrderSecurityAlgoMeta osam) =>
|
||||
AlgoSecurityKey _algo_key(OrderSecurity osam) =>
|
||||
new AlgoSecurityKey(osam.order.Def.CustomDef, _get_spreader_sec(osam), AlgoStructureSerializer.structure_key(osam.order.Def.CustomDef, _dyn_helper.dynamic_type_index));
|
||||
|
||||
SecurityMD? _get_sec_md(OrderSecurityAlgoMeta osam) =>
|
||||
SecurityMD? _get_sec_md(OrderSecurity osam) =>
|
||||
osam.security.HasValue && _security_md_map.TryGetValue(osam.security.Value.Id, out SecurityMD smd) ? smd : (SecurityMD?)null;
|
||||
|
||||
struct AlgoSecurityKey {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.shell.orderbook;
|
||||
using bts.spark;
|
||||
using bts.view;
|
||||
@@ -68,7 +69,7 @@ namespace mbts.spark {
|
||||
_vm.load_from(_state);
|
||||
_view = new OrderBookView(_vm);
|
||||
|
||||
var show_fills_command = SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(() => new FillsWindowFactory.StartupData(_vm.selected_orders.Select(r => r.Key).ToArray()));
|
||||
var show_fills_command = SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(() => new FillsSettings(_vm.selected_orders.Select(r => r.Key).ToArray()));
|
||||
|
||||
var toggle_show_right_panel_command = new SparkCommandCommand(
|
||||
get_toggle_show_right_panel_cmd_title(),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.ui;
|
||||
using bts.shell.fills;
|
||||
using mbts.ui;
|
||||
using ProtoBuf;
|
||||
using System.Collections.Generic;
|
||||
using GridSettings = mbts.ui.settings.GridSettings;
|
||||
@@ -9,11 +10,11 @@ namespace bts.shell.orderbook {
|
||||
[ProtoMember(1)]
|
||||
public GridSettings general { get; set; }
|
||||
[ProtoMember(2)]
|
||||
public FillsWindowFactory.FilterStates filters { get; set; }
|
||||
public FillsFilterStates filters { get; set; }
|
||||
[ProtoMember(3)]
|
||||
public List<SortingCriteria> sorting_criterion { get; set; }
|
||||
|
||||
public FillsWindowFactory.StartupData to_startup_data() =>
|
||||
new FillsWindowFactory.StartupData(null, sorting_criterion, filters, general);
|
||||
public FillsSettings to_startup_data() =>
|
||||
new FillsSettings(null, sorting_criterion, filters, general);
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,6 @@ using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace bts.shell.orderbook {
|
||||
[ProtoContract]
|
||||
@@ -60,7 +58,9 @@ namespace bts.shell.orderbook {
|
||||
[ProtoMember(21)]
|
||||
public string parent_key_regex;
|
||||
[ProtoMember(22)]
|
||||
public string attached_target_order_key_regex;
|
||||
public string attached_target_order_key_regex;
|
||||
[ProtoMember(25)]
|
||||
public UIntMinMaxState piq;
|
||||
|
||||
public bool Equals(FilterStates other) =>
|
||||
other != null &&
|
||||
@@ -85,7 +85,8 @@ namespace bts.shell.orderbook {
|
||||
exchange_message_regex == other.exchange_message_regex &&
|
||||
last_change.Equals(other.last_change) &&
|
||||
EqualityComparer.safe_sequence_equals(order_sub_types, other.order_sub_types) &&
|
||||
EqualityComparer.safe_sequence_equals(symbols, other.symbols);
|
||||
EqualityComparer.safe_sequence_equals(symbols, other.symbols) &&
|
||||
piq.safe_equals(other.piq);
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as FilterStates);
|
||||
|
||||
@@ -112,90 +113,42 @@ namespace bts.shell.orderbook {
|
||||
exchange_message_regex?.GetHashCode() ?? 0,
|
||||
last_change.GetHashCode(),
|
||||
EqualityComparer.safe_aggregate_hash(order_sub_types),
|
||||
EqualityComparer.safe_aggregate_hash(symbols));
|
||||
EqualityComparer.safe_aggregate_hash(symbols),
|
||||
piq?.GetHashCode() ?? 0);
|
||||
}
|
||||
|
||||
static class FilterStatesExtensions {
|
||||
public static Func<OrderSecurityAlgoMeta, bool> build_predicate(this FilterStates fs) {
|
||||
var predicates = new List<Func<OrderSecurityAlgoMeta, bool>>();
|
||||
public static Func<OrderSecurity, bool> build_predicate(this FilterStates fs) {
|
||||
var pb = new ColumnPredicateBuilder<OrderSecurity>();
|
||||
// security
|
||||
if (fs.security_criteria?.is_defined() ?? false)
|
||||
predicates.Add(o => o.security.HasValue && ContractPickerModel.security_in_criteria(fs.security_criteria, o.security.Value));
|
||||
pb.add_security_criteria(fs.security_criteria, o => o.security);
|
||||
// regex
|
||||
add_regex(fs.order_key_regex, o => o.order.Key.ToString());
|
||||
add_regex(fs.exchange_message_regex, o => o.order.State.LastMsg);
|
||||
add_regex(fs.attached_target_order_key_regex, o => o.order.attached_target_order_key().ToString());
|
||||
add_regex(fs.parent_key_regex, o => o.order.ParentKey.ToString());
|
||||
pb.add_regex(fs.order_key_regex, o => o.order.Key.ToString());
|
||||
pb.add_regex(fs.exchange_message_regex, o => o.order.State.LastMsg);
|
||||
pb.add_regex(fs.attached_target_order_key_regex, o => o.order.attached_target_order_key().ToString());
|
||||
pb.add_regex(fs.parent_key_regex, o => o.order.ParentKey.ToString());
|
||||
// time
|
||||
add_time_filter(fs.time_created, o => o.order.created());
|
||||
add_time_filter(fs.last_change, o => o.order.last_change());
|
||||
pb.add_time_filter(fs.time_created, o => o.order.created());
|
||||
pb.add_time_filter(fs.last_change, o => o.order.last_change());
|
||||
// discrete values
|
||||
add_discrete_values(fs.users, o => o.order.user());
|
||||
add_discrete_values(fs.accounts, o => o.order.account());
|
||||
add_discrete_values(fs.exchanges, o => o.security?.Exchange ?? Venue.Unset);
|
||||
add_discrete_values(fs.tifs, o => o.order.time_in_force());
|
||||
add_discrete_values(fs.order_types, o => AlgoID.from_order_def(o.order.Def));
|
||||
add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root_algo.key));
|
||||
add_discrete_values(fs.order_sub_types, o => o.order.sub_type());
|
||||
add_discrete_values(fs.sides, o => o.order.side());
|
||||
add_discrete_values(fs.statuses, o => o.order.status());
|
||||
add_discrete_values(fs.symbols, o => o.security?.Symbol);
|
||||
pb.add_discrete_values(fs.users, o => o.order.user());
|
||||
pb.add_discrete_values(fs.accounts, o => o.order.account());
|
||||
pb.add_discrete_values(fs.exchanges, o => o.security?.Exchange ?? Venue.Unset);
|
||||
pb.add_discrete_values(fs.tifs, o => o.order.time_in_force());
|
||||
pb.add_discrete_values(fs.order_types, o => AlgoID.from_order_def(o.order.Def));
|
||||
pb.add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root_algo.key));
|
||||
pb.add_discrete_values(fs.order_sub_types, o => o.order.sub_type());
|
||||
pb.add_discrete_values(fs.sides, o => o.order.side());
|
||||
pb.add_discrete_values(fs.statuses, o => o.order.status());
|
||||
pb.add_discrete_values(fs.symbols, o => o.security?.Symbol);
|
||||
// numeric
|
||||
add_numeric(fs.qty, o => o.order.quantity());
|
||||
add_numeric(fs.price, o => o.order.price());
|
||||
add_numeric(fs.qty_filled, o => o.order.filled());
|
||||
add_numeric(fs.avg_price, o => o.order.avg_price());
|
||||
add_numeric(fs.last_price, o => o.order.last_price());
|
||||
return predicates.Count == 0 ? null : new Func<OrderSecurityAlgoMeta, bool>(o => predicates.All(p => p(o)));
|
||||
// local methods (impl)
|
||||
void add_regex(string str, Func<OrderSecurityAlgoMeta, string> selector) {
|
||||
var regex = RegexFilterUtils.build_regex(str);
|
||||
if (regex == null)
|
||||
return;
|
||||
predicates.Add(impl);
|
||||
bool impl(OrderSecurityAlgoMeta o) => regex.IsMatch(selector(o) ?? string.Empty);
|
||||
}
|
||||
void add_time_filter(TimeFilterTuple t, Func<OrderSecurityAlgoMeta, DateTime> selector) {
|
||||
if (!t.is_active)
|
||||
return;
|
||||
var dt_pred = TimeFilterUtils.build_predicate(t);
|
||||
predicates.Add(impl);
|
||||
bool impl(OrderSecurityAlgoMeta o) => dt_pred(selector(o));
|
||||
}
|
||||
void add_discrete_values<T>(T[] values, Func<OrderSecurityAlgoMeta, T> selector) {
|
||||
if (values.is_null_or_empty())
|
||||
return;
|
||||
if (values.Length < 16) {
|
||||
predicates.Add(impl);
|
||||
bool impl(OrderSecurityAlgoMeta o) => values.Contains(selector(o));
|
||||
} else {
|
||||
var set = new HashSet<T>(values);
|
||||
predicates.Add(impl);
|
||||
bool impl(OrderSecurityAlgoMeta o) => set.Contains(selector(o));
|
||||
}
|
||||
}
|
||||
void add_numeric<T>(IMinMaxState<T> value, Func<OrderSecurityAlgoMeta, T> selector) where T : struct, IComparable<T> {
|
||||
if (value == null)
|
||||
return;
|
||||
if (value.minimum.active) {
|
||||
var min = value.minimum.value.Value;
|
||||
if (value.maximum.active) {
|
||||
var max = value.maximum.value.Value;
|
||||
predicates.Add(impl);
|
||||
bool impl(OrderSecurityAlgoMeta o) {
|
||||
var v = selector(o);
|
||||
return min.CompareTo(v) <= 0 && max.CompareTo(v) >= 0;
|
||||
}
|
||||
} else {
|
||||
predicates.Add(impl);
|
||||
bool impl(OrderSecurityAlgoMeta o) => min.CompareTo(selector(o)) <= 0;
|
||||
}
|
||||
} else if (value.maximum.active) {
|
||||
var max = value.maximum.value.Value;
|
||||
predicates.Add(impl);
|
||||
bool impl(OrderSecurityAlgoMeta o) => max.CompareTo(selector(o)) >= 0;
|
||||
}
|
||||
}
|
||||
pb.add_numeric(fs.qty, o => o.order.quantity());
|
||||
pb.add_numeric(fs.price, o => o.order.price());
|
||||
pb.add_numeric(fs.qty_filled, o => o.order.filled());
|
||||
pb.add_numeric(fs.avg_price, o => o.order.avg_price());
|
||||
pb.add_numeric(fs.last_price, o => o.order.last_price());
|
||||
pb.add_numeric(fs.piq, o => o.piq ?? uint.MaxValue);
|
||||
return pb.to_predicate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,220 +1,223 @@
|
||||
using bts.api.orderman;
|
||||
using mbts;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
using GridSettings = mbts.ui.settings.GridSettings;
|
||||
|
||||
namespace bts.shell.orderbook {
|
||||
[ProtoContract]
|
||||
class OrderBookStartupData {
|
||||
const bool
|
||||
_default_show_right_panel = true,
|
||||
_default_show_order_params = true;
|
||||
const double
|
||||
_default_right_panel_width = 400,
|
||||
_default_order_params_height = 250,
|
||||
_default_fills_height = 300,
|
||||
_default_ladder_height = 350;
|
||||
[ProtoMember(16)]
|
||||
OrderBookRightPanelSettings _right_panel;
|
||||
// ALREADY USED TAGS
|
||||
// [ProtoMember(5)]
|
||||
// public readonly List<SortingCriteria> sort_criteria;
|
||||
|
||||
public OrderBookRightPanelSettings right_panel => _right_panel ?? (_right_panel = new OrderBookRightPanelSettings());
|
||||
/// TODO: remove properties below, and redirect callers to <see cref="right_panel"/>
|
||||
// right panel (host)
|
||||
public bool show_right_panel { get => right_panel.show; set => right_panel.show = value; }
|
||||
public double right_panel_width { get => right_panel.width; set => right_panel.width = value; }
|
||||
// order params
|
||||
public bool show_order_params { get => right_panel.stack.show_order_params; set => right_panel.stack.show_order_params = value; }
|
||||
public double order_params_height { get; set; }
|
||||
// ladder
|
||||
public bool show_ladder { get; set; }
|
||||
public double ladder_height { get => right_panel.stack.ladder_height; set => right_panel.stack.ladder_height = value; }
|
||||
// fills
|
||||
public bool show_fills { get => right_panel.stack.show_fills; set => right_panel.stack.show_fills = value; }
|
||||
public double fills_height { get => right_panel.stack.fills_height; set => right_panel.stack.fills_height = value; }
|
||||
|
||||
// filters
|
||||
[ProtoMember(10)]
|
||||
public FilterStates filters;
|
||||
// grid
|
||||
[ProtoMember(15)]
|
||||
[Obsolete]
|
||||
public GridSettings grid { get; set; }
|
||||
// orderbook grid
|
||||
[ProtoMember(17)]
|
||||
public OrderBookGridSettings orderbook_grid { get; set; }
|
||||
// fills settings
|
||||
[ProtoMember(26)]
|
||||
public OrderBookFillsGridSettings fills_settings { get; set; }
|
||||
// predicate animations
|
||||
[ProtoMember(20)]
|
||||
[Obsolete]
|
||||
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
|
||||
// use defaults
|
||||
[ProtoMember(25)]
|
||||
public bool use_defaults;
|
||||
|
||||
/// <summary>
|
||||
/// Please use <see cref="Default"/> to get a new instance (the only reason the default constructor still exists is for ProtoBuf deserialization).
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public OrderBookStartupData() {
|
||||
show_right_panel = _default_show_right_panel;
|
||||
show_order_params = _default_show_order_params;
|
||||
right_panel_width = _default_right_panel_width;
|
||||
order_params_height = _default_order_params_height;
|
||||
fills_height = _default_fills_height;
|
||||
ladder_height = _default_ladder_height;
|
||||
}
|
||||
|
||||
public OrderBookStartupData(FilterStates filters, OrderBookGridSettings orderbook_grid = default, bool show_right_panel = false, double right_panel_width = 0, double fills_height = 0, bool show_fills = false, bool use_defaults = false) {
|
||||
this.filters = filters;
|
||||
this.use_defaults = use_defaults;
|
||||
this.show_right_panel = show_right_panel;
|
||||
this.right_panel_width = right_panel_width;
|
||||
this.fills_height = fills_height;
|
||||
this.show_fills = show_fills;
|
||||
this.orderbook_grid = orderbook_grid;
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
|
||||
if (predicate_animations != null)
|
||||
_predicate_animations.AddRange(predicate_animations);
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
}
|
||||
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
[Obsolete]
|
||||
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
|
||||
public OrderBookGridSettings grid_settings_wrapper() {
|
||||
if (use_defaults)
|
||||
return null;
|
||||
if (orderbook_grid == null) {
|
||||
if (grid == null)
|
||||
grid = _default_general_grid;
|
||||
using bts.api.orderman;
|
||||
using bts.shell.fills;
|
||||
using mbts;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
using GridSettings = mbts.ui.settings.GridSettings;
|
||||
|
||||
namespace bts.shell.orderbook {
|
||||
[ProtoContract]
|
||||
class OrderBookStartupData {
|
||||
const bool
|
||||
_default_show_right_panel = true,
|
||||
_default_show_order_params = true;
|
||||
const double
|
||||
_default_right_panel_width = 400,
|
||||
_default_order_params_height = 250,
|
||||
_default_fills_height = 300,
|
||||
_default_ladder_height = 350;
|
||||
[ProtoMember(16)]
|
||||
OrderBookRightPanelSettings _right_panel;
|
||||
// ALREADY USED TAGS
|
||||
// [ProtoMember(5)]
|
||||
// public readonly List<SortingCriteria> sort_criteria;
|
||||
|
||||
public OrderBookRightPanelSettings right_panel => _right_panel ?? (_right_panel = new OrderBookRightPanelSettings());
|
||||
/// TODO: remove properties below, and redirect callers to <see cref="right_panel"/>
|
||||
// right panel (host)
|
||||
public bool show_right_panel { get => right_panel.show; set => right_panel.show = value; }
|
||||
public double right_panel_width { get => right_panel.width; set => right_panel.width = value; }
|
||||
// order params
|
||||
public bool show_order_params { get => right_panel.stack.show_order_params; set => right_panel.stack.show_order_params = value; }
|
||||
public double order_params_height { get; set; }
|
||||
// ladder
|
||||
public bool show_ladder { get; set; }
|
||||
public double ladder_height { get => right_panel.stack.ladder_height; set => right_panel.stack.ladder_height = value; }
|
||||
// fills
|
||||
public bool show_fills { get => right_panel.stack.show_fills; set => right_panel.stack.show_fills = value; }
|
||||
public double fills_height { get => right_panel.stack.fills_height; set => right_panel.stack.fills_height = value; }
|
||||
|
||||
// filters
|
||||
[ProtoMember(10)]
|
||||
public FilterStates filters;
|
||||
// grid
|
||||
[ProtoMember(15)]
|
||||
[Obsolete]
|
||||
public GridSettings grid { get; set; }
|
||||
// orderbook grid
|
||||
[ProtoMember(17)]
|
||||
public OrderBookGridSettings orderbook_grid { get; set; }
|
||||
// fills settings
|
||||
[ProtoMember(26)]
|
||||
public OrderBookFillsGridSettings fills_settings { get; set; }
|
||||
// predicate animations
|
||||
[ProtoMember(20)]
|
||||
[Obsolete]
|
||||
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
|
||||
// use defaults
|
||||
[ProtoMember(25)]
|
||||
public bool use_defaults;
|
||||
|
||||
/// <summary>
|
||||
/// Please use <see cref="Default"/> to get a new instance (the only reason the default constructor still exists is for ProtoBuf deserialization).
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public OrderBookStartupData() {
|
||||
show_right_panel = _default_show_right_panel;
|
||||
show_order_params = _default_show_order_params;
|
||||
right_panel_width = _default_right_panel_width;
|
||||
order_params_height = _default_order_params_height;
|
||||
fills_height = _default_fills_height;
|
||||
ladder_height = _default_ladder_height;
|
||||
}
|
||||
|
||||
public OrderBookStartupData(FilterStates filters, OrderBookGridSettings orderbook_grid = default, bool show_right_panel = false, double right_panel_width = 0, double fills_height = 0, bool show_fills = false, bool use_defaults = false) {
|
||||
this.filters = filters;
|
||||
this.use_defaults = use_defaults;
|
||||
this.show_right_panel = show_right_panel;
|
||||
this.right_panel_width = right_panel_width;
|
||||
this.fills_height = fills_height;
|
||||
this.show_fills = show_fills;
|
||||
this.orderbook_grid = orderbook_grid;
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
|
||||
if (predicate_animations != null)
|
||||
_predicate_animations.AddRange(predicate_animations);
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
}
|
||||
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
[Obsolete]
|
||||
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
|
||||
public OrderBookGridSettings grid_settings_wrapper() {
|
||||
if (use_defaults)
|
||||
return null;
|
||||
if (orderbook_grid == null) {
|
||||
if (grid == null)
|
||||
grid = _default_general_grid;
|
||||
orderbook_grid = new OrderBookGridSettings(grid.clone(), _predicate_animations);
|
||||
}
|
||||
return orderbook_grid;
|
||||
}
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
|
||||
public static OrderBookStartupData Default =>
|
||||
new OrderBookStartupData(
|
||||
new FilterStates { statuses = new[] { OrderStatus.Working } },
|
||||
DefaultGrid,
|
||||
_default_show_right_panel,
|
||||
_default_right_panel_width,
|
||||
_default_fills_height,
|
||||
use_defaults: true) {
|
||||
show_order_params = _default_show_order_params,
|
||||
order_params_height = _default_order_params_height,
|
||||
fills_settings = DefaultFillsGrid
|
||||
};
|
||||
|
||||
public static OrderBookGridSettings DefaultGrid => new OrderBookGridSettings(_default_general_grid) {
|
||||
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
|
||||
};
|
||||
|
||||
public static OrderBookFillsGridSettings DefaultFillsGrid {
|
||||
get {
|
||||
var default_fills = FillsWindowFactory.StartupData.Default();
|
||||
var rv = new OrderBookFillsGridSettings {
|
||||
sorting_criterion = default_fills.sorting_criterion.ToList(),
|
||||
filters = default_fills.filters,
|
||||
general = FillsWindowFactory.StartupData.MiniGridSettings
|
||||
};
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.sorting_criterion);
|
||||
}
|
||||
return orderbook_grid;
|
||||
}
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
|
||||
public static OrderBookStartupData Default =>
|
||||
new OrderBookStartupData(
|
||||
new FilterStates { statuses = new[] { OrderStatus.Working } },
|
||||
DefaultGrid,
|
||||
_default_show_right_panel,
|
||||
_default_right_panel_width,
|
||||
_default_fills_height,
|
||||
use_defaults: true) {
|
||||
show_order_params = _default_show_order_params,
|
||||
order_params_height = _default_order_params_height,
|
||||
fills_settings = DefaultFillsGrid
|
||||
};
|
||||
|
||||
public static OrderBookGridSettings DefaultGrid => new OrderBookGridSettings(_default_general_grid) {
|
||||
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
|
||||
};
|
||||
|
||||
public static OrderBookFillsGridSettings DefaultFillsGrid {
|
||||
get {
|
||||
var default_fills = FillsSettings.Default();
|
||||
var rv = new OrderBookFillsGridSettings {
|
||||
sorting_criterion = default_fills.sorting_criterion.ToList(),
|
||||
filters = default_fills.filters,
|
||||
general = FillsSettings.MiniGridSettings
|
||||
};
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.sorting_criterion);
|
||||
public static OrderBookGridSettings default_mini_grid => new OrderBookGridSettings(GridSettings.Default.with(DefaultColumns.mini, DefaultColumns.sorting_criterion)) {
|
||||
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
|
||||
};
|
||||
|
||||
static class DefaultColumns {
|
||||
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.ToString(), DIYGrid.DefaultColumnHeaders.Security, width: DIYGrid.DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings symbol => new GridColumnSettings(Columns.Symbol.ToString(), DIYGrid.DefaultColumnHeaders.Symbol, width: DIYGrid.DefaultColumnWidths.Symbol, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings time_created => new GridColumnSettings(Columns.Time.ToString(), "Time", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings last_change => new GridColumnSettings(Columns.LastChange.ToString(), "Last Change", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings user => new GridColumnSettings(Columns.User.ToString(), DIYGrid.DefaultColumnHeaders.User, width: DIYGrid.DefaultColumnWidths.User, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings account => new GridColumnSettings(Columns.Account.ToString(), DIYGrid.DefaultColumnHeaders.Account, width: DIYGrid.DefaultColumnWidths.Account, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings exchange => new GridColumnSettings(Columns.Exchange.ToString(), DIYGrid.DefaultColumnHeaders.Exchange, width: DIYGrid.DefaultColumnWidths.Exchange, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings time_in_force => new GridColumnSettings(Columns.TimeInForce.ToString(), DIYGrid.DefaultColumnHeaders.TimeInForce, width: DIYGrid.DefaultColumnWidths.TimeInForce);
|
||||
public static GridColumnSettings order_type => new GridColumnSettings(Columns.OrderType.ToString(), DIYGrid.DefaultColumnHeaders.OrderType, width: DIYGrid.DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings root_algo_type => new GridColumnSettings(Columns.RootAlgoType.ToString(), DIYGrid.DefaultColumnHeaders.RootAlgoType, width: DIYGrid.DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings sub_type => new GridColumnSettings(Columns.SubType.ToString(), "Sub Type", width: 80, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings side => new GridColumnSettings(Columns.Side.ToString(), "B/S", width: DIYGrid.DefaultColumnWidths.Side, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings quantity => new GridColumnSettings(Columns.Size.ToString(), "Qty", width: DIYGrid.DefaultColumnWidths.Size, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings filled_quantity => new GridColumnSettings(Columns.Filled.ToString(), "Filled Qty", width: DIYGrid.DefaultColumnWidths.Filled, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings avg_price => new GridColumnSettings(Columns.AvgFilled.ToString(), "Avg Filled", width: DIYGrid.DefaultColumnWidths.AveragePrice, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings last_price => new GridColumnSettings(Columns.LastPrice.ToString(), "Last Filled", width: DIYGrid.DefaultColumnWidths.LastPrice, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings status => new GridColumnSettings(Columns.Status.ToString(), "Status", width: 80, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings last_message => new GridColumnSettings(Columns.LastMessage.ToString(), "Last Message", width: DIYGrid.DefaultColumnWidths.LastMessage, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, is_visible: false, width: DIYGrid.DefaultColumnWidths.SecurityKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
|
||||
public static IEnumerable<SortingCriteria> sorting_criterion {
|
||||
get {
|
||||
yield return new SortingCriteria(time_created.id, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all {
|
||||
get {
|
||||
yield return security;
|
||||
yield return symbol;
|
||||
yield return time_created;
|
||||
yield return last_change;
|
||||
yield return user;
|
||||
yield return account;
|
||||
yield return exchange;
|
||||
yield return time_in_force;
|
||||
yield return order_type;
|
||||
yield return root_algo_type;
|
||||
yield return sub_type;
|
||||
yield return side;
|
||||
yield return quantity;
|
||||
yield return price;
|
||||
yield return filled_quantity;
|
||||
yield return avg_price;
|
||||
yield return last_price;
|
||||
yield return status;
|
||||
yield return last_message;
|
||||
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> {
|
||||
symbol, last_change, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key, attached_target_order_key
|
||||
};
|
||||
|
||||
public static IEnumerable<GridColumnSettings> mini {
|
||||
get {
|
||||
foreach (var col_setting in all) {
|
||||
if (_mini_mode_columns_to_hide.Contains(col_setting)) {
|
||||
yield return col_setting.with(is_visible: false);
|
||||
} else {
|
||||
yield return col_setting;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static class DefaultColumns {
|
||||
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.ToString(), DIYGrid.DefaultColumnHeaders.Security, width: DIYGrid.DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings symbol => new GridColumnSettings(Columns.Symbol.ToString(), DIYGrid.DefaultColumnHeaders.Symbol, width: DIYGrid.DefaultColumnWidths.Symbol, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings time_created => new GridColumnSettings(Columns.Time.ToString(), "Time", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings last_change => new GridColumnSettings(Columns.LastChange.ToString(), "Last Change", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings user => new GridColumnSettings(Columns.User.ToString(), DIYGrid.DefaultColumnHeaders.User, width: DIYGrid.DefaultColumnWidths.User, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings account => new GridColumnSettings(Columns.Account.ToString(), DIYGrid.DefaultColumnHeaders.Account, width: DIYGrid.DefaultColumnWidths.Account, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings exchange => new GridColumnSettings(Columns.Exchange.ToString(), DIYGrid.DefaultColumnHeaders.Exchange, width: DIYGrid.DefaultColumnWidths.Exchange, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings time_in_force => new GridColumnSettings(Columns.TimeInForce.ToString(), DIYGrid.DefaultColumnHeaders.TimeInForce, width: DIYGrid.DefaultColumnWidths.TimeInForce);
|
||||
public static GridColumnSettings order_type => new GridColumnSettings(Columns.OrderType.ToString(), DIYGrid.DefaultColumnHeaders.OrderType, width: DIYGrid.DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings root_algo_type => new GridColumnSettings(Columns.RootAlgoType.ToString(), DIYGrid.DefaultColumnHeaders.RootAlgoType, width: DIYGrid.DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings sub_type => new GridColumnSettings(Columns.SubType.ToString(), "Sub Type", width: 80, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings side => new GridColumnSettings(Columns.Side.ToString(), "B/S", width: DIYGrid.DefaultColumnWidths.Side, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings quantity => new GridColumnSettings(Columns.Size.ToString(), "Qty", width: DIYGrid.DefaultColumnWidths.Size, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings filled_quantity => new GridColumnSettings(Columns.Filled.ToString(), "Filled Qty", width: DIYGrid.DefaultColumnWidths.Filled, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings avg_price => new GridColumnSettings(Columns.AvgFilled.ToString(), "Avg Filled", width: DIYGrid.DefaultColumnWidths.AveragePrice, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings last_price => new GridColumnSettings(Columns.LastPrice.ToString(), "Last Filled", width: DIYGrid.DefaultColumnWidths.LastPrice, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings status => new GridColumnSettings(Columns.Status.ToString(), "Status", width: 80, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings last_message => new GridColumnSettings(Columns.LastMessage.ToString(), "Last Message", width: DIYGrid.DefaultColumnWidths.LastMessage, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, is_visible: false, width: DIYGrid.DefaultColumnWidths.SecurityKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings piq => new GridColumnSettings(Columns.Piq.ToString(), "PIQ", width: DIYGrid.DefaultColumnWidths.Size, horizontal_alignment: HorizontalAlignment.Right);
|
||||
|
||||
public static IEnumerable<SortingCriteria> sorting_criterion {
|
||||
get {
|
||||
yield return new SortingCriteria(time_created.id, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all {
|
||||
get {
|
||||
yield return security;
|
||||
yield return symbol;
|
||||
yield return time_created;
|
||||
yield return last_change;
|
||||
yield return user;
|
||||
yield return account;
|
||||
yield return exchange;
|
||||
yield return time_in_force;
|
||||
yield return order_type;
|
||||
yield return root_algo_type;
|
||||
yield return sub_type;
|
||||
yield return side;
|
||||
yield return quantity;
|
||||
yield return price;
|
||||
yield return piq;
|
||||
yield return filled_quantity;
|
||||
yield return avg_price;
|
||||
yield return last_price;
|
||||
yield return status;
|
||||
yield return last_message;
|
||||
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> {
|
||||
symbol, last_change, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key, attached_target_order_key
|
||||
};
|
||||
|
||||
public static IEnumerable<GridColumnSettings> mini {
|
||||
get {
|
||||
foreach (var col_setting in all) {
|
||||
if (_mini_mode_columns_to_hide.Contains(col_setting)) {
|
||||
yield return col_setting.with(is_visible: false);
|
||||
} else {
|
||||
yield return col_setting;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,8 @@ namespace mbts.spark {
|
||||
Detach = 1 << 26,
|
||||
Update = 1 << 27,
|
||||
Commit = 1 << 28,
|
||||
RootAlgoType = 1 << 29
|
||||
RootAlgoType = 1 << 29,
|
||||
Piq = 1 << 30
|
||||
}
|
||||
|
||||
public OrderBookGridView() {
|
||||
|
||||
@@ -548,7 +548,7 @@
|
||||
Grid.Column="1"
|
||||
Grid.Row="3"
|
||||
Panel.ZIndex="1"
|
||||
Width="{StaticResource GridSplitterWidth}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Height="{x:Static sp:OrderBookView.GridSplitterHeight}"
|
||||
BorderThickness="0,1,0,0"
|
||||
|
||||
@@ -19,12 +19,12 @@ using static mbts.ui.controls.DIYGrid;
|
||||
namespace bts.shell {
|
||||
internal class PAndLWindowFactory : ShellWindowFactory<PAndLWindowFactory.PAndLStartupData> {
|
||||
readonly IBus _bus;
|
||||
readonly PAndLModel _model;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
|
||||
public PAndLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, PAndLModel model) : base(owner, "SparkPAndL") {
|
||||
public PAndLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) : base(owner, "SparkPAndL") {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
_orders_model = orders_model;
|
||||
_settings_hub = settings_hub;
|
||||
_dispose = bus.subscribe(SparkTags.ShowPAndLTag).OnDispatcher().Subscribe(show);
|
||||
}
|
||||
@@ -45,7 +45,7 @@ namespace bts.shell {
|
||||
state.grid,
|
||||
get: us => us.pnl_grid,
|
||||
set: (us, gs) => us.with(provided_pnl_gridsettings: gs));
|
||||
return new PAndLWindow(_model, this, _bus, state, SparkCommands.ShowPAndL.icon, settings_manager);
|
||||
return new PAndLWindow(_orders_model, this, _bus, state, SparkCommands.ShowPAndL.icon, settings_manager);
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using bts.api;
|
||||
using bts.shell.fills;
|
||||
using bts.shell.manual_fills;
|
||||
using bts.spark;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
@@ -19,6 +21,7 @@ namespace bts.shell {
|
||||
FillsWindowFactory _fwf;
|
||||
PAndLWindowFactory _plwf;
|
||||
AlgoStudioWindowFactory _aswf;
|
||||
ManualFillsWindowFactory _mfwf;
|
||||
|
||||
[ImportingConstructor]
|
||||
public TradingShellModule(
|
||||
@@ -27,15 +30,15 @@ namespace bts.shell {
|
||||
INotificationService notification_service,
|
||||
INotificationServiceDataCache notification_service_data_cache,
|
||||
UserSettingsHub settings_hub,
|
||||
OrdersModel orders_model,
|
||||
PAndLModel pandl_model,
|
||||
OrdersModel orders_model,
|
||||
md.MBOService mbo,
|
||||
ErrorReporter error_reporter) {
|
||||
_lwf = new LadderWindowFactory(this, bus, wm, settings_hub, orders_model, mbo);
|
||||
_obwf = new OrderBookWindowFactory(this, bus, notification_service, notification_service_data_cache, orders_model, settings_hub);
|
||||
_fwf = new FillsWindowFactory(this, bus, settings_hub, orders_model);
|
||||
_plwf = new PAndLWindowFactory(this, bus, settings_hub, pandl_model);
|
||||
_plwf = new PAndLWindowFactory(this, bus, settings_hub, orders_model);
|
||||
_aswf = new AlgoStudioWindowFactory(this, bus, orders_model, error_reporter);
|
||||
_mfwf = new ManualFillsWindowFactory(this, bus, orders_model);
|
||||
}
|
||||
|
||||
public ImageSource icon => Icons.Trades;
|
||||
@@ -46,11 +49,12 @@ namespace bts.shell {
|
||||
|
||||
public IEnumerable<SparkCommand> actions => new [] {
|
||||
SparkCommands.ShowOrders.complete(() => bts.shell.orderbook.OrderBookStartupData.Default),
|
||||
SparkCommands.ShowFills(null).complete(() => new FillsWindowFactory.StartupData()),
|
||||
SparkCommands.ShowFills(null).complete(() => new FillsSettings()),
|
||||
SparkCommands.ShowPAndL.complete(() => new PAndLWindowFactory.PAndLStartupData()),
|
||||
SparkCommands.PickLadder,
|
||||
SparkCommands.PickOrderTicket,
|
||||
SparkCommands.ShowAlgoStudio.complete(() => new AlgoStudioWindowFactory.StartupData())
|
||||
SparkCommands.ShowAlgoStudio.complete(() => new AlgoStudioWindowFactory.StartupData()),
|
||||
SparkCommands.ShowManualFills().complete(() => ManualFillsSettings.Default)
|
||||
};
|
||||
|
||||
IEnumerable<IShellWindowFactory> window_factories() {
|
||||
@@ -59,6 +63,7 @@ namespace bts.shell {
|
||||
yield return _fwf;
|
||||
yield return _plwf;
|
||||
yield return _aswf;
|
||||
yield return _mfwf;
|
||||
}
|
||||
|
||||
public Task<IShellWindow> create(Stream load_stream) =>
|
||||
|
||||
@@ -7,16 +7,17 @@ using static bts.api.orderman.Fill.Types;
|
||||
|
||||
namespace mbts.spark {
|
||||
class FillTuple : IEquatable<FillTuple> {
|
||||
public static readonly Func<FillTuple, string> key_fn = ft => ft.fill.FillKey;
|
||||
public readonly FillImmutable fill;
|
||||
public readonly OrderRecordImmutable? order;
|
||||
public readonly SecurityImmutable? security;
|
||||
public readonly AlgoData? algo;
|
||||
public readonly AlgoDefinitionMetadata? algo_meta;
|
||||
|
||||
public FillTuple((SecurityImmutable? sec, FillImmutable val) fill, OrderSecurityAlgoMeta? order_tuple) {
|
||||
public FillTuple((SecurityImmutable? sec, FillImmutable val) fill, OrderSecurity? order_tuple) {
|
||||
this.fill = fill.val;
|
||||
this.security = fill.sec;
|
||||
if (order_tuple is OrderSecurityAlgoMeta osam) {
|
||||
if (order_tuple is OrderSecurity osam) {
|
||||
order = osam.order;
|
||||
algo = osam.algo;
|
||||
algo_meta = osam.meta;
|
||||
@@ -45,4 +46,24 @@ namespace mbts.spark {
|
||||
order.HasValue ? order.Value.Key.GetHashCode() : 0,
|
||||
security.HasValue ? security.Value.Id.GetHashCode() : 0);
|
||||
}
|
||||
|
||||
static class FillTupleExtensions {
|
||||
public static DateTime fill_time(this FillTuple fill_tuple) =>
|
||||
fill_tuple.fill.Timestamp.to_local_time();
|
||||
|
||||
public static TimeInForce time_in_force(this FillTuple fill_tuple) =>
|
||||
fill_tuple.order.HasValue ? fill_tuple.order.Value.time_in_force() : TimeInForce.Undefined;
|
||||
|
||||
public static CustomOrderDefinition.CustomOneofCase order_type(this FillTuple fill_tuple) =>
|
||||
fill_tuple.order.HasValue ? fill_tuple.order.Value.order_type() : CustomOrderDefinition.CustomOneofCase.None;
|
||||
|
||||
public static Venue exchange(this FillTuple fill_tuple) =>
|
||||
fill_tuple.security.HasValue ? fill_tuple.security.Value.Exchange : Venue.Unset;
|
||||
|
||||
public static uint order_key(this FillTuple fill_tuple) =>
|
||||
fill_tuple.order.HasValue ? fill_tuple.order.Value.Key : 0u;
|
||||
|
||||
public static string user(this FillTuple fill_tuple) =>
|
||||
fill_tuple.order.HasValue ? fill_tuple.order.Value.user() : string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ namespace mbts.spark.utilities {
|
||||
public OrderFactory(AdminModel admin_model, DynamicOrderHelper dynamic_helper) {
|
||||
_admin_model = admin_model;
|
||||
_dynamic_helper = dynamic_helper;
|
||||
var algo_name_index = new HashSet<OneOf<CustomOrderDefinition.CustomOneofCase, string>>(OneOf<CustomOrderDefinition.CustomOneofCase,string>.ValueEqualityComparer);
|
||||
var algo_name_index = new HashSet<OneOf<CustomOrderDefinition.CustomOneofCase, string>>(OneOf<CustomOrderDefinition.CustomOneofCase, string>.ValueEqualityComparer);
|
||||
// add standard types
|
||||
algo_name_index.AddRange(
|
||||
EnumHelper.get_values_except(CustomOrderDefinition.CustomOneofCase.None)
|
||||
@@ -58,7 +58,7 @@ namespace mbts.spark.utilities {
|
||||
algo_name_index.AddRange(dynamic_helper.dynamic_types
|
||||
.Select(dt => new OneOf<CustomOrderDefinition.CustomOneofCase, string>(dt)));
|
||||
// build abbreviation index
|
||||
_algo_abbreviation_index = DisplayNameHelper.get_word_tree_abbreviations(algo_name_index, oneof => oneof.value.ToString(), OneOf<CustomOrderDefinition.CustomOneofCase,string>.ValueEqualityComparer);
|
||||
_algo_abbreviation_index = DisplayNameHelper.get_word_tree_abbreviations(algo_name_index, oneof => oneof.value.ToString(), OneOf<CustomOrderDefinition.CustomOneofCase, string>.ValueEqualityComparer);
|
||||
}
|
||||
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> subscribe_accounts() => _admin_model.on_accounts;
|
||||
@@ -70,7 +70,7 @@ namespace mbts.spark.utilities {
|
||||
public CustomOrderDefinition.CustomOneofCase default_order_type() =>
|
||||
CustomOrderDefinition.CustomOneofCase.Limit;
|
||||
|
||||
public CustomOrderDefinition.CustomOneofCase default_order_type(SecurityImmutable security, bool is_child=false) {
|
||||
public CustomOrderDefinition.CustomOneofCase default_order_type(SecurityImmutable security, bool is_child = false) {
|
||||
if (is_child) return CustomOrderDefinition.CustomOneofCase.Limit;
|
||||
switch (security.ContractCase) {
|
||||
case Security.ContractOneofCase.Synthetic: return CustomOrderDefinition.CustomOneofCase.Spreader;
|
||||
@@ -78,7 +78,7 @@ namespace mbts.spark.utilities {
|
||||
}
|
||||
}
|
||||
|
||||
public CustomOrderDefinition.CustomOneofCase default_order_type(SecurityImmutable? security, bool is_child=false) =>
|
||||
public CustomOrderDefinition.CustomOneofCase default_order_type(SecurityImmutable? security, bool is_child = false) =>
|
||||
security.HasValue ? default_order_type(security.Value, is_child) : default_order_type();
|
||||
|
||||
public TimeInForce default_time_in_force() =>
|
||||
@@ -103,13 +103,13 @@ namespace mbts.spark.utilities {
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<CustomOrderDefinition.CustomOneofCase> default_order_types(SecurityImmutable? security, bool is_child=false) =>
|
||||
public IEnumerable<CustomOrderDefinition.CustomOneofCase> default_order_types(SecurityImmutable? security, bool is_child = false) =>
|
||||
security.HasValue ? default_order_types(security.Value, is_child) : default_order_types();
|
||||
|
||||
public IEnumerable<TimeInForce> default_time_in_forces() =>
|
||||
EnumHelper.get_values_except(TimeInForce.Undefined);
|
||||
|
||||
public IEnumerable<TimeInForce> default_time_in_forces(SecurityImmutable security, bool is_child=false) =>
|
||||
public IEnumerable<TimeInForce> default_time_in_forces(SecurityImmutable security, bool is_child = false) =>
|
||||
default_time_in_forces();
|
||||
|
||||
public IEnumerable<TimeInForce> default_time_in_forces(SecurityImmutable security, CustomOrderDefinition.CustomOneofCase order_type, bool is_child = false) {
|
||||
@@ -120,7 +120,7 @@ namespace mbts.spark.utilities {
|
||||
return tifs;
|
||||
}
|
||||
|
||||
public IEnumerable<TimeInForce> default_time_in_forces(SecurityImmutable? security, bool is_child=false) =>
|
||||
public IEnumerable<TimeInForce> default_time_in_forces(SecurityImmutable? security, bool is_child = false) =>
|
||||
security.HasValue ? default_time_in_forces(security.Value, is_child) : default_time_in_forces();
|
||||
|
||||
public LadderSettings default_ladder_settings(SecurityImmutable security) {
|
||||
@@ -201,6 +201,7 @@ namespace mbts.spark.utilities {
|
||||
public LimitOrderDefinition limit_order() => OrderDataHelper.Defaults.LimitOrderDefinition();
|
||||
public StopOrderDefinition stop_order() => OrderDataHelper.Defaults.StopOrderDefinition();
|
||||
public StopLimitOrderDefinition stop_limit_order() => OrderDataHelper.Defaults.StopLimitOrderDefinition();
|
||||
public GLBXIcebergOrderDefinition glbx_iceberg_order() => OrderDataHelper.Defaults.GLBXIcebergOrderDefinition();
|
||||
public IcebergOrderDefinition iceberg_order() => OrderDataHelper.Defaults.IcebergOrderDefinition();
|
||||
public TimeBoxOrderDefinition time_box_order() => OrderDataHelper.Defaults.TimeBoxOrderDefinition();
|
||||
public TickOrderDefinition tick_order() => OrderDataHelper.Defaults.TickOrderDefinition();
|
||||
@@ -226,14 +227,15 @@ namespace mbts.spark.utilities {
|
||||
|
||||
public CustomOrderDefinition custom_order(CustomOrderDefinition.CustomOneofCase order_type) {
|
||||
var rv = new CustomOrderDefinition();
|
||||
switch(order_type) {
|
||||
switch (order_type) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Market: rv.Market = market_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Limit: rv.Limit = limit_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Stop: rv.Stop = stop_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.StopLimit: rv.StopLimit = stop_limit_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.GlbxIceberg: rv.GlbxIceberg = glbx_iceberg_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Iceberg: rv.Iceberg = iceberg_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.TimeBox: rv.TimeBox = time_box_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Tick: rv.Tick = tick_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Tick: rv.Tick = tick_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Lean: rv.Lean = lean_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Join: rv.Join = join_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Top: rv.Top = top_order(); break;
|
||||
@@ -244,7 +246,7 @@ namespace mbts.spark.utilities {
|
||||
case CustomOrderDefinition.CustomOneofCase.Trigger: rv.Trigger = trigger_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Spreader: rv.Spreader = spreader_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic: rv.Dynamic = dynamic_order(); break;
|
||||
//case CustomOrderDefinition.CustomOneofCase.QueueHolder: rv.QueueHolder = queue_holder_order(); break;
|
||||
//case CustomOrderDefinition.CustomOneofCase.QueueHolder: rv.QueueHolder = queue_holder_order(); break;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
@@ -257,6 +259,7 @@ namespace mbts.spark.utilities {
|
||||
case CustomOrderDefinition.CustomOneofCase.Limit: return new OrderMetadata { KeyBinding = "L", Color = BTSColors.green.rgb_color(), NoProps = true };
|
||||
case CustomOrderDefinition.CustomOneofCase.Stop: return new OrderMetadata { KeyBinding = "S", Color = BTSColors.red_orange.rgb_color(), NoProps = true };
|
||||
case CustomOrderDefinition.CustomOneofCase.StopLimit: return new OrderMetadata { KeyBinding = "SL", Color = BTSColors.pale_green.rgb_color() };
|
||||
case CustomOrderDefinition.CustomOneofCase.GlbxIceberg: return new OrderMetadata { KeyBinding = "GB", Color = BTSColors.Orders.glbx_iceberg_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Iceberg: return new OrderMetadata { KeyBinding = "I", Color = BTSColors.pale_blue.rgb_color() };
|
||||
case CustomOrderDefinition.CustomOneofCase.TimeBox: return new OrderMetadata { KeyBinding = "TB", Color = BTSColors.purple.rgb_color() };
|
||||
case CustomOrderDefinition.CustomOneofCase.Tick: return new OrderMetadata { KeyBinding = "T", Color = BTSColors.Orders.tick_order };
|
||||
@@ -266,7 +269,7 @@ namespace mbts.spark.utilities {
|
||||
case CustomOrderDefinition.CustomOneofCase.Cover: return new OrderMetadata { KeyBinding = "C", Color = BTSColors.Orders.cover_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Oco: return new OrderMetadata { KeyBinding = "C", Color = BTSColors.Orders.oco_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Pricer: return new OrderMetadata { KeyBinding = "C", Color = BTSColors.Orders.pricer_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Offset: return new OrderMetadata { KeyBinding = "O", Color = BTSColors.Orders.tick_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Offset: return new OrderMetadata { KeyBinding = "O", Color = BTSColors.Orders.off_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Trigger: return new OrderMetadata { KeyBinding = "T", Color = BTSColors.Orders.trigger_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Spreader: return new OrderMetadata { KeyBinding = "", Color = BTSColors.Orders.spreader_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic: return new OrderMetadata { KeyBinding = "", Color = BTSColors.Orders.top_order };
|
||||
@@ -418,6 +421,7 @@ namespace mbts.spark.utilities {
|
||||
case CustomOrderDefinition.CustomOneofCase.Limit: return new OrderMetadata { KeyBinding = "L", Color = BTSColors.green.rgb_color(), NoProps = true };
|
||||
case CustomOrderDefinition.CustomOneofCase.Stop: return new OrderMetadata { KeyBinding = "S", Color = BTSColors.red_orange.rgb_color(), NoProps = true };
|
||||
case CustomOrderDefinition.CustomOneofCase.StopLimit: return new OrderMetadata { KeyBinding = "SL", Color = BTSColors.pale_green.rgb_color() };
|
||||
case CustomOrderDefinition.CustomOneofCase.GlbxIceberg: return new OrderMetadata { KeyBinding = "GB", Color = BTSColors.Orders.glbx_iceberg_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Iceberg: return new OrderMetadata { KeyBinding = "I", Color = BTSColors.pale_blue.rgb_color() };
|
||||
case CustomOrderDefinition.CustomOneofCase.TimeBox: return new OrderMetadata { KeyBinding = "TB", Color = BTSColors.purple.rgb_color() };
|
||||
case CustomOrderDefinition.CustomOneofCase.Tick: return new OrderMetadata { KeyBinding = "T", Color = BTSColors.Orders.tick_order };
|
||||
@@ -427,7 +431,7 @@ namespace mbts.spark.utilities {
|
||||
case CustomOrderDefinition.CustomOneofCase.Cover: return new OrderMetadata { KeyBinding = "C", Color = BTSColors.Orders.cover_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Oco: return new OrderMetadata { KeyBinding = "C", Color = BTSColors.Orders.oco_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Pricer: return new OrderMetadata { KeyBinding = "C", Color = BTSColors.Orders.pricer_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Offset: return new OrderMetadata { KeyBinding = "O", Color = BTSColors.Orders.tick_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Offset: return new OrderMetadata { KeyBinding = "O", Color = BTSColors.Orders.off_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Trigger: return new OrderMetadata { KeyBinding = "T", Color = BTSColors.Orders.trigger_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Spreader: return new OrderMetadata { KeyBinding = "", Color = BTSColors.Orders.spreader_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic: return new OrderMetadata { KeyBinding = "", Color = BTSColors.Orders.top_order };
|
||||
@@ -440,6 +444,7 @@ namespace mbts.spark.utilities {
|
||||
switch (algo_type) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Limit: return "LM";
|
||||
case CustomOrderDefinition.CustomOneofCase.Cover: return "CV";
|
||||
case CustomOrderDefinition.CustomOneofCase.GlbxIceberg: return "GB";
|
||||
case CustomOrderDefinition.CustomOneofCase.Iceberg: return "IB";
|
||||
case CustomOrderDefinition.CustomOneofCase.Lean: return "LN";
|
||||
case CustomOrderDefinition.CustomOneofCase.Market: return "MA";
|
||||
@@ -594,6 +599,7 @@ namespace mbts.spark.utilities {
|
||||
CustomOrderDefinition.CustomOneofCase.Limit,
|
||||
CustomOrderDefinition.CustomOneofCase.Stop,
|
||||
CustomOrderDefinition.CustomOneofCase.StopLimit,
|
||||
CustomOrderDefinition.CustomOneofCase.GlbxIceberg,
|
||||
CustomOrderDefinition.CustomOneofCase.Iceberg,
|
||||
CustomOrderDefinition.CustomOneofCase.Lean,
|
||||
CustomOrderDefinition.CustomOneofCase.Join,
|
||||
@@ -636,12 +642,12 @@ namespace mbts.spark.utilities {
|
||||
var tgt_child = cad.get_child_from_impl(tgt_type_def);
|
||||
copy_values(src_child, ref tgt_child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static CustomOrderDefinition.CustomOneofCase get_order_type(IMessage order_type_def) =>
|
||||
order_type_def != null && _algo_type_case_index.TryGetValue(order_type_def.Descriptor.ClrType, out CustomOrderDefinition.CustomOneofCase algo_case) ?
|
||||
algo_case : CustomOrderDefinition.CustomOneofCase.None;
|
||||
|
||||
|
||||
#region Private Members
|
||||
// get child
|
||||
static CustomOrderDefinitionImmutable? _get_iceberg_child(IcebergOrderDefinitionImmutable iceberg) => iceberg.HasChild ? iceberg.Child : (CustomOrderDefinitionImmutable?)null;
|
||||
@@ -857,7 +863,7 @@ namespace mbts.spark.utilities {
|
||||
int phase_mult = 1;
|
||||
switch (def.CustomCase) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Cover:
|
||||
add_component(def.Cover.HedgeTicks.GetHashCode());
|
||||
add_component(def.Cover.HedgeTicks.GetHashCode());
|
||||
add_component(_get_hash(_get_cover_child(def.Cover)));
|
||||
add_component(_get_hash(_get_cover_hedge(def.Cover)));
|
||||
break;
|
||||
@@ -981,6 +987,7 @@ namespace mbts.spark.utilities {
|
||||
case bts.api.orderman.CustomOrderDefinition.CustomOneofCase.Stop: rv.Stop = StopOrderDefinition(); break;
|
||||
case bts.api.orderman.CustomOrderDefinition.CustomOneofCase.StopLimit: rv.StopLimit = StopLimitOrderDefinition(); break;
|
||||
case bts.api.orderman.CustomOrderDefinition.CustomOneofCase.Iceberg: rv.Iceberg = IcebergOrderDefinition(); break;
|
||||
case bts.api.orderman.CustomOrderDefinition.CustomOneofCase.GlbxIceberg: rv.GlbxIceberg = GLBXIcebergOrderDefinition(); break;
|
||||
case bts.api.orderman.CustomOrderDefinition.CustomOneofCase.TimeBox: rv.TimeBox = TimeBoxOrderDefinition(); break;
|
||||
case bts.api.orderman.CustomOrderDefinition.CustomOneofCase.Tick: rv.Tick = TickOrderDefinition(); break;
|
||||
case bts.api.orderman.CustomOrderDefinition.CustomOneofCase.Lean: rv.Lean = LeanOrderDefinition(); break;
|
||||
@@ -1004,6 +1011,7 @@ namespace mbts.spark.utilities {
|
||||
public static LimitOrderDefinition LimitOrderDefinition() => new LimitOrderDefinition { };
|
||||
public static StopOrderDefinition StopOrderDefinition() => new StopOrderDefinition { };
|
||||
public static StopLimitOrderDefinition StopLimitOrderDefinition() => new StopLimitOrderDefinition { };
|
||||
public static GLBXIcebergOrderDefinition GLBXIcebergOrderDefinition() => new GLBXIcebergOrderDefinition { MaxShowQuantity = 1 };
|
||||
public static IcebergOrderDefinition IcebergOrderDefinition() => new IcebergOrderDefinition { Child = CustomOrderDefinition(), ShowQuantity = 1 };
|
||||
public static TimeBoxOrderDefinition TimeBoxOrderDefinition() => new TimeBoxOrderDefinition { Child = CustomOrderDefinition(), Start = Timestamp.FromDateTime(DateTime.UtcNow), Stop = Timestamp.FromDateTime(DateTime.UtcNow) };
|
||||
public static TickOrderDefinition TickOrderDefinition() => new TickOrderDefinition { Child = CustomOrderDefinition(), CrossThreshold = QuantityMeasure };
|
||||
|
||||
@@ -3,13 +3,15 @@ using mbts.spark;
|
||||
using System;
|
||||
|
||||
namespace bts.api.orderman {
|
||||
readonly struct OrderSecurity: IEquatable<OrderSecurity> {
|
||||
readonly struct OrderSecurity : IEquatable<OrderSecurity> {
|
||||
public OrderSecurity(SecurityImmutable? sec, OrderRecordImmutable order) {
|
||||
this.security = sec;
|
||||
this.order = order;
|
||||
piq = null;
|
||||
algo = null;
|
||||
meta = null;
|
||||
root_algo = new AlgoData(order.Def.CustomDef);
|
||||
root_meta = null;
|
||||
}
|
||||
public OrderSecurity((SecurityImmutable? sec, OrderRecordImmutable order) t) {
|
||||
security = t.sec;
|
||||
@@ -17,6 +19,8 @@ namespace bts.api.orderman {
|
||||
piq = null;
|
||||
algo = null;
|
||||
meta = null;
|
||||
root_algo = new AlgoData(order.Def.CustomDef);
|
||||
root_meta = null;
|
||||
}
|
||||
public OrderSecurity(SecurityImmutable? sec, OrderRecordImmutable order, uint? piq) {
|
||||
this.security = sec;
|
||||
@@ -24,6 +28,8 @@ namespace bts.api.orderman {
|
||||
this.piq = piq;
|
||||
algo = null;
|
||||
meta = null;
|
||||
root_algo = new AlgoData(order.Def.CustomDef);
|
||||
root_meta = null;
|
||||
}
|
||||
public OrderSecurity(SecurityImmutable? sec, OrderRecordImmutable order, uint? piq, AlgoData? algo, AlgoDefinitionMetadata? meta) {
|
||||
this.security = sec;
|
||||
@@ -31,24 +37,55 @@ namespace bts.api.orderman {
|
||||
this.piq = piq;
|
||||
this.algo = algo;
|
||||
this.meta = meta;
|
||||
root_algo = algo ?? new AlgoData(order.Def.CustomDef);
|
||||
root_meta = meta;
|
||||
}
|
||||
//public OrderSecurity(OrderSecurity t) {
|
||||
// security = t.security;
|
||||
// order = t.order;
|
||||
// piq = t.piq;
|
||||
//}
|
||||
public OrderSecurity(SecurityImmutable? sec, OrderRecordImmutable order, uint? piq, AlgoData? algo, AlgoDefinitionMetadata? meta, AlgoData? rootalgo = null, AlgoDefinitionMetadata? rootmeta = null) {
|
||||
this.security = sec;
|
||||
this.order = order;
|
||||
this.piq = piq;
|
||||
this.algo = algo;
|
||||
this.meta = meta;
|
||||
if (rootalgo.HasValue) {
|
||||
this.root_algo = rootalgo.Value;
|
||||
this.root_meta = rootmeta;
|
||||
} else {
|
||||
this.root_algo = algo ?? new AlgoData(order.Def.CustomDef);
|
||||
this.root_meta = meta;
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator OrderSecurity(ValueTuple<SecurityImmutable?, OrderRecordImmutable> x) => new OrderSecurity(x);
|
||||
public SecurityImmutable? security { get; }
|
||||
public OrderRecordImmutable order { get; }
|
||||
public uint? piq { get; }
|
||||
public AlgoData? algo { get; }
|
||||
public AlgoDefinitionMetadata? meta { get; }
|
||||
public AlgoData root_algo { get; }
|
||||
public AlgoDefinitionMetadata? root_meta { get; }
|
||||
|
||||
|
||||
public OrderSecurity with(AlgoData? root_algo, AlgoDefinitionMetadata? root_meta = null) =>
|
||||
new OrderSecurity(security, order, piq, algo, meta, root_algo, root_meta);
|
||||
|
||||
public override bool Equals(object obj) => (obj is OrderSecurity os) && Equals(os);
|
||||
public bool Equals(OrderSecurity other) => order.Equals(other.order) && security == other.security && piq == other.piq;
|
||||
public override int GetHashCode() => (security, order, piq).GetHashCode();
|
||||
public bool Equals(OrderSecurity other) =>
|
||||
order == other.order &&
|
||||
security == other.security &&
|
||||
piq == other.piq &&
|
||||
algo == other.algo &&
|
||||
meta == other.meta &&
|
||||
root_algo == other.root_algo;
|
||||
|
||||
public override bool Equals(object obj) => EqualityComparer.equals_obj(this, obj);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
order.GetHashCode(),
|
||||
security?.GetHashCode() ?? 0,
|
||||
piq?.GetHashCode() ?? 0,
|
||||
algo?.GetHashCode() ?? 0,
|
||||
meta?.GetHashCode() ?? 0,
|
||||
root_algo.GetHashCode());
|
||||
|
||||
public static bool operator == (OrderSecurity left, OrderSecurity right) => left.Equals(right);
|
||||
public static bool operator != (OrderSecurity left, OrderSecurity right) => !left.Equals(right);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using bts.api.orderman;
|
||||
|
||||
namespace mbts.spark {
|
||||
static partial class OrderTypeVMFactory {
|
||||
class GLBXIcebergOrderVM : OrderTypeVMBase<GLBXIcebergOrderDefinition, GLBXIcebergOrderDefinitionImmutable> {
|
||||
readonly Property<uint?> _max_show_quantity;
|
||||
|
||||
public GLBXIcebergOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
|
||||
: base(key, display_name, editing_permissions) {
|
||||
_max_show_quantity = add_nullable_property<uint>(nameof(max_show_quantity), GLBXIcebergOrderDefinition.MaxShowQuantityFieldNumber);
|
||||
}
|
||||
|
||||
public uint? max_show_quantity { get => _max_show_quantity.Value; set => _max_show_quantity.set(value); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,7 @@ namespace mbts.spark {
|
||||
public static IAlgoVM pricer(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions) => new PricerOrderVM(key, display_name, editing_permissions);
|
||||
public static IAlgoVM offset(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions) => new OffsetOrderVM(key, display_name, editing_permissions);
|
||||
public static IAlgoVM trigger(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions) => new TriggerOrderVM(key, display_name, editing_permissions);
|
||||
public static IAlgoVM glbx_iceberg(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions) => new GLBXIcebergOrderVM(key, display_name, editing_permissions);
|
||||
public static IAlgoVM iceberg(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions) => new IcebergOrderVM(key, display_name, editing_permissions);
|
||||
public static IAlgoVM spreader(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions) => new SpreaderOrderVM(key, display_name, editing_permissions);
|
||||
public static IAlgoVM dynamic(AlgoKey key, string display_name, string dynamic_type, AlgoEditingPermissions editing_permissions) => new DynamicOrderVM(key, dynamic_type, display_name, editing_permissions);
|
||||
@@ -83,6 +84,7 @@ namespace mbts.spark {
|
||||
case AlgoType.Pricer: return pricer(key, name, editing_permissions);
|
||||
case AlgoType.Offset: vm = offset(key, name, editing_permissions); break;
|
||||
case AlgoType.Trigger: vm = trigger(key, name, editing_permissions); break;
|
||||
case AlgoType.GlbxIceberg: vm = glbx_iceberg(key, name, editing_permissions); break;
|
||||
case AlgoType.Iceberg: vm = iceberg(key, name, editing_permissions); break;
|
||||
case AlgoType.Spreader: vm = spreader(key, name, editing_permissions); break;
|
||||
case AlgoType.Dynamic: vm = dynamic(key, name, name, editing_permissions); break;
|
||||
|
||||
@@ -79,6 +79,23 @@ namespace mbts.spark {
|
||||
public static CustomOrderDefinition.CustomOneofCase order_type(this OrderRecordImmutable order) =>
|
||||
order.HasDef && order.Def.HasCustomDef ? order.Def.CustomDef.CustomCase : CustomOrderDefinition.CustomOneofCase.None;
|
||||
|
||||
/// <summary>
|
||||
/// Unwraps all <see cref="CompositeOrderDefinition"/>s (if any) to return the underlying order type.
|
||||
/// </summary>
|
||||
/// <param name="order">The order who's underlying algo will be returned.</param>
|
||||
/// <returns>The first non-composite algo definition in the specified <see cref="CustomOrderDefinition"/>.</returns>
|
||||
public static CustomOrderDefinition.CustomOneofCase actual_order_type(this OrderRecordImmutable order) {
|
||||
if (!order.HasDef || !order.Def.HasCustomDef)
|
||||
return CustomOrderDefinition.CustomOneofCase.None;
|
||||
var cdef = order.Def.CustomDef;
|
||||
while (cdef.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite) {
|
||||
if (!cdef.Composite.HasDefinition)
|
||||
return CustomOrderDefinition.CustomOneofCase.None;
|
||||
cdef = cdef.Composite.Definition;
|
||||
}
|
||||
return cdef.CustomCase;
|
||||
}
|
||||
|
||||
public static string dynamic_order_type_display_name(this OrderRecordImmutable order) =>
|
||||
order.HasDef && order.Def.HasCustomDef && order.Def.CustomDef.HasDynamic ? order.Def.CustomDef.Dynamic.DynamicOrderType : string.Empty;
|
||||
|
||||
@@ -112,7 +129,8 @@ namespace mbts.spark {
|
||||
case CustomOrderDefinition.CustomOneofCase.Limit:
|
||||
case CustomOrderDefinition.CustomOneofCase.Market:
|
||||
case CustomOrderDefinition.CustomOneofCase.Stop:
|
||||
case CustomOrderDefinition.CustomOneofCase.StopLimit: return true;
|
||||
case CustomOrderDefinition.CustomOneofCase.StopLimit:
|
||||
case CustomOrderDefinition.CustomOneofCase.GlbxIceberg: return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -138,6 +156,8 @@ namespace mbts.spark {
|
||||
|
||||
public static string display_name(this OrderSecurityAlgoMeta order_tuple) =>
|
||||
display_name(order_tuple.order, order_tuple.algo);
|
||||
public static string display_name(this OrderSecurity order_tuple) =>
|
||||
display_name(order_tuple.order, order_tuple.algo);
|
||||
|
||||
public static string display_name(OrderRecordImmutable order, AlgoData? algo) {
|
||||
if (algo.HasValue) {
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace mbts.spark {
|
||||
[Export]
|
||||
class OrdersModel : IDisposable {
|
||||
public readonly MarketDataAuxiliaryModel md_auxiliary_model;
|
||||
public static readonly bool piq_enabled = Configuration.get<bts.config.toggles.PlacementInQueue>().enabled;
|
||||
|
||||
readonly IBus _bus;
|
||||
readonly OrderFactory _factory;
|
||||
@@ -38,9 +39,8 @@ namespace mbts.spark {
|
||||
readonly OrderActivitySummarizer _activity;
|
||||
readonly TableUpdateDemuxer<OrderRecordImmutable, uint, uint> _security_orders_demuxer;
|
||||
readonly TableUpdateDemuxer<OrderSecurity, uint, uint> _security_working_orders_with_piq_demuxer;
|
||||
readonly TableUpdateDemuxer<PositionUnitImmutable, int, uint> _security_positions_demuxer;
|
||||
readonly TableUpdateDemuxer<(SecurityImmutable?, PositionUnitImmutable), int, uint> _security_positions_demuxer;
|
||||
IDisposable _dispose;
|
||||
static readonly bool _piq_enabled = Configuration.get<bts.config.toggles.PlacementInQueue>().enabled;
|
||||
|
||||
[ImportingConstructor]
|
||||
OrdersModel(IBus bus, OrderFactory factory, OrderService service, PositionService position_service, MBOService mbo_service,
|
||||
@@ -67,7 +67,7 @@ namespace mbts.spark {
|
||||
.Table()
|
||||
.Cache();
|
||||
|
||||
on_positions = _position_service.subscribei_position_data().Naggle(interval, scheduler).PermissionFilter(admin_model.on_admin_data).Table().Cache();
|
||||
on_positions = _position_service.subscribei_position_data().Naggle(interval, scheduler).PermissionFilter(admin_model.on_admin_data).join(sstore, pos => pos.SecurityId).Table().Cache();
|
||||
working_orders =
|
||||
on_orders.Scan(
|
||||
ImmutableDictionary<uint,OrderSecurity>.Empty,
|
||||
@@ -83,7 +83,7 @@ namespace mbts.spark {
|
||||
_activity = new OrderActivitySummarizer(bus, on_orders);
|
||||
_security_orders_demuxer = new TableUpdateDemuxer<OrderRecordImmutable, uint, uint>(o => o.Key, o => o.security_key());
|
||||
_security_working_orders_with_piq_demuxer = new TableUpdateDemuxer<OrderSecurity, uint, uint>(o => o.order.Key, o => o.order.security_key());
|
||||
_security_positions_demuxer = new TableUpdateDemuxer<PositionUnitImmutable, int, uint>(o => o.RecordId, p => p.SecurityId);
|
||||
_security_positions_demuxer = new TableUpdateDemuxer<(SecurityImmutable?, PositionUnitImmutable pos), int, uint>(o => o.pos.RecordId, p => p.pos.SecurityId);
|
||||
studio_algos_with_meta = algo_model.definitions.LeftJoin<AlgoData, AlgoDefinitionMetadata, (AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>(
|
||||
algo_model.metadata_service.subscribe_metadata().Table(m => m.id),
|
||||
d => d.key,
|
||||
@@ -97,14 +97,13 @@ namespace mbts.spark {
|
||||
t => t.order.Key,
|
||||
am => am.algo.key,
|
||||
t => new AlgoKey(new bts.persist.algo.AlgoPersistedKey(t.order.algo_template_key())),
|
||||
(os, okey, am, mkey) => new OrderSecurityAlgoMeta(os.order, os.sec, am.algo, am.meta),
|
||||
(os, okey, mkey) => new OrderSecurityAlgoMeta(os.order, os.sec, null, null),
|
||||
(os, okey, am, mkey) => new OrderSecurity(os.sec, os.order, null, am.algo, am.meta),
|
||||
(os, okey, mkey) => new OrderSecurity(os.sec, os.order, null, null, null),
|
||||
ot => ot.order.Key);
|
||||
on_order_tuples = order_secs_algo_meta_join.Cache();
|
||||
|
||||
// need to bring back the config key check
|
||||
on_order_tuples_with_piq = on_order_tuples.LeftJoin<
|
||||
OrderSecurityAlgoMeta,
|
||||
OrderSecurity,
|
||||
OrderPIQImmutable,
|
||||
OrderSecurity,
|
||||
uint
|
||||
@@ -121,6 +120,12 @@ namespace mbts.spark {
|
||||
o => o.order.ParentKey,
|
||||
(osam, root_osam) => osam.with(root_osam.algo ?? new AlgoData(root_osam.order.Def.CustomDef), root_osam.meta)
|
||||
).Cache();
|
||||
// order tuples with root and piq
|
||||
on_order_tuples_with_root_and_piq = on_order_tuples_with_piq.RootDependent(
|
||||
o => o.order.Key,
|
||||
o => o.order.ParentKey,
|
||||
(osam, root_osam) => osam.with(root_osam.algo ?? new AlgoData(root_osam.order.Def.CustomDef), root_osam.meta)
|
||||
).Cache();
|
||||
// fills
|
||||
var on_fills =
|
||||
_service.subscribei_fills()
|
||||
@@ -137,6 +142,28 @@ namespace mbts.spark {
|
||||
(f, fk, ok) => new FillTuple(f, null),
|
||||
f => f.fill.FillKey);
|
||||
on_fill_tuples = fill_order_tuples_join.Cache();
|
||||
// manual fills
|
||||
on_manual_fills_securities = _position_service.subscribei_manual_fills()
|
||||
.Naggle(interval, scheduler)
|
||||
.join(sstore, mf => mf.SecurityKey)
|
||||
.Table().Cache();
|
||||
var manual_fill_tuples_account_join = on_manual_fills_securities.LeftJoin(
|
||||
admin_model.on_accounts.Select(d => MappedDictionary.create(d, kv => new AccountTuple(kv.Key, kv.Value))).Table(a => a.id),
|
||||
mfs => mfs.Item2.RecordId,
|
||||
AccountTuple.key_fn,
|
||||
mfs => mfs.Item2.AccountId,
|
||||
(mfs, mf_id, a, a_id) => new ManualFillTuple(mfs.Item2, mfs.Item1, a, null),
|
||||
(mfs, mf_id, a_id) => new ManualFillTuple(mfs.Item2, mfs.Item1, null, null),
|
||||
ManualFillTuple.key_fn);
|
||||
on_manual_fill_tuples = manual_fill_tuples_account_join.LeftJoin(
|
||||
admin_model.on_users.Select(d => MappedDictionary.create(d, kv => new UserTuple(kv.Key, kv.Value))).Table(a => a.id),
|
||||
ManualFillTuple.key_fn,
|
||||
UserTuple.key_fn,
|
||||
mft => mft.manual_fill.UserId,
|
||||
(mft, mft_id, u, u_id) => new ManualFillTuple(mft.manual_fill, mft.security, mft.account, u),
|
||||
(mft, mft_id, u_id) => mft,
|
||||
ManualFillTuple.key_fn)
|
||||
.Cache();
|
||||
// can attach
|
||||
on_orders_can_attach = on_orders.ParentDependent(
|
||||
t => t.order.Key,
|
||||
@@ -171,15 +198,18 @@ namespace mbts.spark {
|
||||
public DynamicOrderHelper dynamic_helper => _dynamic_helper;
|
||||
public IObservable<OrderUpdate> on_orders { get; }
|
||||
public IObservable<OrderUpdatePiq> on_order_tuples_with_piq { get; }
|
||||
public IObservable<TableUpdate<OrderSecurityAlgoMeta, uint>> on_order_tuples { get; }
|
||||
public IObservable<TableUpdate<OrderSecurityAlgoMeta, uint>> on_order_tuples_with_root { get; }
|
||||
public IObservable<TableUpdate<OrderSecurity, uint>> on_order_tuples { get; }
|
||||
public IObservable<TableUpdate<OrderSecurity, uint>> on_order_tuples_with_root { get; }
|
||||
public IObservable<TableUpdate<OrderSecurity, uint>> on_order_tuples_with_root_and_piq { get; }
|
||||
public IObservable<TableUpdate<KeyValuePair<uint, bool>, uint>> on_orders_can_detach { get; }
|
||||
public IObservable<TableUpdate<KeyValuePair<uint, bool>, uint>> on_orders_can_attach { get; }
|
||||
public IObservable<IIncrementalUpdate<SecurityImmutable, uint>> on_order_securities { get; }
|
||||
public IObservable<TableUpdate<FillTuple, string>> on_fill_tuples { get; }
|
||||
public IObservable<IReadOnlyDictionary<uint,OrderSecurity>> working_orders { get; }
|
||||
public IObservable<TableUpdate<PositionUnitImmutable, int>> on_positions { get; }
|
||||
public IObservable<TableUpdate<(SecurityImmutable?, PositionUnitImmutable), int>> on_positions { get; }
|
||||
public IObservable<TableUpdate<(AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>> studio_algos_with_meta { get; }
|
||||
public IObservable<TableUpdate<(SecurityImmutable?, ManualFillImmutable), int>> on_manual_fills_securities { get; }
|
||||
public IObservable<TableUpdate<ManualFillTuple, int>> on_manual_fill_tuples { get; }
|
||||
|
||||
public AdminModel admin_model { get; }
|
||||
public MBOService mbo_service => _mbo_service;
|
||||
@@ -191,7 +221,7 @@ namespace mbts.spark {
|
||||
public IObservable<TableUpdate<OrderRecordImmutable, uint>> on_security_orders(uint security) =>
|
||||
_security_orders_demuxer.subscribe(security);
|
||||
|
||||
public IObservable<TableUpdate<PositionUnitImmutable, int>> on_security_positions(uint security) =>
|
||||
public IObservable<TableUpdate<(SecurityImmutable?, PositionUnitImmutable), int>> on_security_positions(uint security) =>
|
||||
_security_positions_demuxer.subscribe(security);
|
||||
#endregion
|
||||
|
||||
@@ -261,6 +291,45 @@ namespace mbts.spark {
|
||||
|
||||
(uint? key, string error) _handle_error(Result<Response> result, string template) =>
|
||||
_error.handle_error(result, template, r => r.ErrorCode, e => e == OrderError.Ok, r => new uint?(r.OrderKey));
|
||||
|
||||
#region Manual Fillls
|
||||
public async Task<bool> create_manual_fills(IEnumerable<ManualFill> fills) {
|
||||
return await create_manual_fills(_create_fill_batch(fills));
|
||||
}
|
||||
public async Task<bool> create_manual_fills(ManualFillBatchImmutable fills) {
|
||||
var result = await _position_service.create_manual_fill_async(fills);
|
||||
return _handle_error(result);
|
||||
}
|
||||
|
||||
public async Task<bool> update_manual_fills(IEnumerable<ManualFill> fills) {
|
||||
return await update_manual_fills(_create_fill_batch(fills));
|
||||
}
|
||||
public async Task<bool> update_manual_fills(ManualFillBatchImmutable fills) {
|
||||
var result = await _position_service.update_manual_fill_async(fills);
|
||||
return _handle_error(result);
|
||||
}
|
||||
|
||||
public async Task<bool> delete_manual_fills(IEnumerable<ManualFill> fills) {
|
||||
return await delete_manual_fills(_create_fill_batch(fills));
|
||||
}
|
||||
public async Task<bool> delete_manual_fills(ManualFillBatchImmutable fills) {
|
||||
var result = await _position_service.remove_manual_fill_async(fills);
|
||||
return _handle_error(result);
|
||||
}
|
||||
|
||||
ManualFillBatchImmutable _create_fill_batch(IEnumerable<ManualFill> fills) {
|
||||
var batch = new ManualFillBatch();
|
||||
batch.ManualFill.AddRange(fills);
|
||||
return new ManualFillBatchImmutable(batch);
|
||||
}
|
||||
|
||||
bool _handle_error(Result<PositionServiceResponse> result) {
|
||||
if (!result.success)
|
||||
_error.report_error(result.error_message);
|
||||
return result.value.Response.All(r => r.Ok);
|
||||
}
|
||||
#endregion // Manual Fills
|
||||
|
||||
#endregion
|
||||
|
||||
public void Dispose() =>
|
||||
@@ -356,10 +425,10 @@ namespace mbts.spark {
|
||||
|
||||
public static IObservable<double> security_total_position(this OrdersModel orders_model, uint security_key, string account) =>
|
||||
orders_model.on_security_positions(security_key)
|
||||
.Select(t => {
|
||||
.Select((t,k) => {
|
||||
foreach (var kv in t.state) {
|
||||
if (kv.Value.Account == account)
|
||||
return kv.Value.total_pos();
|
||||
if (kv.Value.Item2.Account == account)
|
||||
return kv.Value.Item2.total_pos();
|
||||
}
|
||||
return double.NaN;
|
||||
}).DistinctUntilChanged()
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.shell.manual_fills;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace bts.spark
|
||||
{
|
||||
namespace bts.spark {
|
||||
static class SparkCommands {
|
||||
// Market
|
||||
public static PartialCommand<SecurityCriteria> ShowMarketView =>
|
||||
@@ -27,9 +28,12 @@ namespace bts.spark
|
||||
SparkCommand.create("Order Book", Icons.Book, SparkTags.ShowOrderBookTag);
|
||||
|
||||
// Fills
|
||||
public static PartialCommand<FillsWindowFactory.StartupData> ShowFills(KeyGesture key_gesture = null) =>
|
||||
public static PartialCommand<FillsSettings> ShowFills(KeyGesture key_gesture = null) =>
|
||||
SparkCommand.create("Fills", Icons.Payment, SparkTags.ShowFillsTag, key_gesture);
|
||||
|
||||
public static PartialCommand<ManualFillsSettings> ShowManualFills(KeyGesture key_gesture = null) =>
|
||||
SparkCommand.create("Manual Fills", Icons.DataEdit, SparkTags.ShowManualFillsTag, key_gesture);
|
||||
|
||||
public static PartialCommand<PAndLWindowFactory.PAndLStartupData> ShowPAndL =>
|
||||
SparkCommand.create("P&L", Icons.Accounting, SparkTags.ShowPAndLTag);
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.shell.manual_fills;
|
||||
using bts.spark;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
@@ -51,7 +53,8 @@ namespace mbts.spark
|
||||
#endregion
|
||||
|
||||
#region Spark Window
|
||||
public static ITag<FillsWindowFactory.StartupData> ShowFillsTag { get; } = new SparkTag<FillsWindowFactory.StartupData>(nameof(ShowFillsTag));
|
||||
public static ITag<FillsSettings> ShowFillsTag { get; } = new SparkTag<FillsSettings>(nameof(ShowFillsTag));
|
||||
public static ITag<ManualFillsSettings> ShowManualFillsTag { get; } = new SparkTag<ManualFillsSettings>(nameof(ShowManualFillsTag));
|
||||
// TODO: maybe get rid of the singlular ShowLadderTag, and just use the plural ShowLaddersTag.
|
||||
// The owners of the various SparkWindows may remove dependencies on the former at their leasure.
|
||||
// For now, both tags go through the same logic in the LadderWindowFactory anyways.
|
||||
|
||||
@@ -83,10 +83,10 @@ namespace mbts.ui.utility {
|
||||
}
|
||||
}
|
||||
|
||||
public static IDisposable connect<T, TResult>(ObservableCollection<T> source, ObservableCollection<TResult> target, Func<T, TResult> create, Action<TResult> on_removed = null) =>
|
||||
public static IDisposable connect<T, TResult>(ObservableCollection<T> source, IList<TResult> target, Func<T, TResult> create, Action<TResult> on_removed = null) =>
|
||||
connect<ObservableCollection<T>, T, TResult>(source, target, create, null);
|
||||
|
||||
public static IDisposable connect<TCollection, T, TResult>(TCollection source, ObservableCollection<TResult> target, Func<T, TResult> create, Action<TResult> on_removed = null)
|
||||
public static IDisposable connect<TCollection, T, TResult>(TCollection source, IList<TResult> target, Func<T, TResult> create, Action<TResult> on_removed = null)
|
||||
where TCollection : IReadOnlyList<T>, INotifyCollectionChanged {
|
||||
Action reset_cb;
|
||||
Action<IList, int> removed_cb,
|
||||
@@ -105,7 +105,8 @@ namespace mbts.ui.utility {
|
||||
if (i < 0) {
|
||||
throw new InvalidOperationException("Source collection invoked a CollectionChanged 'Removed' event without a valid 'OldStartingIndex.'");
|
||||
} else {
|
||||
target.RemoveRange(i, old_items.Count);
|
||||
for (int j = 0; j < old_items.Count; j++)
|
||||
target.RemoveAt(i);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace mbts.collections {
|
||||
int count { get; }
|
||||
}
|
||||
|
||||
class RefCountMap<TKey> : IEnumerable<IKeyRefCount<TKey>>, ICollection {
|
||||
class RefCountMap<TKey> : IReadOnlyCollection<IKeyRefCount<TKey>>, ICollection {
|
||||
readonly IEqualityComparer<TKey> _key_comparer;
|
||||
readonly Dictionary<TKey, KeyRefCount> _dictionary;
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace mbts.collections {
|
||||
public object SyncRoot => null;
|
||||
public bool IsSynchronized => false;
|
||||
public int this[TKey key] => _dictionary[key].count;
|
||||
public ICollection<TKey> keys => _dictionary.Keys;
|
||||
public IReadOnlyCollection<TKey> keys => _dictionary.Keys;
|
||||
public bool contains_key(TKey key) => _dictionary.ContainsKey(key);
|
||||
|
||||
public void CopyTo(Array array, int index) =>
|
||||
@@ -241,4 +241,4 @@ namespace mbts.collections {
|
||||
return ref_count_map.Count > count_before;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ namespace mbts.ui {
|
||||
public AsyncCommand(Func<Task> execute)
|
||||
: base(_ => execute()) { }
|
||||
|
||||
public AsyncCommand(Func<Task> execute, Func<bool> can_execute)
|
||||
public AsyncCommand(Func<Task> execute, Func<bool> can_execute, bool allow_concurrent_executions = false)
|
||||
: base(_ => execute(), _ => can_execute()) { }
|
||||
|
||||
public AsyncCommand(Func<object, Task> execute)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Reactive;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
@@ -51,5 +52,41 @@ namespace mbts.ui {
|
||||
|
||||
public ObservantAsyncCommand(Func<object, Task> execute, bool allow_concurrent_executions = false)
|
||||
: base(execute, allow_concurrent_executions) { }
|
||||
|
||||
public static ICommand create(Func<Task> execute, out IObserver<bool> observer) {
|
||||
var cmd = new ObservantAsyncCommand(execute);
|
||||
observer = cmd;
|
||||
return cmd;
|
||||
}
|
||||
|
||||
public static ICommand create<T>(Func<T, Task> execute, Func<T, bool> get_can_execute, out IObserver<T> observer, bool allow_concurrent_executions = false) {
|
||||
T param = default;
|
||||
bool can_execute = false;
|
||||
var cmd = new AsyncCommand(() => execute(param), () => can_execute, allow_concurrent_executions);
|
||||
observer = new AnonymousObserver<T>(p => {
|
||||
param = p;
|
||||
if (can_execute != get_can_execute(param))
|
||||
cmd.RaiseCanExecuteChanged();
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
public static ICommand create<T>(Func<T, Task> execute, Func<T, IObservable<bool>> get_can_execute_source, out IObserver<T> observer, bool allow_concurrent_executions = false) {
|
||||
T param = default;
|
||||
bool can_execute = false;
|
||||
var cmd = new AsyncCommand(() => execute(param), () => can_execute, allow_concurrent_executions);
|
||||
IDisposable can_execute_sub = null;
|
||||
observer = new AnonymousObserver<T>(p => {
|
||||
param = p;
|
||||
Disposable.dispose(ref can_execute_sub);
|
||||
can_execute_sub = get_can_execute_source(p).Subscribe(ce => {
|
||||
if (can_execute != ce) {
|
||||
can_execute = ce;
|
||||
cmd.RaiseCanExecuteChanged();
|
||||
}
|
||||
});
|
||||
}, () => Disposable.dispose(ref can_execute_sub));
|
||||
return cmd;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,6 +222,7 @@ namespace mbts.ui {
|
||||
public static BTSColor straddle_theovsmarket_positive { get { return BTSCoreColors.green_light; } }
|
||||
|
||||
// Order Types
|
||||
public static BTSColor glbx_iceberg_order => new BTSColor(85, 85, 196);
|
||||
public static BTSColor tick_order { get { return BTSColors.purple.rgb_color(); } }
|
||||
public static BTSColor lean_order { get { return new BTSColor(255, 170, 210); } }
|
||||
public static BTSColor join_order { get { return new BTSColor(200, 171, 232); } }
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace mbts.spark {
|
||||
_ladder = LadderSettings.Default,
|
||||
_notification = NotificationSettings.Default,
|
||||
_market_view_grid = bts.shell.MarketViewWindowFactory.StartupData.DefaultGridSettings,
|
||||
_fills_grid = bts.shell.FillsWindowFactory.StartupData.DefaultGridSettings,
|
||||
_fills_grid = bts.shell.fills.FillsSettings.DefaultGridSettings,
|
||||
_time_and_sales_grid = bts.shell.TimeAndSalesWindowFactory.StartupData.DefaultGridSettings,
|
||||
_synthetic_management_grid = bts.shell.SyntheticManagementWindowFactory.StartupData.DefaultGridSettings,
|
||||
_order_book_grid = bts.shell.orderbook.OrderBookStartupData.DefaultGrid
|
||||
@@ -48,7 +48,7 @@ namespace mbts.spark {
|
||||
public NotificationSettings notification => _notification ?? (_notification = NotificationSettings.Default);
|
||||
public GridSettings market_view_grid => _market_view_grid ?? (_market_view_grid = bts.shell.MarketViewWindowFactory.StartupData.DefaultGridSettings);
|
||||
public GridSettings time_and_sales_grid => _time_and_sales_grid ?? (_time_and_sales_grid = GridSettings.Default.with(bts.shell.TimeAndSalesWindowFactory.StartupData.DefaultColumns.all));
|
||||
public GridSettings fills_grid => _fills_grid ?? (_fills_grid = bts.shell.FillsWindowFactory.StartupData.DefaultGridSettings);
|
||||
public GridSettings fills_grid => _fills_grid ?? (_fills_grid = bts.shell.fills.FillsSettings.DefaultGridSettings);
|
||||
public GridSettings synthetic_management_grid => _synthetic_management_grid ?? (_synthetic_management_grid = bts.shell.SyntheticManagementWindowFactory.StartupData.DefaultGridSettings);
|
||||
public GridSettings pnl_grid => _pnl_grid ?? (_pnl_grid = bts.shell.PAndLWindowFactory.PAndLStartupData.DefaultGridSettings);
|
||||
public bts.shell.orderbook.OrderBookGridSettings order_book_grid => _order_book_grid ?? (_order_book_grid = bts.shell.orderbook.OrderBookStartupData.Default.grid_settings_wrapper());
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace bts.utils {
|
||||
public event Action<T> common_value_changed;
|
||||
public event Action<Plurality> plurality_changed;
|
||||
|
||||
public ICollection<T> distinct_values => _values.keys;
|
||||
public IReadOnlyCollection<T> distinct_values => _values.keys;
|
||||
public T common_value => _values.keys.SafeSingleOrDefault(_default_value);
|
||||
public Plurality plurality {
|
||||
get {
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
namespace mbts.utils {
|
||||
using System;
|
||||
|
||||
namespace mbts.utils {
|
||||
delegate bool TryGetValue<T>(out T value);
|
||||
delegate bool TryGetValue<TKey, TValue>(TKey key, out TValue value);
|
||||
|
||||
static class LogicalDelegates {
|
||||
public static readonly Func<bool, bool, bool>
|
||||
And = (a, b) => a && b,
|
||||
Or = (a, b) => a || b,
|
||||
XOr = (a, b) => a ? !b : b,
|
||||
None = (a, b) => !a && !b;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace System.IO {
|
||||
class NativeFileHelper {
|
||||
const uint
|
||||
FILE_ATTRIBUTE_READONLY = 0x00000001,
|
||||
FILE_ATTRIBUTE_HIDDEN = 0x00000002,
|
||||
FILE_ATTRIBUTE_SYSTEM = 0x00000004,
|
||||
FILE_ATTRIBUTE_DIRECTORY = 0x00000010,
|
||||
FILE_ATTRIBUTE_ARCHIVE = 0x00000020,
|
||||
FILE_ATTRIBUTE_DEVICE = 0x00000040,
|
||||
FILE_ATTRIBUTE_NORMAL = 0x00000080,
|
||||
FILE_ATTRIBUTE_TEMPORARY = 0x00000100,
|
||||
FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200,
|
||||
FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400,
|
||||
FILE_ATTRIBUTE_COMPRESSED = 0x00000800,
|
||||
FILE_ATTRIBUTE_OFFLINE = 0x00001000,
|
||||
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000,
|
||||
FILE_ATTRIBUTE_ENCRYPTED = 0x00004000,
|
||||
FILE_ATTRIBUTE_VIRTUAL = 0x00010000,
|
||||
|
||||
SHGFI_ICON = 0x000000100, // get icon
|
||||
SHGFI_DISPLAYNAME = 0x000000200, // get display name
|
||||
SHGFI_TYPENAME = 0x000000400, // get type name
|
||||
SHGFI_ATTRIBUTES = 0x000000800, // get attributes
|
||||
SHGFI_ICONLOCATION = 0x000001000, // get icon location
|
||||
SHGFI_EXETYPE = 0x000002000, // return exe type
|
||||
SHGFI_SYSICONINDEX = 0x000004000, // get system icon index
|
||||
SHGFI_LINKOVERLAY = 0x000008000, // put a link overlay on icon
|
||||
SHGFI_SELECTED = 0x000010000, // show icon in selected state
|
||||
SHGFI_ATTR_SPECIFIED = 0x000020000, // get only specified attributes
|
||||
SHGFI_LARGEICON = 0x000000000, // get large icon
|
||||
SHGFI_SMALLICON = 0x000000001, // get small icon
|
||||
SHGFI_OPENICON = 0x000000002, // get open icon
|
||||
SHGFI_SHELLICONSIZE = 0x000000004, // get shell size icon
|
||||
SHGFI_PIDL = 0x000000008, // pszPath is a pidl
|
||||
SHGFI_USEFILEATTRIBUTES = 0x000000010; // use passed dwFileAttribute
|
||||
|
||||
public static string get_file_type_description(string file_type) =>
|
||||
SHGetFileInfo(file_type, FILE_ATTRIBUTE_NORMAL, out SHFILEINFO shfi, (uint)Marshal.SizeOf(typeof(SHFILEINFO)), SHGFI_USEFILEATTRIBUTES | SHGFI_TYPENAME) != IntPtr.Zero ?
|
||||
shfi.szTypeName : null;
|
||||
|
||||
[DllImport("shell32")]
|
||||
static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, out SHFILEINFO psfi, uint cbFileInfo, uint flags);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct SHFILEINFO {
|
||||
public IntPtr hIcon;
|
||||
public int iIcon;
|
||||
public uint dwAttributes;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
|
||||
public string szDisplayName;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
|
||||
public string szTypeName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
using bts.api.core;
|
||||
using mbts;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace bts.utils {
|
||||
static class ObjectSnapshot {
|
||||
public static IObservable<TableUpdate<(string name, object current, object original), string>> property_table<T>(T instance, params string[] prop_names) where T : INotifyPropertyChanged =>
|
||||
property_table(instance, Array.ConvertAll(prop_names, Impl<T>.type.GetProperty));
|
||||
|
||||
public static IObservable<TableUpdate<(string name, object current, object original), string>> property_table<T>(T instance, params PropertyInfo[] props) where T : INotifyPropertyChanged =>
|
||||
Observable.Create<TableUpdate<(string name, object current, object original), string>>(o => {
|
||||
var cache = props.ToDictionary(p => p.Name, p => new PropNode(p, p.GetValue(instance)));
|
||||
var table = TableUpdate<(string name, object current, object original), string>.mk_empty(t => t.name).with_added(cache.Values.Select(pn => (pn.prop.Name, pn.current, pn.original)));
|
||||
instance.PropertyChanged += on_prop_changed;
|
||||
return Disposable.Create(() => instance.PropertyChanged -= on_prop_changed);
|
||||
void on_prop_changed(object sender, PropertyChangedEventArgs e) {
|
||||
if (cache.TryGetValue(e.PropertyName, out PropNode pn)) {
|
||||
object oldval = pn.current;
|
||||
pn.current = pn.prop.GetValue(instance);
|
||||
table = table.with_changes(new[] { new Change<(string, object, object)>((pn.prop.Name, oldval, pn.original), (pn.prop.Name, pn.current, pn.original)) });
|
||||
o.OnNext(table);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public static IObservable<bool> dirty_monitor<T>(T instance, params string[] prop_names) where T : INotifyPropertyChanged =>
|
||||
dirty_monitor(instance, Array.ConvertAll(prop_names, Impl<T>.type.GetProperty));
|
||||
|
||||
public static IObservable<bool> dirty_monitor<T>(T instance, out IDisposable snapshot, params string[] prop_names) where T : INotifyPropertyChanged =>
|
||||
dirty_monitor(instance, out snapshot, Array.ConvertAll(prop_names, Impl<T>.type.GetProperty));
|
||||
|
||||
public static IObservable<bool> dirty_monitor<T>(T instance, out IDisposable snapshot, params PropertyInfo[] props) where T : INotifyPropertyChanged {
|
||||
var prop_map = props.ToDictionary(p => p.Name, p => new PropNode(p, p.GetValue(instance)));
|
||||
snapshot = Disposable.Create(() => {
|
||||
foreach (var pn in prop_map.Values)
|
||||
pn.prop.SetValue(instance, pn.original);
|
||||
});
|
||||
return _dirty_monitor(instance, prop_map);
|
||||
}
|
||||
|
||||
public static IObservable<bool> dirty_monitor<T>(T instance, PropertyInfo[] props) where T : INotifyPropertyChanged =>
|
||||
_dirty_monitor(instance, props.ToDictionary(p => p.Name, p => new PropNode(p, p.GetValue(instance))));
|
||||
|
||||
static IObservable<bool> _dirty_monitor<T>(T instance, Dictionary<string, PropNode> prop_map) where T : INotifyPropertyChanged =>
|
||||
Observable.Create<bool>(o => {
|
||||
var dirty_props = new HashSet<string>();
|
||||
instance.PropertyChanged += on_prop_changed;
|
||||
return Disposable.Create(() => instance.PropertyChanged -= on_prop_changed);
|
||||
void on_prop_changed(object sender, PropertyChangedEventArgs e) {
|
||||
if (prop_map.TryGetValue(e.PropertyName, out var p)) {
|
||||
p.current = p.prop.GetValue(instance);
|
||||
if (Equals(p.current, p.original) == p.is_dirty) {
|
||||
p.is_dirty = !p.is_dirty;
|
||||
if (p.is_dirty) {
|
||||
if (dirty_props.Add(p.prop.Name) && dirty_props.Count == 1)
|
||||
o.OnNext(true);
|
||||
} else if (dirty_props.Remove(p.prop.Name) && dirty_props.Count == 0)
|
||||
o.OnNext(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public static IDisposable create<T>(T instance) =>
|
||||
create(instance, Impl<T>.mutable_props);
|
||||
|
||||
public static IDisposable create<T>(T instance, PropertyInfo[] props) {
|
||||
var snapshot = Array.ConvertAll(props, p => p.GetValue(instance));
|
||||
return Disposable.Create(() => {
|
||||
for (int i = 0; i < props.Length; i++)
|
||||
props[i].SetValue(instance, snapshot[i]);
|
||||
});
|
||||
}
|
||||
|
||||
static class Impl<T> {
|
||||
public static readonly Type type = typeof(T);
|
||||
|
||||
public static readonly PropertyInfo[] mutable_props =
|
||||
type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(p => p.GetSetMethod() != null)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
class PropNode {
|
||||
public readonly PropertyInfo prop;
|
||||
|
||||
public PropNode(PropertyInfo prop, object init) {
|
||||
this.prop = prop;
|
||||
original = init;
|
||||
current = init;
|
||||
}
|
||||
|
||||
public bool is_dirty { get; set; }
|
||||
public object original { get; set; }
|
||||
public object current { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
@@ -62,30 +63,37 @@ namespace mbts.utils {
|
||||
|
||||
class PropertyChannel<T> : IPropertyChannel, IObservable<T> {
|
||||
T _last_value;
|
||||
long _id_counter;
|
||||
readonly Func<T> _get;
|
||||
readonly List<IObserver<T>> _observers = new List<IObserver<T>>();
|
||||
ImmutableDictionary<long, IObserver<T>> _observers = ImmutableDictionary<long, IObserver<T>>.Empty;
|
||||
|
||||
public PropertyChannel(Func<T> get) =>
|
||||
_last_value = (_get = get)();
|
||||
|
||||
public void on_change() {
|
||||
_last_value = _get();
|
||||
foreach (var o in _observers)
|
||||
foreach (var o in _observers.Values)
|
||||
o.OnNext(_last_value);
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(IObserver<T> observer) {
|
||||
_observers.Add(observer);
|
||||
long id = _id_counter++;
|
||||
// assume there is no way we ran out of ids, because that would be nuts.
|
||||
_observers = _observers.Add(id, observer);
|
||||
observer.OnNext(_last_value);
|
||||
return Disposable.Create(() => _observers.Remove(observer));
|
||||
return Disposable.Create(() => _observers = _observers.Remove(id));
|
||||
}
|
||||
IObservable<object> IPropertyChannel.subscribe() =>
|
||||
this.Select(v => (object)v);
|
||||
|
||||
public void Dispose() {
|
||||
foreach (var o in _observers)
|
||||
o.OnCompleted();
|
||||
_observers.Clear();
|
||||
ImmutableDictionary<long, IObserver<T>> local;
|
||||
do {
|
||||
local = _observers;
|
||||
foreach (var o in local.Values)
|
||||
o.OnCompleted();
|
||||
} while (!ReferenceEquals(local, _observers)); // remember: observers is immutable
|
||||
_observers = _observers.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using mbts.collections;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Threading;
|
||||
@@ -37,7 +38,7 @@ namespace mbts.utils {
|
||||
|
||||
// Warning: This class is not thread-safe. Use the .OnDispatcher extension in the subscribe_item delegate if necessary to ensure all updates occur on a single thread.
|
||||
class ReactiveThresholdMonitor<T> : IObservable<bool> {
|
||||
readonly HashSet<T> _filtered_items;
|
||||
readonly RefCountMap<T> _filtered_items;
|
||||
readonly List<IObserver<bool>> _observers = new List<IObserver<bool>>();
|
||||
readonly Func<T, IObservable<bool>> _subscribe_item;
|
||||
uint _threshold;
|
||||
@@ -50,7 +51,7 @@ namespace mbts.utils {
|
||||
|
||||
public ReactiveThresholdMonitor(Func<T, IObservable<bool>> subscribe_item, IEqualityComparer<T> comparer) {
|
||||
_subscribe_item = subscribe_item;
|
||||
_filtered_items = new HashSet<T>(comparer);
|
||||
_filtered_items = new RefCountMap<T>(comparer);
|
||||
}
|
||||
|
||||
public bool is_above_threshold => _filtered_items.Count > _threshold;
|
||||
@@ -61,6 +62,7 @@ namespace mbts.utils {
|
||||
_threshold = value;
|
||||
}
|
||||
}
|
||||
public IReadOnlyCollection<T> filtered_items => _filtered_items.keys;
|
||||
|
||||
public IDisposable join(T item) =>
|
||||
_subscribe_item == null ? throw new InvalidOperationException("No 'subscribe-item' delegate was specified in the constructor.") : join(item, _subscribe_item(item));
|
||||
@@ -68,10 +70,13 @@ namespace mbts.utils {
|
||||
public IDisposable join(T item, IObservable<bool> source) =>
|
||||
new CompositeDisposable(source.Subscribe(val => {
|
||||
using (_begin_update()) {
|
||||
if (val) _filtered_items.Add(item);
|
||||
else _filtered_items.Remove(item);
|
||||
if (val) _filtered_items.increment(item);
|
||||
else _filtered_items.decrement(item);
|
||||
}
|
||||
}), Disposable.Create(() => _filtered_items.Remove(item)));
|
||||
}), Disposable.Create(() => {
|
||||
using (_begin_update())
|
||||
_filtered_items.decrement(item);
|
||||
}));
|
||||
|
||||
public IDisposable Subscribe(IObserver<bool> observer) {
|
||||
_observers.Add(observer);
|
||||
|
||||
@@ -217,6 +217,9 @@ namespace mbts.ui.shell {
|
||||
protected PropertyAutoWrapper<TModel, TView> backing<TModel, TView>(string name, TModel initial_model, Func<TModel, TView> convert) =>
|
||||
new PropertyAutoWrapper<TModel, TView>(this, name, initial_model, convert, EqualityComparer<TModel>.Default);
|
||||
|
||||
protected PropertyAutoWrapper<TModel, TView> backing<TModel, TView>(string name, TModel initial_model, Func<TModel, TView> convert, IEqualityComparer<TModel> comparer) =>
|
||||
new PropertyAutoWrapper<TModel, TView>(this, name, initial_model, convert, comparer);
|
||||
|
||||
protected DelegatePropertyAutoWrapper<TModel, TView> backing<TModel, TView>(string name, Func<TModel> get_model, Func<TModel, TView> convert) =>
|
||||
new DelegatePropertyAutoWrapper<TModel, TView>(this, name, get_model, convert, EqualityComparer<TModel>.Default);
|
||||
|
||||
@@ -324,17 +327,21 @@ namespace mbts.ui.shell {
|
||||
public TModel model { get => _model; set => set(value); }
|
||||
public TView view => _view;
|
||||
|
||||
public void set(TModel model) {
|
||||
if (!_comparer.Equals(model, _model)) {
|
||||
TView new_view = _convert(model);
|
||||
changing?.Invoke((_model, _view), (model, new_view));
|
||||
TView old_view = _view;
|
||||
TModel old_model = _model;
|
||||
_model = model;
|
||||
_view = new_view;
|
||||
changed?.Invoke((old_model, old_view), (_model, _view));
|
||||
_owner.RaisePropertyChanged(_prop_name);
|
||||
}
|
||||
public void set(TModel model) =>
|
||||
set_new(model);
|
||||
|
||||
public bool set_new(TModel model) {
|
||||
if (_comparer.Equals(model, _model))
|
||||
return false;
|
||||
TView new_view = _convert(model);
|
||||
changing?.Invoke((_model, _view), (model, new_view));
|
||||
TView old_view = _view;
|
||||
TModel old_model = _model;
|
||||
_model = model;
|
||||
_view = new_view;
|
||||
changed?.Invoke((old_model, old_view), (_model, _view));
|
||||
_owner.RaisePropertyChanged(_prop_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(IObserver<((TModel model, TView view) oldval, (TModel model, TView view) newval)> observer) {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace bts {
|
||||
struct AccountTuple : IEquatable<AccountTuple> {
|
||||
struct AccountTuple : IEquatable<AccountTuple>, IComparable<AccountTuple>, IComparable {
|
||||
public static readonly Func<AccountTuple, uint> key_fn = a => a.id;
|
||||
|
||||
public readonly uint id;
|
||||
public readonly string name;
|
||||
|
||||
@@ -10,6 +12,14 @@ namespace bts {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int CompareTo(AccountTuple other) {
|
||||
int comp = string.Compare(name, other.name);
|
||||
return comp == 0 ? id.CompareTo(other.id) : comp;
|
||||
}
|
||||
|
||||
int IComparable.CompareTo(object obj) =>
|
||||
obj is AccountTuple other ? CompareTo(other) : 1;
|
||||
|
||||
public bool Equals(AccountTuple other) => other.id == id && other.name == name;
|
||||
public override bool Equals(object obj) => obj is AccountTuple other && Equals(other);
|
||||
public override int GetHashCode() => id.GetHashCode();
|
||||
@@ -18,4 +28,4 @@ namespace bts {
|
||||
public static bool operator ==(AccountTuple a, AccountTuple b) => a.Equals(b);
|
||||
public static bool operator !=(AccountTuple a, AccountTuple b) => !a.Equals(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using bts.api;
|
||||
using bts.api.position;
|
||||
using bts.utils;
|
||||
using System;
|
||||
|
||||
namespace bts {
|
||||
struct ManualFillTuple : IEquatable<ManualFillTuple> {
|
||||
public static readonly Func<ManualFillTuple, int> key_fn = m => m.manual_fill.RecordId;
|
||||
|
||||
public readonly ManualFillImmutable manual_fill;
|
||||
public readonly SecurityImmutable? security;
|
||||
public readonly AccountTuple? account;
|
||||
public readonly UserTuple? user;
|
||||
|
||||
public ManualFillTuple(ManualFillImmutable manual_fill, SecurityImmutable? security, AccountTuple? account, UserTuple? user) {
|
||||
this.manual_fill = manual_fill;
|
||||
this.security = security;
|
||||
this.account = account;
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public bool Equals(ManualFillTuple other) =>
|
||||
manual_fill.Equals(other.manual_fill) &&
|
||||
EqualityComparer.nullable_equals(security, other.security) &&
|
||||
EqualityComparer.nullable_equals(account, other.account) &&
|
||||
EqualityComparer.nullable_equals(user, other.user);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is ManualFillTuple other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
manual_fill.GetHashCode(),
|
||||
security?.GetHashCode() ?? 0,
|
||||
account?.GetHashCode() ?? 0,
|
||||
user?.GetHashCode() ?? 0);
|
||||
|
||||
public static bool operator ==(ManualFillTuple a, ManualFillTuple b) => a.Equals(b);
|
||||
public static bool operator !=(ManualFillTuple a, ManualFillTuple b) => !a.Equals(b);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace bts {
|
||||
struct UserTuple : IEquatable<UserTuple> {
|
||||
struct UserTuple : IEquatable<UserTuple>, IComparable<UserTuple>, IComparable {
|
||||
public static readonly Func<UserTuple, uint> key_fn = u => u.id;
|
||||
|
||||
public readonly uint id;
|
||||
public readonly string name;
|
||||
|
||||
@@ -10,6 +12,14 @@ namespace bts {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int CompareTo(UserTuple other) {
|
||||
int comp = string.Compare(name, other.name);
|
||||
return comp == 0 ? id.CompareTo(other.id) : comp;
|
||||
}
|
||||
|
||||
int IComparable.CompareTo(object obj) =>
|
||||
obj is UserTuple other ? CompareTo(other) : 1;
|
||||
|
||||
public bool Equals(UserTuple other) => other.id == id && other.name == name;
|
||||
public override bool Equals(object obj) => obj is UserTuple other && Equals(other);
|
||||
public override int GetHashCode() => id.GetHashCode();
|
||||
@@ -18,4 +28,4 @@ namespace bts {
|
||||
public static bool operator ==(UserTuple a, UserTuple b) => a.Equals(b);
|
||||
public static bool operator !=(UserTuple a, UserTuple b) => !a.Equals(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using bts.utils;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
@@ -8,8 +9,10 @@ namespace mbts.ui.controls.adorners {
|
||||
internal enum HorizontalPosition { Left, Middle, Right }
|
||||
internal enum VerticalPosition { Top, Middle, Botton }
|
||||
|
||||
internal class PopupAdorner : Adorner
|
||||
{
|
||||
class PopupAdorner : Adorner {
|
||||
bool _enable_drag_move;
|
||||
IDisposable _drag_move_hook;
|
||||
|
||||
public PopupAdorner(UIElement adornedElement, FrameworkElement popupTarget)
|
||||
: this(adornedElement, popupTarget, null, HorizontalPosition.Middle, VerticalPosition.Botton) { }
|
||||
|
||||
@@ -59,6 +62,7 @@ namespace mbts.ui.controls.adorners {
|
||||
target.InvalidateVisual();
|
||||
target.RaiseEvent(new RoutedEventArgs(ClosedEvent, target));
|
||||
}
|
||||
target._reset_drag_move_behavior();
|
||||
}
|
||||
public bool IsOpen { get { return (bool)GetValue(IsOpenProperty); } set { SetValue(IsOpenProperty, value); } }
|
||||
|
||||
@@ -79,6 +83,15 @@ namespace mbts.ui.controls.adorners {
|
||||
#endregion
|
||||
|
||||
public bool OpenRelativeToMouse { get; set; }
|
||||
public bool EnableDragMove {
|
||||
get => _enable_drag_move;
|
||||
set {
|
||||
if (value != _enable_drag_move) {
|
||||
_enable_drag_move = value;
|
||||
_reset_drag_move_behavior();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override int VisualChildrenCount { get { return _visualChildrenCount; } }
|
||||
|
||||
@@ -144,5 +157,41 @@ namespace mbts.ui.controls.adorners {
|
||||
}
|
||||
return _popupTarget.TranslatePoint(tl, _popupTarget);
|
||||
}
|
||||
|
||||
void _reset_drag_move_behavior() {
|
||||
Disposable.dispose(ref _drag_move_hook);
|
||||
if (_enable_drag_move && IsOpen) {
|
||||
Point? drag_origin = null;
|
||||
_drag_move_hook = DragDropHelper.subscribe_drag_coordinates(this).Subscribe(delta => {
|
||||
if (delta.HasValue) {
|
||||
var d = delta.Value;
|
||||
var rs = AdornedElement.RenderSize;
|
||||
// ensure within bounds of AdornedElement
|
||||
var relative_pos = Mouse.GetPosition(AdornedElement);
|
||||
if (relative_pos.X < 0)
|
||||
d.X -= relative_pos.X;
|
||||
else if (relative_pos.X > rs.Width)
|
||||
d.X -= (relative_pos.X - rs.Width);
|
||||
if (relative_pos.Y < 0)
|
||||
d.Y -= relative_pos.Y;
|
||||
else if (relative_pos.Y > rs.Height)
|
||||
d.Y -= (relative_pos.Y - rs.Height);
|
||||
//
|
||||
if (drag_origin.HasValue) {
|
||||
var m = Margin;
|
||||
m.Left += d.X - drag_origin.Value.X;
|
||||
m.Top += d.Y - drag_origin.Value.Y;
|
||||
Margin = m;
|
||||
} else {
|
||||
drag_origin = d;
|
||||
Mouse.Capture(this);
|
||||
}
|
||||
} else {
|
||||
drag_origin = null;
|
||||
ReleaseMouseCapture();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using bts.api;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark.filters {
|
||||
class ColumnPredicateBuilder<TRow> {
|
||||
readonly List<Func<TRow, bool>> _predicates = new List<Func<TRow, bool>>();
|
||||
|
||||
public void add_discrete_values<T>(T[] values, Func<TRow, T> selector) {
|
||||
if (values.is_null_or_empty())
|
||||
return;
|
||||
var set = new HashSet<T>(values);
|
||||
_predicates.Add(impl);
|
||||
bool impl(TRow o) => set.Contains(selector(o));
|
||||
}
|
||||
|
||||
public void add_numeric<T>(IMinMaxState<T> value, Func<TRow, T> selector) where T : struct, IComparable<T> {
|
||||
if (value == null)
|
||||
return;
|
||||
if (value.minimum.active) {
|
||||
var min = value.minimum.value.Value;
|
||||
if (value.maximum.active) {
|
||||
var max = value.maximum.value.Value;
|
||||
_predicates.Add(impl);
|
||||
bool impl(TRow o) {
|
||||
var v = selector(o);
|
||||
return min.CompareTo(v) <= 0 && max.CompareTo(v) >= 0;
|
||||
}
|
||||
} else {
|
||||
_predicates.Add(impl);
|
||||
bool impl(TRow o) => min.CompareTo(selector(o)) <= 0;
|
||||
}
|
||||
} else if (value.maximum.active) {
|
||||
var max = value.maximum.value.Value;
|
||||
_predicates.Add(impl);
|
||||
bool impl(TRow o) => max.CompareTo(selector(o)) >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void add_regex(string str, Func<TRow, string> selector) {
|
||||
var regex = RegexFilterUtils.build_regex(str);
|
||||
if (regex == null)
|
||||
return;
|
||||
_predicates.Add(impl);
|
||||
bool impl(TRow o) => regex.IsMatch(selector(o) ?? string.Empty);
|
||||
}
|
||||
|
||||
public void add_security_criteria(SecurityCriteria security_criteria, Func<TRow, SecurityImmutable?> selector) {
|
||||
if (security_criteria != null && security_criteria.is_defined())
|
||||
_predicates.Add(r => {
|
||||
var sec = selector(r);
|
||||
return sec.HasValue && ContractPickerModel.security_in_criteria(security_criteria, sec.Value);
|
||||
});
|
||||
}
|
||||
|
||||
public void add_time_filter(TimeFilterTuple t, Func<TRow, DateTime> selector) {
|
||||
if (!t.is_active)
|
||||
return;
|
||||
var dt_pred = TimeFilterUtils.build_predicate(t);
|
||||
_predicates.Add(impl);
|
||||
bool impl(TRow o) => dt_pred(selector(o));
|
||||
}
|
||||
|
||||
public Func<TRow, bool> to_predicate() =>
|
||||
_predicates.Count == 0 ? null : new Func<TRow, bool>(o => _predicates.All(p => p(o)));
|
||||
}
|
||||
}
|
||||
@@ -226,7 +226,11 @@
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:PredicateTemplateKeys.SecurityCriteria}">
|
||||
<DataTemplate>
|
||||
<c:ContractPickerPopup SelectedCriteria="{Binding value}" SecurityMap="{Binding Path='[security_map]', UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"/>
|
||||
<c:ContractPickerPopup
|
||||
SelectedCriteria="{Binding value}"
|
||||
SecurityMap="{Binding Path='[security_map]', UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"
|
||||
Header="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using bts;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -7,13 +8,30 @@ using System.ComponentModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
static class DIYGridRowsManager {
|
||||
public static DIYGridRowsManager<T, TColKey, TFiltersState> create<T, TColKey, TFiltersState>(
|
||||
string window_name,
|
||||
TColKey primary_key,
|
||||
ObservableKeyedCollection<DIYGridColumnData<T, TColKey, TFiltersState>, TColKey> columns,
|
||||
IReadOnlyObservableKeyedCollection<(DIYGrid.ColumnBase col, TColKey key), TColKey> map,
|
||||
Func<TColKey, IComparer<T>> get_comparer_for_column,
|
||||
Func<TFiltersState, Func<T, bool>> build_filter_from_state = null) where TColKey : struct where TFiltersState : new() =>
|
||||
//
|
||||
new DIYGridRowsManager<T, TColKey, TFiltersState>(
|
||||
DIYGridFiltersManager.create(columns),
|
||||
new DIYGridSortManager<TColKey>(map, primary_key),
|
||||
get_comparer_for_column,
|
||||
window_name,
|
||||
build_filter_from_state);
|
||||
}
|
||||
|
||||
class DIYGridRowsManager<T, TColKey, TFiltersState> where TColKey : struct {
|
||||
readonly IDIYGridFiltersManager<T, TColKey, TFiltersState> _filters;
|
||||
readonly DIYGridSortManager<TColKey> _sorting;
|
||||
readonly DeferrableActionWrapper _update;
|
||||
bool _filters_dirty, _sorting_dirty;
|
||||
|
||||
public DIYGridRowsManager(IDIYGridFiltersManager<T, TColKey, TFiltersState> filters_manager, DIYGridSortManager<TColKey> sort_manager, Func<TColKey, IComparer<T>> get_comparer_for_column, Action<Func<T, bool>> set_filter, Action<IComparer<T>> set_sorting, Action<IComparer<T>, Func<T, bool>> reset, string window_name) {
|
||||
public DIYGridRowsManager(IDIYGridFiltersManager<T, TColKey, TFiltersState> filters_manager, DIYGridSortManager<TColKey> sort_manager, Func<TColKey, IComparer<T>> get_comparer_for_column, string window_name, Func<TFiltersState, Func<T, bool>> build_filter_from_state = null) {
|
||||
_filters = filters_manager;
|
||||
_sorting = sort_manager;
|
||||
_update = new DeferrableActionWrapper(() => {
|
||||
@@ -22,16 +40,17 @@ namespace mbts.ui.controls {
|
||||
var comp = build_comparer(out string comp_desc);
|
||||
var filter = build_filter(out string filter_desc);
|
||||
Log.Ui.info($"{window_name} begin filter '{filter_desc}' and sort '{comp_desc}.'");
|
||||
reset(comp, filter);
|
||||
filter_changed?.Invoke(filter);
|
||||
comparer_changed?.Invoke(comp);
|
||||
} else {
|
||||
var filter = build_filter(out string filter_desc);
|
||||
Log.Ui.info($"{window_name} begin filter '{filter_desc}.'");
|
||||
set_filter(filter);
|
||||
filter_changed?.Invoke(filter);
|
||||
}
|
||||
} else if (_sorting_dirty) {
|
||||
var comp = build_comparer(out string comp_desc);
|
||||
Log.Ui.info($"{window_name} begin sort '{comp_desc}.'");
|
||||
set_sorting(comp);
|
||||
comparer_changed?.Invoke(comp);
|
||||
}
|
||||
_sorting_dirty = false;
|
||||
_filters_dirty = false;
|
||||
@@ -58,14 +77,26 @@ namespace mbts.ui.controls {
|
||||
desc = "(none)";
|
||||
return null;
|
||||
} else {
|
||||
// TODO: the actual IDataGridFilter implementations should be able to provide a concise description of their filter state/parameters (e.g., for a min/max filter: 'min=40, max=60')
|
||||
desc = string.Join(", ", col_filters.Select(f => f.col));
|
||||
var filters = Array.ConvertAll(col_filters, cf => cf.filter);
|
||||
return new Func<T, bool>(r => filters.All(f => f.filter(r)));
|
||||
if (build_filter_from_state != null) {
|
||||
var filter_state = filters_manager.create();
|
||||
return build_filter_from_state(filter_state);
|
||||
} else {
|
||||
// TODO: the actual IDataGridFilter implementations should be able to provide a concise description of their filter state/parameters (e.g., for a min/max filter: 'min=40, max=60')
|
||||
var filters = Array.ConvertAll(col_filters, cf => cf.filter);
|
||||
return new Func<T, bool>(r => filters.All(f => f.filter(r)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<Func<T, bool>> filter_changed;
|
||||
public event Action<IComparer<T>> comparer_changed;
|
||||
|
||||
public TFiltersState get_filters() => _filters.create();
|
||||
public IEnumerable<SortingCriteria> get_sorting_criterion() => _sorting.create();
|
||||
|
||||
public void on_sorting(DIYGridSortingEventArgs e) => _sorting.on_sorting(e);
|
||||
public void on_settings(TFiltersState filters, IEnumerable<SortingCriteria> sorting) {
|
||||
using (_update.defer_execution()) {
|
||||
_filters.load_from(filters);
|
||||
|
||||
@@ -63,8 +63,8 @@ namespace mbts.spark.filters {
|
||||
) => new DynamicItemsFilter<TRow, TValue, TState>(items, get_row_val, get_state, load_state, prop_name, equality_comparer ?? EqualityComparer<TValue>.Default, comparer);
|
||||
|
||||
public static IDataGridColumnPersistentFilter<TRow, TKey[]> CreateTableUpdate<TRow, TValue, TKey>(
|
||||
Func<TRow, TValue> factory, Func<TRow, TKey> row_key, Func<TValue, TKey> key, out IObserver<TableUpdate<TValue, TKey>> items_observer, out ListCollectionView<TValue> items_view) where TKey : struct, IEquatable<TKey> {
|
||||
var filter = new TableUpdateItemsFilter<TRow, TValue, TKey, TKey[]>(factory, row_key, key, keys => keys, keys => keys.ToArray());
|
||||
Func<TRow, TValue> factory, Func<TValue, TKey> key, out IObserver<TableUpdate<TValue, TKey>> items_observer, out ListCollectionView<TValue> items_view) where TKey : struct, IEquatable<TKey> {
|
||||
var filter = new TableUpdateItemsFilter<TRow, TValue, TKey, TKey[]>(factory, key, keys => keys, keys => keys.ToArray());
|
||||
items_observer = filter;
|
||||
items_view = filter.items_view;
|
||||
return filter;
|
||||
@@ -255,17 +255,15 @@ namespace mbts.spark.filters {
|
||||
readonly KeyedMultiSelector<TValue, TKey> _selection_model;
|
||||
readonly Func<TValue, TKey> _key;
|
||||
readonly Func<TRow, TValue> _factory;
|
||||
readonly Func<TRow, TKey> _row_key;
|
||||
readonly Func<TState, IEnumerable<TKey>> _load_state;
|
||||
readonly Func<IEnumerable<TKey>, TState> _get_state;
|
||||
readonly IEqualityComparer<TValue> _comparer;
|
||||
readonly HashSet<TKey> _selected_keys = new HashSet<TKey>(), _desired_keys = new HashSet<TKey>();
|
||||
ListCollectionView<TValue> _items_view;
|
||||
|
||||
public TableUpdateItemsFilter(Func<TRow, TValue> factory, Func<TRow, TKey> row_key, Func<TValue, TKey> key, Func<TState, IEnumerable<TKey>> load_state, Func<IEnumerable<TKey>, TState> get_state)
|
||||
public TableUpdateItemsFilter(Func<TRow, TValue> factory, Func<TValue, TKey> key, Func<TState, IEnumerable<TKey>> load_state, Func<IEnumerable<TKey>, TState> get_state)
|
||||
: base(factory, null) {
|
||||
_factory = factory;
|
||||
_row_key = row_key;
|
||||
_key = key;
|
||||
_load_state = load_state;
|
||||
_get_state = get_state;
|
||||
|
||||
@@ -36,6 +36,12 @@ namespace mbts.ui {
|
||||
|
||||
#endregion
|
||||
|
||||
public static bool Confirm(string message, string title) =>
|
||||
Show(message, title, BTSMessageBoxButtons.Yes | BTSMessageBoxButtons.No, MessageBoxImage.Question, true, true, true) == BTSMessageBoxButtons.Yes;
|
||||
|
||||
public static void Error(string message, string title) =>
|
||||
Show(message, title, BTSMessageBoxButtons.OK, MessageBoxImage.Error, true, true, true);
|
||||
|
||||
public static Task<BTSMessageBoxButtons> ShowAsync(string message, string title) {
|
||||
return ShowAsync(message, title, BTSMessageBoxButtons.OK, MessageBoxImage.None, false, false);
|
||||
}
|
||||
|
||||
@@ -872,6 +872,45 @@
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:AlgoContentTemplateSelector.TriggerOrderTemplate>
|
||||
<usp:AlgoContentTemplateSelector.GLBXIcebergOrderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid x:Name="ContentRoot">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager Target="{Binding Source={u:DirectReference ContentRoot}}">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding DataContext.editing_permissions, Source={u:DirectReference ContentRoot}, Converter={StaticResource ValuesEditingStateConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="ValuesEditingStates">
|
||||
<VisualState Name="{StaticResource CanEditValuesState}"/>
|
||||
<VisualState Name="{StaticResource CannotEditValuesState}">
|
||||
<VisualState.Storyboard>
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_MaxShowQuantity_TickUpDownNullableUInt" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState.Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}" MaxWidth="{Binding Source={StaticResource HorizontalPush1}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Max Show Quantity</TextBlock>
|
||||
<c:TickUpDownNullableUInt x:Name="PART_MaxShowQuantity_TickUpDownNullableUInt" Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding max_show_quantity}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:AlgoContentTemplateSelector.GLBXIcebergOrderTemplate>
|
||||
<usp:AlgoContentTemplateSelector.IcebergOrderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid x:Name="ContentRoot">
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace mbts.ui.spark {
|
||||
public DataTemplate PricerOrderTemplate { get; set; }
|
||||
public DataTemplate OffsetOrderTemplate { get; set; }
|
||||
public DataTemplate TriggerOrderTemplate { get; set; }
|
||||
public DataTemplate GLBXIcebergOrderTemplate { get; set; }
|
||||
public DataTemplate IcebergOrderTemplate { get; set; }
|
||||
public DataTemplate SpreaderTemplate { get; set; }
|
||||
public DataTemplate DynamicOrderTemplate { get; set; }
|
||||
@@ -62,6 +63,7 @@ namespace mbts.ui.spark {
|
||||
case CustomOrderDefinition.CustomOneofCase.Pricer: return PricerOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Offset: return OffsetOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Trigger: return TriggerOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.GlbxIceberg: return GLBXIcebergOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Iceberg: return IcebergOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Spreader: return SpreaderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic: return DynamicOrderTemplate;
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
|
||||
>
|
||||
<Style TargetType="uag:AlgoParamsView">
|
||||
<Setter Property="Background" Value="{x:Static u:BTSColors.common_background_brush}"/>
|
||||
<Setter Property="BorderBrush" Value="DarkGray"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="uag:AlgoParamsView">
|
||||
<Border
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
>
|
||||
<Grid Grid.IsSharedSizeScope="True" DataContext="{Binding order_properties_vm}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{x:Static u:BTSColors.dialog_skirt_brush}"/>
|
||||
<uag:AlgoTemplateToolBar
|
||||
Margin="4,0,0,0"
|
||||
MinWidth="230"
|
||||
Padding="0,1,0,3"
|
||||
Definition="{Binding selected_algo_definition}"
|
||||
RevertDefinitionCommand="{Binding revert_algo_params_cmd}"
|
||||
ShowRevertDefinition="True"
|
||||
/>
|
||||
<usp:AlgoEditorGridView
|
||||
Grid.Row="1"
|
||||
Margin="12,8,12,12"
|
||||
AnimateDirty="False"
|
||||
CanUserChangeAlgoRoot="False"
|
||||
AlgoParamRows="{Binding algo_param_rows}"
|
||||
Visibility="Visible"
|
||||
/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace mbts.ui.spark.algo {
|
||||
/// <summary>
|
||||
/// Interaction logic for AlgoParamsView.xaml
|
||||
/// </summary>
|
||||
class AlgoParamsView : Control {
|
||||
static AlgoParamsView() =>
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(AlgoParamsView), new FrameworkPropertyMetadata(typeof(AlgoParamsView)));
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,17 @@
|
||||
<ResourceDictionary
|
||||
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:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
|
||||
>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/Popup/PopupControl.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoContentTemplateSelector.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoTemplateToolBar.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoPickerNodeView.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<Style x:Key="AlgoFavoriteItemContainerStyle" TargetType="ListBoxItem">
|
||||
<Setter Property="Background" Value="{Binding badge_fill}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
@@ -56,15 +64,100 @@
|
||||
Visibility="Collapsed"
|
||||
/>
|
||||
</Grid>
|
||||
<Popup x:Name="AlgoParamsPopup" PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}" StaysOpen="False">
|
||||
<Border Style="{StaticResource PopupContainerStyleBase}" Padding="0">
|
||||
<Grid Grid.IsSharedSizeScope="True" DataContext="{Binding DataContext.order_properties_vm, RelativeSource={RelativeSource AncestorType=uag:AlgoTemplateFavoriteButtonsView}}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle Grid.ColumnSpan="{Binding ColumnDefinitions.Count, RelativeSource={RelativeSource AncestorType=Grid}}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{x:Static u:BTSColors.dialog_skirt_brush}"/>
|
||||
<uag:AlgoTemplateToolBar
|
||||
x:Name="PART_AlgoTemplateToolBar"
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
MinWidth="230"
|
||||
ShowRenameDefinition="{DynamicResource {x:Static sp:AlgoStudioWindow.ShowAlgoToolBarRenameDefinitionKey}}"
|
||||
ShowSaveDefinition="{DynamicResource {x:Static sp:AlgoStudioWindow.ShowAlgoToolBarSaveDefinitionKey}}"
|
||||
ShowTemplatesDropDown="{DynamicResource {x:Static sp:AlgoStudioWindow.TemplatesEnabledKey}}"
|
||||
Definition="{Binding DataContext, RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
|
||||
EditableCollectionModel="{Binding algo_template_collection_model}"
|
||||
Padding="0,1,0,3"
|
||||
RemoveCommand="{Binding remove_template_cmd}"
|
||||
UndoCommand="{Binding undo_cmd}"
|
||||
RedoCommand="{Binding redo_cmd}"
|
||||
UndoDefinitionCommand="{Binding undo_definition_cmd}"
|
||||
RedoDefinitionCommand="{Binding redo_definition_cmd}"
|
||||
ShowUndoRedoButtons="{DynamicResource {x:Static sp:AlgoStudioWindow.ShowAlgoToolBarUndoRedoDefinitionKey}}"
|
||||
SetDefaultsCommand="{Binding set_default_algo_params_cmd}"
|
||||
RevertDefinitionCommand="{Binding revert_algo_params_cmd}"
|
||||
ShowRevertDefinition="{DynamicResource {x:Static sp:AlgoStudioWindow.ShowAlgoToolBarRevertDefinitionKey}}"
|
||||
SaveCommand="{Binding save_template_cmd}"
|
||||
SaveDefinitionCommand="{Binding save_definition_cmd}"
|
||||
ToggleFavoriteCommand="{Binding toggle_favorite_cmd}"
|
||||
SelectedItem="{Binding selected_algo_template, Mode=TwoWay}"
|
||||
ItemContainerStyle="{StaticResource AlgoTemplateItemContainerStyle}"
|
||||
>
|
||||
<uag:AlgoTemplateToolBar.Resources>
|
||||
<DataTemplate DataType="{x:Type sp:AlgoDefinitionTemplateVM}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="{Binding name}"/>
|
||||
<Button
|
||||
x:Name="remove_button"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="2,0,0,0"
|
||||
Style="{StaticResource AlgoTemplateRemoveButtonStyle}"
|
||||
/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Static sp:AlgoDefinitionTemplateVM.CustomType}">
|
||||
<TextBlock Text="{Binding name}"/>
|
||||
</DataTemplate>
|
||||
</uag:AlgoTemplateToolBar.Resources>
|
||||
<uag:AlgoTemplateToolBar.CommandBindings>
|
||||
<CommandBinding Command="{x:Static uag:AlgoTemplateToolBar.BeginAddNewCommand}" Executed="{x:Static uag:AlgoTemplateToolBar.BeginAddNewExecuted}"/>
|
||||
<CommandBinding Command="{x:Static uag:AlgoTemplateToolBar.BeginSaveCommand}" Executed="{x:Static uag:AlgoTemplateToolBar.BeginSaveExecuted}"/>
|
||||
<CommandBinding Command="{x:Static uag:AlgoTemplateToolBar.BeginSaveDefinitionCommand}" Executed="{x:Static uag:AlgoTemplateToolBar.BeginSaveDefinitionExecuted}"/>
|
||||
<CommandBinding Command="{x:Static uag:AlgoTemplateToolBar.BeginSaveAsCommand}" Executed="{x:Static uag:AlgoTemplateToolBar.BeginSaveAsExecuted}"/>
|
||||
<CommandBinding Command="{x:Static uag:AlgoTemplateToolBar.BeginSaveDefinitionAsCommand}" Executed="{x:Static uag:AlgoTemplateToolBar.BeginSaveDefinitionAsExecuted}"/>
|
||||
<CommandBinding Command="{x:Static uag:AlgoTemplateToolBar.BeginRevertCommand}" Executed="{x:Static uag:AlgoTemplateToolBar.BeginRevertExecuted}"/>
|
||||
<CommandBinding Command="{x:Static uag:AlgoTemplateToolBar.BeginRevertDefinitionCommand}" Executed="{x:Static uag:AlgoTemplateToolBar.BeginRevertDefinitionExecuted}"/>
|
||||
<CommandBinding Command="{x:Static uag:AlgoTemplateToolBar.BeginRemoveCommand}" Executed="{x:Static uag:AlgoTemplateToolBar.BeginRemoveExecuted}"/>
|
||||
</uag:AlgoTemplateToolBar.CommandBindings>
|
||||
</uag:AlgoTemplateToolBar>
|
||||
<ContentControl
|
||||
x:Name="StudioView"
|
||||
Grid.Row="1"
|
||||
Margin="12,8,12,12"
|
||||
Content="{Binding SelectedItem.visible_algo_root, RelativeSource={RelativeSource AncestorType=uag:OrderDetailsPopupControl}}"
|
||||
ContentTemplateSelector="{StaticResource AlgoTemplateSelector}"
|
||||
Visibility="Collapsed"
|
||||
/>
|
||||
<usp:AlgoEditorGridView
|
||||
x:Name="ParamGridView"
|
||||
Grid.Row="1"
|
||||
Margin="12,8,12,12"
|
||||
AnimateDirty="False"
|
||||
CanUserChangeAlgoRoot="False"
|
||||
AlgoParamRows="{Binding algo_param_rows}"
|
||||
Visibility="Visible"
|
||||
/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Popup>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="TextElement.FontWeight" Value="Bold"/>
|
||||
<Setter TargetName="name_text" Property="FontWeight" Value="Bold"/>
|
||||
<Setter Property="Opacity" Value="1"/>
|
||||
<Setter Property="BorderThickness" Value="2"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter Property="TextElement.FontWeight" Value="Bold"/>
|
||||
<Setter TargetName="name_text" Property="FontWeight" Value="Bold"/>
|
||||
<Setter Property="Opacity" Value="1"/>
|
||||
<Setter TargetName="_checkmark" Property="Visibility" Value="Visible"/>
|
||||
</Trigger>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using mbts.ui.utilities;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Specialized;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark.algo {
|
||||
@@ -36,12 +38,24 @@ namespace mbts.ui.spark.algo {
|
||||
|
||||
protected override DependencyObject GetContainerForItemOverride() {
|
||||
var lbi = new ListBoxItem();
|
||||
lbi.PreviewMouseLeftButtonDown += on_preview_mouse_left_button_down;
|
||||
var popup = new VisualChildLookup<Popup>(lbi, "AlgoParamsPopup");
|
||||
lbi.PreviewMouseDown += on_preview_mouse_down;
|
||||
return lbi;
|
||||
void on_preview_mouse_left_button_down(object sender, MouseButtonEventArgs e) {
|
||||
if (lbi.IsSelected) {
|
||||
lbi.SetCurrentValue(ListBoxItem.IsSelectedProperty, false);
|
||||
e.Handled = true;
|
||||
void on_preview_mouse_down(object sender, MouseButtonEventArgs e) {
|
||||
switch (e.ChangedButton) {
|
||||
case MouseButton.Middle:
|
||||
if (popup.has_value) {
|
||||
lbi.set_current_if_changed(ListBoxItem.IsSelectedProperty, true);
|
||||
popup.value.SetCurrentValue(Popup.IsOpenProperty, !popup.value.IsOpen);
|
||||
e.Handled = true;
|
||||
}
|
||||
break;
|
||||
case MouseButton.Left:
|
||||
if (lbi.IsSelected && !(popup.value?.IsOpen ?? false)) {
|
||||
lbi.SetCurrentValue(ListBoxItem.IsSelectedProperty, false);
|
||||
e.Handled = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,55 @@
|
||||
<ResourceDictionary
|
||||
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:uag="clr-namespace:mbts.ui.spark.algo"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
>
|
||||
<DataTemplate x:Key="AlgoTemplateHeaderContentTemplate" DataType="sp:AlgoDefinitionVM">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
x:Name="order_type"
|
||||
Text="{Binding algo_root.order_type, Converter={StaticResource OrderTypeDisplayTextConverter}, StringFormat='{}{0}: ', Mode=OneWay}"
|
||||
FontWeight="Bold"
|
||||
/>
|
||||
<TextBlock x:Name="display_name" Text="{Binding name, Mode=OneWay}" FontWeight="Normal"/>
|
||||
</StackPanel>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Value="{u:True}">
|
||||
<DataTrigger.Binding>
|
||||
<MultiBinding Converter="{x:Static cv:ValueConverters.AreEqualConverter}">
|
||||
<Binding Path="(sp:AlgoDefinitionVM.algo_root).(sp:IAlgoVM.order_type)" Converter="{StaticResource OrderTypeDisplayTextConverter}"/>
|
||||
<Binding Path="(sp:AlgoDefinitionVM.name)"/>
|
||||
</MultiBinding>
|
||||
</DataTrigger.Binding>
|
||||
<Setter TargetName="order_type" Property="Visibility" Value="{x:Static Visibility.Collapsed}"/>
|
||||
<Setter TargetName="display_name" Property="FontWeight" Value="{x:Static FontWeights.Bold}"/>
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
<Style x:Key="AlgoTemplateItemContainerStyle" TargetType="ComboBoxItem" BasedOn="{StaticResource {x:Type ComboBoxItem}}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="AlgoMenuItem" TargetType="MenuItem">
|
||||
<Setter Property="ToolTipService.ShowOnDisabled" Value="True"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="Width" Value="20"/>
|
||||
<Setter Property="Height" Value="20"/>
|
||||
<Setter Property="Padding" Value="2"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="BorderBrush" Value="{StaticResource Button.MouseOver.BorderBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Opacity" Value="0.5"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
<Style TargetType="uag:AlgoTemplateToolBar">
|
||||
<Setter Property="Background" Value="{StaticResource dialog_skirt}"/>
|
||||
<Setter Property="BorderBrush" Value="Gray"/>
|
||||
@@ -14,9 +58,11 @@
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="Header" Value="{Binding Definition, RelativeSource={RelativeSource Self}}"/>
|
||||
<Setter Property="HeaderTemplate" Value="{StaticResource AlgoTemplateHeaderContentTemplate}"/>
|
||||
<Setter Property="ItemContainerStyle" Value="{StaticResource AlgoTemplateItemContainerStyle}"/>
|
||||
<Setter Property="ItemsSource" Value="{Binding algo_templates_view}"/>
|
||||
<Setter Property="SelectedItem" Value="{Binding selected_algo_template, Mode=TwoWay}"/>
|
||||
<Setter Property="ItemContainerStyle" Value="{StaticResource {x:Type ComboBoxItem}}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="uag:AlgoTemplateToolBar">
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
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:uag="clr-namespace:mbts.ui.spark.algo"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
@@ -29,31 +28,6 @@
|
||||
<Setter Property="CommandParameter" Value="{Binding}"/>
|
||||
<Setter Property="Visibility" Value="{Binding Visibility, RelativeSource={RelativeSource AncestorType=ComboBoxItem}, FallbackValue={u:Visibility Collapsed}}"/>
|
||||
</Style>
|
||||
<Style x:Key="AlgoTemplateItemContainerStyle" TargetType="ComboBoxItem" BasedOn="{StaticResource {x:Type ComboBoxItem}}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
</Style>
|
||||
<DataTemplate x:Key="AlgoTemplateHeaderContentTemplate" DataType="sp:AlgoDefinitionVM">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
x:Name="order_type"
|
||||
Text="{Binding algo_root.order_type, Converter={StaticResource OrderTypeDisplayTextConverter}, StringFormat='{}{0}: ', Mode=OneWay}"
|
||||
FontWeight="Bold"
|
||||
/>
|
||||
<TextBlock x:Name="display_name" Text="{Binding name, Mode=OneWay}" FontWeight="Normal"/>
|
||||
</StackPanel>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Value="{u:True}">
|
||||
<DataTrigger.Binding>
|
||||
<MultiBinding Converter="{x:Static cv:ValueConverters.AreEqualConverter}">
|
||||
<Binding Path="(sp:AlgoDefinitionVM.algo_root).(sp:IAlgoVM.order_type)" Converter="{StaticResource OrderTypeDisplayTextConverter}"/>
|
||||
<Binding Path="(sp:AlgoDefinitionVM.name)"/>
|
||||
</MultiBinding>
|
||||
</DataTrigger.Binding>
|
||||
<Setter TargetName="order_type" Property="Visibility" Value="{x:Static Visibility.Collapsed}"/>
|
||||
<Setter TargetName="display_name" Property="FontWeight" Value="{x:Static FontWeights.Bold}"/>
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
<Style TargetType="uag:OrderDetailsPopupControl">
|
||||
<Setter Property="Background" Value="{StaticResource ComboBox.Static.Background}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource ComboBox.Static.Border}"/>
|
||||
@@ -142,8 +116,6 @@
|
||||
Definition="{Binding SelectedItem, RelativeSource={RelativeSource AncestorType=uag:OrderDetailsPopupControl}}"
|
||||
EditableCollectionModel="{Binding algo_template_collection_model}"
|
||||
Padding="0,1,0,3"
|
||||
Header="{Binding SelectedItem, RelativeSource={RelativeSource AncestorType=uag:OrderDetailsPopupControl}}"
|
||||
HeaderTemplate="{StaticResource AlgoTemplateHeaderContentTemplate}"
|
||||
RemoveCommand="{Binding remove_template_cmd}"
|
||||
UndoCommand="{Binding undo_cmd}"
|
||||
RedoCommand="{Binding redo_cmd}"
|
||||
@@ -157,7 +129,6 @@
|
||||
SaveDefinitionCommand="{Binding save_definition_cmd}"
|
||||
ToggleFavoriteCommand="{Binding toggle_favorite_cmd}"
|
||||
SelectedItem="{Binding selected_algo_template, Mode=TwoWay}"
|
||||
ItemContainerStyle="{StaticResource AlgoTemplateItemContainerStyle}"
|
||||
>
|
||||
<uag:AlgoTemplateToolBar.Resources>
|
||||
<DataTemplate DataType="{x:Type sp:AlgoDefinitionTemplateVM}">
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
<Style TargetType="c:PopupControl">
|
||||
<Setter Property="PopupContainerStyle" Value="{StaticResource PopupContainerStyleBase}"/>
|
||||
<Setter Property="Background" Value="{StaticResource ComboBox.Static.Background}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource ComboBox.Static.Border}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
|
||||
@@ -90,13 +90,20 @@ namespace mbts.ui.spark {
|
||||
nameof(ContractSpec),
|
||||
(o, v) => {
|
||||
o._spec = v;
|
||||
if (o._raw_value.HasValue)
|
||||
if (o._raw_value.HasValue) {
|
||||
o.SetCurrentValue(ValueProperty, o._raw_value.Value);
|
||||
if (o.AlwaysReformatOnSpecChange)
|
||||
o._update_text();
|
||||
}
|
||||
}, SecurityDataHelper.Defaults.ContractSpecificationImmutable);
|
||||
|
||||
public ContractSpecificationImmutable? ContractSpec { get => GetValue(ContractSpecProperty) as ContractSpecificationImmutable?; set => SetValue(ContractSpecProperty, value); }
|
||||
#endregion
|
||||
|
||||
/// TODO: verify that this control behaves correctly when <see cref="AlwaysReformatOnSpecChange"/> is true (ideally this prop will be removed, and this control will always behave is if the prop is true).
|
||||
/// Because this control is ubiquitious in our application, such verification will require careful, meticulous analysis.
|
||||
/// Until such verification is complete, this boolean property will be used to trigger the behavior, so unsuspecting instances of this control will not be affected.
|
||||
public bool AlwaysReformatOnSpecChange { get; set; }
|
||||
// explicit props
|
||||
public bool EnableArrowKeyTick { get => _enable_arrow_key_tick.value; set => _enable_arrow_key_tick.value = value; }
|
||||
public ModifierKeys ArrowKeyTickModifiers { get => _arrow_key_tick_modifiers.value; set => _arrow_key_tick_modifiers.value = value; }
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using bts;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using bts;
|
||||
using mbts.ui.native;
|
||||
using mbts.ui.shell;
|
||||
|
||||
namespace mbts.ui
|
||||
{
|
||||
namespace mbts.ui {
|
||||
public static class DragDropHelper
|
||||
{
|
||||
public enum TargetSide
|
||||
@@ -38,6 +33,43 @@ namespace mbts.ui
|
||||
|
||||
private static DragDropDocumentPreview _dddp;
|
||||
|
||||
public static IObservable<Point?> subscribe_drag_coordinates(FrameworkElement element) =>
|
||||
Observable.Create<Point?>(o => {
|
||||
Point? mouse_down = null, drag_start = null;
|
||||
element.MouseDown += on_mouse_down;
|
||||
element.MouseMove += on_mouse_move;
|
||||
element.MouseUp += on_mouse_up;
|
||||
return Disposable.Create(() => {
|
||||
element.MouseDown -= on_mouse_down;
|
||||
element.MouseMove -= on_mouse_move;
|
||||
element.MouseUp -= on_mouse_up;
|
||||
});
|
||||
// callbacks
|
||||
void on_mouse_up(object sender, MouseButtonEventArgs e) {
|
||||
mouse_down = null;
|
||||
if (drag_start.HasValue)
|
||||
o.OnNext(drag_start = null);
|
||||
}
|
||||
void on_mouse_move(object sender, MouseEventArgs e) {
|
||||
if (drag_start.HasValue) {
|
||||
o.OnNext(Mouse.LeftButton == MouseButtonState.Pressed ? e.GetPosition(element) : (drag_start = null));
|
||||
e.Handled = true;
|
||||
} else if (mouse_down.HasValue) {
|
||||
var curr_pt = e.GetPosition(element);
|
||||
if (is_sufficient_delta(mouse_down.Value, curr_pt)) {
|
||||
e.Handled = true;
|
||||
drag_start = curr_pt;
|
||||
mouse_down = null;
|
||||
o.OnNext(curr_pt);
|
||||
}
|
||||
}
|
||||
}
|
||||
void on_mouse_down(object sender, MouseButtonEventArgs e) {
|
||||
drag_start = null;
|
||||
mouse_down = e.GetPosition(element);
|
||||
}
|
||||
});
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private static void log(string message, params object[] args) {
|
||||
Console.WriteLine(message, args);
|
||||
|
||||
@@ -7,6 +7,12 @@ using System.Windows.Forms;
|
||||
|
||||
namespace mbts.ui {
|
||||
public static class FileDialogCommand {
|
||||
public static IDialogCommand open_file(Action<string> execute, string initial_dir = null, params string[] extensions) =>
|
||||
open_file(execute, initial_dir, Array.ConvertAll(extensions, ext => (NativeFileHelper.get_file_type_description("." + ext), new[] { ext })));
|
||||
|
||||
public static IDialogCommand open_file(Action<string> execute, string initial_dir, params (string name, string[] extensions)[] extensions) =>
|
||||
new AnonymousFileDialogCommand<object>(_ => _create_open_file_dialog(null, extensions), (_, file) => execute(file), _ => true);
|
||||
|
||||
public static IDialogCommand export_proto<TProto>(
|
||||
Func<TProto> get_proto,
|
||||
Func<bool> can_execute = null,
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using System;
|
||||
using mbts.ui.controls;
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui
|
||||
{
|
||||
@@ -128,32 +130,11 @@ namespace mbts.ui
|
||||
#endregion
|
||||
|
||||
#region SingleClickEdit
|
||||
public static readonly DependencyProperty SingleClickEditProperty = DependencyProperty.RegisterAttached("SingleClickEdit", typeof(bool), typeof(DataGridBehavior), new PropertyMetadata(false, OnSingleClickEditChanged));
|
||||
private static void OnSingleClickEditChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
if ((bool)e.NewValue) {
|
||||
var grid = (DataGrid)d;
|
||||
var on_selected = new RoutedEventHandler((_, args) => {
|
||||
var cell = args.OriginalSource as DataGridCell;
|
||||
if (cell != null) {
|
||||
grid.BeginEdit(args);
|
||||
grid.SelectedItem = cell.DataContext;
|
||||
}
|
||||
});
|
||||
grid.AddHandler(DataGridCell.SelectedEvent, on_selected);
|
||||
SetSingleClickEditDispose(grid, Disposable.Create(() => grid.RemoveHandler(DataGridCell.SelectedEvent, on_selected)));
|
||||
} else SetSingleClickEditDispose((DataGrid)d, null);
|
||||
}
|
||||
public static readonly DependencyProperty SingleClickEditProperty = DependencyProperty.RegisterAttached("SingleClickEdit", typeof(bool), typeof(DataGridBehavior), new PropertyMetadata(false, SingleClickEdit.on_property_changed));
|
||||
public static bool GetSingleClickEdit(DataGrid target) { return (bool)target.GetValue(SingleClickEditProperty); }
|
||||
public static void SetSingleClickEdit(DataGrid target, bool value) { target.SetValue(SingleClickEditProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region SingleClickEditDispose
|
||||
private static readonly DependencyPropertyKey SingleClickEditDisposePropertyKey = DependencyProperty.RegisterAttachedReadOnly("SingleClickEditDispose", typeof(IDisposable), typeof(DataGridBehavior), new PropertyMetadata(null, FrameworkElementLifeDependencyHelper.DisposeOld));
|
||||
internal static readonly DependencyProperty SingleClickEditDisposeProperty = SingleClickEditDisposePropertyKey.DependencyProperty;
|
||||
internal static IDisposable GetSingleClickEditDispose(DataGrid target) { return target.GetValue(SingleClickEditDisposeProperty) as IDisposable; }
|
||||
private static void SetSingleClickEditDispose(DataGrid target, IDisposable value) { target.SetValue(SingleClickEditDisposePropertyKey, value); }
|
||||
#endregion
|
||||
|
||||
#region SelectRowWithCell
|
||||
public static readonly DependencyProperty SelectRowWithCellProperty = DependencyProperty.RegisterAttached("SelectRowWithCell", typeof(bool), typeof(DataGridBehavior), new PropertyMetadata(false, OnSelectRowWithCellChanged));
|
||||
private static void OnSelectRowWithCellChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
@@ -217,5 +198,35 @@ namespace mbts.ui
|
||||
protected override void OnDisposed() { Disposable.dispose(ref _sub); }
|
||||
}
|
||||
#endregion
|
||||
|
||||
static class SingleClickEdit {
|
||||
public static void on_property_changed(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
if (!(d is DataGrid data_grid))
|
||||
return;
|
||||
if ((bool)e.NewValue) {
|
||||
data_grid.PreviewMouseLeftButtonDown += _on_preview_mouse_left_button_down;
|
||||
} else {
|
||||
data_grid.PreviewMouseLeftButtonDown -= _on_preview_mouse_left_button_down;
|
||||
}
|
||||
}
|
||||
|
||||
static void _on_preview_mouse_left_button_down(object sender, MouseButtonEventArgs e) {
|
||||
if ((e.OriginalSource as DataGridCell ?? (e.OriginalSource as FrameworkElement)?.find_ancestor_of_type<DataGridCell>()) is DataGridCell cell && !cell.IsReadOnly && !cell.IsEditing) {
|
||||
var data_grid = sender as DataGrid;
|
||||
if (data_grid.TryGetRowView(cell.DataContext, out var row_view)) {
|
||||
if (!KeyboardExtensions.IsCtrlDown() && !KeyboardExtensions.IsShiftDown()) {
|
||||
data_grid.set_current_if_changed(DataGrid.SelectedItemProperty, row_view.Item);
|
||||
} else {
|
||||
row_view.set_current_if_changed(DataGridRow.IsSelectedProperty, true);
|
||||
}
|
||||
}
|
||||
cell.Focus();
|
||||
data_grid.BeginEdit();
|
||||
}
|
||||
}
|
||||
|
||||
static DataGridCell _get_cell(object original_source) =>
|
||||
original_source as DataGridCell ?? (original_source as FrameworkElement)?.find_ancestor_of_type<DataGridCell>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
using bts;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
@@ -26,7 +28,7 @@ namespace mbts.ui {
|
||||
object obj = get_template_child(control, name);
|
||||
return ConvertUtils.try_convert<T>(obj);
|
||||
}
|
||||
|
||||
|
||||
public static bool try_get_template_child(this Control control, string name, out object result) {
|
||||
if (control == null) throw new ArgumentNullException(nameof(control));
|
||||
var t = control.Template;
|
||||
@@ -107,8 +109,8 @@ namespace mbts.ui {
|
||||
}
|
||||
|
||||
public static Pen frozen(this Pen pen) {
|
||||
pen.Freeze();
|
||||
return pen;
|
||||
pen.Freeze();
|
||||
return pen;
|
||||
}
|
||||
|
||||
public static ImageSource frozen(this ImageSource image_source) {
|
||||
@@ -123,13 +125,12 @@ namespace mbts.ui {
|
||||
// Matching children are *not* further traversed
|
||||
public static IEnumerable<FrameworkElement> visual_children_where(this FrameworkElement v, Func<FrameworkElement, bool> match) {
|
||||
var children = visual_children(v).OfType<FrameworkElement>().ToList();
|
||||
while(children.Count() > 0) {
|
||||
while (children.Count() > 0) {
|
||||
var next = new List<FrameworkElement>();
|
||||
foreach (var c in children) {
|
||||
if (match(c)) {
|
||||
yield return c;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
next.AddRange(visual_children(c).OfType<FrameworkElement>());
|
||||
}
|
||||
}
|
||||
@@ -154,74 +155,47 @@ namespace mbts.ui {
|
||||
return result != null;
|
||||
}
|
||||
|
||||
public static T find_ancestor_of_type<T>(this FrameworkElement element) where T : DependencyObject {
|
||||
if (element.Parent is T)
|
||||
return (T)element.Parent;
|
||||
else if (element.TemplatedParent is T)
|
||||
return (T)element.TemplatedParent;
|
||||
else {
|
||||
FrameworkElement logical_parent = VisualTreeHelper.GetParent(element) as FrameworkElement;
|
||||
if (logical_parent != null) {
|
||||
var rv = logical_parent.find_ancestor_of_type<T>();
|
||||
if (rv is T)
|
||||
return (T)rv;
|
||||
}
|
||||
FrameworkContentElement content_parent = VisualTreeHelper.GetParent(element) as FrameworkContentElement;
|
||||
if (content_parent != null) {
|
||||
var rv = content_parent.find_ancestor_of_type<T>();
|
||||
if (rv is T)
|
||||
return (T)rv;
|
||||
}
|
||||
if (element.Parent is FrameworkElement) {
|
||||
var rv = ((FrameworkElement)element.Parent).find_ancestor_of_type<T>();
|
||||
if (rv is T)
|
||||
return (T)rv;
|
||||
} else if (element.Parent is FrameworkContentElement) {
|
||||
var rv = ((FrameworkContentElement)element.Parent).find_ancestor_of_type<T>();
|
||||
if (rv is T)
|
||||
return (T)rv;
|
||||
}
|
||||
if (element.TemplatedParent is FrameworkElement) {
|
||||
var rv = ((FrameworkElement)element.TemplatedParent).find_ancestor_of_type<T>();
|
||||
if (rv is T)
|
||||
return (T)rv;
|
||||
} else if (element.TemplatedParent is FrameworkContentElement) {
|
||||
var rv = ((FrameworkContentElement)element.TemplatedParent).find_ancestor_of_type<T>();
|
||||
if (rv is T)
|
||||
return (T)rv;
|
||||
}
|
||||
}
|
||||
return default(T);
|
||||
}
|
||||
public static T find_ancestor_of_type<T>(this FrameworkElement element) where T : DependencyObject {
|
||||
return candidates().ExceptNull().Distinct().Select(impl).ExceptNull().FirstOrDefault();
|
||||
T impl(DependencyObject d) {
|
||||
if (d is T direct_match)
|
||||
return direct_match;
|
||||
if (d is FrameworkElement el) {
|
||||
if (el.find_ancestor_of_type<T>() is T rv)
|
||||
return rv;
|
||||
} else if (d is FrameworkContentElement cel) {
|
||||
if (cel.find_ancestor_of_type<T>() is T rv)
|
||||
return rv;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
IEnumerable<DependencyObject> candidates() {
|
||||
yield return element.Parent;
|
||||
yield return element.TemplatedParent;
|
||||
yield return VisualTreeHelper.GetParent(element);
|
||||
}
|
||||
}
|
||||
|
||||
public static T find_ancestor_of_type<T>(this FrameworkContentElement element) where T : DependencyObject {
|
||||
if (element.Parent is T)
|
||||
return (T)element.Parent;
|
||||
else if (element.TemplatedParent is T)
|
||||
return (T)element.TemplatedParent;
|
||||
else {
|
||||
if (element.Parent is FrameworkElement) {
|
||||
var rv = ((FrameworkElement)element.Parent).find_ancestor_of_type<T>();
|
||||
if (rv is T)
|
||||
return (T)rv;
|
||||
} else if (element.Parent is FrameworkContentElement) {
|
||||
var rv = ((FrameworkContentElement)element.Parent).find_ancestor_of_type<T>();
|
||||
if (rv is T)
|
||||
return (T)rv;
|
||||
}
|
||||
if (element.TemplatedParent is FrameworkElement) {
|
||||
var rv = ((FrameworkElement)element.TemplatedParent).find_ancestor_of_type<T>();
|
||||
if (rv is T)
|
||||
return (T)rv;
|
||||
}
|
||||
if (element.TemplatedParent is FrameworkContentElement) {
|
||||
var rv = ((FrameworkContentElement)element.TemplatedParent).find_ancestor_of_type<T>();
|
||||
if (rv is T)
|
||||
return (T)rv;
|
||||
}
|
||||
}
|
||||
return default(T);
|
||||
}
|
||||
public static T find_ancestor_of_type<T>(this FrameworkContentElement element) where T : DependencyObject {
|
||||
return candidates().ExceptNull().Distinct().Select(impl).ExceptNull().FirstOrDefault();
|
||||
T impl(DependencyObject d) {
|
||||
if (d is T direct_match)
|
||||
return direct_match;
|
||||
if (d is FrameworkElement el) {
|
||||
if (el.find_ancestor_of_type<T>() is T rv)
|
||||
return rv;
|
||||
} else if (d is FrameworkContentElement cel) {
|
||||
if (cel.find_ancestor_of_type<T>() is T rv)
|
||||
return rv;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
IEnumerable<DependencyObject> candidates() {
|
||||
yield return element.Parent;
|
||||
yield return element.TemplatedParent;
|
||||
yield return VisualTreeHelper.GetParent(element);
|
||||
}
|
||||
}
|
||||
|
||||
public static void clear_bindings(this DependencyObject d_obj, params DependencyProperty[] d_props) {
|
||||
foreach (var dp in d_props)
|
||||
@@ -253,7 +227,7 @@ namespace mbts.ui {
|
||||
BindingOperations.ClearBinding(target, target_property);
|
||||
BindingOperations.SetBinding(target, target_property, binding);
|
||||
}
|
||||
|
||||
|
||||
public static void TryRaiseCanExecuteChanged(this ICommand command) {
|
||||
if (command == null) return;
|
||||
else if (command is IRaiseCanExecuteChangedCommand temp)
|
||||
@@ -290,7 +264,7 @@ namespace mbts.ui {
|
||||
throw new ArgumentException("Could not find property descriptor given type");
|
||||
}
|
||||
|
||||
public static IObservable<T> Observe<T>(this DependencyObject instance, DependencyProperty property) {
|
||||
public static IObservable<T> Observe<T>(this DependencyObject instance, DependencyProperty property) {
|
||||
Log.assert(typeof(T) == instance.GetType());
|
||||
return Observable.Create<T>(observer => {
|
||||
return instance.ObserveProperty(property, (o, e) => {
|
||||
@@ -379,6 +353,24 @@ namespace mbts.ui {
|
||||
.Where(sc => sc.col_name != null)
|
||||
.ToList();
|
||||
|
||||
public static IEnumerable<SortingCriteria> get_sorting_criteria(this ICollectionView collection_view, IEnumerable<DataGridColumn> columns) =>
|
||||
get_sorting_criteria(collection_view.SortDescriptions, columns.Select(c => (controls.DataGridEx.GetCellKey(c)?.ToString(), c.SortMemberPath)));
|
||||
|
||||
public static IEnumerable<SortingCriteria> get_sorting_criteria(IEnumerable<SortDescription> sort_descriptions, IEnumerable<(string id, string path)> columns) =>
|
||||
from sd in sort_descriptions
|
||||
join col in columns
|
||||
on sd.PropertyName equals col.path
|
||||
select new SortingCriteria(col.id, sd.Direction == ListSortDirection.Ascending);
|
||||
|
||||
public static IEnumerable<SortDescription> get_sort_descriptions(this IEnumerable<SortingCriteria> sorting_criterion, IEnumerable<DataGridColumn> columns) =>
|
||||
get_sort_descriptions(sorting_criterion, columns.Select(c => (controls.DataGridEx.GetCellKey(c)?.ToString(), c.SortMemberPath)));
|
||||
|
||||
public static IEnumerable<SortDescription> get_sort_descriptions(this IEnumerable<SortingCriteria> sorting_criterion, IEnumerable<(string id, string path)> columns) =>
|
||||
from sc in sorting_criterion
|
||||
join col in columns
|
||||
on sc.col_name equals col.id
|
||||
select new SortDescription(col.path, sc.direction());
|
||||
|
||||
/// <summary>
|
||||
/// Converts each <see cref="SortingCriteria"/> into a <see cref="SortDescription"/> and assigns the result to <paramref name="collection_view"/>.SortDescriptions.
|
||||
/// </summary>
|
||||
@@ -391,5 +383,22 @@ namespace mbts.ui {
|
||||
.Where(sd => !string.IsNullOrEmpty(sd.PropertyName)));
|
||||
}
|
||||
}
|
||||
|
||||
public static IObservable<IReadOnlyList<SortingCriteria>> subscribe_sorting_criterion(this ICollectionView collection_view, Func<string, string> sort_member_to_id) =>
|
||||
Observable.Create<IReadOnlyList<SortingCriteria>>(o => {
|
||||
INotifyCollectionChanged notification_src = collection_view.SortDescriptions;
|
||||
IList<SortDescription> sort_descriptions = collection_view.SortDescriptions;
|
||||
var sorting_criterion = sort_descriptions.Select(to_criteria).ToList();
|
||||
notification_src.CollectionChanged += on_sort_descriptions_changed;
|
||||
o.OnNext(sorting_criterion);
|
||||
return Disposable.Create(dispose);
|
||||
// local methods
|
||||
void on_sort_descriptions_changed(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
CollectionHelper.reflect_change(sorting_criterion, collection_view.SortDescriptions, to_criteria, null, e);
|
||||
o.OnNext(sorting_criterion);
|
||||
}
|
||||
SortingCriteria to_criteria(SortDescription sd) => new SortingCriteria(sort_member_to_id(sd.PropertyName), sd.Direction == ListSortDirection.Ascending);
|
||||
void dispose() => notification_src.CollectionChanged -= on_sort_descriptions_changed;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -539,7 +539,7 @@
|
||||
<Border
|
||||
x:Name="Border"
|
||||
CornerRadius="0"
|
||||
BorderThickness="1"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
Background="{TemplateBinding Background}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
|
||||
@@ -261,6 +261,7 @@
|
||||
<Setter Property="Padding" Value="5" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
||||
<Setter Property="Template" Value="{StaticResource DataGridColumnHeaderTemplateWithSortArrowAndFilterButton}" />
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="SelectRowHeaderCell" TargetType="DataGridRowHeader">
|
||||
|
||||
@@ -14,8 +14,9 @@ using static mbts.spark.SparkTags;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal class ContractPickerModel : ViewModelBase, IDisposable {
|
||||
|
||||
private const string dot = "•";
|
||||
bool _is_edit_mode;
|
||||
SecurityImmutable? _pending_sec;
|
||||
|
||||
public ContractPickerModel(IBus bus, bool edit_mode, SecurityCriteria existing_criteria, SecurityCriteria world_view_filter = null)
|
||||
: this(bus.subscribe(SecuritiesTag), edit_mode, existing_criteria, world_view_filter) { }
|
||||
@@ -201,6 +202,10 @@ namespace mbts.spark {
|
||||
_update_security_types();
|
||||
_update_symbols();
|
||||
_update_securities();
|
||||
if (_pending_sec.HasValue && changed_securities.ContainsKey(_pending_sec.Value.Id)) {
|
||||
select_security_request?.Invoke(_pending_sec.Value);
|
||||
_pending_sec = null;
|
||||
}
|
||||
}
|
||||
internal void criteria_selection_changed(IEnumerable<SecurityCriteriaClause> selected_clauses) {
|
||||
selected_security_criteria_clauses = selected_clauses.ToList();
|
||||
@@ -264,9 +269,40 @@ namespace mbts.spark {
|
||||
public Action ok_action { get; set; }
|
||||
public Action cancel_action { get; set; }
|
||||
|
||||
public bool is_edit_mode { get; set; }
|
||||
public bool is_edit_mode { get => _is_edit_mode; set => Set(ref _is_edit_mode, value); }
|
||||
|
||||
public event EventHandler<SecurityCriteriaClauseChangedEventsArgs> security_criteria_clause_changed;
|
||||
public event Action<SecurityImmutable> select_security_request;
|
||||
|
||||
public SecurityMD? selected_security_md {
|
||||
get => selected_securities.Count == 1 && all_securities.TryGetValue(selected_securities[0], out var smd) ? smd : (SecurityMD?)null;
|
||||
set {
|
||||
if (value != selected_security_md) {
|
||||
if (value.HasValue) {
|
||||
if (all_securities.ContainsKey(value.Value.security.Id)) {
|
||||
select_security_request?.Invoke(value.Value.security);
|
||||
} else {
|
||||
_pending_sec = value.Value.security;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SecurityImmutable? selected_security {
|
||||
get => (selected_securities.Count == 1 && all_securities.TryGetValue(selected_securities[0], out var smd) ? smd : (SecurityMD?)null)?.security;
|
||||
set {
|
||||
if (value != selected_security) {
|
||||
if (value.HasValue) {
|
||||
if (all_securities.ContainsKey(value.Value.Id)) {
|
||||
select_security_request?.Invoke(value.Value);
|
||||
} else {
|
||||
_pending_sec = value.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SecurityMD result {
|
||||
get {
|
||||
|
||||
@@ -5,14 +5,12 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
Header="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
|
||||
ToolTip="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
|
||||
ShowExpanderArrow="False"
|
||||
Padding="2,0"
|
||||
>
|
||||
<sp:ContractPickerView
|
||||
DataContext="{Binding Controller.model, RelativeSource={RelativeSource AncestorType=c:ContractPickerPopup}}"
|
||||
CloseOnOK="False"
|
||||
CloseOnCancel="False"
|
||||
ShowDialogSkirt="False"
|
||||
ShowDialogSkirt="{Binding ShowDialogSkirt, RelativeSource={RelativeSource AncestorType=c:ContractPickerPopup}}"
|
||||
/>
|
||||
</c:PopupControl>
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.spark;
|
||||
using bts.api;
|
||||
using mbts.spark;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
@@ -13,14 +14,32 @@ namespace mbts.ui.controls {
|
||||
/// Interaction logic for ContractPickerView.xaml
|
||||
/// </summary>
|
||||
partial class ContractPickerPopup : PopupControl {
|
||||
IReadOnlyDictionary<uint, SecurityMD> _cached_security_map = DictionaryHelper.empty<uint, SecurityMD>();
|
||||
ContractPickerPopupController _controller;
|
||||
bool _is_lazy;
|
||||
|
||||
public ContractPickerPopup() {
|
||||
SetCurrentValue(SecurityMapProperty, new Dictionary<uint, SecurityMD>(0));
|
||||
var sec_map_source = Observable.Create<IReadOnlyDictionary<uint, SecurityMD>>(o => {
|
||||
o.OnNext(SecurityMap);
|
||||
_security_map_changed += o.OnNext;
|
||||
return Disposable.Create(() => _security_map_changed -= o.OnNext);
|
||||
try_notify();
|
||||
_security_map_changed += on_security_map_changed;
|
||||
_is_lazy_changed += on_is_lazy_changed;
|
||||
Opened += on_opened;
|
||||
Closed += on_closed;
|
||||
return Disposable.Create(() => {
|
||||
_security_map_changed -= on_security_map_changed;
|
||||
_is_lazy_changed -= on_is_lazy_changed;
|
||||
Opened -= on_opened;
|
||||
Closed -= on_closed;
|
||||
});
|
||||
void on_security_map_changed(IReadOnlyDictionary<uint, SecurityMD> value) => try_notify();
|
||||
void on_is_lazy_changed(bool value) => try_notify();
|
||||
void on_opened(object sender, RoutedEventArgs e) => try_notify();
|
||||
void on_closed(object sender, RoutedEventArgs e) => try_notify();
|
||||
void try_notify() {
|
||||
if (!IsLazy || IsOpen)
|
||||
o.OnNext(_cached_security_map);
|
||||
}
|
||||
});
|
||||
_controller = new ContractPickerPopupController(this, new ContractPickerModel(sec_map_source, true, null));
|
||||
InitializeComponent();
|
||||
@@ -40,22 +59,65 @@ namespace mbts.ui.controls {
|
||||
|
||||
event Action<IReadOnlyDictionary<uint, SecurityMD>> _security_map_changed;
|
||||
event Action<SecurityCriteria> _selected_criteria_changed;
|
||||
event Action<bool> _is_lazy_changed;
|
||||
|
||||
public static IValueConverter SecurityCriteriaTextConverter =
|
||||
ValueConverter.create<SecurityCriteria, string>(s => s == null ? "(None)" : string.Join(", ", s.clauses.empty_if_null()));
|
||||
|
||||
#region ShowDialogSkirt
|
||||
public static readonly DependencyProperty ShowDialogSkirtProperty =
|
||||
WPFHelper.create_dp<bool, ContractPickerPopup>(nameof(ShowDialogSkirt));
|
||||
|
||||
public bool ShowDialogSkirt {
|
||||
get => (bool)GetValue(ShowDialogSkirtProperty);
|
||||
set => SetValue(ShowDialogSkirtProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SecurityMap
|
||||
public static readonly DependencyProperty SecurityMapProperty =
|
||||
WPFHelper.create_dp<IReadOnlyDictionary<uint, SecurityMD>, ContractPickerPopup>(
|
||||
nameof(SecurityMap),
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||
(o, v) => o._security_map_changed?.Invoke(v),
|
||||
(o, v) => {
|
||||
o._cached_security_map = v;
|
||||
o._security_map_changed?.Invoke(v);
|
||||
},
|
||||
(o, v) => v ?? new Dictionary<uint, SecurityMD>());
|
||||
|
||||
public IReadOnlyDictionary<uint, SecurityMD> SecurityMap {
|
||||
get => GetValue(SecurityMapProperty) as IReadOnlyDictionary<uint, SecurityMD>;
|
||||
set => SetValue(SecurityMapProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SelectedSecurity
|
||||
public static readonly DependencyProperty SelectedSecurityProperty =
|
||||
WPFHelper.create_dp<SecurityImmutable?, ContractPickerPopup>(
|
||||
nameof(SelectedSecurity),
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||
(o, v) => o.Controller.model.selected_security = v);
|
||||
|
||||
public SecurityImmutable? SelectedSecurity {
|
||||
get => GetValue(SelectedCriteriaProperty) as SecurityImmutable?;
|
||||
set => SetValue(SelectedCriteriaProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SelectedSecurityMD
|
||||
public static readonly DependencyProperty SelectedSecurityMDProperty =
|
||||
WPFHelper.create_dp<SecurityMD?, ContractPickerPopup>(
|
||||
nameof(SelectedSecurityMD),
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||
(o, v) => o.Controller.model.selected_security_md = v);
|
||||
|
||||
public SecurityMD? SelectedSecurityMD {
|
||||
get => GetValue(SelectedCriteriaProperty) as SecurityMD?;
|
||||
set => SetValue(SelectedCriteriaProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SelectedCriteria
|
||||
public static readonly DependencyProperty SelectedCriteriaProperty =
|
||||
WPFHelper.create_dp<SecurityCriteria, ContractPickerPopup>(
|
||||
nameof(SelectedCriteria),
|
||||
@@ -66,9 +128,56 @@ namespace mbts.ui.controls {
|
||||
get => GetValue(SelectedCriteriaProperty) as SecurityCriteria;
|
||||
set => SetValue(SelectedCriteriaProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region CloseOnOK
|
||||
public static readonly DependencyProperty CloseOnOKProperty =
|
||||
WPFHelper.create_dp<bool, ContractPickerPopup>(
|
||||
nameof(CloseOnOK),
|
||||
(o, v) => {
|
||||
o.Controller.model.ok_action = v ? new Action(() => {
|
||||
o.set_current_if_changed(SelectedSecurityMDProperty, o.Controller.model.selected_security_md);
|
||||
o.set_current_if_changed(SelectedSecurityProperty, o.Controller.model.selected_security);
|
||||
o.set_current_if_changed(SelectedCriteriaProperty, o.Controller.model.result_criteria);
|
||||
o.SetCurrentValue(IsOpenProperty, false);
|
||||
}) : null;
|
||||
});
|
||||
|
||||
public bool CloseOnOK {
|
||||
get => (bool)GetValue(CloseOnOKProperty);
|
||||
set => SetValue(CloseOnOKProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region CloseOnCancel
|
||||
public static readonly DependencyProperty CloseOnCancelProperty =
|
||||
WPFHelper.create_dp<bool, ContractPickerPopup>(
|
||||
nameof(CloseOnCancel),
|
||||
(o, v) => o.Controller.model.cancel_action = v ? new Action(() => o.SetCurrentValue(IsOpenProperty, false)) : null);
|
||||
|
||||
public bool CloseOnCancel {
|
||||
get => (bool)GetValue(CloseOnCancelProperty);
|
||||
set => SetValue(CloseOnCancelProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public ContractPickerPopupController Controller => _controller;
|
||||
|
||||
public bool IsEditMode {
|
||||
get => _controller.model.is_edit_mode;
|
||||
set => _controller.model.is_edit_mode = value;
|
||||
}
|
||||
|
||||
public bool IsLazy {
|
||||
get => _is_lazy;
|
||||
set {
|
||||
if (_is_lazy != value) {
|
||||
_is_lazy = value;
|
||||
_is_lazy_changed?.Invoke(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ContractPickerPopupController : DisposableViewModelBase {
|
||||
readonly Property<SecurityCriteria> _selected_criteria;
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace mbts.spark
|
||||
public ContractPickerView(ContractPickerModel context)
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContextChanged += on_data_context_changed;
|
||||
if (context != null) this.DataContext = context;
|
||||
|
||||
_exchanges.SelectionChanged += exchanges_changed;
|
||||
@@ -41,7 +42,20 @@ namespace mbts.spark
|
||||
_criteria.SelectionChanged += criteria_changed;
|
||||
_add_criteria_button.Click += _add_criteria_button_Click;
|
||||
//context.security_criteria_clause_changed += security_criteria_clause_changed;
|
||||
|
||||
void on_data_context_changed(object sender, DependencyPropertyChangedEventArgs e) {
|
||||
if (e.OldValue is ContractPickerModel old_cpm)
|
||||
old_cpm.select_security_request -= on_select_security_request;
|
||||
if (e.NewValue is ContractPickerModel new_cpm)
|
||||
new_cpm.select_security_request += on_select_security_request;
|
||||
}
|
||||
void on_select_security_request(SecurityImmutable security) {
|
||||
select_item(_exchanges, security.Exchange);
|
||||
select_item(_security_types, security.ContractCase);
|
||||
select_item(_symbols, security.Symbol);
|
||||
select_item(_securities, security);
|
||||
}
|
||||
void select_item(ListBox view, object value) =>
|
||||
view.SetCurrentValue(ListBox.SelectedItemProperty, value);
|
||||
}
|
||||
|
||||
private void _add_criteria_button_Click(object sender, RoutedEventArgs e) {
|
||||
@@ -110,19 +124,25 @@ namespace mbts.spark
|
||||
|
||||
#region CloseOnOK
|
||||
public static readonly DependencyProperty CloseOnOKProperty =
|
||||
DialogSkirtOKCancel.CloseOnOKProperty.AddOwner(typeof(ContractPickerView));
|
||||
DialogSkirtOKCancel.CloseOnOKProperty.AddOwner(typeof(ContractPickerView), new PropertyMetadata(false));
|
||||
|
||||
public bool CloseOnOK { get => (bool)GetValue(CloseOnOKProperty); set => SetValue(CloseOnOKProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region CloseOnCancel
|
||||
public static readonly DependencyProperty CloseOnCancelProperty =
|
||||
DialogSkirtOKCancel.CloseOnCancelProperty.AddOwner(typeof(ContractPickerView));
|
||||
DialogSkirtOKCancel.CloseOnCancelProperty.AddOwner(typeof(ContractPickerView), new PropertyMetadata(false));
|
||||
|
||||
public bool CloseOnCancel { get => (bool)GetValue(CloseOnCancelProperty); set => SetValue(CloseOnCancelProperty, value); }
|
||||
#endregion
|
||||
|
||||
public bool ShowDialogSkirt { get; set; } = true;
|
||||
public static readonly DependencyProperty ShowDialogSkirtProperty =
|
||||
WPFHelper.create_dp<bool, ContractPickerView>(nameof(ShowDialogSkirt), true);
|
||||
|
||||
public bool ShowDialogSkirt {
|
||||
get => (bool)GetValue(ShowDialogSkirtProperty);
|
||||
set => SetValue(ShowDialogSkirtProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
//[Export(typeof(IShellModule))]
|
||||
@@ -159,10 +179,13 @@ namespace mbts.spark
|
||||
//return ShellWindow.create(this, "test", view, vm.get_state, Icons.Abacus);
|
||||
|
||||
var x = new Window {
|
||||
Content = new ContractPickerView(new ContractPickerModel(_bus, false, null))
|
||||
Content = new ContractPickerView(new ContractPickerModel(_bus, false, null)) {
|
||||
CloseOnOK = true,
|
||||
CloseOnCancel = true
|
||||
}
|
||||
};
|
||||
//x.SizeToContent = SizeToContent.WidthAndHeight;
|
||||
x.Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,9 @@ namespace mbts.spark {
|
||||
public static DateTime to_local_time(this Google.Protobuf.WellKnownTypes.Timestamp timestamp) =>
|
||||
timestamp.ToDateTimeOffset().LocalDateTime;
|
||||
|
||||
public static string fmt_position(double value) =>
|
||||
double.IsNaN(value) ? "-" : value.ToString("0");
|
||||
|
||||
// there is a path to cast null to SecurityImmutable, so it is important that the SecurityImmutable?() type is used explicitly.
|
||||
// the path: null -> Security -> SecurityImmutable
|
||||
public static SecurityImmutable? security(this SecurityMD? security_md) =>
|
||||
|
||||
Loaded 100 of 190 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user