Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5b114b9ce | ||
|
|
6cc1b38358 | ||
|
|
0f3169ef05 | ||
|
|
f72753cd77 | ||
|
|
ea180d8ebc | ||
|
|
9937fc7cd2 | ||
|
|
f3f53d05c8 | ||
|
|
b49b7f01d5 | ||
|
|
a699865342 | ||
|
|
1b5f58c572 | ||
|
|
bae0b797e9 | ||
|
|
79a69e29a0 | ||
|
|
38bf38c62d | ||
|
|
4e08a43d0c | ||
|
|
4f912f99bd | ||
|
|
ae7e1761a7 | ||
|
|
4891eea5ba | ||
|
|
bc36db0480 | ||
|
|
cdaba74c06 | ||
|
|
b045307f30 | ||
|
|
60a7b504db | ||
|
|
0f62f14a1d | ||
|
|
87030dbb88 | ||
|
|
e209ec59e4 | ||
|
|
92dbedf28f | ||
|
|
7993f75fb1 | ||
|
|
1cddd2bcf3 | ||
|
|
9f7e5846b8 | ||
|
|
f7b3aac11a | ||
|
|
ec32101cef | ||
|
|
4e0cf68fa3 | ||
|
|
4e9a7d576e | ||
|
|
5e056dfff4 | ||
|
|
4029ccf8e6 | ||
|
|
22911d582b | ||
|
|
6ef7a2ccb5 | ||
|
|
737a07cecb | ||
|
|
1cbea3d1ee | ||
|
|
4dbedfa1f7 | ||
|
|
074f87dd37 | ||
|
|
6ee0ca9589 | ||
|
|
5745c13355 | ||
|
|
5f99edde0e | ||
|
|
d39bc5c73c | ||
|
|
9cb6d5ab88 | ||
|
|
278432a35b | ||
|
|
13c414f301 | ||
|
|
34f5265a42 | ||
|
|
83103e6dc4 | ||
|
|
add631b04d | ||
|
|
e08dc5dfba | ||
|
|
6a8e995c9c | ||
|
|
d7c460ac98 | ||
|
|
7871dc5e4f | ||
|
|
1aab1db4ca | ||
|
|
373187c091 | ||
|
|
423d127ee9 | ||
|
|
14f306d50d | ||
|
|
71fe863f1b | ||
|
|
ac47f0246f | ||
|
|
8e43f96331 | ||
|
|
3187e4134f | ||
|
|
0331501ce4 | ||
|
|
bd0f1a6f84 | ||
|
|
8776b80b07 | ||
|
|
f5a72841b4 | ||
|
|
f9ada7fca2 | ||
|
|
1cb4f24610 | ||
|
|
021c88f26b | ||
|
|
014fa8e291 | ||
|
|
71d54224bb | ||
|
|
bebbf2a0f1 | ||
|
|
7413814e4a | ||
|
|
36474ef3ea | ||
|
|
974be037fe | ||
|
|
a9ee246436 | ||
|
|
b692742e30 | ||
|
|
c61cbba316 | ||
|
|
4d99ffc86c | ||
|
|
52001a55c6 | ||
|
|
570dd387d2 | ||
|
|
28a122aa7a | ||
|
|
b2facaecd9 | ||
|
|
e346858516 | ||
|
|
be8ca67be5 | ||
|
|
197e68e357 | ||
|
|
dfa3e4a38d | ||
|
|
0f7625ff4a | ||
|
|
88ab4fbb79 | ||
|
|
5cc9b8c875 | ||
|
|
187793d47c | ||
|
|
656c7104f6 | ||
|
|
359e01e8bd | ||
|
|
2a452d3201 | ||
|
|
806ed427ab | ||
|
|
78cefa187a | ||
|
|
8531b330dc | ||
|
|
54d9bde6b7 | ||
|
|
fe6dabb5fe | ||
|
|
f0b089d2e5 | ||
|
|
8979c595a5 | ||
|
|
ba4771aeee | ||
|
|
719c1e474b | ||
|
|
72123a61a9 | ||
|
|
3b1c2dc52b | ||
|
|
0546f0d9fb | ||
|
|
05f1be77ef | ||
|
|
79e876e1dc | ||
|
|
b6cc413733 | ||
|
|
a5e9f40f01 | ||
|
|
e40dc48fa8 | ||
|
|
5aab4312fc | ||
|
|
9ede4c7736 | ||
|
|
b63b0b6303 | ||
|
|
e004580d99 | ||
|
|
7403057c58 | ||
|
|
47730bd3d4 | ||
|
|
9964d2579a | ||
|
|
6960ee1a12 | ||
|
|
9edde6b8e3 | ||
|
|
8ef02ea4cb | ||
|
|
dc99ea6ea4 | ||
|
|
9c6993d70c | ||
|
|
c6be4dad17 | ||
|
|
bd2e2674bf | ||
|
|
71ddcd135b | ||
|
|
bffb7686a8 | ||
|
|
a73bf0ce7c | ||
|
|
94c5a1bcd1 | ||
|
|
ca2a346054 | ||
|
|
b990923cf5 | ||
|
|
8490151844 | ||
|
|
78c8234eef | ||
|
|
1f1f92d337 | ||
|
|
a6426e7a98 | ||
|
|
dc1a7b3f5d | ||
|
|
2a549e551f | ||
|
|
c158188557 | ||
|
|
17614b663e | ||
|
|
5e36a22aba | ||
|
|
442fbd2888 | ||
|
|
d1975cdd00 | ||
|
|
a2c75f08d8 | ||
|
|
02a867f8a4 | ||
|
|
adc48d7871 | ||
|
|
33bd089e74 | ||
|
|
edcb5a5a24 | ||
|
|
6f39705d4f | ||
|
|
07377b8cc9 | ||
|
|
77ad9ca011 | ||
|
|
10f24aa2e9 | ||
|
|
37fc5880ea | ||
|
|
910d991d92 | ||
|
|
ced8bd9fea | ||
|
|
c5004f46ee | ||
|
|
1deb5d25b0 | ||
|
|
b5950ab8c0 | ||
|
|
a233451333 | ||
|
|
deef8136df | ||
|
|
9dda440a48 | ||
|
|
f22442e665 | ||
|
|
1ba7ceaf6a | ||
|
|
54676d5aa9 | ||
|
|
edf5c78405 | ||
|
|
f621d08f72 | ||
|
|
724243c762 | ||
|
|
f70e8de497 | ||
|
|
c97e4be79e | ||
|
|
95be0015d6 | ||
|
|
cbe8917f28 | ||
|
|
653b1c7814 | ||
|
|
9a1098669e | ||
|
|
dcf6c69ef9 | ||
|
|
fdcd245ef5 | ||
|
|
cf9133da8d | ||
|
|
802501a337 | ||
|
|
b9c6f8d309 | ||
|
|
ee94f43628 | ||
|
|
ed06924df1 | ||
|
|
b78179ccee | ||
|
|
e0add4cc47 | ||
|
|
cd670c4bd1 | ||
|
|
acd637000c | ||
|
|
c09f03a922 | ||
|
|
f9fbf9c45a | ||
|
|
5a00c5eb2a | ||
|
|
74fa41f151 | ||
|
|
32d5fc2c37 | ||
|
|
1049046d0b | ||
|
|
1f8e0a1e2b | ||
|
|
846f6d80d8 | ||
|
|
9819a01e38 | ||
|
|
d0ad69e259 | ||
|
|
f0abd13c48 | ||
|
|
ea403b7138 | ||
|
|
4a94beaa83 | ||
|
|
3f03ceb2b5 | ||
|
|
0dd988fd1e | ||
|
|
93d0a21c9d | ||
|
|
92b3cd1da0 | ||
|
|
8fc42f6193 | ||
|
|
73d5b44a7f | ||
|
|
e47c4ead2a | ||
|
|
21dfe5ce43 | ||
|
|
e032dc822f | ||
|
|
87148b41f0 | ||
|
|
68cb21a0a9 | ||
|
|
093ce9a0c3 | ||
|
|
5003b23f18 | ||
|
|
0de0ddbf16 | ||
|
|
5d79c0c79a | ||
|
|
8e4d4782ce | ||
|
|
b8bd3944ce | ||
|
|
5d3d5251c7 | ||
|
|
1fc38234e5 | ||
|
|
625d47d98e | ||
|
|
8c8f2d3179 | ||
|
|
669ccd5ffc | ||
|
|
12db326f1d | ||
|
|
55fc880b2e | ||
|
|
30ca30442a | ||
|
|
802160abf1 | ||
|
|
3da1bfaeb1 | ||
|
|
03eff1f388 | ||
|
|
b53825baa9 | ||
|
|
0065dba0f0 | ||
|
|
078b9d41b1 | ||
|
|
564cd3e273 | ||
|
|
709229ac14 | ||
|
|
375786b283 | ||
|
|
9e5970c93e | ||
|
|
ab5fca73f6 | ||
|
|
840115db03 | ||
|
|
db7f74f85d | ||
|
|
3f381f248b | ||
|
|
b5a93e4466 | ||
|
|
1b56ff3327 | ||
|
|
1f823003d7 | ||
|
|
a64749cd07 | ||
|
|
3ac6e882d4 | ||
|
|
4c239f31f8 | ||
|
|
f9759b9533 | ||
|
|
56fc1019de | ||
|
|
abe7c25bf4 | ||
|
|
4dcaf224ef | ||
|
|
8c492fdad3 | ||
|
|
b811fb6a32 | ||
|
|
0f78cbc80e | ||
|
|
eb3067448d | ||
|
|
f4a3256cb8 | ||
|
|
b7a3d5a23c | ||
|
|
4f4bfea70f | ||
|
|
76d6e37f59 | ||
|
|
8e535b0303 | ||
|
|
44887d80e6 | ||
|
|
bd63c0798e | ||
|
|
6e06b3566a | ||
|
|
ebc7432995 | ||
|
|
16e9d100d2 | ||
|
|
5009c4c29a | ||
|
|
82d3e59024 | ||
|
|
6fe59386df | ||
|
|
767cb36856 | ||
|
|
8cef60b5ab | ||
|
|
bbf961ed78 | ||
|
|
f3b8ce8088 | ||
|
|
026bda165e | ||
|
|
9727f17edf | ||
|
|
61b2913a31 |
No files matched your search
Vendored
+1
-1
@@ -37,7 +37,7 @@
|
||||
"type": "clr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/Debug/bin/net471/spark_sdk.exe",
|
||||
"args": ["md","GLBX","Future","ES","ESM8"],
|
||||
"args": ["md","GLBX","Future","ZN", "ZNU8"],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
|
||||
+26
-6
@@ -32,10 +32,30 @@ if (WIN32)
|
||||
|
||||
add_subdirectory(src/lib)
|
||||
else ()
|
||||
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} \
|
||||
-Wall -pedantic -Wextra -Werror -Wno-missing-braces -Wno-ignored-qualifiers --std=c++14 -fdiagnostics-color")
|
||||
set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_DEBUG} -DNDEBUG -O3")
|
||||
add_subdirectory(src/lib)
|
||||
add_subdirectory(src/server/sparkd)
|
||||
add_subdirectory(src/web)
|
||||
# so that sparkd looks for libspark.so in its directory
|
||||
set (CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
|
||||
set (CMAKE_INSTALL_RPATH ".")
|
||||
|
||||
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} \
|
||||
-fPIC -Wall -pedantic -Wextra -Werror \
|
||||
-Wno-missing-braces -Wno-ignored-qualifiers \
|
||||
--std=c++14 -fdiagnostics-color")
|
||||
set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_DEBUG} -DNDEBUG -O3")
|
||||
add_subdirectory(src/server/sparkd)
|
||||
add_subdirectory(src/lib)
|
||||
add_subdirectory(src/web)
|
||||
add_subdirectory(src/sdk)
|
||||
add_subdirectory(src/data_files)
|
||||
|
||||
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND mkdir -p ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/src/server/sparkd/sparkd ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/src/server/sparkd/defaults ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/web ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/data_files ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/src/lib/libspark.so ${CMAKE_BINARY_DIR}/server
|
||||
COMMENT "Creating server runtime layout"
|
||||
DEPENDS sparkd data_files_dir)
|
||||
|
||||
add_custom_target(server_layout ALL DEPENDS ${CMAKE_BINARY_DIR}/server)
|
||||
endif()
|
||||
+1
-1
Submodule docker/linux updated: 6037d094b7...aedfd79e02.
+1
-1
Submodule externals updated: 281bd00a2d...83046b27ef.
@@ -0,0 +1,6 @@
|
||||
1.1.0
|
||||
- With this release we dropped wild cards from limits, so any user
|
||||
that has limits expressed in terms of wild cards will have to have
|
||||
those re-entered explicitly
|
||||
- With this release we add per user location and smp id tags. At least
|
||||
the location will have to be set through the admin UI for every user
|
||||
@@ -0,0 +1,28 @@
|
||||
1.1.20
|
||||
- Update CME market data handler for price precision extension.
|
||||
https://www.cmegroup.com/confluence/display/EPICSANDBOX/Price+Precision+Extension
|
||||
|
||||
1.1.8
|
||||
- Enabled wild cards in risk management UI
|
||||
- Fixed spreader order updates
|
||||
|
||||
1.1.7
|
||||
- Fixed spread leg price fill
|
||||
- Fixed HO cash per point value
|
||||
|
||||
1.1.6
|
||||
- Fixed occasional crash when filtering fills
|
||||
- Fixed several cash per point multipliers
|
||||
- Colored time of sales row with aggressor side
|
||||
- Iceberg orders now cancel (rathern than error) when a child is canceled
|
||||
|
||||
1.1.0
|
||||
- Added per user self match protection, location and self match protection FIX tag settings
|
||||
- Implemented a new "Trigger Order" algo
|
||||
- Corrected tick formatting for a number of products
|
||||
- Added the hability to recreate canceled orders
|
||||
- Added leg fills to facilitate futured based PnL
|
||||
- PnL report now has PnL columns calculated based on your start of day, current day, and total position
|
||||
- Start, Day and Total PnL columns are painted with a red or green background color based on if the pnl value is negative or positive respectively
|
||||
- Corrected cash per point values, used in pnl calculations, for a number of products
|
||||
- PnL report will now remember any customizations you make to filtering and/or sorting criteria
|
||||
+1
-1
@@ -4,7 +4,7 @@ VisualStudioVersion = 15.0.27130.2024
|
||||
MinimumVisualStudioVersion = 15.0.26124.0
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_api", "src\client\spark_api\spark_api.csproj", "{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "spark_lib", "build\src\lib\spark_lib.vcxproj", "{502FD050-679A-3B50-B438-098500700919}"
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libspark", "build\src\lib\libspark.vcxproj", "{502FD050-679A-3B50-B438-098500700919}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spark", "src\client\spark\Spark.csproj", "{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}"
|
||||
EndProject
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ VisualStudioVersion = 15.0.26730.10
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sparkd", "src\server\sparkd\sparkd.vcxproj", "{BBBAFD38-312D-4A8D-9AF0-F2BDEE72CE6F}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "spark_lib", "build\src\lib\spark_lib.vcxproj", "{502FD050-679A-3B50-B438-098500700919}"
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libspark", "build\src\lib\libspark.vcxproj", "{502FD050-679A-3B50-B438-098500700919}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
||||
@@ -36,7 +36,6 @@ namespace bts {
|
||||
_deleted_synthetics = new HashSet<uint>(t.Where(kv => kv.Value.Deleted).Select(kv => kv.Key));
|
||||
_bus.post(SparkTags.SyntheticstrategiesTag, t);
|
||||
}));
|
||||
new DataPersistor(_bus);
|
||||
Task.Run(run);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
using bts.api;
|
||||
using mbts.spark;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.ui {
|
||||
[Export(typeof(IShellDaemon))]
|
||||
class RecenterDaemon : IShellDaemon, IDisposable {
|
||||
bool _run = true;
|
||||
readonly IBus _bus;
|
||||
readonly TimeSpan _time_to_center;
|
||||
DateTime _last_recenter;
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
public RecenterDaemon(IBus bus) {
|
||||
_bus = bus;
|
||||
_time_to_center = TimeSpan.FromSeconds(config.Configuration.get<bts.config.LadderKnobs>().seconds_to_recenter);
|
||||
_last_recenter = DateTime.UtcNow;
|
||||
|
||||
_dispose = _bus.subscribe(SparkTags.RequestRecenterLadder).Subscribe(_ => trigger_recenter(SparkTags.RecenterLadderNow));
|
||||
run();
|
||||
}
|
||||
|
||||
void trigger_recenter(ITag<DateTime> tag) {
|
||||
_last_recenter = DateTime.UtcNow;
|
||||
_bus.post(tag, _last_recenter);
|
||||
}
|
||||
|
||||
async void run() {
|
||||
_run = true;
|
||||
while (_run) {
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
if (_last_recenter + _time_to_center < DateTime.UtcNow) {
|
||||
trigger_recenter(SparkTags.RecenterLadderBegin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Disposable.dispose(ref _dispose);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using bts.api.core;
|
||||
using bts.metrics;
|
||||
using mbts.config;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.perf {
|
||||
|
||||
[Export, Export(typeof(IShellDaemon))]
|
||||
class ShellLatencyDaemon : IShellDaemon, IDisposable {
|
||||
readonly ShellLatencyService _service;
|
||||
readonly string _hostname;
|
||||
readonly string _version;
|
||||
IDisposable _subs;
|
||||
|
||||
[ImportingConstructor]
|
||||
public ShellLatencyDaemon(ShellLatencyService service) {
|
||||
_service = service;
|
||||
_version = Configuration.get<bts.config.System>().version;
|
||||
_hostname = Environment.MachineName;
|
||||
_subs = _service.subscribei_clock().Subscribe(on_tick);
|
||||
}
|
||||
|
||||
void on_tick(Update<TickImmutable, int> update) {
|
||||
if (update.added.Count > 0) {
|
||||
var tick = update.added.Last();
|
||||
_service.report_async(new Report() {
|
||||
HostName = _hostname,
|
||||
Version = _version,
|
||||
Timestamp = tick.Timestamp
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ using bts.api.orderman;
|
||||
using bts.api.synthetic;
|
||||
using bts.api.diagnostic;
|
||||
using bts.api.admin;
|
||||
using bts.metrics;
|
||||
|
||||
namespace Spark
|
||||
{
|
||||
@@ -41,5 +42,9 @@ namespace Spark
|
||||
[Export]
|
||||
public SyntheticSecurityService synthetic_security_service => context.locate<SyntheticSecurityService>();
|
||||
|
||||
}
|
||||
[Export]
|
||||
public ShellLatencyService shell_latency_service => context.locate<ShellLatencyService>();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ namespace mbts.ui
|
||||
//public static ImageSource Ticket { get { return load_image("Ticket.png"); } } // replaced with AddOrders
|
||||
public static ImageSource Date { get { return load_image("Date.png"); } }
|
||||
|
||||
public static ImageSource Cube3d { get { return (DrawingImage)_resources["Cube"]; /*return load_image("Cube-Molecule.png");*/ } }
|
||||
public static ImageSource Cube3d { get { return (DrawingImage)_resources["Cube"]; } }
|
||||
public static ImageSource Algo { get { return Cube3d; } }
|
||||
public static ImageSource Quote { get { return Math; } }
|
||||
public static ImageSource Math { get { return (DrawingImage)_resources["Surface"]; } }
|
||||
@@ -62,9 +62,7 @@ namespace mbts.ui
|
||||
#pragma warning restore CS0618
|
||||
|
||||
// Vector icons
|
||||
public static ImageSource Favorites { get { return Favorites_Gold; } }
|
||||
public static ImageSource Favorites_Gold { get { return (DrawingImage)_resources["FavoritesGold"]; } }
|
||||
public static ImageSource Favorites_White { get { return (DrawingImage)_resources["FavoritesWhite"]; } }
|
||||
public static ImageSource Bookmarks { get { return (DrawingImage)_resources["Bookmarks"]; } }
|
||||
public static ImageSource Window { get { return (DrawingImage)_resources["Window"]; } }
|
||||
public static ImageSource ActiveWindow { get { return (DrawingImage)_resources["ActiveWindow"]; } }
|
||||
public static ImageSource Trash { get { return (DrawingImage)_resources["Trash"]; } }
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.3 KiB |
@@ -3,33 +3,17 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mbts="clr-namespace:mbts.ui">
|
||||
<DrawingImage x:Key="FavoritesGold">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing Geometry="M1540.22,2082.07L1546.95,2102.78 1568.73,2102.78 1551.11,2115.58 1557.84,2136.29 1540.22,2123.49 1522.6,2136.29 1529.33,2115.58 1511.71,2102.78 1533.49,2102.78 1540.22,2082.07z">
|
||||
<GeometryDrawing.Pen>
|
||||
<Pen Thickness="1" Brush="Black" />
|
||||
</GeometryDrawing.Pen>
|
||||
<GeometryDrawing.Brush>
|
||||
<SolidColorBrush Color="#ffd932" />
|
||||
</GeometryDrawing.Brush>
|
||||
</GeometryDrawing>
|
||||
</DrawingGroup.Children>
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
|
||||
<DrawingImage x:Key="FavoritesWhite">
|
||||
<DrawingImage x:Key="Bookmarks">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing Geometry="M1540.22,2082.07L1546.95,2102.78 1568.73,2102.78 1551.11,2115.58 1557.84,2136.29 1540.22,2123.49 1522.6,2136.29 1529.33,2115.58 1511.71,2102.78 1533.49,2102.78 1540.22,2082.07z">
|
||||
<GeometryDrawing.Pen>
|
||||
<Pen Thickness="1" Brush="Black" />
|
||||
<Pen Thickness="3" Brush="Black" />
|
||||
</GeometryDrawing.Pen>
|
||||
<GeometryDrawing.Brush>
|
||||
<SolidColorBrush Color="#ffd932" />
|
||||
<SolidColorBrush Color="Transparent" />
|
||||
</GeometryDrawing.Brush>
|
||||
</GeometryDrawing>
|
||||
</DrawingGroup.Children>
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
using bts.spark;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace bts.ui {
|
||||
[Export(typeof(IShellModule))]
|
||||
public class BookmarkShellModule : ILazyMenuModule {
|
||||
readonly WindowManager _wm;
|
||||
readonly IBookmarks _bookmarks;
|
||||
readonly ISavedWindowFactory _window_factory;
|
||||
readonly WorkspacesModel _workspaces;
|
||||
|
||||
[ImportingConstructor]
|
||||
public BookmarkShellModule(
|
||||
WorkspacesModel workspaces,
|
||||
WindowManager wm,
|
||||
IBookmarks bm,
|
||||
ISavedWindowFactory swf) {
|
||||
_workspaces = workspaces;
|
||||
_wm = wm;
|
||||
_bookmarks = bm;
|
||||
_window_factory = swf;
|
||||
}
|
||||
|
||||
public ImageSource icon => Icons.Bookmarks;
|
||||
|
||||
public string name => "My Windows";
|
||||
|
||||
public int priority => 35;
|
||||
|
||||
public IEnumerable<SparkCommand> actions {
|
||||
get {
|
||||
// The windows that are open
|
||||
var open =
|
||||
_wm.list_documents()
|
||||
.Select(td => td.ViewModel)
|
||||
.Where(vm => vm.HasCustomTitle)
|
||||
.Select(vm =>
|
||||
SparkCommand.create(vm.Title, vm.Icon, SparkTags.ActivateWindow, () => vm.Model))
|
||||
.Cast<SparkCommand>();
|
||||
|
||||
var open_set = new HashSet<string>(open.Select(c => c.title));
|
||||
|
||||
// The windows that are not open
|
||||
var closed =
|
||||
_bookmarks.bookmarks
|
||||
.Where(state => !open_set.Contains(state.title))
|
||||
.Select(state =>
|
||||
SparkCommand.create(state.title, Icons.Bookmarks, SparkTags.ShowWindowAsync,
|
||||
() => {
|
||||
_bookmarks.remove(state);
|
||||
return _window_factory.restore(state);
|
||||
}))
|
||||
.Cast<SparkCommand>();
|
||||
|
||||
// todo: The windows that are closed
|
||||
foreach (var w in open.Concat(closed).OrderBy(w => w.title)) {
|
||||
yield return w;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task<IShellWindow> create(Stream load_stream) {
|
||||
// This module shouldn't have any windows
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using mbts.ui.settings;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.ui.shell {
|
||||
|
||||
public interface IBookmarks {
|
||||
void save_bookmark(IShellWindow window);
|
||||
void save_bookmark(WindowState window);
|
||||
void remove(WindowState window);
|
||||
void clear();
|
||||
IEnumerable<WindowState> bookmarks { get; }
|
||||
}
|
||||
|
||||
[Export(typeof(IBookmarks))]
|
||||
public class BookmarksModel : IBookmarks {
|
||||
readonly ISettingsCollector _settings_collector;
|
||||
readonly Dictionary<string, WindowState> _bookmarks = new Dictionary<string, WindowState>();
|
||||
|
||||
[ImportingConstructor]
|
||||
public BookmarksModel(ISettingsCollector settings) {
|
||||
_settings_collector = settings;
|
||||
}
|
||||
public void save_bookmark(IShellWindow window) {
|
||||
var state = _settings_collector.get_window_state(window);
|
||||
if (state != null) {
|
||||
_bookmarks[state.title] = state;
|
||||
}
|
||||
}
|
||||
public void save_bookmark(WindowState window) {
|
||||
_bookmarks[window.title] = window;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
_bookmarks.Clear();
|
||||
}
|
||||
|
||||
public void remove(WindowState window) {
|
||||
_bookmarks.Remove(window.title);
|
||||
}
|
||||
|
||||
public IEnumerable<WindowState> bookmarks => _bookmarks.Values;
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ namespace mbts.spark {
|
||||
})
|
||||
),
|
||||
SparkCommands.ShowTimeAndSales.complete(_vm.selected_securities_observable(), _ => true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria),
|
||||
SparkCommands.ShowSyntheticCreate.complete(_vm.selected_securities_observable(), secs => secs.Count > 0 && secs.Count < 3, secs => new SecurityCriteria(secs))
|
||||
SparkCommands.ShowSyntheticCreate.complete(_vm.selected_securities_observable(), secs => secs.Count > 0 && secs.Count <= 24, secs => new SecurityCriteria(secs))
|
||||
};
|
||||
_subscriptions = _bus.subscribe(update_securities_tag).OnDispatcher().Subscribe(c =>
|
||||
_state_changed?.Invoke(_state = new MarketViewWindowFactory.StartupData(c, _vm.rows_view.GetSortingCriteria(), _vm.get_filter_states())));
|
||||
@@ -67,4 +67,4 @@ namespace mbts.spark {
|
||||
Commands.OfType<IDisposable>().DisposeAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,14 @@ using mbts.ui.controls;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.TimeAndSalesWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
|
||||
namespace mbts.spark {
|
||||
class TimeAndSalesVM : SecurityGrid<TimeAndSalesVM.Row, TradeInfo> {
|
||||
@@ -26,23 +29,32 @@ namespace mbts.spark {
|
||||
security_filter.load_state(filters.security);
|
||||
price_filter.load_state(filters.price);
|
||||
size_filter.load_state(filters.size);
|
||||
aggressor_filter.load_state(filters.aggressor_sides);
|
||||
trade_key_filter.load_state(filters.trade_key);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
}
|
||||
// Columns
|
||||
add_column(TimeAndSalesView.Columns.Security, "Security", DIYGrid.DefaultColumnWidths.Security, security_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_column(TimeAndSalesView.Columns.Time, "Time", DIYGrid.DefaultColumnWidths.Time, time_filter, DataGridFilterKnownTemplates.TimeFilter);
|
||||
add_column(TimeAndSalesView.Columns.Price, "Price", DIYGrid.DefaultColumnWidths.Price, price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_column(TimeAndSalesView.Columns.Size, "Size", DIYGrid.DefaultColumnWidths.Size, size_filter, DataGridFilterKnownTemplates.UIntMinMax);
|
||||
add_column(TimeAndSalesView.Columns.TradeKey, "Trade Key", DIYGrid.DefaultColumnWidths.TradeKey, trade_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_column(TimeAndSalesView.Columns.SecurityKey, "Security Key", DIYGrid.DefaultColumnWidths.SecurityKey, security_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
void add_column(TimeAndSalesView.Columns col_key, string header, double width, IDataGridColumnFilter filter, string filter_template) =>
|
||||
columns.Add(new DIYGrid.RenderTextColumn<Row>(r => r[col_key], filter, filter_template) { Header = header, Width = width });
|
||||
//
|
||||
add_column(TimeAndSalesView.Columns.Security, DefaultColumnHeaders.Security, DefaultColumnWidths.Security, security_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_column(TimeAndSalesView.Columns.Time, DefaultColumnHeaders.Time, DefaultColumnWidths.Time, time_filter, DataGridFilterKnownTemplates.TimeFilter);
|
||||
add_column(TimeAndSalesView.Columns.Price, DefaultColumnHeaders.Price, DefaultColumnWidths.Price, price_filter, DataGridFilterKnownTemplates.DoubleMinMax, System.Windows.HorizontalAlignment.Right);
|
||||
add_column(TimeAndSalesView.Columns.Size, DefaultColumnHeaders.Size, DefaultColumnWidths.Size, size_filter, DataGridFilterKnownTemplates.UIntMinMax, System.Windows.HorizontalAlignment.Right);
|
||||
add_column(TimeAndSalesView.Columns.Aggressor, DefaultColumnHeaders.Aggressor, DIYGrid.DefaultColumnWidths.Aggressor, aggressor_filter, DataGridFilterKnownTemplates.EnumValues);
|
||||
add_column(TimeAndSalesView.Columns.TradeKey, DefaultColumnHeaders.TradeKey, DIYGrid.DefaultColumnWidths.TradeKey, trade_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_column(TimeAndSalesView.Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, DIYGrid.DefaultColumnWidths.SecurityKey, security_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
void add_column(TimeAndSalesView.Columns col_key, string header, double width, IDataGridColumnFilter filter, string filter_template, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new TimeAndSalesColumn(
|
||||
r => r[col_key]
|
||||
, (r) => _get_aggressor_brush(r)
|
||||
, filter
|
||||
, filter_template
|
||||
)
|
||||
{ Header = header, Width = width, horizontal_alignment = h_alignment }
|
||||
);
|
||||
|
||||
_resources.AddRange(
|
||||
show_ladder,
|
||||
DataGridFilterHelper.hook_filters(
|
||||
rows_view, time_filter, security_filter, price_filter, size_filter, trade_key_filter, security_key_filter
|
||||
rows_view, time_filter, security_filter, price_filter, size_filter, aggressor_filter, trade_key_filter, security_key_filter
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -50,11 +62,22 @@ namespace mbts.spark {
|
||||
public ObservableCollection<DIYGrid.ColumnBase> columns { get; } = new ObservableCollection<DIYGrid.ColumnBase>();
|
||||
public DelegateCommandParamObserver<Row> show_ladder { get; }
|
||||
|
||||
private Brush _get_aggressor_brush(Row r) {
|
||||
BTSColor c;
|
||||
Brush b;
|
||||
if (r.aggressor_side == AggressorSide.Buy) c = BTSColors.MarketData.bid;
|
||||
else if (r.aggressor_side == AggressorSide.Sell) c = BTSColors.MarketData.ask;
|
||||
else c = Brushes.Transparent;
|
||||
(b = new SolidColorBrush(c) { Opacity = 0.33 }).Freeze();
|
||||
return b;
|
||||
}
|
||||
|
||||
#region Filters
|
||||
public IDataGridColumnPersistentFilter<Row, TimeFilterTuple> time_filter { get; } = new TimeFilterVM<Row>(r => r.time, nameof(Row.time));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> price_filter { get; } = new DoubleMinMax<Row>(r => r.price, nameof(Row.price));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> size_filter { get; } = new UIntMinMax<Row>(r => r.size, nameof(Row.size));
|
||||
public IDataGridColumnPersistentFilter<Row, AggressorSide[]> aggressor_filter { get; } = DiscreteValuesFilterVM.Create<Row, AggressorSide>(EnumHelper.get_values<AggressorSide>().ToArray(), r => r.aggressor_side, nameof(Row.aggressor_side));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
|
||||
public IDataGridColumnPersistentFilter<Row, string> trade_key_filter { get; } = new RegexFilterVM<Row>(r => r.trade_key.ToString(), nameof(Row.trade_key));
|
||||
|
||||
@@ -65,7 +88,8 @@ namespace mbts.spark {
|
||||
price = price_filter.get_state(),
|
||||
size = size_filter.get_state(),
|
||||
trade_key = trade_key_filter.get_state(),
|
||||
security_key = security_key_filter.get_state()
|
||||
security_key = security_key_filter.get_state(),
|
||||
aggressor_sides = aggressor_filter.get_state()
|
||||
};
|
||||
#endregion
|
||||
|
||||
@@ -90,6 +114,7 @@ namespace mbts.spark {
|
||||
_time_text,
|
||||
_price_text,
|
||||
_size_text,
|
||||
_aggressor_text,
|
||||
_trade_key_text,
|
||||
_security_key_text;
|
||||
|
||||
@@ -98,11 +123,13 @@ namespace mbts.spark {
|
||||
time = trade.Time.lldatetime();
|
||||
price = trade.Mantissa * Math.Pow(10, trade.Exponent);
|
||||
size = trade.Size;
|
||||
aggressor_side = trade.AggressorSide;
|
||||
show_ladder = new DelegateCommand(() => parent._show_ladder_async(this).ignore());
|
||||
_security_text = security.ToString();
|
||||
_time_text = time.ToString("M/d h:mm:ss tt");
|
||||
_price_text = security.ContractSpec.format(price);
|
||||
_size_text = size.ToString();
|
||||
_aggressor_text = aggressor_side.ToString();
|
||||
_trade_key_text = _trade_key.ToString();
|
||||
_security_key_text = security_key.ToString();
|
||||
}
|
||||
@@ -113,6 +140,7 @@ namespace mbts.spark {
|
||||
public DateTime time { get; }
|
||||
public double price { get; }
|
||||
public uint size { get; }
|
||||
public AggressorSide aggressor_side { get; }
|
||||
public ICommand show_ladder { get; }
|
||||
|
||||
public string this[TimeAndSalesView.Columns col] {
|
||||
@@ -122,6 +150,7 @@ namespace mbts.spark {
|
||||
case TimeAndSalesView.Columns.Time: return _time_text;
|
||||
case TimeAndSalesView.Columns.Price: return _price_text;
|
||||
case TimeAndSalesView.Columns.Size: return _size_text;
|
||||
case TimeAndSalesView.Columns.Aggressor: return _aggressor_text;
|
||||
case TimeAndSalesView.Columns.TradeKey: return _trade_key_text;
|
||||
case TimeAndSalesView.Columns.SecurityKey: return _security_key_text;
|
||||
default: return null;
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace mbts.ui.spark {
|
||||
/// Interaction logic for TimeAndSalesView.xaml
|
||||
/// </summary>
|
||||
partial class TimeAndSalesView : UserControl {
|
||||
public enum Columns { Time, Security, Price, Size, OrderType, TradeKey, SecurityKey }
|
||||
public enum Columns { Time, Security, Price, Size, OrderType, TradeKey, SecurityKey, Aggressor }
|
||||
|
||||
public TimeAndSalesView() =>
|
||||
InitializeComponent();
|
||||
|
||||
@@ -12,6 +12,7 @@ using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.TimeAndSalesWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
|
||||
namespace mbts.spark {
|
||||
class TimeAndSalesWindow : SparkWindow<StartupData>, ISlowWindow {
|
||||
@@ -36,7 +37,16 @@ namespace mbts.spark {
|
||||
public override string window_type => _window_type;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
public override UIElement Content => _view;
|
||||
public override double MinWidth => 444 + SPARK_MENU_COLLAPSED_WIDTH + LAUNCHER_BUTTON_BORDER_WIDTH;
|
||||
public override double MinWidth => DefaultColumnWidths.Security
|
||||
+ DefaultColumnWidths.Time
|
||||
+ DefaultColumnWidths.Price
|
||||
+ DefaultColumnWidths.Size
|
||||
+ DefaultColumnWidths.Aggressor
|
||||
+ DefaultColumnWidths.TradeKey
|
||||
+ DefaultColumnWidths.SecurityKey
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH;
|
||||
|
||||
public override StartupData SaveState => _state = new StartupData(_state.security_criteria, _vm.value.rows_view.GetSortingCriteria(), _vm.value.get_filter_states());
|
||||
|
||||
public override void Dispose() =>
|
||||
|
||||
@@ -70,6 +70,8 @@ namespace bts.shell {
|
||||
public string trade_key;
|
||||
[ProtoMember(6)]
|
||||
public string security_key;
|
||||
[ProtoMember(7)]
|
||||
public co2.AggressorSide[] aggressor_sides;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ namespace bts.ui {
|
||||
|
||||
public int priority => 40;
|
||||
|
||||
public IEnumerable<SparkCommand> actions => new []{
|
||||
public IEnumerable<SparkCommand> actions => new SparkCommand[]{
|
||||
SparkCommands.ShowWorkspaces
|
||||
//SparkCommands.ShowSettings
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<ResourceDictionary Source="/view/Resources/DataTemplates.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Popup/PopupControl.xaml"/>
|
||||
<ResourceDictionary Source="/Modules/Spreader/ParameterSectionView.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/TickFactorsPicker/TickFactorsPicker.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<sys:Double x:Key="RowHeaderWidth">285</sys:Double>
|
||||
<sys:Double x:Key="RowHeight">16</sys:Double>
|
||||
@@ -365,20 +366,11 @@
|
||||
Grid.Column="2" VerticalAlignment="Center" Margin="6,0,0,0" Text="Ticks" IsEnabled="{TemplateBinding IsNew}"
|
||||
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
|
||||
/>
|
||||
<ComboBox
|
||||
x:Name="PART_Tick_ComboBox" Grid.Column="3" Margin="6,0,0,0" IsEnabled="{TemplateBinding IsNew}" HorizontalAlignment="Stretch"
|
||||
ItemsSource="{TemplateBinding AvailableTickFactors}" SelectedItem="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
<c:TickFactorsPicker
|
||||
x:Name="PART_TickFactorsPicker" Grid.Column="3" Margin="6,0,0,0" IsEnabled="{TemplateBinding IsNew}" HorizontalAlignment="Stretch"
|
||||
SelectedTickFactors="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
|
||||
>
|
||||
<ComboBox.GroupStyle>
|
||||
<GroupStyle ContainerStyle="{StaticResource ComboBox.DelineatedGroupContainerStyle}"/>
|
||||
</ComboBox.GroupStyle>
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={StaticResource TickFactorsTextConverter}}"/>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
/>
|
||||
<TextBlock Grid.Column="4" VerticalAlignment="Center" Margin="6,0,0,0">Pricing</TextBlock>
|
||||
<ComboBox
|
||||
Grid.Column="5" Margin="6,0,0,0" IsEnabled="False"
|
||||
|
||||
@@ -50,13 +50,6 @@ namespace mbts.ui.spark {
|
||||
public TickFactors TickFactors { get => GetValue(TickFactorsProperty) as TickFactors; set => SetValue(TickFactorsProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region AvailableTickFactors
|
||||
public static readonly DependencyProperty AvailableTickFactorsProperty = DependencyProperty.Register(
|
||||
nameof(AvailableTickFactors), typeof(IEnumerable), typeof(ExecutionStrategyEditor), new PropertyMetadata(new TickFactors[0]));
|
||||
|
||||
public IEnumerable AvailableTickFactors { get => GetValue(AvailableTickFactorsProperty) as IEnumerable; set => SetValue(AvailableTickFactorsProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region IsNew
|
||||
public static readonly DependencyProperty IsNewProperty =
|
||||
DependencyProperty.Register(
|
||||
@@ -253,11 +246,11 @@ namespace mbts.ui.spark {
|
||||
|
||||
class ExecutionStrategyErrorAdorner : NotifyDataErrorAdorner<ExecutionStrategyEditor, SyntheticEditorVM>, IDisposable {
|
||||
readonly ChildProperty<TextBox> _name_text_box;
|
||||
readonly ChildProperty<ComboBox> _tick_combo_box;
|
||||
readonly ChildProperty<TickFactorsPicker> _tick_factors_picker;
|
||||
|
||||
public ExecutionStrategyErrorAdorner(ExecutionStrategyEditor editor) : base(editor) {
|
||||
_name_text_box = child_property<TextBox>("PART_Name_TextBox");
|
||||
_tick_combo_box = child_property<ComboBox>("PART_Tick_ComboBox");
|
||||
_tick_factors_picker = child_property<TickFactorsPicker>("PART_TickFactorsPicker");
|
||||
}
|
||||
|
||||
protected override void OnRender(DrawingContext drawing_context) {
|
||||
@@ -269,7 +262,7 @@ namespace mbts.ui.spark {
|
||||
if (_vm != null && _vm.GetErrors(nameof(SyntheticEditorVM.name)).Cast<string>().Any())
|
||||
this.draw_border(drawing_context, _name_text_box.value, null, new Pen(Brushes.Red, 1));
|
||||
if (_vm != null && _vm.GetErrors(nameof(SyntheticEditorVM.tick_factors)).Cast<string>().Any())
|
||||
this.draw_border(drawing_context, _tick_combo_box.value, null, new Pen(Brushes.Red, 1));
|
||||
this.draw_border(drawing_context, _tick_factors_picker.value, null, new Pen(Brushes.Red, 1));
|
||||
string error_text = string.Join(", ", _vm.AllErrors);
|
||||
this.draw_relative_text(RelativePosition.Below, error_text, drawing_context, _name_text_box.value, BTSColors.header_error_text_brush, 0, y_offset);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
HorizontalAlignment="Stretch" Margin="6"
|
||||
SyntheticName="{Binding name}"
|
||||
TickFactors="{Binding tick_factors}"
|
||||
AvailableTickFactors="{Binding available_tick_factors_view}"
|
||||
IsNew="{Binding is_new}"
|
||||
CanUserAddNewLeg="{Binding can_add_leg}"
|
||||
IsNewLegPopupOpen="{Binding is_new_leg_popup_open}"
|
||||
|
||||
@@ -72,9 +72,9 @@
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.security_key_filter, Source={u:DirectReference grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.security_key_filter.active, Source={u:DirectReference grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.security_key_filter.open, Source={u:DirectReference grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource IntMinMaxTemplate}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource RegexTemplate}"
|
||||
/>
|
||||
</DataGrid.Columns>
|
||||
</c:DataGridEx>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
@@ -53,6 +53,8 @@ namespace bts.shell {
|
||||
public IntMinMaxState n_legs;
|
||||
[ProtoMember(5)]
|
||||
public UIntMinMaxState n_working_orders;
|
||||
[ProtoMember(6)]
|
||||
public string security_key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
@@ -10,11 +11,9 @@ using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
@@ -23,7 +22,6 @@ namespace mbts.spark {
|
||||
/// The view model designed to serve as the DataContext for the SyntheticEditorView
|
||||
/// </summary>
|
||||
partial class SyntheticEditorVM : DisposableViewModelBase, IRequestViewClose, IRequestBindingSourceUpdate, IDisposable {
|
||||
readonly ObservableCollection<TickFactors> _available_tick_factors = new ObservableCollection<TickFactors>(TickUtils.all_present_ticks());
|
||||
readonly QuantityModel _quantity_model;
|
||||
readonly PriceMultiplierModel _price_mult_model;
|
||||
readonly List<IDisposable> _resources = new List<IDisposable>();
|
||||
@@ -121,7 +119,7 @@ namespace mbts.spark {
|
||||
flash_error_msg("Name already taken", SparkUtils.UI.MessageFlashDuration, nameof(name)).ignore();
|
||||
valid = false;
|
||||
}
|
||||
if (_tick_factors == null || !_tick_factors.is_valid() || !_available_tick_factors.Contains(_tick_factors)) {
|
||||
if (_tick_factors == null || !_tick_factors.is_valid()) {
|
||||
flash_error_msg("Please select ticks", SparkUtils.UI.MessageFlashDuration, nameof(tick_factors)).ignore();
|
||||
valid = false;
|
||||
}
|
||||
@@ -133,6 +131,8 @@ namespace mbts.spark {
|
||||
_synthetic.ContractSpec.LowTick = tick_frac;
|
||||
_synthetic.ContractSpec.HighTick = tick_frac;
|
||||
_synthetic.ContractSpec.PriceBreak = double.MaxValue;
|
||||
_synthetic.ContractSpec.MainFraction = (uint)MathUtils.factor_out(_tick_factors.tick_base, 10);
|
||||
_synthetic.ContractSpec.PriceDisplayFormat = (uint)_tick_factors.display_digits();
|
||||
uint? id = await model.create_synthetic(_synthetic);
|
||||
if (id.HasValue && id != 0) {
|
||||
if (await model.save_strategy(id.Value, get_strategy())) {
|
||||
@@ -152,8 +152,6 @@ namespace mbts.spark {
|
||||
_set_ticks_automatically = is_new;
|
||||
_set_quantities_automatically = is_new;
|
||||
_set_price_mults_automatically = is_new;
|
||||
available_tick_factors_view = CollectionViewSource.GetDefaultView(_available_tick_factors);
|
||||
available_tick_factors_view.GroupDescriptions.Add(new PropertyGroupDescription(nameof(TickFactors.tick_base)));
|
||||
|
||||
_quantity_model = new QuantityModel(!is_new);
|
||||
_price_mult_model = new PriceMultiplierModel(!is_new);
|
||||
@@ -245,9 +243,11 @@ namespace mbts.spark {
|
||||
public TickFactors tick_factors {
|
||||
get => _tick_factors;
|
||||
set {
|
||||
_tick_factors = value;
|
||||
_set_ticks_automatically = false;
|
||||
RaisePropertyChanged();
|
||||
if (_tick_factors != value) {
|
||||
_tick_factors = value;
|
||||
_set_ticks_automatically = false;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool has_working_orders => _has_working_orders.Value;
|
||||
@@ -261,7 +261,6 @@ namespace mbts.spark {
|
||||
public IRaiseCanExecuteChangedCommand commit { get; }
|
||||
public ICommand remove_leg { get; }
|
||||
public ContractPickerModel contract_picker_model { get; }
|
||||
public ICollectionView available_tick_factors_view { get; }
|
||||
|
||||
public ExecutionStrategyImmutable get_strategy() {
|
||||
var mutable_strategy = _strategy.CloneToMutable();
|
||||
@@ -328,8 +327,6 @@ namespace mbts.spark {
|
||||
|
||||
void _deduce_ticks() {
|
||||
_tick_factors = TickUtils.deduce_ticks(leg_tuples.Select(l => l.leg.Security.ContractSpec));
|
||||
if (_tick_factors != null && !_available_tick_factors.Contains(tick_factors))
|
||||
_available_tick_factors.Add(_tick_factors);
|
||||
RaisePropertyChanged(nameof(tick_factors));
|
||||
}
|
||||
|
||||
@@ -338,4 +335,4 @@ namespace mbts.spark {
|
||||
else clear_error(nameof(leg_count));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,9 @@ namespace mbts.spark {
|
||||
description_filter.load_state(filters.description);
|
||||
n_legs_filter.load_state(filters.n_legs);
|
||||
n_working_orders_filter.load_state(filters.n_working_orders);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
}
|
||||
_dispose = DataGridFilterHelper.hook_filters(rows_view, create_time_filter, name_filter, description_filter, n_legs_filter, n_working_orders_filter);
|
||||
_dispose = DataGridFilterHelper.hook_filters(rows_view, create_time_filter, name_filter, description_filter, n_legs_filter, n_working_orders_filter, security_key_filter);
|
||||
}
|
||||
|
||||
protected override Row row_factory((ExecutionStrategyImmutable? strategy, SecurityMD md) row_model) =>
|
||||
@@ -41,6 +42,7 @@ namespace mbts.spark {
|
||||
public IDataGridColumnPersistentFilter<Row, string> description_filter { get; } = new RegexFilterVM<Row>(r => r.description, nameof(Row.description));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> n_legs_filter { get; } = new IntMinMax<Row>(r => r.n_legs, nameof(Row.n_legs));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> n_working_orders_filter { get; } = new UIntMinMax<Row>(r => r.n_working_orders, nameof(Row.n_legs));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
|
||||
|
||||
public FilterStates get_filter_states() =>
|
||||
new FilterStates {
|
||||
@@ -48,7 +50,8 @@ namespace mbts.spark {
|
||||
create_time = create_time_filter.get_state(),
|
||||
name = name_filter.get_state(),
|
||||
n_legs = n_legs_filter.get_state(),
|
||||
n_working_orders = n_working_orders_filter.get_state()
|
||||
n_working_orders = n_working_orders_filter.get_state(),
|
||||
security_key = security_key_filter.get_state()
|
||||
};
|
||||
#endregion
|
||||
|
||||
@@ -104,4 +107,4 @@ namespace mbts.spark {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Data;
|
||||
using static bts.api.orderman.Fill.Types;
|
||||
using static bts.shell.FillsWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using static mbts.ui.spark.FillsView;
|
||||
@@ -52,6 +53,7 @@ namespace mbts.spark {
|
||||
side_filter.load_state(filters.side);
|
||||
price_filter.load_state(filters.price);
|
||||
size_filter.load_state(filters.size);
|
||||
fill_kind_filter.load_state(filters.fill_kind);
|
||||
fill_key_filter.load_state(filters.fill_key);
|
||||
order_key_filter.load_state(filters.order_key);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
@@ -74,6 +76,7 @@ namespace mbts.spark {
|
||||
columns.Add(new SideColumn<Row, Columns>(Columns.Side, r => r.side, side_filter) { sort_member_path = nameof(Row.side) });
|
||||
add_col(Columns.Size, DefaultColumnHeaders.Size, r => r.size.ToString(), nameof(Row.size), size_filter, DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.Size);
|
||||
add_col(Columns.Price, DefaultColumnHeaders.Price, r => r.format_price(r.price), nameof(Row.price), price_filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.Price);
|
||||
add_col(Columns.FillKind, "Fill Kind", r => r.fill_kind.ToString(), nameof(Row.fill_kind), fill_kind_filter, DataGridFilterKnownTemplates.DiscreteValues, 70);
|
||||
add_col(Columns.FillKey, DefaultColumnHeaders.FillKey, r => r.fill_key.ToString(), nameof(Row.fill_key), fill_key_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.FillKey);
|
||||
add_col(Columns.OrderKey, DefaultColumnHeaders.OrderKey, r => r.order_key.ToString(), nameof(Row.order_key), order_key_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.OrderKey);
|
||||
add_col(Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, r => r.security_key.ToString(), nameof(Row.security_key), security_key_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.SecurityKey);
|
||||
@@ -87,7 +90,7 @@ namespace mbts.spark {
|
||||
//
|
||||
_resources.Add(DataGridFilterHelper.hook_filters(rows_view,
|
||||
fill_time_filter, time_in_force_filter, order_type_filter, account_filter, user_filter, exchange_filter,
|
||||
security_filter, side_filter, price_filter, size_filter, fill_key_filter, order_key_filter, security_key_filter
|
||||
security_filter, side_filter, price_filter, size_filter, fill_kind_filter, fill_key_filter, order_key_filter, security_key_filter
|
||||
));
|
||||
}
|
||||
|
||||
@@ -104,6 +107,7 @@ namespace mbts.spark {
|
||||
public IDataGridColumnPersistentFilter<Row, Side[]> side_filter { get; } = DiscreteValuesFilterVM.Create<Row, Side>(new[] { Side.Buy, Side.Sell }, r => r.side, nameof(Row.side));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> price_filter { get; } = new DoubleMinMax<Row>(r => r.price, nameof(Row.price));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> size_filter { get; } = new DoubleMinMax<Row>(r => r.size, nameof(Row.size));
|
||||
public IDataGridColumnPersistentFilter<Row, FillKind[]> fill_kind_filter { get; } = DiscreteValuesFilterVM.Create<Row, FillKind>(EnumHelper.get_values<FillKind>().ToArray(), r => r.fill_kind, nameof(Row.fill_kind));
|
||||
public IDataGridColumnPersistentFilter<Row, string> fill_key_filter { get; } = new RegexFilterVM<Row>(r => r.fill_key, nameof(Row.fill_key));
|
||||
public IDataGridColumnPersistentFilter<Row, string> order_key_filter { get; } = new RegexFilterVM<Row>(r => r.order_key.ToString(), nameof(Row.order_key));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
|
||||
@@ -120,6 +124,7 @@ namespace mbts.spark {
|
||||
side = side_filter.get_state(),
|
||||
price = price_filter.get_state(),
|
||||
size = size_filter.get_state(),
|
||||
fill_kind = fill_kind_filter.get_state(),
|
||||
fill_key = fill_key_filter.get_state(),
|
||||
order_key = order_key_filter.get_state(),
|
||||
security_key = security_key_filter.get_state()
|
||||
@@ -157,6 +162,7 @@ namespace mbts.spark {
|
||||
public Side side => _fill.Side;
|
||||
public double price => _fill.LastPrice;
|
||||
public double size => _fill.LastShares;
|
||||
public FillKind fill_kind => _fill.FillKind;
|
||||
public string fill_key => _fill.FillKey;
|
||||
public string order_key => _order.Key.ToString();
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace mbts.ui.spark {
|
||||
/// Interaction logic for FillsView.xaml
|
||||
/// </summary>
|
||||
partial class FillsView : UserControl {
|
||||
public enum Columns { FillTime, User, Account, Exchange, Security, TimeInForce, OrderType, Side, Price, Size, FillKey, OrderKey, SecurityKey, ExchangeOrderID }
|
||||
public enum Columns { FillTime, User, Account, Exchange, Security, TimeInForce, OrderType, Side, Price, Size, FillKind, FillKey, OrderKey, SecurityKey, ExchangeOrderID }
|
||||
|
||||
public FillsView() =>
|
||||
InitializeComponent();
|
||||
|
||||
@@ -74,6 +74,8 @@ namespace bts.shell {
|
||||
public string order_key;
|
||||
[ProtoMember(13)]
|
||||
public string security_key;
|
||||
[ProtoMember(14)]
|
||||
public Fill.Types.FillKind[] fill_kind;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,6 +85,8 @@ namespace bts.shell {
|
||||
public TimeFilterTuple last_change;
|
||||
[ProtoMember(18)]
|
||||
public string security_regex;
|
||||
[ProtoMember(19)]
|
||||
public api.orderman.OrderSubType[] order_sub_types;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.spark;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.spark;
|
||||
using ProtoBuf;
|
||||
@@ -20,7 +22,6 @@ namespace bts.shell {
|
||||
_dispose = bus.subscribe(SparkTags.ShowPAndLTag).OnDispatcher().Subscribe(show);
|
||||
}
|
||||
|
||||
|
||||
public override Task<IShellWindow> load(PAndLStartupData state) {
|
||||
var w = new PAndLWindow(_model, this, _bus, state, SparkCommands.ShowPAndL.icon) as IShellWindow;
|
||||
return Task.FromResult(w);
|
||||
@@ -33,6 +34,61 @@ namespace bts.shell {
|
||||
|
||||
[ProtoContract]
|
||||
public class PAndLStartupData {
|
||||
|
||||
/// <summary>
|
||||
/// Parameterless constructor for protobuf
|
||||
/// </summary>
|
||||
public PAndLStartupData() { }
|
||||
|
||||
public PAndLStartupData(List<SortingCriteria> sorting_criteria, FilterStates filters, pnl_calc_type calc_type, TimeSpan start_of_day_time) {
|
||||
this.sort_criteria = sorting_criteria;
|
||||
this.filters = filters;
|
||||
this.calc_type = calc_type;
|
||||
this.start_of_day_time = start_of_day_time;
|
||||
}
|
||||
|
||||
[ProtoMember(5)]
|
||||
public readonly List<SortingCriteria> sort_criteria;
|
||||
[ProtoMember(10)]
|
||||
public FilterStates filters;
|
||||
[ProtoMember(15)]
|
||||
public pnl_calc_type calc_type;
|
||||
[ProtoMember(20)]
|
||||
public TimeSpan? start_of_day_time;
|
||||
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public class FilterStates {
|
||||
[ProtoMember(1)]
|
||||
public string account_regex;
|
||||
[ProtoMember(2)]
|
||||
public string exchange_regex;
|
||||
[ProtoMember(3)]
|
||||
public string symbol_regex;
|
||||
[ProtoMember(4)]
|
||||
public string security_regex;
|
||||
[ProtoMember(5)]
|
||||
public IntMinMaxState start_net_pos;
|
||||
[ProtoMember(6)]
|
||||
public DoubleMinMaxState start_pnl;
|
||||
[ProtoMember(7)]
|
||||
public UIntMinMaxState day_long_pos;
|
||||
[ProtoMember(8)]
|
||||
public UIntMinMaxState day_short_pos;
|
||||
[ProtoMember(9)]
|
||||
public DoubleMinMaxState day_pnl;
|
||||
[ProtoMember(10)]
|
||||
public IntMinMaxState total_net_pos;
|
||||
[ProtoMember(11)]
|
||||
public DoubleMinMaxState total_pnl;
|
||||
[ProtoMember(12)]
|
||||
public DoubleMinMaxState pnl_price;
|
||||
|
||||
// [ProtoMember(13)]
|
||||
// ProtoMember 13 cannot be used further
|
||||
[ProtoMember(14)]
|
||||
public SecurityType[] security_types;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,10 +21,9 @@ namespace mbts.spark {
|
||||
public ExistingOrderTicketVM(OrdersModel model, IObservable<OrderRecordTuple> order_source, IObservable<int[]> calculator_buttons) {
|
||||
uint? key = null;
|
||||
var security_md_source = order_source.Where(ot => ot.security_md.HasValue).Select(ot => ot.security_md.Value);
|
||||
var security_source = security_md_source.Select(smd => smd.security);
|
||||
//
|
||||
_security = backing(nameof(security), security_source, SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
properties_vm = new OrderPropertiesEditorVM(model, security_source.OnDispatcher(), order_source.Select(ot => new InlineOrderProperties(model, ot.order.Def)));
|
||||
_security = backing(nameof(security), security_md_source.Select(smd => smd.security), SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
properties_vm = new OrderPropertiesEditorVM(model, security_md_source, order_source.Select(ot => new InlineOrderProperties(model, ot.order.Def)));
|
||||
_price = backing(nameof(price), double.NaN);
|
||||
size_calculator = new CalculatorLogic(null, calculator_buttons);
|
||||
_current_price = backing(nameof(current_price), order_source.Select(ort => ort.order.State.Price));
|
||||
|
||||
@@ -90,18 +90,16 @@ namespace mbts.spark {
|
||||
HashSet<uint> changed_keys = new HashSet<uint>();
|
||||
foreach (var o in orders.removed) {
|
||||
if (_order_fills.try_lookup(o.Key, out ISet<string> fill_keys)) {
|
||||
IEnumerable<FillImmutable> fills = fill_keys.Select(fk => _fills_cache[fk]);
|
||||
SecurityMD? sec_md = _get_security_md(o.security_key());
|
||||
foreach (FillImmutable fill in fills) {
|
||||
FillTuple old_ft = new FillTuple(fill, o, sec_md);
|
||||
removed.Add(old_ft.key, old_ft);
|
||||
}
|
||||
OrderRecordImmutable? curr_order = null;
|
||||
if (orders.try_get(o.Key, out OrderRecordImmutable newval)) {
|
||||
curr_order = newval;
|
||||
changed_keys.Add(o.Key);
|
||||
}
|
||||
IEnumerable<FillImmutable> fills = fill_keys.Select(fk => _fills_cache[fk]);
|
||||
foreach (FillImmutable fill in fills) {
|
||||
SecurityMD? sec_md = _get_security_md(fill.SecurityKey);
|
||||
FillTuple old_ft = new FillTuple(fill, o, sec_md);
|
||||
removed.Add(old_ft.key, old_ft);
|
||||
FillTuple new_ft = new FillTuple(fill, curr_order, sec_md);
|
||||
added.Add(new_ft.key, new_ft);
|
||||
}
|
||||
@@ -110,8 +108,8 @@ namespace mbts.spark {
|
||||
foreach (var o in orders.added) {
|
||||
if (!changed_keys.Contains(o.Key) && _order_fills.try_lookup(o.Key, out ISet<string> fill_keys)) {
|
||||
IEnumerable<FillImmutable> fills = fill_keys.Select(fk => _fills_cache[fk]);
|
||||
SecurityMD? sec_md = _get_security_md(o.security_key());
|
||||
foreach (FillImmutable fill in fills) {
|
||||
SecurityMD? sec_md = _get_security_md(fill.SecurityKey);
|
||||
FillTuple old_ft = new FillTuple(fill, null, sec_md);
|
||||
removed.Add(old_ft.key, old_ft);
|
||||
FillTuple new_ft = new FillTuple(fill, o, sec_md);
|
||||
|
||||
@@ -47,9 +47,8 @@ namespace mbts.spark {
|
||||
IObservable<InlineOrderTicketProperties> props_source,
|
||||
IObservable<int[]> calculator_buttons
|
||||
) {
|
||||
var security_source = security_md_source.Select(smd => smd.security);
|
||||
_security = backing(nameof(security), security_source, SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
properties_vm = new OrderPropertiesEditorVM(model, security_source, props_source.Select(p => p.order_props));
|
||||
_security = backing(nameof(security), security_md_source.Select(smd => smd.security), SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
properties_vm = new OrderPropertiesEditorVM(model, security_md_source, props_source.Select(p => p.order_props));
|
||||
bbo = new BBOVM(security_md_source);
|
||||
send_order = new DelegateCommand<Side>(side => {
|
||||
bool price_valid = _validate_price(), size_valid = size_calculator.validate();
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace mbts.ui.spark {
|
||||
public DataTemplate LeanOrderTemplate { get; set; }
|
||||
public DataTemplate JoinOrderTemplate { get; set; }
|
||||
public DataTemplate TopOrderTemplate { get; set; }
|
||||
public DataTemplate TriggerOrderTemplate { get; set; }
|
||||
public DataTemplate IcebergOrderTemplate { get; set; }
|
||||
public DataTemplate SpreaderTemplate { get; set; }
|
||||
public DataTemplate BTSStopOrderTemplate { get; set; }
|
||||
@@ -41,6 +42,7 @@ namespace mbts.ui.spark {
|
||||
case CustomOrderDefinition.CustomOneofCase.Lean: return LeanOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Join: return JoinOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Top: return TopOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Trigger: return TriggerOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Iceberg: return IcebergOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Spreader: return SpreaderTemplate;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
|
||||
namespace mbts.spark {
|
||||
class InlineOrderProperties {
|
||||
@@ -44,8 +43,9 @@ namespace mbts.spark {
|
||||
readonly SelectorProperty<IOrderTypeVM> _order_type;
|
||||
IDisposable _order_type_param_errors_sub;
|
||||
|
||||
public OrderPropertiesEditorVM(OrdersModel model, IObservable<SecurityImmutable> security_source, IObservable<InlineOrderProperties> order_properties) {
|
||||
public OrderPropertiesEditorVM(OrdersModel model, IObservable<SecurityMD> security_md_source, IObservable<InlineOrderProperties> order_properties) {
|
||||
_model = model;
|
||||
var security_source = security_md_source.Select(smd => smd.security);
|
||||
_security = backing(nameof(security), security_source.OnDispatcher(), SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
// guarentee that "this.security" will always be set before observers are notified
|
||||
// this is important bc some observers will reference "this.security" in their callbacks
|
||||
@@ -75,7 +75,7 @@ namespace mbts.spark {
|
||||
_dispose = Disposable.Create(() => _order_type.Changed -= on_selected_order_type_changed);
|
||||
// local methods
|
||||
IOrderTypeVM create_order_type_vm(CustomOrderDefinitionImmutable cd) {
|
||||
var vm = OrderTypeVMFactory.create(cd.CustomCase, model, security_source);
|
||||
var vm = OrderTypeVMFactory.create(cd.CustomCase, model, security_md_source);
|
||||
vm.load_from(cd);
|
||||
return vm;
|
||||
}
|
||||
@@ -136,6 +136,7 @@ namespace mbts.spark {
|
||||
protected override void Dispose(bool disposing) {
|
||||
base.Dispose(disposing);
|
||||
Disposable.dispose(ref _order_type_param_errors_sub);
|
||||
_order_type.items.OfType<IDisposable>().DisposeAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
class IcebergOrderVM : OrderTypeVMBase<IcebergOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable {
|
||||
readonly IObservable<SecurityImmutable> _security_source;
|
||||
readonly IObservable<SecurityMD> _security_md_source;
|
||||
SecurityImmutable? _security;
|
||||
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
|
||||
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
|
||||
@@ -34,18 +34,18 @@ namespace mbts.spark {
|
||||
IOrderTypeVM _child_order;
|
||||
IDisposable _dispose, _child_order_errors_sub;
|
||||
|
||||
public IcebergOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source)
|
||||
: this(model, security_source, security_source.Select(s =>
|
||||
new InlineIcebergOrderProperties(model.factory.default_order_type(s), model.factory.default_order_types(s).ToList()))) { }
|
||||
public IcebergOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
|
||||
: this(model, security_md_source, security_md_source.Select(smd =>
|
||||
new InlineIcebergOrderProperties(model.factory.default_order_type(smd.security), model.factory.default_order_types(smd.security).ToList()))) { }
|
||||
|
||||
public IcebergOrderVM(OrdersModel model,
|
||||
IObservable<SecurityImmutable> security_source,
|
||||
IObservable<SecurityMD> security_md_source,
|
||||
IObservable<InlineIcebergOrderProperties> inline_properties)
|
||||
: base(model) {
|
||||
_security_source = security_source;
|
||||
_security_md_source = security_md_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_source.OnDispatcher()
|
||||
.Subscribe(s => _security = s),
|
||||
security_md_source.OnDispatcher()
|
||||
.Subscribe(smd => _security = smd.security),
|
||||
inline_properties.OnDispatcher()
|
||||
.Subscribe(p => {
|
||||
if (_order_types.Count == 0 || p.child_order_types.force_override) {
|
||||
@@ -68,7 +68,7 @@ namespace mbts.spark {
|
||||
public ICollection<IOrderTypeVM> child_order_types {
|
||||
get {
|
||||
if (_child_order_choices == null) {
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
|
||||
}
|
||||
return _child_order_choices;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace mbts.spark
|
||||
|
||||
class JoinOrderVM : OrderTypeVMBase<JoinOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
|
||||
{
|
||||
readonly IObservable<SecurityImmutable> _security_source;
|
||||
readonly IObservable<SecurityMD> _security_md_source;
|
||||
SecurityImmutable? _security;
|
||||
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
|
||||
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
|
||||
@@ -79,17 +79,17 @@ namespace mbts.spark
|
||||
}
|
||||
public int hedge_ticks { get; set; } = 1;
|
||||
|
||||
public JoinOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source)
|
||||
: this(model, security_source, security_source.Select(s =>
|
||||
new InlineJoinOrderProperties(model.factory.default_order_type(s), model.factory.default_order_types(s).ToList()))) { }
|
||||
public JoinOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
|
||||
: this(model, security_md_source, security_md_source.Select(smd =>
|
||||
new InlineJoinOrderProperties(model.factory.default_order_type(smd.security), model.factory.default_order_types(smd.security).ToList()))) { }
|
||||
|
||||
public JoinOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source,
|
||||
public JoinOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source,
|
||||
IObservable<InlineJoinOrderProperties> inline_properties) : base(model)
|
||||
{
|
||||
_security_source = security_source;
|
||||
_security_md_source = security_md_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_source.OnDispatcher()
|
||||
.Subscribe(s => _security = s),
|
||||
security_md_source.OnDispatcher()
|
||||
.Subscribe(smd => _security = smd.security),
|
||||
inline_properties.OnDispatcher()
|
||||
.Subscribe(p => {
|
||||
if (_order_types.Count == 0 || p.child_order_types.force_override)
|
||||
@@ -157,7 +157,7 @@ namespace mbts.spark
|
||||
get
|
||||
{
|
||||
if (_child_order_choices == null)
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
|
||||
return _child_order_choices;
|
||||
}
|
||||
}
|
||||
@@ -167,7 +167,7 @@ namespace mbts.spark
|
||||
get
|
||||
{
|
||||
if (_hedge_order_choices == null)
|
||||
_hedge_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
|
||||
_hedge_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
|
||||
return _hedge_order_choices;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace mbts.spark
|
||||
|
||||
class LeanOrderVM : OrderTypeVMBase<LeanOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
|
||||
{
|
||||
readonly IObservable<SecurityImmutable> _security_source;
|
||||
readonly IObservable<SecurityMD> _security_md_source;
|
||||
SecurityImmutable? _security;
|
||||
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
|
||||
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
|
||||
@@ -46,17 +46,17 @@ namespace mbts.spark
|
||||
public bts.spark.QuantityMeasure cross_threshold { get; set; } = new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1 };
|
||||
public uint max_chase_ticks { get; set; } = 10;
|
||||
|
||||
public LeanOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source)
|
||||
: this(model, security_source, security_source.Select(s =>
|
||||
new InlineLeanOrderProperties(model.factory.default_order_type(s), model.factory.default_order_types(s).ToList()))) { }
|
||||
public LeanOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
|
||||
: this(model, security_md_source, security_md_source.Select(smd =>
|
||||
new InlineLeanOrderProperties(model.factory.default_order_type(smd.security), model.factory.default_order_types(smd.security).ToList()))) { }
|
||||
|
||||
public LeanOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source,
|
||||
public LeanOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source,
|
||||
IObservable<InlineLeanOrderProperties> inline_properties) : base(model)
|
||||
{
|
||||
_security_source = security_source;
|
||||
_security_md_source = security_md_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_source.OnDispatcher()
|
||||
.Subscribe(s => _security = s),
|
||||
security_md_source.OnDispatcher()
|
||||
.Subscribe(smd => _security = smd.security),
|
||||
inline_properties.OnDispatcher()
|
||||
.Subscribe(p => {
|
||||
if (_order_types.Count == 0 || p.child_order_types.force_override)
|
||||
@@ -104,7 +104,7 @@ namespace mbts.spark
|
||||
get
|
||||
{
|
||||
if (_child_order_choices == null)
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
|
||||
return _child_order_choices;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using bts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Input;
|
||||
using static bts.api.orderman.CustomOrderDefinition;
|
||||
|
||||
@@ -12,31 +13,30 @@ namespace mbts.spark {
|
||||
public static IOrderTypeVM market(OrdersModel model) => new MarketOrderVM(model);
|
||||
public static IOrderTypeVM limit(OrdersModel model) => new LimitOrderVM(model);
|
||||
public static IOrderTypeVM stop(OrdersModel model) => new StopOrderVM(model);
|
||||
public static IOrderTypeVM stop_limit(OrdersModel model) => new StopLimitOrderVM(model);
|
||||
public static IOrderTypeVM time_box(OrdersModel model, IObservable<SecurityImmutable> security_source) => new TimeBoxOrderVM(model, security_source);
|
||||
public static IOrderTypeVM tick(OrdersModel model, IObservable<SecurityImmutable> security_source) => new TickOrderVM(model, security_source);
|
||||
public static IOrderTypeVM lean(OrdersModel model, IObservable<SecurityImmutable> security_source) => new LeanOrderVM(model, security_source);
|
||||
public static IOrderTypeVM join(OrdersModel model, IObservable<SecurityImmutable> security_source) => new JoinOrderVM(model, security_source);
|
||||
public static IOrderTypeVM top(OrdersModel model, IObservable<SecurityImmutable> security_source) => new TopOrderVM(model, security_source);
|
||||
public static IOrderTypeVM iceberg(OrdersModel model, IObservable<SecurityImmutable> security_source) => new IcebergOrderVM(model, security_source);
|
||||
public static IOrderTypeVM stop_limit(OrdersModel model, IObservable<SecurityMD> security_md_source) => new StopLimitOrderVM(model, security_md_source);
|
||||
public static IOrderTypeVM time_box(OrdersModel model, IObservable<SecurityMD> security_md_source) => new TimeBoxOrderVM(model, security_md_source);
|
||||
public static IOrderTypeVM tick(OrdersModel model, IObservable<SecurityMD> security_md_source) => new TickOrderVM(model, security_md_source);
|
||||
public static IOrderTypeVM lean(OrdersModel model, IObservable<SecurityMD> security_md_source) => new LeanOrderVM(model, security_md_source);
|
||||
public static IOrderTypeVM join(OrdersModel model, IObservable<SecurityMD> security_md_source) => new JoinOrderVM(model, security_md_source);
|
||||
public static IOrderTypeVM top(OrdersModel model, IObservable<SecurityMD> security_md_source) => new TopOrderVM(model, security_md_source);
|
||||
public static IOrderTypeVM trigger(OrdersModel model, IObservable<SecurityMD> security_md_source) => new TriggerOrderVM(model, security_md_source);
|
||||
public static IOrderTypeVM iceberg(OrdersModel model, IObservable<SecurityMD> security_md_source) => new IcebergOrderVM(model, security_md_source);
|
||||
public static IOrderTypeVM spreader(OrdersModel model, IObservable<SecurityImmutable> security_source) => new SpreaderOrderVM(model, security_source);
|
||||
|
||||
static Func<CustomOneofCase, IOrderTypeVM> _get_factory(OrdersModel orders_model, IObservable<SecurityImmutable> security_source) =>
|
||||
new Func<CustomOneofCase, IOrderTypeVM>(ot => create(ot, orders_model, security_source));
|
||||
|
||||
public static IOrderTypeVM create(CustomOneofCase order_type, OrdersModel model, IObservable<SecurityImmutable> security_source) {
|
||||
public static IOrderTypeVM create(CustomOneofCase order_type, OrdersModel model, IObservable<SecurityMD> security_md_source) {
|
||||
switch (order_type) {
|
||||
case CustomOneofCase.Market: return market(model);
|
||||
case CustomOneofCase.Limit: return limit(model);
|
||||
case CustomOneofCase.Stop: return stop(model);
|
||||
case CustomOneofCase.StopLimit: return stop_limit(model);
|
||||
case CustomOneofCase.TimeBox: return time_box(model, security_source);
|
||||
case CustomOneofCase.Tick: return tick(model, security_source);
|
||||
case CustomOneofCase.Lean: return lean(model, security_source);
|
||||
case CustomOneofCase.Join: return join(model, security_source);
|
||||
case CustomOneofCase.Top: return top(model, security_source);
|
||||
case CustomOneofCase.Iceberg: return iceberg(model, security_source);
|
||||
case CustomOneofCase.Spreader: return spreader(model, security_source);
|
||||
case CustomOneofCase.StopLimit: return stop_limit(model, security_md_source);
|
||||
case CustomOneofCase.TimeBox: return time_box(model, security_md_source);
|
||||
case CustomOneofCase.Tick: return tick(model, security_md_source);
|
||||
case CustomOneofCase.Lean: return lean(model, security_md_source);
|
||||
case CustomOneofCase.Join: return join(model, security_md_source);
|
||||
case CustomOneofCase.Top: return top(model, security_md_source);
|
||||
case CustomOneofCase.Trigger: return trigger(model, security_md_source);
|
||||
case CustomOneofCase.Iceberg: return iceberg(model, security_md_source);
|
||||
case CustomOneofCase.Spreader: return spreader(model, security_md_source.Select(smd => smd.security));
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,61 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.utils;
|
||||
using mbts.co2;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
static partial class OrderTypeVMFactory {
|
||||
private class StopLimitOrderVM : OrderTypeVMBase<StopLimitOrderDefinition> {
|
||||
double _stop_price = 0;
|
||||
class InlineStopLimitOrderProperties {
|
||||
readonly Func<InlineUserProperty<double>> _get_stop_price;
|
||||
|
||||
internal StopLimitOrderVM(OrdersModel model) : base(model) { }
|
||||
public static IObservable<InlineStopLimitOrderProperties> subscribe(IObservable<SecurityMD> security_md_source) {
|
||||
ulong time = 0;
|
||||
return security_md_source.Select(smd => new InlineStopLimitOrderProperties(() => {
|
||||
smd.md.get_bbo(ref time, out NativePriceSize bid, out NativePriceSize ask);
|
||||
return new InlineUserProperty<double>(bid.price);
|
||||
}));
|
||||
}
|
||||
|
||||
public InlineStopLimitOrderProperties(Func<InlineUserProperty<double>> get_stop_price) =>
|
||||
_get_stop_price = get_stop_price;
|
||||
|
||||
public InlineUserProperty<double> stop_price => _get_stop_price();
|
||||
}
|
||||
|
||||
private class StopLimitOrderVM : OrderTypeVMBase<StopLimitOrderDefinition>, IDisposable {
|
||||
UserProperty<double> _stop_price;
|
||||
InlineStopLimitOrderProperties _inline_props;
|
||||
IDisposable _dispose;
|
||||
|
||||
internal StopLimitOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
|
||||
: this(model, security_md_source.Select(smd => smd.security.spec_or_default()), InlineStopLimitOrderProperties.subscribe(security_md_source)) { }
|
||||
|
||||
internal StopLimitOrderVM(OrdersModel model, IObservable<ContractSpecificationImmutable> spec_source, IObservable<InlineStopLimitOrderProperties> inline_properties) : base(model) {
|
||||
_stop_price = new UserProperty<double>(backing(nameof(stop_price), double.NaN));
|
||||
_dispose = Disposable.Create(
|
||||
spec_source.OnDispatcher().Subscribe(s => {
|
||||
spec = s;
|
||||
RaisePropertyChanged(nameof(spec));
|
||||
}), inline_properties.OnDispatcher().Subscribe(p => _inline_props = p));
|
||||
}
|
||||
|
||||
public override bool editable_properties_open {
|
||||
get => base.editable_properties_open;
|
||||
set {
|
||||
if (value) {
|
||||
if (_update_inline_props())
|
||||
_validate();
|
||||
}
|
||||
base.editable_properties_open = value;
|
||||
}
|
||||
}
|
||||
|
||||
public double stop_price {
|
||||
get => _stop_price;
|
||||
get => _stop_price.Value;
|
||||
set {
|
||||
_stop_price = value;
|
||||
_stop_price.Value = value;
|
||||
_validate();
|
||||
}
|
||||
}
|
||||
@@ -21,6 +64,8 @@ namespace mbts.spark {
|
||||
SecurityDataHelper.Defaults.ContractSpecificationImmutable;
|
||||
|
||||
public override void assign_to(CustomOrderDefinition value) {
|
||||
_update_inline_props();
|
||||
_validate();
|
||||
if (!HasErrors) {
|
||||
var sl = factory.stop_limit_order();
|
||||
sl.StopPrice = stop_price;
|
||||
@@ -28,9 +73,22 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() => Disposable.dispose(ref _dispose);
|
||||
|
||||
// no need to constantly poll the md, just check the inline props when the popup opens, and right before sending an order
|
||||
// (if the user never looks at the popup, assume that the bid_price will be used)
|
||||
// TODO: add settings so the trigger-price can default to something relative to the md, e.g "bid-price" or "ask-price - nticks" or "best-bid-price with a minimum size of 10"
|
||||
bool _update_inline_props() {
|
||||
var inline_stop_price = _inline_props.stop_price;
|
||||
if (inline_stop_price.has_value && (inline_stop_price.force_override || !_stop_price.setter_called)) {
|
||||
_stop_price.bypass_setter(inline_stop_price.value);
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
void _validate() {
|
||||
using (validation())
|
||||
validation_assert(!DoubleHelper.IsNaNOrInfinity(_stop_price), string.Empty, nameof(stop_price));
|
||||
validation_assert(!DoubleHelper.IsNaNOrInfinity(stop_price), string.Empty, nameof(stop_price));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace mbts.spark
|
||||
|
||||
class TickOrderVM : OrderTypeVMBase<TickOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
|
||||
{
|
||||
readonly IObservable<SecurityImmutable> _security_source;
|
||||
readonly IObservable<SecurityMD> _security_md_source;
|
||||
SecurityImmutable? _security;
|
||||
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
|
||||
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
|
||||
@@ -55,17 +55,17 @@ namespace mbts.spark
|
||||
public uint max_chase_ticks { get; set; } = 10;
|
||||
public bool detach_on_toward { get; set; } = true;
|
||||
|
||||
public TickOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source)
|
||||
: this(model, security_source, security_source.Select(s =>
|
||||
new InlineTickOrderProperties(model.factory.default_order_type(s), model.factory.default_order_types(s).ToList()))) { }
|
||||
public TickOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
|
||||
: this(model, security_md_source, security_md_source.Select(smd =>
|
||||
new InlineTickOrderProperties(model.factory.default_order_type(smd.security), model.factory.default_order_types(smd.security).ToList()))) { }
|
||||
|
||||
public TickOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source,
|
||||
public TickOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source,
|
||||
IObservable<InlineTickOrderProperties> inline_properties) : base(model)
|
||||
{
|
||||
_security_source = security_source;
|
||||
_security_md_source = security_md_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_source.OnDispatcher()
|
||||
.Subscribe(s => _security = s),
|
||||
security_md_source.OnDispatcher()
|
||||
.Subscribe(smd => _security = smd.security),
|
||||
inline_properties.OnDispatcher()
|
||||
.Subscribe(p => {
|
||||
if (_order_types.Count == 0 || p.child_order_types.force_override)
|
||||
@@ -131,7 +131,7 @@ namespace mbts.spark
|
||||
get
|
||||
{
|
||||
if (_child_order_choices == null)
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
|
||||
return _child_order_choices;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace mbts.spark {
|
||||
|
||||
class TimeBoxOrderVM : OrderTypeVMBase<TimeBoxOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable {
|
||||
const double _start_immediately_delay_ms = 500;
|
||||
readonly IObservable<SecurityImmutable> _security_source;
|
||||
readonly IObservable<SecurityMD> _security_md_source;
|
||||
SecurityImmutable? _security;
|
||||
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
|
||||
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
|
||||
@@ -40,18 +40,18 @@ namespace mbts.spark {
|
||||
IOrderTypeVM _child_order;
|
||||
IDisposable _dispose, _child_order_errors_sub;
|
||||
|
||||
public TimeBoxOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source)
|
||||
: this(model, security_source, security_source.Select(s =>
|
||||
new InlineTimeBoxOrderProperties(model.factory.default_order_type(s), model.factory.default_order_types(s).ToList()))) { }
|
||||
public TimeBoxOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
|
||||
: this(model, security_md_source, security_md_source.Select(smd =>
|
||||
new InlineTimeBoxOrderProperties(model.factory.default_order_type(smd.security), model.factory.default_order_types(smd.security).ToList()))) { }
|
||||
|
||||
public TimeBoxOrderVM(OrdersModel model,
|
||||
IObservable<SecurityImmutable> security_source,
|
||||
IObservable<SecurityMD> security_md_source,
|
||||
IObservable<InlineTimeBoxOrderProperties> inline_properties)
|
||||
: base(model) {
|
||||
_security_source = security_source;
|
||||
_security_md_source = security_md_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_source.OnDispatcher()
|
||||
.Subscribe(s => _security = s),
|
||||
security_md_source.OnDispatcher()
|
||||
.Subscribe(smd => _security = smd.security),
|
||||
inline_properties.OnDispatcher()
|
||||
.Subscribe(p => {
|
||||
if (_order_types.Count == 0 || p.child_order_types.force_override) {
|
||||
@@ -94,7 +94,7 @@ namespace mbts.spark {
|
||||
public ICollection<IOrderTypeVM> child_order_types {
|
||||
get {
|
||||
if (_child_order_choices == null) {
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
|
||||
}
|
||||
return _child_order_choices;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace mbts.spark
|
||||
|
||||
class TopOrderVM : OrderTypeVMBase<TopOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
|
||||
{
|
||||
readonly IObservable<SecurityImmutable> _security_source;
|
||||
readonly IObservable<SecurityMD> _security_md_source;
|
||||
SecurityImmutable? _security;
|
||||
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
|
||||
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
|
||||
@@ -77,18 +77,18 @@ namespace mbts.spark
|
||||
}
|
||||
public int hedge_ticks { get; set; } = 1;
|
||||
|
||||
public TopOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source)
|
||||
: this(model, security_source, security_source.Select(s =>
|
||||
new InlineTopOrderProperties(model.factory.default_order_type(s), model.factory.default_order_types(s).ToList())))
|
||||
public TopOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
|
||||
: this(model, security_md_source, security_md_source.Select(smd =>
|
||||
new InlineTopOrderProperties(model.factory.default_order_type(smd.security), model.factory.default_order_types(smd.security).ToList())))
|
||||
{ }
|
||||
|
||||
public TopOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source,
|
||||
public TopOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source,
|
||||
IObservable<InlineTopOrderProperties> inline_properties) : base(model)
|
||||
{
|
||||
_security_source = security_source;
|
||||
_security_md_source = security_md_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_source.OnDispatcher()
|
||||
.Subscribe(s => _security = s),
|
||||
security_md_source.OnDispatcher()
|
||||
.Subscribe(smd => _security = smd.security),
|
||||
inline_properties.OnDispatcher()
|
||||
.Subscribe(p =>
|
||||
{
|
||||
@@ -157,7 +157,7 @@ namespace mbts.spark
|
||||
get
|
||||
{
|
||||
if (_child_order_choices == null)
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
|
||||
return _child_order_choices;
|
||||
}
|
||||
}
|
||||
@@ -167,7 +167,7 @@ namespace mbts.spark
|
||||
get
|
||||
{
|
||||
if (_hedge_order_choices == null)
|
||||
_hedge_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
|
||||
_hedge_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
|
||||
return _hedge_order_choices;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.ui;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using static bts.api.orderman.CustomOrderDefinition;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
static partial class OrderTypeVMFactory
|
||||
{
|
||||
class InlineTriggerOrderProperties
|
||||
{
|
||||
public InlineTriggerOrderProperties (CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types)
|
||||
{
|
||||
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
|
||||
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
|
||||
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
|
||||
|
||||
trigger_price_type = new InlineUserProperty<TriggerPriceType>(TriggerPriceType.Bid, false);
|
||||
order_price = new InlineUserProperty<double>(double.NaN, true);
|
||||
}
|
||||
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
|
||||
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
|
||||
|
||||
public readonly InlineUserProperty<TriggerPriceType> trigger_price_type;
|
||||
public readonly InlineUserProperty<double> order_price;
|
||||
}
|
||||
|
||||
class TriggerOrderVM : OrderTypeVMBase<TriggerOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
|
||||
{
|
||||
readonly IObservable<SecurityMD> _security_md_source;
|
||||
SecurityImmutable? _security;
|
||||
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
|
||||
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
|
||||
InlineTriggerOrderProperties _inline_props;
|
||||
IOrderTypeVM _child_order;
|
||||
IDisposable _dispose, _child_order_errors_sub;
|
||||
|
||||
public TriggerPriceType trigger_price_type { get; set; } = TriggerPriceType.Bid;
|
||||
public double order_price { get; set; } = double.NaN;
|
||||
|
||||
public TriggerOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
|
||||
: this(model, security_md_source, security_md_source.Select(smd =>
|
||||
new InlineTriggerOrderProperties(model.factory.default_order_type(smd.security), model.factory.default_order_types(smd.security).ToList()))) { }
|
||||
|
||||
public TriggerOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source,
|
||||
IObservable<InlineTriggerOrderProperties> inline_properties) : base(model)
|
||||
{
|
||||
_security_md_source = security_md_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_md_source.OnDispatcher()
|
||||
.Subscribe(smd => _security = smd.security),
|
||||
inline_properties.OnDispatcher()
|
||||
.Subscribe(p => {
|
||||
if (_order_types.Count == 0 || p.child_order_types.force_override)
|
||||
{
|
||||
_order_types.SetItems(p.child_order_types.value);
|
||||
}
|
||||
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override))
|
||||
{
|
||||
_select_child_order(p.selected_child_order_type.value);
|
||||
}
|
||||
if (p.trigger_price_type.force_override)
|
||||
{
|
||||
trigger_price_type = p.trigger_price_type.value;
|
||||
RaisePropertyChanged(nameof(trigger_price_type));
|
||||
}
|
||||
if (p.order_price.force_override)
|
||||
{
|
||||
order_price = p.order_price.value;
|
||||
RaisePropertyChanged(nameof(order_price));
|
||||
}
|
||||
_inline_props = p;
|
||||
_validate();
|
||||
})
|
||||
);
|
||||
_validate();
|
||||
}
|
||||
|
||||
public IEnumerable<TriggerPriceType> trigger_price_types => EnumHelper.get_values_except<TriggerPriceType>(TriggerPriceType.LastTradePrice);
|
||||
|
||||
public override void assign_to(CustomOrderDefinition value)
|
||||
{
|
||||
if (_child_order == null)
|
||||
_select_child_order(model.factory.default_order_type(_security.Value, true));
|
||||
_validate();
|
||||
if (!HasErrors)
|
||||
{
|
||||
var i = factory.trigger_order();
|
||||
i.TriggerCondition = new TriggerCondition()
|
||||
{
|
||||
PriceType = trigger_price_type,
|
||||
AbsolutePriceTrigger = new AbsolutePriceTrigger() { TriggerQuantity = new TriggerQuantity() }
|
||||
};
|
||||
i.TriggerAction = new TriggerAction()
|
||||
{
|
||||
Price = order_price,
|
||||
SecurityId = _security.Value.Id
|
||||
};
|
||||
child_order.assign_to(i.Child);
|
||||
value.Trigger = i;
|
||||
}
|
||||
}
|
||||
|
||||
public double price_tick
|
||||
{
|
||||
get { return _security.Value.ContractSpec.LowTick; }
|
||||
}
|
||||
|
||||
public ContractSpecificationImmutable spec
|
||||
{
|
||||
get { return _security.Value.ContractSpec; }
|
||||
}
|
||||
|
||||
public ICollection<IOrderTypeVM> child_order_types
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_child_order_choices == null)
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
|
||||
return _child_order_choices;
|
||||
}
|
||||
}
|
||||
|
||||
public IOrderTypeVM child_order
|
||||
{
|
||||
get
|
||||
{
|
||||
init();
|
||||
return _child_order;
|
||||
}
|
||||
set
|
||||
{
|
||||
Disposable.dispose(ref _child_order_errors_sub);
|
||||
_child_order = value;
|
||||
_child_order?.init();
|
||||
if (_child_order is INotifyDataErrorInfo ndei)
|
||||
{
|
||||
ndei.ErrorsChanged += on_errors_changed;
|
||||
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
|
||||
_validate();
|
||||
_child_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
|
||||
}
|
||||
_validate();
|
||||
RaisePropertyChanged(nameof(child_order));
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Disposable.dispose(ref _dispose);
|
||||
if (_child_order_choices != null)
|
||||
_child_order_choices.OfType<IDisposable>().DisposeAll();
|
||||
}
|
||||
|
||||
public override void init()
|
||||
{
|
||||
if (_child_order == null && _security.HasValue)
|
||||
_select_child_order();
|
||||
_validate();
|
||||
}
|
||||
|
||||
void _select_child_order()
|
||||
{
|
||||
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
|
||||
_inline_props.selected_child_order_type.value :
|
||||
model.factory.default_order_type(_security.Value, true);
|
||||
_select_child_order(ot);
|
||||
}
|
||||
|
||||
void _select_child_order(CustomOneofCase ot)
|
||||
{
|
||||
_child_order = child_order_types.FirstOrDefault(vm => vm.order_type == ot);
|
||||
if (_child_order == null)
|
||||
_child_order = child_order_types.FirstOrDefault();
|
||||
RaisePropertyChanged(nameof(child_order));
|
||||
}
|
||||
|
||||
void _validate()
|
||||
{
|
||||
if (_child_order == null || (_child_order is INotifyDataErrorInfo ndei && ndei.HasErrors))
|
||||
set_error(nameof(child_order), string.Empty, true);
|
||||
else clear_error(nameof(child_order));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ namespace mbts.spark {
|
||||
_dispose = Disposable.Create(
|
||||
on_orders.Subscribe(_security_orders_demuxer),
|
||||
_bus.subscribe(SparkTags.CreateOrder).Subscribe(a => send_order(a.definition, a.price, a.size).ignore()),
|
||||
_bus.subscribe(SparkTags.RecreateOrder).Subscribe(a => a.ForEach( x => send_order(x.definition, x.price, x.size).ignore())),
|
||||
_bus.subscribe(SparkTags.CancelOrders).Subscribe(x => cancel_orders(x).ignore()),
|
||||
_bus.subscribe(SparkTags.CrackOrders).Subscribe(x => crack_orders(x).ignore()),
|
||||
_bus.subscribe(SparkTags.UpdateOrder).Subscribe(x => this.update_order(x.key, x.price, x.size).ignore())
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace mbts.ui.tests
|
||||
Logger.log(bts.log.Level.Info, bts.log.Subsystem.Ui, "Begin: test_windows_disposed");
|
||||
// Make sure that an IShellWindow and ISlowWindow added to the Windows collection is eventually disposed
|
||||
var thread_factory = new ShellThreadFactory();
|
||||
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), new TestUndo(), new SimpleBus());
|
||||
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), null, new SimpleBus());
|
||||
Assert.IsNotNull(Application.Current);
|
||||
Assert.IsNotNull(Application.Current.Windows);
|
||||
Assert.AreEqual(Application.Current.Windows.Count, 0);
|
||||
@@ -100,7 +100,7 @@ namespace mbts.ui.tests
|
||||
Logger.log(bts.log.Level.Info, bts.log.Subsystem.Ui, "Begin: test_slow_windows_disposed");
|
||||
// Make sure that an IShellWindow and ISlowWindow added to the Windows collection is eventually disposed
|
||||
var thread_factory = new ShellThreadFactory();
|
||||
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), new TestUndo(), new SimpleBus());
|
||||
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), null, new SimpleBus());
|
||||
Assert.AreEqual(Application.Current.Windows.Count, 0);
|
||||
|
||||
// an ISlowWindow gets its own thread
|
||||
|
||||
@@ -177,9 +177,9 @@ namespace mbts.api.Test {
|
||||
|
||||
|
||||
class ApiTestsNativeMethods {
|
||||
[DllImport("spark_lib")]
|
||||
[DllImport("libspark")]
|
||||
internal static extern IntPtr apitest_start_apitest_service(IntPtr context);
|
||||
[DllImport("spark_lib")]
|
||||
[DllImport("libspark")]
|
||||
internal static extern void apitest_stop_apitest_service(IntPtr server);
|
||||
|
||||
}
|
||||
|
||||
@@ -102,5 +102,7 @@ namespace bts.spark
|
||||
SparkCommand.create("Cancel Sells", Icons.CancelSells, SparkTags.CancelOrders);
|
||||
public static PartialCommand<uint> ShowModifyOrder =>
|
||||
SparkCommand.create("Modify", Icons.ModifyOrder, SparkTags.ShowModifyOrderTag);
|
||||
public static PartialCommand<IEnumerable<(OrderDefinitionImmutable, double, uint)>> RecreateSelectedOrders =>
|
||||
SparkCommand.create("Recreate", Icons.Beaker, SparkTags.RecreateOrder);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using bts.spark;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using static mbts.spark.SparkBusData;
|
||||
|
||||
namespace mbts.spark
|
||||
@@ -18,6 +19,8 @@ namespace mbts.spark
|
||||
|
||||
#region Order
|
||||
public static ITag<(OrderDefinitionImmutable definition, double price, uint size)> CreateOrder { get; } = new SparkTag<(OrderDefinitionImmutable, double, uint)>();
|
||||
public static ITag<IEnumerable<(OrderDefinitionImmutable definition, double price, uint size)>> RecreateOrder { get; } = new SparkTag<IEnumerable<(OrderDefinitionImmutable, double, uint)>>();
|
||||
|
||||
public static ITag<IEnumerable<uint>> CancelOrders { get; } = new SparkTag<IEnumerable<uint>>();
|
||||
public static ITag<IEnumerable<uint>> CrackOrders { get; } = new SparkTag<IEnumerable<uint>>();
|
||||
public static ITag<(uint key, double price, uint size)> UpdateOrder { get; } = new SparkTag<(uint, double, uint)>();
|
||||
@@ -34,6 +37,8 @@ namespace mbts.spark
|
||||
public static ITag<(PartialCommand<SecurityMD>, SecurityCriteria)> FilterPickOne { get; } = new SparkTag<(PartialCommand<SecurityMD>, SecurityCriteria)>("FilterPick1");
|
||||
|
||||
public static ITag<IShellWindow> ShowWindow { get; } = new SparkTag<IShellWindow>("Show Window");
|
||||
public static ITag<Task<IShellWindow>> ShowWindowAsync { get; } = new SparkTag<Task<IShellWindow>>("ShowWindowAsync");
|
||||
public static ITag<IShellWindow> ActivateWindow { get; } = new SparkTag<IShellWindow>("Activate Window");
|
||||
public static ITag<Nothing> ShowWorkspaces { get; } = new SparkTag<Nothing>(nameof(ShowWorkspaces));
|
||||
public static ITag<Nothing> ShowSettings { get; } = new SparkTag<Nothing>(nameof(ShowSettings));
|
||||
public static ITag<Uri> ShowUri { get; } = new SparkTag<Uri>("ShowUri");
|
||||
@@ -64,9 +69,11 @@ namespace mbts.spark
|
||||
public static ITag<SecurityCriteria> ShowTimeAndSalesTag { get; } = new SparkTag<SecurityCriteria>("Show TimeAndSales");
|
||||
#endregion
|
||||
|
||||
#region Persist
|
||||
public static ITag<PersistMessage> PersistCommandTag { get; } = new SparkTag<PersistMessage>("PersistCommandTag");
|
||||
public static ITag<PersistedItems> PersistedItemsTag { get; } = new SparkTag<PersistedItems>("PersistedItemsTag");
|
||||
#region Ladder
|
||||
public static ITag<Nothing> RequestRecenterLadder { get; } = new SparkTag<Nothing>(nameof(RequestRecenterLadder));
|
||||
public static ITag<DateTime> RecenterLadderBegin { get; } = new SparkTag<DateTime>(nameof(RecenterLadderBegin));
|
||||
public static ITag<DateTime> RecenterLadderNow { get; } = new SparkTag<DateTime>(nameof(RecenterLadderNow));
|
||||
|
||||
#endregion
|
||||
|
||||
public static ITag<uint> SyntheticDeleteTag { get; } = new SparkTag<uint>(nameof(SyntheticDeleteTag));
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
using bts.api.core;
|
||||
using IronPython.Runtime.Types;
|
||||
using mbts;
|
||||
@@ -49,6 +50,9 @@ namespace bts.api.diagnostic {
|
||||
readonly StringBuilder _pending = new StringBuilder();
|
||||
readonly OutputPublisher _out;
|
||||
|
||||
readonly Thread _thread;
|
||||
Dispatcher _dispatcher;
|
||||
|
||||
public ShellScriptServer(ServiceContext context, ShellDiagnosticsServiceServer parent, string qualifier) : base(context, qualifier) {
|
||||
_parent = parent;
|
||||
_out = make_output_publisher();
|
||||
@@ -59,11 +63,19 @@ namespace bts.api.diagnostic {
|
||||
_scope.SetVariable("shell_objects", new Func<object,object>(o => get_exports(o,null)));
|
||||
_scope.SetVariable("shell_named_object", new Func<object,string,object>(get_export));
|
||||
_scope.SetVariable("shell_named_objects", new Func<object,string,object>(get_exports));
|
||||
|
||||
_thread = new Thread(_ => {
|
||||
_dispatcher = Dispatcher.CurrentDispatcher;
|
||||
Thread.CurrentThread.Name = $"ShellScriptServer-{qualifier}";
|
||||
Dispatcher.Run();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public override Task<VoidMessage> dispose(VoidMessage request) {
|
||||
log("Finished", true);
|
||||
_parent.terminate(this);
|
||||
_dispatcher.BeginInvokeShutdown(DispatcherPriority.ApplicationIdle);
|
||||
return Task.FromResult(new VoidMessage());
|
||||
}
|
||||
|
||||
@@ -124,45 +136,58 @@ namespace bts.api.diagnostic {
|
||||
});
|
||||
}
|
||||
|
||||
Task<ScriptResultMessage> run_code(ScriptSource code) {
|
||||
ScriptResultMessage run_code(ScriptSource code) {
|
||||
try {
|
||||
object result = code.Execute(_scope);
|
||||
return Task.FromResult(new ScriptResultMessage { Res = ScriptResult.Complete, Message = (result?.ToString()) ?? "" });
|
||||
return new ScriptResultMessage { Res = ScriptResult.Complete, Message = (result?.ToString()) ?? "" };
|
||||
}
|
||||
catch (Exception e) {
|
||||
//log($"Exception: {e.Message}");
|
||||
return Task.FromResult(new ScriptResultMessage { Res = ScriptResult.RunError, Message = e.Message });
|
||||
return (new ScriptResultMessage { Res = ScriptResult.RunError, Message = e.Message });
|
||||
}
|
||||
}
|
||||
|
||||
public override Task<ScriptResultMessage> run(ScriptLines request) {
|
||||
|
||||
// append lines to _pending
|
||||
foreach (var line in request.Lines) {
|
||||
_pending.AppendLine(line);
|
||||
}
|
||||
// CreateScriptSource from pending
|
||||
var code = _engine.CreateScriptSourceFromString(_pending.ToString().TrimStart(' ', '\n', '\r'), SourceCodeKind.InteractiveCode);
|
||||
var parse_result = code.GetCodeProperties();
|
||||
if (parse_result == ScriptCodeParseResult.IncompleteStatement && code.GetCode().EndsWith(Environment.NewLine+Environment.NewLine)) {
|
||||
parse_result = Microsoft.Scripting.ScriptCodeParseResult.Complete;
|
||||
}
|
||||
// todo: If code is 'incomplete' but we have 2 blank lines run it!
|
||||
switch (parse_result) {
|
||||
// If 'complete' run code
|
||||
case ScriptCodeParseResult.Complete:
|
||||
_pending.Clear();
|
||||
return run_code(code);
|
||||
// if 'invalid' return error
|
||||
case ScriptCodeParseResult.Invalid:
|
||||
Console.WriteLine("Invalid");
|
||||
_pending.Clear();
|
||||
return Task.FromResult(new ScriptResultMessage { Res = ScriptResult.Invalid, Message = code.GetCode() });
|
||||
// otherwise continue buffering
|
||||
default:
|
||||
Console.WriteLine("Incomplete");
|
||||
return Task.FromResult(new ScriptResultMessage { Res = ScriptResult.Incomplete });
|
||||
var tcs = new TaskCompletionSource<ScriptResultMessage>();
|
||||
void ret(ScriptResultMessage r) {
|
||||
tcs.TrySetResult(r);
|
||||
}
|
||||
|
||||
_dispatcher.BeginInvoke(() => {
|
||||
|
||||
// append lines to _pending
|
||||
foreach (var line in request.Lines) {
|
||||
_pending.AppendLine(line);
|
||||
}
|
||||
// CreateScriptSource from pending
|
||||
var code = _engine.CreateScriptSourceFromString(_pending.ToString().TrimStart(' ', '\n', '\r'), SourceCodeKind.InteractiveCode);
|
||||
var parse_result = code.GetCodeProperties();
|
||||
if (parse_result == ScriptCodeParseResult.IncompleteStatement && code.GetCode().EndsWith(Environment.NewLine+Environment.NewLine)) {
|
||||
parse_result = Microsoft.Scripting.ScriptCodeParseResult.Complete;
|
||||
}
|
||||
// todo: If code is 'incomplete' but we have 2 blank lines run it!
|
||||
switch (parse_result) {
|
||||
// If 'complete' run code
|
||||
case ScriptCodeParseResult.Complete:
|
||||
_pending.Clear();
|
||||
ret(run_code(code));
|
||||
break;
|
||||
// if 'invalid' return error
|
||||
case ScriptCodeParseResult.Invalid:
|
||||
Console.WriteLine("Invalid");
|
||||
_pending.Clear();
|
||||
ret(new ScriptResultMessage { Res = ScriptResult.Invalid, Message = code.GetCode() });
|
||||
break;
|
||||
// otherwise continue buffering
|
||||
default:
|
||||
Console.WriteLine("Incomplete");
|
||||
ret(new ScriptResultMessage { Res = ScriptResult.Incomplete });
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,10 +48,10 @@ namespace System.Collections.Generic {
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
public static IEnumerable<T> ToIEnumerable<T>(this T item) {
|
||||
yield return item;
|
||||
}
|
||||
// use "new [] { item }" - avoid putting this on the intellisense for every item everywhere
|
||||
//public static IEnumerable<T> ToIEnumerable<T>(this T item) {
|
||||
// yield return item;
|
||||
//}
|
||||
|
||||
public static IEnumerable<T> TakeWhile<T, TAccumulate>(this IEnumerable<T> source, TAccumulate seed, Func<TAccumulate, T, TAccumulate> accumulate, Func<TAccumulate, T, bool> predicate) {
|
||||
TAccumulate a = seed;
|
||||
|
||||
@@ -17,8 +17,10 @@ namespace mbts {
|
||||
public static double tick_unit_frac(this TickFactors tick) =>
|
||||
tick.tick_unit / tick.tick_base;
|
||||
|
||||
public static double reduced_base(this TickFactors tick) => factor_out(tick.tick_base, 10);
|
||||
|
||||
public static string format(this TickFactors tick, double value) =>
|
||||
format(tick, value, double.NaN, true, _nan_text);
|
||||
format(tick, value, double.NaN, false, _nan_text);
|
||||
|
||||
public static string format(this TickFactors tick, double value, bool show_commas) =>
|
||||
format(tick, value, double.NaN, show_commas, _nan_text);
|
||||
@@ -176,9 +178,10 @@ namespace mbts {
|
||||
if (DoubleHelper.IsNaNOrInfinity(input)) return input;
|
||||
return input <= regional_tick.low_tick_inclusive_end ? regional_tick.low_tick.round(input) : regional_tick.high_tick.round(input);
|
||||
}
|
||||
public static int display_digits(this TickFactors tick) => display_digits(tick.tick_base);
|
||||
#endregion
|
||||
|
||||
public static int n_display_digits(double tick_base) {
|
||||
public static int display_digits(double tick_base) {
|
||||
double log_tick_base = Math.Log10(tick_base);
|
||||
int floor = (int)Math.Floor(log_tick_base);
|
||||
double dec = log_tick_base - floor;
|
||||
@@ -216,7 +219,7 @@ namespace mbts {
|
||||
if (numerator_part >= tick_base) numerator_part /= Math.Pow(10, Math.Ceiling(Math.Log10(numerator_part) - Math.Log10(tick_base)));
|
||||
else {
|
||||
//e.g [tick_base 640] 1989-8 == 1989-80, or 1989-32 == 1989-320
|
||||
int ndd_diff = n_display_digits(tick_base) - numerator_text.Length;
|
||||
int ndd_diff = display_digits(tick_base) - numerator_text.Length;
|
||||
if (ndd_diff > 0) numerator_part *= Math.Pow(10, ndd_diff);
|
||||
if (numerator_part >= tick_base) numerator_part /= 10;
|
||||
}
|
||||
@@ -255,16 +258,21 @@ namespace mbts {
|
||||
double numerator_dec = value - integer_part;
|
||||
double unit_frac = tick.tick_unit / tick.tick_base;
|
||||
int n_units = (int)Math.Round(numerator_dec / unit_frac);
|
||||
if (n_units == tick.tick_base) return (integer_part + 1).ToString() + tick.separator() + new string('0', n_display_digits(tick.tick_base));
|
||||
if (n_units == tick.tick_base) return (integer_part + 1).ToString() + tick.separator() + new string('0', display_digits(tick.tick_base));
|
||||
double decimal_part = n_units * tick.tick_unit;
|
||||
if (decimal_part == tick.tick_base) {
|
||||
++integer_part;
|
||||
decimal_part = 0;
|
||||
} else {
|
||||
// some ticks are not formatted with 100% precision, using less than the required display_digits
|
||||
// for such cases, we need to truncate the decimal part rather than round it.
|
||||
decimal_part = Math.Floor(decimal_part);
|
||||
}
|
||||
string integer_part_text = show_commas ? integer_part.ToString("#,0") : integer_part.ToString();
|
||||
return integer_part_text + tick.separator() + decimal_part.ToString(new string('0', n_display_digits(tick.tick_base)));
|
||||
return integer_part_text + tick.separator() + decimal_part.ToString(new string('0', display_digits(tick.tick_base)));
|
||||
}
|
||||
} catch (OverflowException ex) {
|
||||
}
|
||||
catch (OverflowException ex) {
|
||||
Logger.warn(bts.log.Subsystem.Ui, $"Arithmetic overflow exception in format_tick (tick:{tick}, value:{value}):{ex}");
|
||||
return "Overflow";
|
||||
}
|
||||
@@ -272,7 +280,7 @@ namespace mbts {
|
||||
|
||||
private static double _round_tick(double tick_unit_frac, double input) {
|
||||
return input - Math.IEEERemainder(input, tick_unit_frac);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,10 @@ namespace mbts.ui.native
|
||||
{
|
||||
public static class BTSUINativeMethods
|
||||
{
|
||||
[DllImport("spark_lib")]
|
||||
[DllImport("libspark")]
|
||||
public static extern void enable_limits();
|
||||
|
||||
[DllImport("spark_lib", CharSet = CharSet.Unicode)]
|
||||
[DllImport("libspark", CharSet = CharSet.Unicode)]
|
||||
public static extern void set_window_app_id(IntPtr hwnd, string stack_id);
|
||||
}
|
||||
}
|
||||
@@ -69,16 +69,18 @@ namespace mbts.ui.settings
|
||||
if (window == null || window.Owner == null) return null;
|
||||
MemoryStream out_stream = new MemoryStream();
|
||||
window.GetState(out_stream);
|
||||
|
||||
var pos = _interop.GetPosition(window);
|
||||
var state = new WindowState() {
|
||||
owner = window.Owner.key(),
|
||||
app_data = out_stream.ToArray(),
|
||||
position = _interop.GetPosition(window),
|
||||
title = window.Title
|
||||
position = pos,
|
||||
title = string.IsNullOrEmpty(pos.custom_title) ? window.Title : pos.custom_title
|
||||
};
|
||||
return state;
|
||||
}
|
||||
catch (Exception e) {
|
||||
Logger.log(bts.log.Level.Error, bts.log.Subsystem.Ui, "Error saving window state for type {0}.\n {1}".fmt(window.GetType(), e.Message));
|
||||
Logger.log(Level.Error, Subsystem.Ui, "Error saving window state for type {0}.\n {1}".fmt(window.GetType(), e.Message));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,9 @@ namespace mbts.ui.shell {
|
||||
[ProtoMember(102), DefaultValue(true)]
|
||||
public bool always_on_top = true;
|
||||
//[ProtoMember(103)] //used
|
||||
|
||||
[ProtoMember(104)]
|
||||
public string custom_title;
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
@@ -58,7 +61,7 @@ namespace mbts.ui.shell {
|
||||
public WindowPosition position;
|
||||
|
||||
[ProtoMember(9)]
|
||||
public string title;
|
||||
public string title; // for debugging
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
|
||||
@@ -23,6 +23,9 @@ namespace mbts.ui
|
||||
Task<IShellWindow> create(Stream load_stream);
|
||||
}
|
||||
|
||||
// ILazyModule may also impement IShellMenu to get opening/closing notification
|
||||
public interface ILazyMenuModule : IShellModule { }
|
||||
|
||||
public interface IPersistShellModule : IShellModule {
|
||||
// Called at startup before the UI is shown
|
||||
// This is an opportunity for you to kick off background services
|
||||
|
||||
@@ -17,5 +17,5 @@ namespace mbts.ui
|
||||
WindowPosition GetPosition(IShellWindow window);
|
||||
bool IsVisible(IShellWindow window);
|
||||
void PinWindow(IShellWindow window);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace mbts.utils {
|
||||
@@ -25,6 +26,9 @@ namespace mbts.utils {
|
||||
public static IEqualityComparer<T?> CreateNullable<T>(Func<T, T, bool> equals) where T : struct =>
|
||||
new AnonymousEqualityComparer<T?>((a, b) => a.HasValue ? b.HasValue && equals(a.Value, b.Value) : !b.HasValue);
|
||||
|
||||
public static IEqualityComparer to_non_generic<T>(this IEqualityComparer<T> comparer) =>
|
||||
new AnonymousEqualityComparer<T>(comparer.Equals, comparer.GetHashCode);
|
||||
|
||||
public static bool NullCheckEquals<T>(T x, T y, Func<T, T, bool> non_null_equals) where T : class, IEquatable<T> =>
|
||||
x == null ? y == null :
|
||||
y != null &&
|
||||
@@ -55,7 +59,7 @@ namespace mbts.utils {
|
||||
static void _accumulate_hash(ref int aggregate_hash, ref int item_hash) =>
|
||||
aggregate_hash = aggregate_hash * _hash_multiplier + item_hash;
|
||||
|
||||
private class AnonymousEqualityComparer<T> : IEqualityComparer<T> {
|
||||
private class AnonymousEqualityComparer<T> : IEqualityComparer<T>, IEqualityComparer {
|
||||
readonly Func<T, T, bool> _equals;
|
||||
readonly Func<T, int> _get_hash_code;
|
||||
|
||||
@@ -67,6 +71,9 @@ namespace mbts.utils {
|
||||
|
||||
public bool Equals(T x, T y) => _equals(x, y);
|
||||
public int GetHashCode(T obj) => _get_hash_code(obj);
|
||||
|
||||
bool IEqualityComparer.Equals(object x, object y) => x is T xt && y is T yt && _equals(xt, yt);
|
||||
int IEqualityComparer.GetHashCode(object obj) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace mbts.utils {
|
||||
public static class CompareUtils {
|
||||
@@ -19,5 +21,22 @@ namespace mbts.utils {
|
||||
b.HasValue ? compare(a.Value, b.Value) : 1 :
|
||||
b.HasValue ? -1 : 0;
|
||||
#endregion
|
||||
|
||||
#region Comparer
|
||||
public static IComparer to_non_generic<T>(this IComparer<T> comparer) =>
|
||||
new NonGenericComparerWrapper<T>(comparer);
|
||||
|
||||
public static IComparer<TOutter> wrap<TInner, TOutter>(this IComparer<TInner> inner_comparer, Func<TOutter, TInner> selector) =>
|
||||
Comparer<TOutter>.Create((a, b) => inner_comparer.Compare(a == null ? default : selector(a), b == null ? default : selector(b)));
|
||||
|
||||
class NonGenericComparerWrapper<T> : IComparer {
|
||||
readonly IComparer<T> _comparer;
|
||||
|
||||
public NonGenericComparerWrapper(IComparer<T> comparer) => _comparer = comparer;
|
||||
|
||||
public int Compare(object x, object y) =>
|
||||
_comparer.Compare(ObjectHelper.try_convert<T>(x), ObjectHelper.try_convert<T>(y));
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace mbts.spark {
|
||||
interface IValueFactoryVM<T> {
|
||||
T create();
|
||||
void load_from(T value);
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,8 @@ namespace mbts.ui.controls {
|
||||
Time = 100,
|
||||
TimeInForce = 47,
|
||||
TradeKey = 76,
|
||||
User = 53;
|
||||
User = 53,
|
||||
Aggressor = 80;
|
||||
}
|
||||
|
||||
public static class DefaultColumnHeaders {
|
||||
@@ -40,7 +41,9 @@ namespace mbts.ui.controls {
|
||||
Size = "Qty",
|
||||
TimeInForce = "TIF",
|
||||
TradeKey = "Trade Key",
|
||||
User = "User";
|
||||
User = "User",
|
||||
Time = "Time",
|
||||
Aggressor = "Aggressor";
|
||||
}
|
||||
|
||||
internal interface IKeyedColumn<TColKey> {
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui.utility.text;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
public partial class DIYGrid {
|
||||
internal class TimeAndSalesColumn : RenderTextColumn<TimeAndSalesVM.Row> {
|
||||
|
||||
readonly Func<TimeAndSalesVM.Row, string> _get_text;
|
||||
readonly Func<TimeAndSalesVM.Row, Brush> _get_brush;
|
||||
|
||||
public TimeAndSalesColumn(Func<TimeAndSalesVM.Row, string> get_text, Func<TimeAndSalesVM.Row, Brush> get_brush, IDataGridColumnFilter filter = null, string filter_template = null, GlyphContext glyph_context = null) : base(get_text, filter, filter_template, glyph_context) {
|
||||
_get_text = get_text;
|
||||
_get_brush = get_brush;
|
||||
}
|
||||
|
||||
public override void draw(TimeAndSalesVM.Row row, DrawingContext dc, ColumnGeometry position, double row_height) {
|
||||
CellRender.draw_text(dc
|
||||
, glyph_context
|
||||
, font_size
|
||||
, _get_text(row)
|
||||
, position
|
||||
, row_height
|
||||
, foreground
|
||||
, _get_brush(row)
|
||||
, horizontal_alignment
|
||||
, vertical_alignment
|
||||
, padding);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,17 +190,21 @@ namespace mbts.ui.controls
|
||||
}
|
||||
}
|
||||
|
||||
void item_Unselected(object sender, RoutedEventArgs e) {
|
||||
if (_selection_changing) return;
|
||||
var item = ((ListBoxItem)sender).DataContext;
|
||||
var selected_items = Selection;
|
||||
if (selected_items.Contains(item)) {
|
||||
using (_selection_change()) {
|
||||
selected_items.Remove(item);
|
||||
_update_is_all_selected();
|
||||
}
|
||||
}
|
||||
}
|
||||
void item_Unselected(object sender, RoutedEventArgs e) {
|
||||
if (!_selection_changing && sender is ListBoxItem container) {
|
||||
// the [ListBoxItem] sender may be "Disconnected" https://stackoverflow.com/questions/14282894/wpf-listbox-virtualization-creates-disconnecteditems/14358688
|
||||
var item = ItemContainerGenerator.ItemFromContainer(container);
|
||||
if (item != DependencyProperty.UnsetValue) {
|
||||
var selected_items = Selection;
|
||||
if (selected_items.Contains(item)) {
|
||||
using (_selection_change()) {
|
||||
selected_items.Remove(item);
|
||||
_update_is_all_selected();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void item_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) {
|
||||
using (_selection_change()) ((ListBoxItem)sender).IsSelected = Selection.Contains(e.NewValue);
|
||||
@@ -368,4 +372,4 @@ namespace mbts.ui.controls
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -335,7 +335,45 @@
|
||||
/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:OrderModificationTemplateSelector.TopOrderTemplate>
|
||||
</usp:OrderModificationTemplateSelector.TopOrderTemplate>
|
||||
<usp:OrderModificationTemplateSelector.TriggerOrderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Row="1" VerticalAlignment="Center">Trigger Price Type</TextBlock>
|
||||
<ComboBox
|
||||
Grid.Row="1" Grid.Column="2"
|
||||
ItemsSource="{Binding trigger_price_types}"
|
||||
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
|
||||
SelectedItem="{Binding trigger_price_type}"
|
||||
/>
|
||||
<TextBlock Grid.Row="3" VerticalAlignment="Center">Order Price</TextBlock>
|
||||
<usp:PriceEntryControl Grid.Row="3" Grid.Column="2" Value="{Binding order_price}" ContractSpec="{Binding spec}" />
|
||||
<TextBlock Grid.Row="5">Child</TextBlock>
|
||||
<uag:OrderDetailsPopupControl
|
||||
Grid.Row="5" Grid.Column="2"
|
||||
IsOpen="{Binding child_order.editable_properties_open, FallbackValue=False}"
|
||||
ItemsSource="{Binding child_order_types}"
|
||||
SelectedItem="{Binding child_order}"
|
||||
HasEditableProperties="{Binding child_order.has_editable_properties, FallbackValue=False}"
|
||||
/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:OrderModificationTemplateSelector.TriggerOrderTemplate>
|
||||
<usp:OrderModificationTemplateSelector.IcebergOrderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
@@ -375,7 +413,6 @@
|
||||
DataContext="{Binding strategy_editor}"
|
||||
SyntheticName="{Binding name}"
|
||||
TickFactors="{Binding tick_factors}"
|
||||
AvailableTickFactors="{Binding available_tick_factors_view}"
|
||||
IsNew="False"
|
||||
IsNewLegPopupOpen="{Binding is_new_leg_popup_open}"
|
||||
PriceCalc="{Binding price_calc}"
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace mbts.ui.spark {
|
||||
|
||||
#region ContractSpec
|
||||
public static readonly DependencyProperty ContractSpecProperty = DependencyProperty.Register("ContractSpec", typeof(ContractSpecificationImmutable), typeof(PriceEntryControl),
|
||||
new PropertyMetadata(SecurityDataHelper.Defaults.ContractSpecificationImmutable, OnContractSpecChanged));
|
||||
new PropertyMetadata(SecurityDataHelper.Defaults.ContractSpecificationImmutablePennies, OnContractSpecChanged));
|
||||
static void OnContractSpecChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) => ((PriceEntryControl)d)._update_text_binding();
|
||||
public ContractSpecificationImmutable? ContractSpec { get => GetValue(ContractSpecProperty) as ContractSpecificationImmutable?; set => SetValue(ContractSpecProperty, value); }
|
||||
#endregion
|
||||
|
||||
@@ -10,9 +10,11 @@ namespace mbts.ui.controls {
|
||||
static SparkMenu() =>
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(SparkMenu), new FrameworkPropertyMetadata(typeof(SparkMenu)));
|
||||
|
||||
public SparkMenu() =>
|
||||
public SparkMenu() {
|
||||
SetCurrentValue(FocusableProperty, false);
|
||||
}
|
||||
|
||||
|
||||
public SparkMenu(IEnumerable<SparkCommand> commands, IBus bus) : this() =>
|
||||
ItemsSource = commands.Select(c => new SparkCommandVM(c, bus)).ToList();
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<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:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:txt="clr-namespace:mbts.ui.utility.text"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Resources/ComboBoxes.xaml"/>
|
||||
<ResourceDictionary Source="/view/Resources/Converters.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<Style TargetType="c:TickFactorsPicker">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="c:TickFactorsPicker">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="4"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ComboBox
|
||||
MinWidth="48"
|
||||
IsEditable="True"
|
||||
DisplayMemberPath="value"
|
||||
SelectedValuePath="value"
|
||||
ItemsSource="{Binding TickUnitPresetsView, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
SelectedValue="{Binding Controller.tick_unit, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
Text="{Binding Controller.tick_unit, Converter={StaticResource DoubleTextConverter}, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
>
|
||||
<ComboBox.GroupStyle>
|
||||
<GroupStyle ContainerStyle="{StaticResource ComboBox.DelineatedGroupContainerStyle}"/>
|
||||
</ComboBox.GroupStyle>
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{x:Static c:TickFactorsPicker.TickUnitVisibleTextConverter}">
|
||||
<Binding Path="Controller.tick_base" RelativeSource="{RelativeSource AncestorType=c:TickFactorsPicker}"/>
|
||||
<Binding Path="value"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Column="1" Foreground="{u:BTSColor UI.input_disabled_text}" Text="{Binding value, Converter={x:Static c:TickFactorsPicker.TickUnitHiddenTextConverter}}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<ComboBox
|
||||
Grid.Column="2"
|
||||
MinWidth="60"
|
||||
IsEditable="True"
|
||||
DisplayMemberPath="value"
|
||||
SelectedValuePath="value"
|
||||
ItemsSource="{Binding TickBasePresetsView, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
SelectedValue="{Binding Controller.tick_base, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
Text="{Binding Controller.tick_base, Converter={StaticResource DoubleTextConverter}, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
>
|
||||
<ComboBox.GroupStyle>
|
||||
<GroupStyle ContainerStyle="{StaticResource ComboBox.DelineatedGroupContainerStyle}"/>
|
||||
</ComboBox.GroupStyle>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,157 @@
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using mbts.user.settings;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
/// <summary>
|
||||
/// Interaction logic for TickPicker.xaml
|
||||
/// </summary>
|
||||
class TickFactorsPicker : Control {
|
||||
readonly ObservableCollection<double> _tick_unit_presets = new ObservableCollection<double>();
|
||||
|
||||
static TickFactorsPicker() =>
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(TickFactorsPicker), new FrameworkPropertyMetadata(typeof(TickFactorsPicker)));
|
||||
|
||||
public TickFactorsPicker() {
|
||||
Controller = new TickFactorsController(this);
|
||||
TickBasePresetsView.GroupDescriptions.Add(new PropertyGroupDescription(nameof(TickBaseVM.reduced_base_group)));
|
||||
((ListCollectionView)TickBasePresetsView).CustomSort = TickUtils.CompareUtils.base_comparer.wrap<double, TickBaseVM>(vm => vm.value).to_non_generic();
|
||||
TickUnitPresetsView = CollectionViewSource.GetDefaultView(new ObservableAutoWrapper<TickUnitVM, double>(_tick_unit_presets, v => new TickUnitVM(v)));
|
||||
TickUnitPresetsView.GroupDescriptions.Add(new PropertyGroupDescription(nameof(TickUnitVM.reduced_unit_group)));
|
||||
((ListCollectionView)TickUnitPresetsView).CustomSort = Comparer<double>.Default.wrap<double, TickUnitVM>(vm => vm.value).to_non_generic();
|
||||
}
|
||||
|
||||
public static IEnumerable<double> TickBasePresets =>
|
||||
new [] {
|
||||
8, 16, 32, 64, 100
|
||||
}.SelectMany(i => Enumerable.Range(0, 4).Select(log10 => i * Math.Pow(10, log10)));
|
||||
|
||||
public static IMultiValueConverter TickUnitVisibleTextConverter =>
|
||||
ValueConverter.create((double tick_base, double tick_unit) => {
|
||||
int display_digits = TickExtensions.display_digits(tick_base);
|
||||
return ((int)(tick_unit)).ToString(new string('0', display_digits));
|
||||
});
|
||||
|
||||
public static IValueConverter TickUnitHiddenTextConverter =>
|
||||
ValueConverter.create((double tick_unit) => new string(tick_unit.ToString().SkipWhile(c => c != '.').ToArray()));
|
||||
|
||||
public static readonly DependencyProperty SelectedTickFactorsProperty =
|
||||
WPFHelper.create_dp<TickFactors, TickFactorsPicker>(
|
||||
nameof(SelectedTickFactors),
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||
(o, v) => o.Controller.load_from(v),
|
||||
new TickFactors());
|
||||
|
||||
public TickFactors SelectedTickFactors {
|
||||
get => GetValue(SelectedTickFactorsProperty) as TickFactors;
|
||||
set => SetValue(SelectedTickFactorsProperty, value);
|
||||
}
|
||||
|
||||
public TickFactorsController Controller { get; }
|
||||
|
||||
public ICollectionView TickBasePresetsView { get; } = CollectionViewSource.GetDefaultView(TickBasePresets.Select(v => new TickBaseVM(v)).ToList());
|
||||
public ICollectionView TickUnitPresetsView { get; }
|
||||
|
||||
static IEnumerable<double> _get_tick_unit_presets(double tick_base) {
|
||||
double reduced_base = MathUtils.factor_out(tick_base, 10, out int base_log10);
|
||||
return reduced_base == 1 ?
|
||||
_get_dbased_tick_unit_presets(base_log10) :
|
||||
_get_2based_tick_unit_presets((int)Math.Log(reduced_base, 2), base_log10);
|
||||
}
|
||||
|
||||
static IEnumerable<double> _get_2based_tick_unit_presets(int log2, int log10) =>
|
||||
(log10 == 0 ? new[] { 1.0 } : _get_dbased_tick_unit_presets(log10)).Concat(
|
||||
Enumerable.Range(1, log2 - 1).SelectMany(pow2 => {
|
||||
double root = Math.Pow(2, pow2);
|
||||
return Enumerable.Range(0, log10 + 1).Select(pow10 => root * Math.Pow(10, pow10));
|
||||
}));
|
||||
|
||||
static IEnumerable<double> _get_dbased_tick_unit_presets(int log10) =>
|
||||
Enumerable.Range(0, log10).SelectMany(_coins);
|
||||
|
||||
static IEnumerable<double> _coins(int pow10) {
|
||||
double d = Math.Pow(10, pow10);
|
||||
yield return d;
|
||||
yield return 1.25 * d;
|
||||
yield return 2.5 * d;
|
||||
yield return 5.0 * d;
|
||||
}
|
||||
|
||||
public class TickFactorsController : ViewModelBase, IValueFactoryVM<TickFactors> {
|
||||
readonly TickFactorsPicker _view;
|
||||
readonly Property<double> _tick_base, _tick_unit;
|
||||
|
||||
public TickFactorsController(TickFactorsPicker view) {
|
||||
var init = (_view = view).SelectedTickFactors;
|
||||
_tick_base = backing(nameof(tick_base), init.tick_base);
|
||||
_tick_unit = backing(nameof(tick_unit), init.tick_unit);
|
||||
}
|
||||
|
||||
public double tick_base {
|
||||
get => _tick_base.Value;
|
||||
set {
|
||||
_tick_base.set(value);
|
||||
WPFHelper.set_current_if_changed(_view, SelectedTickFactorsProperty, new TickFactors(value, tick_unit));
|
||||
_view._tick_unit_presets.sync_with(_get_tick_unit_presets(value));
|
||||
}
|
||||
}
|
||||
public double tick_unit {
|
||||
get => _tick_unit.Value;
|
||||
set {
|
||||
_tick_unit.set(value);
|
||||
WPFHelper.set_current_if_changed(_view, SelectedTickFactorsProperty, new TickFactors(tick_base, value));
|
||||
}
|
||||
}
|
||||
|
||||
public TickFactors create() => new TickFactors(tick_base, tick_unit);
|
||||
public void load_from(TickFactors value) {
|
||||
if (value != null) {
|
||||
_tick_base.set(value.tick_base);
|
||||
_tick_unit.set(value.tick_unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TickBaseVM : IEquatable<TickBaseVM> {
|
||||
public TickBaseVM(double value) =>
|
||||
this.value = value;
|
||||
|
||||
public double value { get; }
|
||||
public double reduced_base_group => MathUtils.factor_out(value, 10);
|
||||
|
||||
public bool Equals(TickBaseVM other) => other != null && value == other.value;
|
||||
public override bool Equals(object obj) => Equals(obj as TickBaseVM);
|
||||
public override int GetHashCode() => value.GetHashCode();
|
||||
}
|
||||
|
||||
public class TickUnitVM : IEquatable<TickUnitVM> {
|
||||
public TickUnitVM(double value) =>
|
||||
this.value = value;
|
||||
|
||||
public double value { get; }
|
||||
public double reduced_unit_group {
|
||||
get {
|
||||
if (!MathUtils.is_int(value))
|
||||
return 1;
|
||||
else return MathUtils.factor_out(value, 10, 5);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(TickUnitVM other) => other != null && value == other.value;
|
||||
public override bool Equals(object obj) => Equals(obj as TickUnitVM);
|
||||
public override int GetHashCode() => value.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,15 @@
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.converters
|
||||
{
|
||||
namespace mbts.ui.converters {
|
||||
static class ValueConverter {
|
||||
public static IValueConverter create<TIn, TOut>(Func<TIn, TOut> convert) =>
|
||||
new AnonymousValueConverter<TIn, TOut>(convert);
|
||||
|
||||
public static IMultiValueConverter create<TIn1, TIn2, TOut>(Func<TIn1, TIn2, TOut> convert) =>
|
||||
new AnonymousMultiValueConverter<TIn1, TIn2, TOut>(convert);
|
||||
}
|
||||
|
||||
public class AnonymousValueConverter : IValueConverter {
|
||||
public AnonymousValueConverter(Func<object, object> convert) : this(convert, null) { }
|
||||
public AnonymousValueConverter(Func<object, object> convert, Func<object, object> convert_back) {
|
||||
|
||||
@@ -13,7 +13,6 @@ namespace mbts.ui.converters
|
||||
Minimum = Double.NaN;
|
||||
Maximum = Double.NaN;
|
||||
Multiplier = Double.NaN;
|
||||
Format = FORMAT_WITH_COMMAS;
|
||||
HideNaN = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,20 @@ namespace mbts.ui.converters {
|
||||
static NumericSignBrushConverter() =>
|
||||
_error_brush.Freeze();
|
||||
|
||||
public Color PositiveColor { get => _positive_brush.Color; set => (_positive_brush = new SolidColorBrush(value)).Freeze(); }
|
||||
public Color NeutralColor { get => _neutral_brush.Color; set => (_neutral_brush = new SolidColorBrush(value)).Freeze(); }
|
||||
public Color NegativeColor { get => _negative_brush.Color; set => (_negative_brush = new SolidColorBrush(value)).Freeze(); }
|
||||
public Color PositiveColor { get => _positive_brush.Color; set => (_positive_brush = new SolidColorBrush(value) { Opacity = _opacity }).Freeze(); }
|
||||
public Color NeutralColor { get => _neutral_brush.Color; set => (_neutral_brush = new SolidColorBrush(value) { Opacity = _opacity }).Freeze(); }
|
||||
public Color NegativeColor { get => _negative_brush.Color; set => (_negative_brush = new SolidColorBrush(value) { Opacity = _opacity }).Freeze(); }
|
||||
|
||||
private double _opacity = 1.0;
|
||||
public Double Opacity {
|
||||
get { return _opacity; }
|
||||
set {
|
||||
_opacity = value;
|
||||
(_positive_brush = new SolidColorBrush(_positive_brush.Color) { Opacity = _opacity }).Freeze();
|
||||
(_negative_brush = new SolidColorBrush(_negative_brush.Color) { Opacity = _opacity }).Freeze();
|
||||
(_neutral_brush = new SolidColorBrush(_neutral_brush.Color) { Opacity = _opacity }).Freeze();
|
||||
}
|
||||
}
|
||||
public IComparable Zero { get; set; } = 0.0;
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
|
||||
@@ -26,23 +26,16 @@ namespace mbts.ui.spark.converters {
|
||||
}
|
||||
|
||||
class TickDisplayTwoWayConverter : IValueConverter {
|
||||
public ContractSpecificationImmutable? ContractSpec { get; set; } =
|
||||
new ContractSpecification {
|
||||
CashPerPoint = 1.0,
|
||||
LowTick = 0.01,
|
||||
HighTick = 0.01,
|
||||
PriceBreak = double.MaxValue,
|
||||
PriceMultiplier = 1.0
|
||||
};
|
||||
public ContractSpecificationImmutable? ContractSpec { get; set; } = SecurityDataHelper.Defaults.ContractSpecificationImmutablePennies;
|
||||
|
||||
public double DisplayMultiplier { get; set; } = double.NaN;
|
||||
public bool ShowCommas { get; set; } = false;
|
||||
public string NaNText { get; set; } = string.Empty;
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
ContractSpec.Value.format((double)value, DisplayMultiplier, ShowCommas, NaNText);
|
||||
ContractSpec.format((double)value, DisplayMultiplier, ShowCommas, NaNText);
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
ContractSpec.Value.parse(value as string, DisplayMultiplier, NaNText);
|
||||
ContractSpec.parse(value as string, DisplayMultiplier, NaNText);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ namespace mbts.ui.converters {
|
||||
class TickFactorsTextConverter : IValueConverter {
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
if (value is TickFactors tf) {
|
||||
int n_digits = TickExtensions.n_display_digits(tf.tick_base);
|
||||
int n_digits = tf.display_digits();
|
||||
if (n_digits > 1) {
|
||||
string n_digits_format = new string('0', n_digits);
|
||||
string format = "{0:" + n_digits_format + "}/{1}";
|
||||
|
||||
@@ -61,16 +61,16 @@ namespace mbts.ui
|
||||
_source_window = window as FloatingWindow;
|
||||
if (_source != null) {
|
||||
_mouse_start = e.GetPosition(window);
|
||||
if (!is_button_descendant((DependencyObject)e.OriginalSource)) {
|
||||
if (!is_clickable_descendant((DependencyObject)e.OriginalSource)) {
|
||||
Mouse.Capture(_source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_button_descendant(DependencyObject target) {
|
||||
static bool is_clickable_descendant(DependencyObject target) {
|
||||
while (target != null && !(target is TabDocument)) {
|
||||
target = VisualTreeHelper.GetParent(target);
|
||||
if (target is Button) return true;
|
||||
if (target is Button || target is TextBox) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -24,38 +24,7 @@ namespace mbts.ui
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task FromEvent(this object target, string event_name) {
|
||||
var evt = target.GetType().GetEvent(event_name);
|
||||
var delegate_type = evt.EventHandlerType;
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
Action result = () => { tcs.TrySetResult(null); };
|
||||
var d = Delegate.CreateDelegate(evt.EventHandlerType, result, "Invoke");
|
||||
try {
|
||||
evt.AddEventHandler(target, d);
|
||||
await tcs.Task;
|
||||
}
|
||||
finally {
|
||||
evt.RemoveEventHandler(target, d);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<T> FromEvent<T>(this object target, string event_name) {
|
||||
var evt = target.GetType().GetEvent(event_name);
|
||||
var delegate_type = evt.EventHandlerType;
|
||||
var tcs = new TaskCompletionSource<T>();
|
||||
Action<T> result = (arg) => { tcs.TrySetResult(arg); };
|
||||
var d = Delegate.CreateDelegate(evt.EventHandlerType, result, "Invoke");
|
||||
try {
|
||||
evt.AddEventHandler(target, d);
|
||||
await tcs.Task;
|
||||
return tcs.Task.Result;
|
||||
}
|
||||
finally {
|
||||
evt.RemoveEventHandler(target, d);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<Tuple<T1,T2>> FromEvent<T1,T2>(this object target, string event_name) {
|
||||
public static async Task<Tuple<T1,T2>> FromEvent<T1,T2>(this DependencyObject target, string event_name) {
|
||||
var evt = target.GetType().GetEvent(event_name);
|
||||
var delegate_type = evt.EventHandlerType;
|
||||
var tcs = new TaskCompletionSource<Tuple<T1,T2>>();
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace mbts.ui {
|
||||
|
||||
#region TraverseOnEnter
|
||||
|
||||
public static readonly DependencyProperty TraverseOnEnterProperty = DependencyProperty.RegisterAttached("TraverseOnEnter", typeof(bool), typeof(TextBoxBehavior), new PropertyMetadata(false, OnTraverseOnEnterChanged));
|
||||
public static readonly DependencyProperty TraverseOnEnterProperty = DependencyProperty.RegisterAttached("TraverseOnEnter", typeof(bool), typeof(TextBoxBehavior), new PropertyMetadata(false, OnTraverseOnEnterChanged));
|
||||
private static void OnTraverseOnEnterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
var text_box = (TextBox)d;
|
||||
if ((bool)e.NewValue) text_box.PreviewKeyDown += commit_on_enter_PreviewKeyDownHandler;
|
||||
@@ -67,6 +67,65 @@ namespace mbts.ui {
|
||||
public static void SetCommitOnEnter(DependencyObject obj, bool value) => obj.SetValue(CommitOnEnterProperty, value);
|
||||
#endregion
|
||||
|
||||
#region CancelOnEnter
|
||||
|
||||
public static readonly DependencyProperty CancelOnEscapeProperty =
|
||||
DependencyProperty.RegisterAttached("CancelOnEscape", typeof(bool), typeof(TextBoxBehavior), new PropertyMetadata(false, cancel_on_escape_changed));
|
||||
|
||||
private static void cancel_on_escape_changed(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
var tb = (TextBox)d;
|
||||
if ((bool)e.NewValue) {
|
||||
tb.KeyDown += tbx_keydown;
|
||||
}
|
||||
else {
|
||||
tb.KeyDown += tbx_keydown;
|
||||
}
|
||||
}
|
||||
|
||||
static void tbx_keydown(object sender, KeyEventArgs e) {
|
||||
if (e.Key == Key.Escape) {
|
||||
abort_edit(sender as DependencyObject);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the binding value back and forth to generate a set to the source property with the original value
|
||||
private static void abort_edit(DependencyObject sender) {
|
||||
if (sender != null) {
|
||||
var binding = BindingOperations.GetBindingExpression(sender, TextBox.TextProperty);
|
||||
binding?.UpdateTarget();
|
||||
binding?.UpdateSource();
|
||||
}
|
||||
}
|
||||
|
||||
public static bool GetCancelOnEscape(DependencyObject obj) => (bool)obj.GetValue(CancelOnEscapeProperty);
|
||||
public static void SetCancelOnEscape(DependencyObject obj, bool value) => obj.SetValue(CancelOnEscapeProperty, value);
|
||||
|
||||
#endregion
|
||||
|
||||
#region TryCancelOnFocusLost
|
||||
public static readonly DependencyProperty TryCancelOnFocusLostProperty =
|
||||
DependencyProperty.RegisterAttached("TryCancelOnFocusLost", typeof(bool), typeof(TextBoxBehavior), new PropertyMetadata(false, cancel_on_focus_lost_changed));
|
||||
|
||||
private static void cancel_on_focus_lost_changed(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
var tb = (TextBox)d;
|
||||
if ((bool)e.NewValue) {
|
||||
tb.LostFocus += tb_lostfocus;
|
||||
}
|
||||
else {
|
||||
tb.LostFocus += tb_lostfocus;
|
||||
}
|
||||
}
|
||||
|
||||
static void tb_lostfocus(object sender, EventArgs e) {
|
||||
abort_edit(sender as DependencyObject);
|
||||
}
|
||||
|
||||
public static bool GetTryCancelOnFocusLost(DependencyObject obj) => (bool)obj.GetValue(TryCancelOnFocusLostProperty);
|
||||
public static void SetTryCancelOnFocusLost(DependencyObject obj, bool value) => obj.SetValue(TryCancelOnFocusLostProperty, value);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region ReleaseOnEscape
|
||||
public static readonly DependencyProperty ReleaseOnEscapeProperty = DependencyProperty.RegisterAttached(
|
||||
"ReleaseOnEscape", typeof(bool), typeof(TextBoxBehavior), new PropertyMetadata(false, OnReleaseOnEscapeChanged));
|
||||
@@ -79,7 +138,8 @@ namespace mbts.ui {
|
||||
tb.ReleaseMouseCapture();
|
||||
Keyboard.ClearFocus();
|
||||
}));
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
tb.ClearKeyBindings(Key.Escape, ModifierKeys.None);
|
||||
tb.ClearCommandBindings(ReleaseCaptureCommand);
|
||||
}
|
||||
@@ -180,17 +240,18 @@ namespace mbts.ui {
|
||||
"IncrementDecrementOnMouseClick", typeof(bool), typeof(TextBoxBehavior), new PropertyMetadata(false, IncrementDecrementOnMouseClickPropertyChanged)
|
||||
);
|
||||
|
||||
public static bool GetIncrementDecrementOnMouseClick(TextBox element) { return (bool)element.GetValue(IncrementDecrementOnMouseClickProperty); }
|
||||
public static bool GetIncrementDecrementOnMouseClick(TextBox element) { return (bool)element.GetValue(IncrementDecrementOnMouseClickProperty); }
|
||||
public static void SetIncrementDecrementOnMouseClick(TextBox element, bool value) { element.SetValue(IncrementDecrementOnMouseClickProperty, value); }
|
||||
|
||||
|
||||
private static void IncrementDecrementOnMouseClickPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
var text_box = d as TextBox;
|
||||
if(text_box != null) {
|
||||
if (text_box != null) {
|
||||
if ((bool)e.NewValue) {
|
||||
text_box.PreviewMouseLeftButtonDown += OnMouseLeftButtonDownDecrementValue;
|
||||
text_box.PreviewMouseRightButtonDown += OnMouseRightButtonDownIncrementValue;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
text_box.PreviewMouseLeftButtonDown -= OnMouseLeftButtonDownDecrementValue;
|
||||
text_box.PreviewMouseRightButtonDown -= OnMouseRightButtonDownIncrementValue;
|
||||
}
|
||||
@@ -201,7 +262,8 @@ namespace mbts.ui {
|
||||
var text_box = sender as TextBox;
|
||||
if (text_box == null || String.IsNullOrWhiteSpace(text_box.Text)) {
|
||||
return;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (uint.TryParse(text_box.Text, out uint val)) {
|
||||
if (val < uint.MaxValue) {
|
||||
val++;
|
||||
@@ -226,5 +288,6 @@ namespace mbts.ui {
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -14,18 +14,19 @@ namespace mbts.ui.shell
|
||||
public partial class FloatingWindow : Window
|
||||
{
|
||||
bool _closed = false;
|
||||
private IUndoActions _undo;
|
||||
readonly IBookmarks _bookmark;
|
||||
private IWindowManager _wm;
|
||||
private ISavedWindowFactory _window_factory;
|
||||
private ISettingsCollector _window_settings;
|
||||
private Stack<Action> _recently_closed_windows = new Stack<Action>();
|
||||
|
||||
// if is_loaded is true, will NOT set width/height
|
||||
internal FloatingWindow(TabDocument document, IWindowManager wm, IUndoActions undo, ISettingsCollector settings, ISavedWindowFactory factory, bool is_loaded=false) {
|
||||
internal FloatingWindow(TabDocument document,
|
||||
IWindowManager wm, IBookmarks bookmarks, ISettingsCollector settings, ISavedWindowFactory factory, bool is_loaded=false) {
|
||||
_wm = wm;
|
||||
_undo = undo;
|
||||
_window_settings = settings;
|
||||
_window_factory = factory;
|
||||
_bookmark = bookmarks;
|
||||
|
||||
InitializeComponent();
|
||||
if (string.IsNullOrEmpty(document.ViewModel.host_id)) {
|
||||
@@ -67,6 +68,7 @@ namespace mbts.ui.shell
|
||||
|
||||
|
||||
private ItemCollection Items { get { return _tabs.Items; } }
|
||||
public IEnumerable<TabDocument> Documents => _tabs.Items.OfType<TabDocument>();
|
||||
public bool IsEmpty { get { return _tabs.Items.Count == 0; } }
|
||||
public bool SingleChild { get { return _tabs.Items.Count == 1; } }
|
||||
public string host_id { get; private set; }
|
||||
@@ -88,10 +90,12 @@ namespace mbts.ui.shell
|
||||
if (sender != this) {
|
||||
var undoaction = create_undo_action(document);
|
||||
if (undoaction != null) {
|
||||
IDisposable undoitem = _undo.push("Restore Tab '{0}'".fmt(document.Title), undoaction);
|
||||
_recently_closed_windows.Push(() => { undoaction(); undoitem.Dispose(); });
|
||||
_recently_closed_windows.Push(() => { undoaction(); });
|
||||
}
|
||||
}
|
||||
if (document.HasCustomTitle) {
|
||||
_bookmark.save_bookmark(document.Model);
|
||||
}
|
||||
}
|
||||
|
||||
public void remove_document(TabDocument document) {
|
||||
@@ -144,22 +148,9 @@ namespace mbts.ui.shell
|
||||
|
||||
protected override void OnClosed(EventArgs e) {
|
||||
_closed = true;
|
||||
TabDocument[] items = _tabs.Items.OfType<TabDocument>().ToArray();
|
||||
if (items.Length > 0) {
|
||||
// If we're closing and still have Tabs open we save the whole "window" worth of state in the undo stack
|
||||
// If the application is shutting down this might be unnecessary work but it's not too costly
|
||||
var undo_actions = items.Select(t => create_undo_action(t.ViewModel)).Where(item => item != null).ToArray();
|
||||
if (undo_actions.Length > 0) {
|
||||
string name = "Restore Window: '{0}'".fmt(string.Join("', '", items.Select(t => t.ViewModel.Title)));
|
||||
_undo.push(name, () => {
|
||||
foreach (var u in undo_actions) { u(); }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
base.OnClosed(e);
|
||||
|
||||
foreach (TabDocument document in items) {
|
||||
foreach (TabDocument document in _tabs.Items.OfType<TabDocument>().ToList()) {
|
||||
if (document != null && document.ViewModel.CloseCommand.CanExecute(this)) {
|
||||
document.ViewModel.CloseCommand.Execute(this);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
mc:Ignorable="d" Style="{StaticResource {x:Type TabItem}}" d:DesignWidth="125"
|
||||
xmlns:ui="clr-namespace:mbts.ui"
|
||||
xmlns:convert="clr-namespace:mbts.ui.converters"
|
||||
mc:Ignorable="d" d:DesignWidth="125"
|
||||
Header="{Binding}" Content="{Binding AppContent}"
|
||||
Background="#e6e6e6">
|
||||
|
||||
@@ -18,6 +20,19 @@
|
||||
<Storyboard x:Key="_slide_back">
|
||||
<DoubleAnimation Storyboard.TargetName="translate_transform" Storyboard.TargetProperty="X" To="100" Duration="0:0:0.25" FillBehavior="Stop" />
|
||||
</Storyboard>
|
||||
<DataTemplate x:Key="_edit_template">
|
||||
<TextBox Text="{Binding TitleOverride}"
|
||||
ui:TextBoxBehavior.CommitOnEnter="True"
|
||||
ui:TextBoxBehavior.FocusOnLoaded="True"
|
||||
ui:TextBoxBehavior.SelectAllOnFocus="True"
|
||||
ui:TextBoxBehavior.CancelOnEscape="True"
|
||||
ui:TextBoxBehavior.TryCancelOnFocusLost="True"
|
||||
Width="150" HorizontalAlignment="Left" VerticalAlignment="Stretch" Grid.Column="0" Margin="0,0,4,0" />
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="_template">
|
||||
<TextBlock Text="{Binding Title}" TextTrimming="CharacterEllipsis" HorizontalAlignment="Left" VerticalAlignment="Stretch" Grid.Column="0" Margin="0,0,4,0"></TextBlock>
|
||||
</DataTemplate>
|
||||
<convert:BooleanToObjectConverter x:Key="MenuHeaderConverter" ValueForTrue="Rename Window" ValueForFalse="Add to My Windows" />
|
||||
</ResourceDictionary>
|
||||
</TabItem.Resources>
|
||||
<TabItem.LayoutTransform>
|
||||
@@ -28,25 +43,32 @@
|
||||
<TabItem.RenderTransform>
|
||||
<TranslateTransform x:Name="translate_transform" X="0" />
|
||||
</TabItem.RenderTransform>
|
||||
<TabItem.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Title}" TextTrimming="CharacterEllipsis" HorizontalAlignment="Left" VerticalAlignment="Stretch" Grid.Column="0" Margin="0,0,4,0"> </TextBlock>
|
||||
</DataTemplate>
|
||||
</TabItem.HeaderTemplate>
|
||||
|
||||
<TabItem.Template>
|
||||
<ControlTemplate TargetType="{x:Type TabItem}">
|
||||
<Border x:Name="bd" BorderBrush="Black" BorderThickness="0,0,0.5,0" Background="#d6d6d6" Width="{DynamicResource TabWidth}" Height="{DynamicResource TabHeight}" MinWidth="40" Margin="0" SnapsToDevicePixels="True" DockPanel.Dock="Left">
|
||||
<Border.ToolTip>
|
||||
<TextBlock Text="{Binding Title}" />
|
||||
</Border.ToolTip>
|
||||
<Border.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="{Binding HasCustomTitle, Converter={StaticResource MenuHeaderConverter}}" Command="{Binding rename_command}">
|
||||
<MenuItem.Icon>
|
||||
<Image Source="{StaticResource Bookmarks}" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Remove from My Windows" Command="{Binding unrename_command}" IsEnabled="{Binding HasCustomTitle}" />
|
||||
</ContextMenu>
|
||||
</Border.ContextMenu>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image SnapsToDevicePixels="True" MaxHeight="20" VerticalAlignment="Center" HorizontalAlignment="Left" Stretch="Uniform" Margin="5,0,5,0" Source="{Binding Icon, Mode=OneTime}" RenderOptions.BitmapScalingMode="HighQuality" />
|
||||
<Image SnapsToDevicePixels="True" MaxHeight="20" VerticalAlignment="Center" HorizontalAlignment="Left" Stretch="Uniform" Margin="5,0,5,0"
|
||||
Source="{Binding Icon, Mode=OneTime}" RenderOptions.BitmapScalingMode="HighQuality" />
|
||||
<!--<Image MaxHeight="10" HorizontalAlignment="Left" VerticalAlignment="Bottom" Stretch="Uniform" Margin="5"
|
||||
Source="{StaticResource Bookmarks}" Visibility="{Binding HasCustomTitle, Converter={StaticResource BoolToVisibility}}" />-->
|
||||
<ContentPresenter Grid.Column="1" x:Name="content" ContentSource="Header" TextBlock.Foreground="#666"
|
||||
RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||
HorizontalAlignment="Left"
|
||||
@@ -62,12 +84,18 @@
|
||||
<Setter Property="Panel.ZIndex" Value="1" />
|
||||
<Setter Property="Background" TargetName="bd" Value="#F9F9F9" />
|
||||
<Setter Property="TextBlock.Foreground" TargetName="content" Value="black" />
|
||||
<!--<Setter Property="BorderThickness" TargetName="bd" Value="0,4,0.5,0" />-->
|
||||
<!--<Setter Property="Margin" Value="-2,-2,-2,-1" />
|
||||
<Setter Property="Margin" TargetName="content" Value="0,0,0,1" />
|
||||
<Setter Property="Margin" TargetName="bd" Value="0,0,0,0" />-->
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</TabItem.Template>
|
||||
<TabItem.Style>
|
||||
<Style TargetType="TabItem" BasedOn="{StaticResource {x:Type TabItem}}">
|
||||
<Setter Property="HeaderTemplate" Value="{StaticResource _template}" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding EditMode}" Value="True">
|
||||
<Setter Property="HeaderTemplate" Value="{StaticResource _edit_template}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TabItem.Style>
|
||||
</TabItem>
|
||||
@@ -18,8 +18,15 @@ namespace mbts.ui.shell {
|
||||
public ImageSource IconSource { get { return _module.icon; } }
|
||||
public UIElement MenuContent => new SparkMenu(_module.actions, _bus);
|
||||
|
||||
public void opening() { }
|
||||
public void opening() {
|
||||
(_module as IShellMenu)?.opening();
|
||||
if (_module is ILazyMenuModule) {
|
||||
RaisePropertyChanged(nameof(MenuContent));
|
||||
}
|
||||
}
|
||||
|
||||
public void closing() { }
|
||||
public void closing() {
|
||||
(_module as IShellMenu)?.closing();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,10 +24,11 @@ namespace mbts.ui.shell
|
||||
Dispatcher _dispatcher;
|
||||
HwndContentHost _hwnd_host;
|
||||
|
||||
|
||||
public ShellWindowVM(IShellWindow model, IWindowManager wm, IShellThreadFactory threads, string host = "") {
|
||||
Logger.assert(model != null);
|
||||
_model = model;
|
||||
_model.TitleChanged += () => RaisePropertyChanged("Title");
|
||||
_model.TitleChanged += () => { if (!HasCustomTitle) RaisePropertyChanged("Title"); };
|
||||
_model.IconChanged += () => { _icon = null; RaisePropertyChanged("Icon"); };
|
||||
_wm = wm;
|
||||
host_id = host;
|
||||
@@ -40,13 +41,55 @@ namespace mbts.ui.shell
|
||||
if (HasTabAffinity) {
|
||||
this.host_id = ((ITabAffineWindow)model).tab_group;
|
||||
}
|
||||
|
||||
_edit_mode = backing(nameof(EditMode), false);
|
||||
}
|
||||
|
||||
public string host_id { get; set; }
|
||||
public Dispatcher Dispatcher { get { return _dispatcher; } }
|
||||
public string Title { get { return _model.Title; } }
|
||||
|
||||
public string Title { get { return HasCustomTitle? TitleOverride : _model.Title; } }
|
||||
|
||||
string _title_override;
|
||||
public string TitleOverride {
|
||||
get => _title_override;
|
||||
set {
|
||||
if (_title_override != value) {
|
||||
if (value == _model.Title) {
|
||||
_title_override = null;
|
||||
}
|
||||
else {
|
||||
_title_override = value;
|
||||
}
|
||||
RaisePropertyChanged(nameof(TitleOverride), nameof(Title), nameof(HasCustomTitle));
|
||||
}
|
||||
_edit_mode.set(false);
|
||||
}
|
||||
}
|
||||
public bool HasCustomTitle => !string.IsNullOrWhiteSpace(TitleOverride);
|
||||
|
||||
Property<bool> _edit_mode;
|
||||
public bool EditMode => _edit_mode.Value;
|
||||
|
||||
|
||||
public ICommand rename_command => new DelegateCommand(begin_rename);
|
||||
public ICommand unrename_command => new DelegateCommand(remove_custom);
|
||||
|
||||
private void begin_rename() {
|
||||
if (string.IsNullOrEmpty(TitleOverride)) {
|
||||
_title_override = Title;
|
||||
}
|
||||
_edit_mode.set(true);
|
||||
}
|
||||
|
||||
private void remove_custom() {
|
||||
TitleOverride = string.Empty;
|
||||
//begin_close(this);
|
||||
}
|
||||
|
||||
public string OwnerName { get { return _model.Owner == null ? "" : _model.Owner.name; } }
|
||||
public ICommand CloseCommand { get { return new DelegateCommand(begin_close); } }
|
||||
|
||||
public double InitialWidth { get; private set; }
|
||||
public double InitialHeight { get; private set; }
|
||||
|
||||
@@ -155,18 +198,14 @@ namespace mbts.ui.shell
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] GetSaveState() {
|
||||
MemoryStream stream = new MemoryStream();
|
||||
_model.GetState(stream);
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
public IShellWindow Model { get { return _model; } }
|
||||
|
||||
public bool IsSlow { get { return _model is ISlowWindow; } }
|
||||
public bool IsShared { get { return _model is ISharedWindow; } }
|
||||
public bool HasTabAffinity { get { return _model is ITabAffineWindow; } }
|
||||
|
||||
|
||||
|
||||
public void Dispose() {
|
||||
if (IsShared) {
|
||||
// Shared threads continue running
|
||||
|
||||
@@ -20,25 +20,31 @@ namespace mbts.ui.shell {
|
||||
|
||||
[Export(typeof(IWindowManager))]
|
||||
[Export(typeof(IWindowsInterop))]
|
||||
[Export]
|
||||
public class WindowManager : IWindowManager, IWindowsInterop, IDisposable {
|
||||
Dictionary<IShellWindow, TabDocument> _documents = new Dictionary<IShellWindow, TabDocument>();
|
||||
Dictionary<string, FloatingWindow> _windows = new Dictionary<string, FloatingWindow>();
|
||||
IShellThreadFactory _threads;
|
||||
readonly IShellThreadFactory _threads;
|
||||
Lazy<ISettingsCollector> _window_settings;
|
||||
ISavedWindowFactory _window_factory;
|
||||
IUndoActions _undo;
|
||||
readonly ISavedWindowFactory _window_factory;
|
||||
readonly Lazy<IBookmarks> _bookmarks;
|
||||
IDisposable _dispose;
|
||||
object _lock = new object();
|
||||
|
||||
[ImportingConstructor]
|
||||
public WindowManager(
|
||||
IShellThreadFactory threads, Lazy<ISettingsCollector> settings,
|
||||
ISavedWindowFactory factory, IUndoActions undo, IBus bus) {
|
||||
ISavedWindowFactory factory, Lazy<IBookmarks> bookmarks, IBus bus) {
|
||||
_threads = threads;
|
||||
_window_settings = settings;
|
||||
_window_factory = factory;
|
||||
_undo = undo;
|
||||
_dispose = bus.subscribe(SparkTags.ShowWindow).OnDispatcher().Subscribe(w => add(w).ignore());
|
||||
_bookmarks = bookmarks;
|
||||
_dispose =
|
||||
Disposable.Create(
|
||||
bus.subscribe(SparkTags.ShowWindow).OnDispatcher().Subscribe(w => add(w).ignore()),
|
||||
bus.subscribe(SparkTags.ShowWindowAsync).OnDispatcher().Subscribe(w => add(w).ignore()),
|
||||
bus.subscribe(SparkTags.ActivateWindow).OnDispatcher().Subscribe(MakeVisible)
|
||||
);
|
||||
}
|
||||
|
||||
#region Window creation
|
||||
@@ -48,6 +54,7 @@ namespace mbts.ui.shell {
|
||||
var state = model.start_position();
|
||||
if (state != null) {
|
||||
viewmodel.host_id = state.host_id ?? "";
|
||||
viewmodel.TitleOverride = state.custom_title;
|
||||
}
|
||||
var window = CreateWindow(content, state);
|
||||
|
||||
@@ -66,10 +73,10 @@ namespace mbts.ui.shell {
|
||||
}
|
||||
} else {
|
||||
if (position != null) {
|
||||
window = new FloatingWindow(document, this, _undo, _window_settings.Value, _window_factory, true);
|
||||
window = new FloatingWindow(document, this, _bookmarks.Value, _window_settings.Value, _window_factory, true);
|
||||
window.set_position(position);
|
||||
} else
|
||||
window = new FloatingWindow(document, this, _undo, _window_settings.Value, _window_factory);
|
||||
window = new FloatingWindow(document, this, _bookmarks.Value, _window_settings.Value, _window_factory);
|
||||
_windows.Add(window.host_id, window);
|
||||
window.Closed += (o, e) => _windows.Remove(window.host_id);
|
||||
}
|
||||
@@ -81,12 +88,19 @@ namespace mbts.ui.shell {
|
||||
public IReadOnlyList<IShellWindow> list_windows() =>
|
||||
_documents.Keys.ToList();
|
||||
|
||||
public IReadOnlyList<TabDocument> list_documents() => _documents.Values.ToList();
|
||||
|
||||
public async Task add(IShellWindow window) {
|
||||
if (!Application.Current.Dispatcher.CheckAccess())
|
||||
await Application.Current.Dispatcher.InvokeAsync(() => add(window));
|
||||
Logger.info(bts.log.Subsystem.Ui, $"Opening window {window.diagnostic_name()}.");
|
||||
lock (_lock)
|
||||
lock (_lock) {
|
||||
_documents[window] = _create_document(window);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task add(Task<IShellWindow> window) {
|
||||
await add(await window);
|
||||
}
|
||||
|
||||
public void remove(IShellWindow window) {
|
||||
@@ -139,14 +153,13 @@ namespace mbts.ui.shell {
|
||||
|
||||
#region IWindowsInterop
|
||||
public WindowPosition GetPosition(IShellWindow shell_window) {
|
||||
return GetPosition(shell_window, null);
|
||||
}
|
||||
public WindowPosition GetPosition(IShellWindow shell_window, FloatingWindow owning_window) {
|
||||
FloatingWindow window = owning_window == null ? window_for(shell_window) : owning_window;
|
||||
FloatingWindow window = window_for(shell_window);
|
||||
var doc = window.Documents.FirstOrDefault(td => td.ViewModel.Model == shell_window);
|
||||
if (window != null) {
|
||||
WindowPosition pos = window.get_position();
|
||||
pos.tab_index = window.document_index(shell_window);
|
||||
pos.host_id = window.host_id;
|
||||
pos.custom_title = doc?.ViewModel?.TitleOverride;
|
||||
return pos;
|
||||
}
|
||||
Logger.log(bts.log.Level.Warn, bts.log.Subsystem.Ui, "IShellWindow '{0}' does not have a valid position. It may not be displayed".fmt(shell_window.Title));
|
||||
|
||||
@@ -19,6 +19,7 @@ using mbts.persist;
|
||||
|
||||
namespace mbts.ui.shell
|
||||
{
|
||||
|
||||
[Export]
|
||||
public class WorkspacesModel : ViewModelBaseWithValidation
|
||||
{
|
||||
@@ -35,10 +36,11 @@ namespace mbts.ui.shell
|
||||
private bool _closing = false;
|
||||
private ILauncher _launcher;
|
||||
private IUndoActions _undo;
|
||||
readonly IBookmarks _bookmarks;
|
||||
|
||||
[ImportingConstructor]
|
||||
public WorkspacesModel(ISettingsCollector settings, ISavedWindowFactory window_restore,
|
||||
IWindowManager windows, ObservableCollection<IShellModule> modules, IPersist persist,
|
||||
IWindowManager windows, ObservableCollection<IShellModule> modules, IPersist persist, IBookmarks bm,
|
||||
IUserPersist user_persist, ILauncher launcher, IUndoActions undo, [Import("workspace")]string initial_workspace) {
|
||||
_settings_collector = settings;
|
||||
_window_restore = window_restore;
|
||||
@@ -48,6 +50,7 @@ namespace mbts.ui.shell
|
||||
_modules = modules;
|
||||
_launcher = launcher;
|
||||
_undo = undo;
|
||||
_bookmarks = bm;
|
||||
_launcher.closing += _launcher_closing;
|
||||
Workspaces = new ObservableCollection<WorkspaceViewModel>();
|
||||
init(initial_workspace).ignore();
|
||||
@@ -150,6 +153,7 @@ namespace mbts.ui.shell
|
||||
workspace.windows = _settings_collector.get_window_state().ToList();
|
||||
workspace.modules = _settings_collector.get_module_state().ToList();
|
||||
workspace.launcher_state = _settings_collector.get_launcher_state();
|
||||
workspace.named_windows = _bookmarks.bookmarks.ToList();
|
||||
return workspace;
|
||||
}
|
||||
|
||||
@@ -166,6 +170,7 @@ namespace mbts.ui.shell
|
||||
await Task.Yield();
|
||||
}
|
||||
workspace.launcher_state = _settings_collector.get_launcher_state();
|
||||
workspace.named_windows = _bookmarks.bookmarks.ToList();
|
||||
return workspace;
|
||||
}
|
||||
|
||||
@@ -226,10 +231,15 @@ namespace mbts.ui.shell
|
||||
}
|
||||
}
|
||||
|
||||
// Notify workspace changed
|
||||
if (WorkspaceLoaded != null) {
|
||||
WorkspaceLoaded(name);
|
||||
_bookmarks.clear();
|
||||
if (def.named_windows != null) {
|
||||
foreach (var window in def.named_windows) {
|
||||
_bookmarks.save_bookmark(window);
|
||||
}
|
||||
}
|
||||
|
||||
// Notify workspace changed
|
||||
WorkspaceLoaded?.Invoke(name);
|
||||
}
|
||||
|
||||
public Task delete_workspace(string name, WorkspaceDefinition def) {
|
||||
@@ -244,18 +254,20 @@ namespace mbts.ui.shell
|
||||
}
|
||||
return (def.is_shared ? _public : _private).clear(WORKSPACE_NAMESPACE, name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// The serialized data for the Workspace
|
||||
[ProtoContract(Name = "WorkspaceDefinition")]
|
||||
public class WorkspaceDefinition
|
||||
{
|
||||
// Converted the protobuf type ShellSettings to c#
|
||||
// Avoid re-using id: 1,3,4,5,6,7
|
||||
|
||||
[ProtoMember(2, IsRequired = false)]
|
||||
public List<WindowState> windows { get; set; }
|
||||
|
||||
// named windows which are not currently open
|
||||
[ProtoMember(3)]
|
||||
public List<WindowState> named_windows { get; set; }
|
||||
|
||||
[ProtoMember(9, IsRequired = false)]
|
||||
public List<ModuleState> modules { get; set; }
|
||||
|
||||
|
||||
@@ -376,7 +376,7 @@ namespace mbts.spark
|
||||
return x is SecurityImmutable && y is SecurityImmutable ? Compare((SecurityImmutable)x, (SecurityImmutable)y) : -1;
|
||||
}
|
||||
public int Compare(SecurityImmutable x, SecurityImmutable y) {
|
||||
return x.EarliestExpiration().CompareTo(y.EarliestExpiration());
|
||||
return SecurityHelper.Comparer.Compare(x, y);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using bts.api;
|
||||
using System.IO;
|
||||
using mbts.ui.shell;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
public class DataPersistor
|
||||
{
|
||||
private List<IDisposable> _disp_list = new List<IDisposable>();
|
||||
private readonly IBus _bus;
|
||||
private readonly string _loc = @"C:\ProgramData\bts\temp_securities.bin"; /// TODO
|
||||
|
||||
private SparkBusData.PersistedItems _persisted_items = new SparkBusData.PersistedItems();
|
||||
|
||||
public DataPersistor(IBus bus)
|
||||
{
|
||||
_bus = bus;
|
||||
var sub = _bus.subscribe<SparkBusData.PersistMessage>(SparkTags.PersistCommandTag);
|
||||
_disp_list.Add(sub.Subscribe(p => _on_new_persist_message(p)));
|
||||
_persisted_items = _read_data_from_file();
|
||||
_bus.post(SparkTags.PersistedItemsTag, _persisted_items);
|
||||
}
|
||||
|
||||
private void _on_new_persist_message(SparkBusData.PersistMessage p)
|
||||
{
|
||||
_persisted_items.items.Add(p);
|
||||
_write_to_file(_persisted_items);
|
||||
_bus.post(SparkTags.PersistedItemsTag, new SparkBusData.PersistedItems(_persisted_items.items));
|
||||
}
|
||||
|
||||
private void _write_to_file(SparkBusData.PersistedItems items)
|
||||
{
|
||||
byte[] data = SparkUtils.serialize_proto(items);
|
||||
try
|
||||
{
|
||||
File.WriteAllBytes(_loc, data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
mbts.Logger.log(bts.log.Level.Error, bts.log.Subsystem.gen, ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private SparkBusData.PersistedItems _read_data_from_file()
|
||||
{
|
||||
var rv = new SparkBusData.PersistedItems();
|
||||
try
|
||||
{
|
||||
if (File.Exists(_loc)) {
|
||||
byte[] data = File.ReadAllBytes(_loc);
|
||||
rv = SparkUtils.deserialize_proto<SparkBusData.PersistedItems>(data);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,18 +121,6 @@ namespace mbts.spark {
|
||||
public static string order_type_display_name(CustomOrderDefinition.CustomOneofCase order_type) =>
|
||||
camel_to_title_case(Regex.Replace(order_type.ToString(), "Order", string.Empty, RegexOptions.IgnoreCase));
|
||||
|
||||
public static string order_collection_summary(ICollection<OrderRecordImmutable> orders, Comparison<double> compare_price, ContractSpecificationImmutable spec) {
|
||||
if (orders.Count == 0) return string.Empty;
|
||||
var price_lookup = orders.ToLookup(o => o.State.Price);
|
||||
var best_price = price_lookup.MaxElement(g => g.Key, compare_price);
|
||||
var best_price_volume = best_price.Sum(o => o.working_quantity());
|
||||
return order_collection_summary(
|
||||
best_price.Key,
|
||||
best_price_volume,
|
||||
best_price_volume + price_lookup.Where(g => g.Key != best_price.Key).SelectMany(g => g).Sum(o => o.working_quantity()),
|
||||
spec);
|
||||
}
|
||||
|
||||
public static string order_collection_summary(double best_price, int best_price_volume, int total_volume, ContractSpecificationImmutable spec) {
|
||||
if (total_volume == 0)
|
||||
return string.Empty;
|
||||
|
||||
@@ -6,105 +6,40 @@ using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
static class TickUtils {
|
||||
#region TickFactors Cache
|
||||
static readonly TickFactors _default_tick = new TickFactors(100, 1);
|
||||
internal static ContractSpecificationImmutable spec_or_default(this ContractSpecificationImmutable? spec) =>
|
||||
spec.HasValue ? spec.Value : SecurityDataHelper.Defaults.ContractSpecificationImmutable;
|
||||
|
||||
static readonly ConcurrentDictionary<double, bool> _resolved_unknown_ticks = new ConcurrentDictionary<double, bool>();
|
||||
internal static ContractSpecificationImmutable spec_or_default(this SecurityImmutable security) =>
|
||||
security.HasContractSpec ? security.ContractSpec : SecurityDataHelper.Defaults.ContractSpecificationImmutable;
|
||||
|
||||
static readonly ConcurrentDictionary<(double low_tick, double high_tick, double price_break), RegionalTickFactors> _regional_ticks_cache =
|
||||
new ConcurrentDictionary<(double low_tick, double high_tick, double price_break), RegionalTickFactors>();
|
||||
|
||||
static readonly double[] _descending_tick_fractional_units;
|
||||
|
||||
static readonly IReadOnlyDictionary<double, TickFactors> _ticks_cache = new[] {
|
||||
// 100 based
|
||||
new { tick_unit = 0.01, tick_factors = new TickFactors(100, 1) }, // 1/100
|
||||
new { tick_unit = 0.05, tick_factors = new TickFactors(100, 5) }, // 1/20
|
||||
new { tick_unit = 0.10, tick_factors = new TickFactors(100, 10) }, // 1/10
|
||||
new { tick_unit = 0.25, tick_factors = new TickFactors(100, 25) }, // 1/4
|
||||
new { tick_unit = 0.50, tick_factors = new TickFactors(100, 50) }, // 1/2
|
||||
// 1,000 based
|
||||
new { tick_unit = 0.001, tick_factors = new TickFactors(1000, 1) }, // 1/1,000
|
||||
new { tick_unit = 0.005, tick_factors = new TickFactors(1000, 5) }, // 1/200
|
||||
new { tick_unit = 0.025, tick_factors = new TickFactors(1000, 25) }, // 1/40
|
||||
// 10,000 based
|
||||
new { tick_unit = 0.0001, tick_factors = new TickFactors(10000, 1) }, // 1/10,000
|
||||
new { tick_unit = 0.0005, tick_factors = new TickFactors(10000, 5) }, // 1/2,000
|
||||
new { tick_unit = 0.0025, tick_factors = new TickFactors(10000, 25) }, // 1/400
|
||||
// 64 based
|
||||
new { tick_unit = 0.015625, tick_factors = new TickFactors(64, 1) }, // 1/64
|
||||
new { tick_unit = 0.03125, tick_factors = new TickFactors(64, 2) }, // 1/32
|
||||
new { tick_unit = 0.0625, tick_factors = new TickFactors(64, 4) }, // 1/16
|
||||
new { tick_unit = 0.125, tick_factors = new TickFactors(64, 8) }, // 1/8
|
||||
// 640 based
|
||||
new { tick_unit = 0.0015625, tick_factors = new TickFactors(640, 1) }, // 1/640
|
||||
new { tick_unit = 0.003125, tick_factors = new TickFactors(640, 2) }, // 1/320
|
||||
new { tick_unit = 0.00625, tick_factors = new TickFactors(640, 4) }, // 1/160
|
||||
new { tick_unit = 0.0078125, tick_factors = new TickFactors(640, 5) }, // 1/128
|
||||
new { tick_unit = 0.0125, tick_factors = new TickFactors(640, 8) }, // 1/80
|
||||
new { tick_unit = 0.0390625, tick_factors = new TickFactors(640, 25) }, // 5/128
|
||||
new { tick_unit = 0.078125, tick_factors = new TickFactors(640, 50) }, // 5/64
|
||||
// 6,400 based
|
||||
new { tick_unit = 0.00015625, tick_factors = new TickFactors(6400, 1) }, // 1/6,400
|
||||
new { tick_unit = 0.0003125, tick_factors = new TickFactors(6400, 2) }, // 1/3,200
|
||||
new { tick_unit = 0.000625, tick_factors = new TickFactors(6400, 4) }, // 1/1,600
|
||||
new { tick_unit = 0.00078125, tick_factors = new TickFactors(6400, 5) }, // 1/1,280
|
||||
new { tick_unit = 0.00125, tick_factors = new TickFactors(6400, 8) }, // 1/800
|
||||
new { tick_unit = 0.00390625, tick_factors = new TickFactors(6400, 25) }, // 1/256
|
||||
// 64,000 based
|
||||
new { tick_unit = 0.000015625, tick_factors = new TickFactors(64000, 1) }, // 1/6,400
|
||||
new { tick_unit = 0.00003125, tick_factors = new TickFactors(64000, 2) }, // 1/3,200
|
||||
new { tick_unit = 0.0000625, tick_factors = new TickFactors(64000, 4) }, // 1/1,600
|
||||
new { tick_unit = 0.000078125, tick_factors = new TickFactors(64000, 5) }, // 1/12,800
|
||||
new { tick_unit = 0.000125, tick_factors = new TickFactors(64000, 8) }, // 1/8,000
|
||||
new { tick_unit = 0.000390625, tick_factors = new TickFactors(64000, 25) }, // 1/2,560
|
||||
new { tick_unit = 0.001953125, tick_factors = new TickFactors(64000, 125) },// 1/512
|
||||
}.ToReadOnlyDictionary(obj => obj.tick_unit, obj => obj.tick_factors, EqualityComparer.Create<double>(MathUtils.epsilon_equals));
|
||||
#endregion
|
||||
|
||||
static TickUtils() {
|
||||
// overkill? Maybe, but ticks are fundamental to the entire application.
|
||||
// Any changes to the tick defaults must be deliberate, and scrutinized.
|
||||
Logger.assert(_ticks_cache.Count == 35);
|
||||
foreach(var kv in _ticks_cache) {
|
||||
double result = kv.Value.tick_unit / kv.Value.tick_base;
|
||||
Logger.assert(result == kv.Key, $"{kv.Value.tick_unit}/{kv.Value.tick_base} ({result} != {kv.Key})");
|
||||
}
|
||||
_descending_tick_fractional_units = _ticks_cache.Keys.Ordered(new ReverseComparer<double>(Comparer<double>.Default)).ToLArray();
|
||||
}
|
||||
internal static ContractSpecificationImmutable spec_or_default(this SecurityImmutable? security) =>
|
||||
security.HasValue && security.Value.HasContractSpec ? security.Value.ContractSpec : SecurityDataHelper.Defaults.ContractSpecificationImmutable;
|
||||
|
||||
public static double ticksize(this ContractSpecificationImmutable spec, double price) =>
|
||||
Math.Abs(price) > spec.PriceBreak ? spec.HighTick : spec.LowTick;
|
||||
|
||||
public static string format(this ContractSpecificationImmutable spec, double price) {
|
||||
var tick_factors = _get_tick_factors(spec.ticksize(price));
|
||||
return tick_factors.format(price, false);
|
||||
}
|
||||
public static string format(this ContractSpecificationImmutable? spec, double price) {
|
||||
if (spec.HasValue) {
|
||||
return spec.Value.format(price);
|
||||
}
|
||||
return price.ToString("0.00000");
|
||||
}
|
||||
public static string format(this ContractSpecificationImmutable spec, double price) =>
|
||||
TickStore.get(spec).format(price);
|
||||
|
||||
public static string format(this ContractSpecificationImmutable spec, double price, double display_multiplier, bool show_commas, string nan_text) {
|
||||
var tick_factors = _get_tick_factors(spec.ticksize(price));
|
||||
return tick_factors.format(price, display_multiplier, show_commas, nan_text);
|
||||
}
|
||||
public static string format(this ContractSpecificationImmutable? spec, double price) =>
|
||||
spec.spec_or_default().format(price);
|
||||
|
||||
public static double parse(this ContractSpecificationImmutable spec, string text) {
|
||||
var regional_tfs = get_regional_ticks(spec);
|
||||
return regional_tfs.parse(text);
|
||||
}
|
||||
public static string format(this ContractSpecificationImmutable spec, double price, double display_multiplier, bool show_commas, string nan_text) =>
|
||||
TickStore.get(spec).format(price, display_multiplier, show_commas, nan_text);
|
||||
|
||||
public static double parse(this ContractSpecificationImmutable spec, string text, double display_multiplier, string nan_text) {
|
||||
var regional_tfs = get_regional_ticks(spec);
|
||||
return regional_tfs.parse(text, display_multiplier, nan_text);
|
||||
}
|
||||
public static string format(this ContractSpecificationImmutable? spec, double price, double display_multiplier, bool show_commas, string nan_text) =>
|
||||
spec.spec_or_default().format(price, display_multiplier, show_commas, nan_text);
|
||||
|
||||
public static double parse(this ContractSpecificationImmutable spec, string text, double display_multiplier, string nan_text) =>
|
||||
TickStore.get(spec).parse(text, display_multiplier, nan_text);
|
||||
|
||||
public static double parse(this ContractSpecificationImmutable? spec, string text, double display_multiplier, string nan_text) =>
|
||||
spec.spec_or_default().parse(text, display_multiplier, nan_text);
|
||||
|
||||
public static double low_tick(NativePriceSize[] bids, NativePriceSize[] asks) {
|
||||
double low = bids[bids.Length - 1].price;
|
||||
@@ -132,6 +67,9 @@ namespace mbts.spark {
|
||||
return n_low + n_high;
|
||||
}
|
||||
|
||||
public static int normalized_tick_diff(SecurityImmutable security, double x, double y) =>
|
||||
normalized_tick_diff(security.spec_or_default(), x, y);
|
||||
|
||||
public static double mid_tick(ContractSpecificationImmutable spec, double bid, double ask) {
|
||||
double raw_mid;
|
||||
if (double.IsNaN(bid)) {
|
||||
@@ -146,36 +84,35 @@ namespace mbts.spark {
|
||||
return raw_mid - (raw_mid % tick_size);
|
||||
}
|
||||
|
||||
public static IReadOnlyList<TickFactors> all_present_ticks() =>
|
||||
_ticks_cache.Values.ToReadOnlyList();
|
||||
|
||||
public static TickFactors deduce_ticks(IEnumerable<ContractSpecification> contract_specs) {
|
||||
var gcd = MathUtils.GCD(contract_specs.Select(s => s.LowTick));
|
||||
if (gcd == 0) return null;
|
||||
return _try_get_tick_factors(gcd);
|
||||
var ticks = new HashSet<TickFactors>(contract_specs.Select(TickStore.get).SelectMany(rtf => new[] { rtf.low_tick, rtf.high_tick }));
|
||||
switch (ticks.Count) {
|
||||
case 0: return TickStore.default_rtf.low_tick;
|
||||
case 1: return ticks.First();
|
||||
default:
|
||||
double tick_base_lcm = MathUtils.LCM(ticks.Select(t => (int)t.tick_base));
|
||||
double tick_unit_gcd = MathUtils.GCD(ticks.Select(t => t.tick_unit * (tick_base_lcm / t.tick_base)));
|
||||
return new TickFactors(tick_base_lcm, tick_unit_gcd);
|
||||
}
|
||||
}
|
||||
|
||||
public static double tickup(this ContractSpecificationImmutable contract, double value) =>
|
||||
get_regional_ticks(contract).tickup(value);
|
||||
public static double tickup(this ContractSpecificationImmutable spec, double value) =>
|
||||
TickStore.get(spec).tickup(value);
|
||||
|
||||
public static double tickup(this ContractSpecificationImmutable contract, double value, int count) =>
|
||||
get_regional_ticks(contract).tickup(value, (uint)count);
|
||||
public static double tickup(this ContractSpecificationImmutable spec, double value, int count) =>
|
||||
TickStore.get(spec).tickup(value, (uint)count);
|
||||
|
||||
public static double tickdown(this ContractSpecificationImmutable contract, double value) =>
|
||||
get_regional_ticks(contract).tickdown(value);
|
||||
public static double tickdown(this ContractSpecificationImmutable spec, double value) =>
|
||||
TickStore.get(spec).tickdown(value);
|
||||
|
||||
public static double tickdown(this ContractSpecificationImmutable contract, double value, int count) =>
|
||||
get_regional_ticks(contract).tickdown(value, (uint)count);
|
||||
public static double tickdown(this ContractSpecificationImmutable spec, double value, int count) =>
|
||||
TickStore.get(spec).tickdown(value, (uint)count);
|
||||
|
||||
public static bool tick_equals(this ContractSpecificationImmutable contract, double x, double y) =>
|
||||
MathUtils.RoundFractionalUnits(x, contract.LowTick) == MathUtils.RoundFractionalUnits(y, contract.LowTick);
|
||||
|
||||
public static IEqualityComparer<double> tick_equality_comparer(this ContractSpecificationImmutable contract)
|
||||
{
|
||||
var eq = EqualityComparer.Create((double a, double b) => TickUtils.tick_equals(contract, a, b),
|
||||
x => MathUtils.RoundFractionalUnits(x, contract.LowTick));
|
||||
return eq;
|
||||
}
|
||||
public static IEqualityComparer<double> tick_equality_comparer(this ContractSpecificationImmutable contract) =>
|
||||
EqualityComparer.Create((double a, double b) => tick_equals(contract, a, b), x => MathUtils.RoundFractionalUnits(x, contract.LowTick));
|
||||
|
||||
public static double round_to_tick(this ContractSpecificationImmutable spec, double value) {
|
||||
double intpart = (long)value;
|
||||
@@ -186,46 +123,196 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public static RegionalTickFactors get_regional_ticks(ContractSpecificationImmutable spec) =>
|
||||
_regional_ticks_cache.GetOrAdd((spec.LowTick, spec.HighTick, spec.PriceBreak), _create_regional_ticks);
|
||||
TickStore.get(spec);
|
||||
|
||||
public static RegionalTickFactors get_regional_ticks(ContractSpecification spec) =>
|
||||
_regional_ticks_cache.GetOrAdd((spec.LowTick, spec.HighTick, spec.PriceBreak), _create_regional_ticks);
|
||||
public static RegionalTickFactors get_regional_ticks(SecurityImmutable security) =>
|
||||
get_regional_ticks(security.spec_or_default());
|
||||
|
||||
static RegionalTickFactors _create_regional_ticks((double low_tick, double high_tick, double price_break) def) =>
|
||||
new RegionalTickFactors(_get_tick_factors(def.low_tick), _get_tick_factors(def.high_tick), def.price_break);
|
||||
static TickFactors _inflate_to_base(TickFactors tick_factors, double new_base) =>
|
||||
new TickFactors(new_base, tick_factors.tick_unit * new_base / tick_factors.tick_base);
|
||||
|
||||
static TickFactors _get_tick_factors(double tick) {
|
||||
if (_ticks_cache.TryGetValue(tick, out TickFactors result))
|
||||
return result;
|
||||
// try to return the largest known tick that is divisible by this tick
|
||||
foreach (var tfu in _descending_tick_fractional_units) {
|
||||
double mod = tick % tfu;
|
||||
if (MathUtils.epsilon_equals(mod, 0)) {
|
||||
if (_resolved_unknown_ticks.TryAdd(tick, true))
|
||||
Logger.warn(bts.log.Subsystem.Ui, $"Resolved unknown tick {tick} to {tfu}, if this tick is expected, talk to John about adding it to the hard-coded defaults ASAP.");
|
||||
return _ticks_cache[tfu];
|
||||
}
|
||||
public static class CompareUtils {
|
||||
public static IComparer<double> base_comparer =>
|
||||
Comparer<double>.Create(_compare_base);
|
||||
|
||||
public static IComparer<double> unit_comparer =>
|
||||
Comparer<double>.Create(_compare_unit);
|
||||
|
||||
public static IComparer<TickFactors> base_unit_comparer =>
|
||||
Comparer<TickFactors>.Create(utils.CompareUtils.safe_comparison<TickFactors>(compare_base_unit));
|
||||
|
||||
public static int compare_base_unit(TickFactors a, TickFactors b) {
|
||||
int comp = _compare_base(a.tick_base, b.tick_base);
|
||||
return comp == 0 ? a.tick_unit.CompareTo(b.tick_unit) : comp;
|
||||
}
|
||||
|
||||
static int _compare_base(double a, double b) {
|
||||
double reduced_a = MathUtils.factor_out(a, 10);
|
||||
double reduced_b = MathUtils.factor_out(b, 10);
|
||||
int comp = reduced_a.CompareTo(reduced_b);
|
||||
return comp == 0 ? a.CompareTo(b) : comp;
|
||||
}
|
||||
|
||||
static int _compare_unit(double a, double b) =>
|
||||
_compare_unit(a, b, 0, 0);
|
||||
|
||||
static int _compare_unit(double a, double b, int a_mult10, int b_mult10) {
|
||||
// fractional-coins are a special case, as they may not be an integer to begin with
|
||||
if (!MathUtils.is_int(a))
|
||||
return _compare_unit(a * 10, b, ++a_mult10, b_mult10);
|
||||
if (!MathUtils.is_int(b))
|
||||
return _compare_unit(a, b * 10, a_mult10, ++b_mult10);
|
||||
double reduced_a = a_mult10 > 0 ? a : MathUtils.factor_out(a, 10, 5);
|
||||
double reduced_b = b_mult10 > 0 ? b : MathUtils.factor_out(b, 10, 5);
|
||||
int comp = reduced_a.CompareTo(reduced_b);
|
||||
return comp == 0 ? (a * Math.Pow(10, -a_mult10)).CompareTo(b * Math.Pow(10, -b_mult10)) : comp;
|
||||
}
|
||||
Logger.assert(false, $"Unable to resolve tick ({tick}): going down because the application cannot run properly with unresolved ticks. " +
|
||||
$"FOR DEVS: If this tick was not caused by a bug, please talk to John about adding it to the hard-coded defaults. " +
|
||||
$"If you believe this tick belongs to a synthetic security, and should not be in the defaults, please delete said synthetic from the db " +
|
||||
$"(note: the synthetic creation dialog has changed to only allow known ticks from now on, so if you get the latest code and delete old synthetics from the db, this will not happen again). " +
|
||||
$"Direct any further questions to John.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Almost the same function as _get_tick_factors, only difference being no assert/log.
|
||||
// This seemed more practical than trying to factor this behavior out, it's only a few lines of code.
|
||||
static TickFactors _try_get_tick_factors(double tick) {
|
||||
if (_ticks_cache.TryGetValue(tick, out TickFactors result))
|
||||
return result;
|
||||
// try to return the largest known tick that is divisible by this tick
|
||||
foreach (var tfu in _descending_tick_fractional_units) {
|
||||
double mod = tick % tfu;
|
||||
if (MathUtils.epsilon_equals(mod, 0))
|
||||
return _ticks_cache[tfu];
|
||||
static class TickStore {
|
||||
public static readonly RegionalTickFactors default_rtf = new RegionalTickFactors(new TickFactors(1000, 1), new TickFactors(1000, 1), double.MaxValue);
|
||||
static readonly ConcurrentDictionary<SpecKey, RegionalTickFactors> _exchange_ticks = new ConcurrentDictionary<SpecKey, RegionalTickFactors>();
|
||||
|
||||
public static RegionalTickFactors get(ContractSpecification spec) =>
|
||||
_exchange_ticks.GetOrAdd(new SpecKey(spec), _create_rtf);
|
||||
|
||||
public static RegionalTickFactors get(ContractSpecificationImmutable spec) =>
|
||||
_exchange_ticks.GetOrAdd(new SpecKey(spec), _create_rtf);
|
||||
|
||||
public static RegionalTickFactors get(SecurityImmutable security) =>
|
||||
security.HasContractSpec ? get(security.ContractSpec) : default_rtf;
|
||||
|
||||
public static RegionalTickFactors get(SecurityImmutable? security) =>
|
||||
security.HasValue ? get(security.Value) : default_rtf;
|
||||
|
||||
static RegionalTickFactors _create_rtf(SpecKey key) {
|
||||
double tick_base = key.denominator;
|
||||
if (tick_base == 0) {
|
||||
// indicates tick is a decimal
|
||||
tick_base = 100;
|
||||
}
|
||||
return _create_rtf(key.low_tick, key.high_tick, key.price_break, tick_base, key.display_digits);
|
||||
}
|
||||
|
||||
static RegionalTickFactors _create_rtf(double low_tick, double high_tick, double price_break, double reduced_target_base, uint display_digits_override) {
|
||||
var low_tf = _try_create_tf(low_tick, reduced_target_base, display_digits_override);
|
||||
var high_tf = _try_create_tf(high_tick, reduced_target_base, display_digits_override);
|
||||
if (low_tf == null) {
|
||||
low_tf = high_tf ?? (high_tf = default_rtf.low_tick);
|
||||
} else if (high_tf == null)
|
||||
high_tf = low_tf;
|
||||
else {
|
||||
// if low/high tick-bases do not match, inflate the smaller base so they do
|
||||
// Note: this assumes the tick-bases are related by factors of 10 (i.e. they have all the same prime factors except for a different number of pairs of 5 and 2, e.g. 64 and 640)
|
||||
if (low_tf.tick_base > high_tf.tick_base) {
|
||||
high_tf = _inflate_to_base(high_tf, low_tf.tick_base);
|
||||
} else if (low_tf.tick_base < high_tf.tick_base) {
|
||||
low_tf = _inflate_to_base(low_tf, high_tf.tick_base);
|
||||
}
|
||||
}
|
||||
return new RegionalTickFactors(low_tf, high_tf, price_break);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates TickFactors with a base equal to "<paramref name="tick_base"/> * 10^n," where 'n' is a natural number (including 0).
|
||||
/// <para>Note: If <paramref name="display_digits_override"/> is non-zero, 'n' will be "<paramref name="display_digits_override"/> - Math.Ceiling(Log10(<paramref name="tick_base"/>))"</para>
|
||||
/// </summary>
|
||||
/// <param name="tick_size">The raw tick-size.</param>
|
||||
/// <param name="tick_base">The raw tick-base.</param>
|
||||
/// <returns>Well-defined TickFactors</returns>
|
||||
static TickFactors _try_create_tf(double tick_size, double tick_base, uint display_digits_override) {
|
||||
var tf = _try_create_tf(tick_size, tick_base);
|
||||
if (tf != null && display_digits_override > 0) {
|
||||
int display_digit_diff = (int)display_digits_override - tf.display_digits();
|
||||
if (display_digit_diff != 0)
|
||||
tf = _inflate_to_base(tf, tf.tick_base * Math.Pow(10, display_digit_diff));
|
||||
}
|
||||
return tf;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates TickFactors with a base equal to "reduced_target_base * 10^n," where 'n' is a natural number (including 0).
|
||||
/// </summary>
|
||||
/// <param name="tick_size">The raw tick-size.</param>
|
||||
/// <param name="reduced_target_base">The smallest acceptable tick-base.</param>
|
||||
/// <returns>Well-defined TickFactors</returns>
|
||||
static TickFactors _try_create_tf(double tick_size, double reduced_target_base) {
|
||||
if (!_check_tick_size(tick_size))
|
||||
return null;
|
||||
double raw_base = 1 / tick_size;
|
||||
double raw_base_log10 = Math.Log10(raw_base);
|
||||
double reduced_target_base_log10 = Math.Log10(reduced_target_base);
|
||||
double log10_diff = raw_base_log10 - reduced_target_base_log10;
|
||||
if (MathUtils.is_int(log10_diff))
|
||||
return raw_base > reduced_target_base ? new TickFactors(raw_base, 1) : new TickFactors(reduced_target_base, Math.Pow(10, -log10_diff));
|
||||
int count = 0;
|
||||
double factored_base = log10_diff > 0 ? reduced_target_base * Math.Pow(10, Math.Ceiling(log10_diff)) : reduced_target_base;
|
||||
do {
|
||||
if (factored_base % raw_base == 0)
|
||||
return new TickFactors(factored_base, factored_base / raw_base);
|
||||
factored_base *= 10;
|
||||
} while (++count < 10);
|
||||
return null;
|
||||
}
|
||||
|
||||
static TickFactors _create_tf(double tick_size) {
|
||||
if (!_check_tick_size(tick_size))
|
||||
return default_rtf.low_tick;
|
||||
double matched_unit = double.MinValue;
|
||||
TickFactors matched_tf = null;
|
||||
foreach (var kv in _exchange_ticks) {
|
||||
if (kv.Key.high_tick == tick_size) {
|
||||
return kv.Value.high_tick;
|
||||
} else if (kv.Key.low_tick == tick_size) {
|
||||
return kv.Value.low_tick;
|
||||
} else if (is_better_match(kv.Key.high_tick)) {
|
||||
matched_unit = kv.Key.high_tick;
|
||||
matched_tf = kv.Value.high_tick;
|
||||
} else if (is_better_match(kv.Key.low_tick)) {
|
||||
matched_unit = kv.Key.low_tick;
|
||||
matched_tf = kv.Value.low_tick;
|
||||
}
|
||||
}
|
||||
return matched_tf ?? new TickFactors(1.0 / tick_size, 1);
|
||||
|
||||
bool is_better_match(double known_unit) =>
|
||||
known_unit < tick_size && known_unit > matched_unit && tick_size % known_unit == 0;
|
||||
}
|
||||
|
||||
static bool _check_tick_size(double tick_size) =>
|
||||
tick_size > 0 && tick_size <= 0.5;
|
||||
|
||||
struct SpecKey : IEquatable<SpecKey> {
|
||||
public readonly uint denominator, display_digits;
|
||||
public readonly double low_tick, high_tick, price_break;
|
||||
|
||||
public SpecKey(ContractSpecification spec)
|
||||
: this(spec.MainFraction, spec.PriceDisplayFormat, spec.LowTick, spec.HighTick, spec.PriceBreak) { }
|
||||
|
||||
public SpecKey(ContractSpecificationImmutable spec)
|
||||
: this(spec.MainFraction, spec.PriceDisplayFormat, spec.LowTick, spec.HighTick, spec.PriceBreak) { }
|
||||
|
||||
public SpecKey(uint denominator, uint display_digits, double low_tick, double high_tick, double price_break) {
|
||||
this.denominator = denominator;
|
||||
this.display_digits = display_digits;
|
||||
this.low_tick = low_tick;
|
||||
this.high_tick = high_tick;
|
||||
this.price_break = price_break;
|
||||
}
|
||||
|
||||
public bool Equals(SpecKey other) =>
|
||||
denominator == other.denominator &&
|
||||
display_digits == other.display_digits &&
|
||||
low_tick == other.low_tick &&
|
||||
high_tick == other.high_tick &&
|
||||
price_break == other.price_break;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is SpecKey sk && Equals(sk);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash((int)denominator, (int)display_digits, low_tick.GetHashCode(), high_tick.GetHashCode(), price_break.GetHashCode());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.co2;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -8,15 +7,15 @@ namespace mbts.spark {
|
||||
readonly OrdersModel _order_gateway;
|
||||
|
||||
public LadderModel(SecurityMD source, OrdersModel ordergw) {
|
||||
security = source.security;
|
||||
md = source.md;
|
||||
security_md = source;
|
||||
_order_gateway = ordergw;
|
||||
}
|
||||
|
||||
public enum CancelType { All, Bids, Asks }
|
||||
|
||||
public SecurityImmutable security { get; }
|
||||
public MDConsumerHandle md { get; }
|
||||
public SecurityMD security_md { get; }
|
||||
public SecurityImmutable security => security_md.security;
|
||||
public MDConsumerHandle md => security_md.md;
|
||||
public OrdersModel order_gateway => _order_gateway;
|
||||
|
||||
public Task cancel_orders(CancelType ct) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace mbts.spark {
|
||||
|
||||
_dispose = Disposable.Create(
|
||||
settings_command,
|
||||
order_properties_vm = new OrderPropertiesEditorVM(model.order_gateway, Observable.Return(security), _settings_manager.subscribe_current().Select(s => new InlineOrderProperties(model.order_gateway, s))),
|
||||
order_properties_vm = new OrderPropertiesEditorVM(model.order_gateway, Observable.Return(model.security_md), _settings_manager.subscribe_current().Select(s => new InlineOrderProperties(model.order_gateway, s))),
|
||||
size_calculator = new CalculatorLogic(0, _settings_manager.subscribe_current().Select(s => s.calculator_buttons)),
|
||||
bus.subscribe(SparkTags.LogActivity).OnDispatcher().Subscribe(status_update),
|
||||
model.order_gateway.working_orders.OnDispatcher().Subscribe(working => {
|
||||
@@ -143,6 +143,8 @@ namespace mbts.spark {
|
||||
);
|
||||
}
|
||||
|
||||
public IBus bus => _bus;
|
||||
|
||||
public event Action on_tick;
|
||||
|
||||
public MDConsumerHandle md => _model.md;
|
||||
|
||||
@@ -483,6 +483,7 @@
|
||||
Background="{x:Null}" BorderBrush="{x:Null}"
|
||||
PieBackground="Transparent"
|
||||
SliceBackground="#a000"
|
||||
Bus="{Binding bus}"
|
||||
Command="{Binding RecenterCommand, ElementName=_ladder}">
|
||||
<spark:RecenterCountdown.SliceStroke>
|
||||
<Pen Thickness="1" Brush="Black" />
|
||||
|
||||
@@ -67,8 +67,8 @@ namespace mbts.spark {
|
||||
double up = mid, down = mid;
|
||||
// Starting from the middle iterate out to the edges
|
||||
for (int x = 1; x <= range; ++x) {
|
||||
up = TickUtils.tickup(spec, up);
|
||||
down = TickUtils.tickdown(spec, down);
|
||||
up = spec.tickup(up);
|
||||
down = spec.tickdown(down);
|
||||
result[middle - x] = up;
|
||||
result[middle + x] = down;
|
||||
}
|
||||
|
||||
@@ -1,81 +1,56 @@
|
||||
using bts.api;
|
||||
using bts.api;
|
||||
using mbts.co2;
|
||||
using mbts.ui.controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace mbts.spark {
|
||||
// Implement the recenter logic in a UI control
|
||||
// Kind of a hack but it makes receiving input from the UI easier
|
||||
public class RecenterCountdown : CountDownTimer {
|
||||
DispatcherTimer _timer = new DispatcherTimer(DispatcherPriority.Background) {
|
||||
Interval = TimeSpan.FromSeconds(1)
|
||||
};
|
||||
SecurityImmutable? _security;
|
||||
MDConsumerHandle _handle;
|
||||
DateTime _last_recenter = DateTime.MaxValue;
|
||||
double _last_mid = 0.0;
|
||||
IDisposable _dispose;
|
||||
readonly TimeSpan _seconds_to_center;
|
||||
IBus _bus;
|
||||
|
||||
public RecenterCountdown() {
|
||||
DataContextChanged += RecenterCountdown_DataContextChanged;
|
||||
Unloaded += (o,e) => on_unload(false);
|
||||
_timer.Tick += _timer_Tick;
|
||||
_timer.Start();
|
||||
_seconds_to_center = TimeSpan.FromSeconds(config.Configuration.get<bts.config.LadderKnobs>().seconds_to_recenter);
|
||||
Unloaded += (o, e) => on_unload(false);
|
||||
}
|
||||
|
||||
static (double,double) tick_range(double price, int tick_count, ContractSpecificationImmutable spec) {
|
||||
return (TickUtils.tickdown(spec, price, tick_count), TickUtils.tickup(spec, price, tick_count));
|
||||
public IBus Bus {
|
||||
get { return (IBus)GetValue(busProperty); }
|
||||
set { SetValue(busProperty, value); }
|
||||
}
|
||||
|
||||
private void _timer_Tick(object sender, EventArgs e) {
|
||||
if (_security != null) {
|
||||
var mid = _handle.mid();
|
||||
if (//mid < low || mid > high ||
|
||||
(_last_recenter + _seconds_to_center < DateTime.UtcNow)) {
|
||||
trigger_recenter(mid);
|
||||
}
|
||||
public static readonly DependencyProperty busProperty =
|
||||
DependencyProperty.Register("Bus", typeof(IBus), typeof(RecenterCountdown), new PropertyMetadata(null, bus_changed));
|
||||
|
||||
static void bus_changed(object sender, DependencyPropertyChangedEventArgs args) {
|
||||
if (sender is RecenterCountdown self) {
|
||||
self.setbus(args.NewValue as IBus);
|
||||
}
|
||||
}
|
||||
|
||||
void setbus(IBus value) {
|
||||
if (value != null) {
|
||||
Disposable.dispose(ref _dispose);
|
||||
_bus = value;
|
||||
_dispose =
|
||||
Disposable.Create(
|
||||
_bus?.subscribe(SparkTags.RecenterLadderBegin).OnDispatcher().Subscribe(begin_recenter),
|
||||
_bus?.subscribe(SparkTags.RecenterLadderNow).OnDispatcher().Subscribe(_ => base.OnClick())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void begin_recenter(DateTime _) => start(null);
|
||||
|
||||
private void on_unload(bool _) {
|
||||
Disposable.dispose(ref _dispose);
|
||||
_timer.Stop();
|
||||
}
|
||||
|
||||
private void RecenterCountdown_DataContextChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e) {
|
||||
Disposable.dispose(ref _dispose);
|
||||
var vm = e.NewValue as LadderVM;
|
||||
if (vm != null) {
|
||||
_security = vm.security;
|
||||
_handle = vm.md;
|
||||
reset_counters();
|
||||
vm.OnDisposed += on_unload;
|
||||
_dispose =
|
||||
Disposable.Create(
|
||||
Disposable.Create(() => vm.OnDisposed -= on_unload));
|
||||
}
|
||||
}
|
||||
|
||||
void trigger_recenter(double mid) {
|
||||
reset_counters();
|
||||
start(null);
|
||||
}
|
||||
|
||||
protected override void OnClick() {
|
||||
reset_counters();
|
||||
base.OnClick();
|
||||
}
|
||||
|
||||
void reset_counters() {
|
||||
_last_recenter = DateTime.UtcNow;
|
||||
_last_mid = _handle.mid();
|
||||
_bus?.post(SparkTags.RequestRecenterLadder, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
@@ -10,69 +11,111 @@ using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Data;
|
||||
using static bts.shell.PAndLWindowFactory;
|
||||
using static bts.api.orderman.Fill.Types;
|
||||
|
||||
namespace mbts.spark {
|
||||
class PAndLVM : DisposableViewModelBase {
|
||||
|
||||
|
||||
public PAndLVM(IBus bus, PAndLModel model, PAndLStartupData state) {
|
||||
|
||||
public PAndLVM(IBus bus, PAndLModel model, PAndLStartupData startup_data) {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
_selected_pnl_calc_type = backing<pnl_calc_type>(nameof(selected_pnl_calc_type));
|
||||
// get initial value from settings
|
||||
// else default to midpoint
|
||||
// TODO:
|
||||
// need to respond to settings change somehow as the
|
||||
// combobox that selected_pnl_calc_type is bound to will be
|
||||
// gone from this form.
|
||||
// ditto start of day changes, but this will be a bit more involved
|
||||
// since we'll need to create a new PnLCalculator when this changes. (infrequent, if ever)
|
||||
_selected_pnl_calc_type.Value = (startup_data != null && startup_data.calc_type != pnl_calc_type.none) ? startup_data.calc_type : pnl_calc_type.midpoint;
|
||||
_selected_pnl_calc_type.Changed += pnl_calc_type_changed;
|
||||
_start_of_day_ts = (startup_data != null && startup_data.start_of_day_time.HasValue) ? startup_data.start_of_day_time.Value : new TimeSpan(16, 30, 0);
|
||||
var pnl = model.on_pnl_data
|
||||
.Sample(_POSITION_UPDATE_PERIOD)
|
||||
.OnDispatcher()
|
||||
.Subscribe(p => {
|
||||
if (p != null) {
|
||||
PnLCalculator calc = new PnLCalculator(p.state.Values);
|
||||
PnLCalculator calc = new PnLCalculator(p.state.Values, _selected_pnl_calc_type.Value, _start_of_day_ts);
|
||||
var summary_position_rows = calc.get_summary_position();
|
||||
_pnl_data.sync_with(summary_position_rows);
|
||||
_last_pnl_update_time = DateTime.Now;
|
||||
}
|
||||
});
|
||||
pnl_view = CollectionViewSource.GetDefaultView(_pnl_data);
|
||||
pnl_view.SetSortingCriteria(
|
||||
new List<SortingCriteria>() {
|
||||
new SortingCriteria("account", true)
|
||||
, new SortingCriteria("exchange_string", true)
|
||||
, new SortingCriteria("symbol", true)
|
||||
, new SortingCriteria("security_text", true)
|
||||
}
|
||||
);
|
||||
|
||||
// restore filter state
|
||||
var fs = startup_data?.filters;
|
||||
if (fs != null) {
|
||||
account_filter.load_state(fs.account_regex);
|
||||
exchange_string_filter.load_state(fs.exchange_regex);
|
||||
symbol_filter.load_state(fs.symbol_regex);
|
||||
security_filter.load_state(fs.security_regex);
|
||||
start_position_filter.load_state(fs.start_net_pos);
|
||||
start_pnl_filter.load_state(fs.start_pnl);
|
||||
day_long_quantity_filter.load_state(fs.day_long_pos);
|
||||
day_short_quantity_filter.load_state(fs.day_short_pos);
|
||||
day_pnl_filter.load_state(fs.day_pnl);
|
||||
total_net_position_filter.load_state(fs.total_net_pos);
|
||||
total_pnl_filter.load_state(fs.total_pnl);
|
||||
pnl_price_filter.load_state(fs.pnl_price);
|
||||
security_type_filter.load_state(fs.security_types);
|
||||
}
|
||||
// restore sort state or supply default value
|
||||
if (startup_data?.sort_criteria?.Any() == true) {
|
||||
pnl_view.SetSortingCriteria(startup_data.sort_criteria);
|
||||
} else {
|
||||
pnl_view.SetSortingCriteria(new List<SortingCriteria>() {
|
||||
new SortingCriteria(nameof(PnLSummaryRow.account), true)
|
||||
, new SortingCriteria(nameof(PnLSummaryRow.exchange_string), true)
|
||||
, new SortingCriteria(nameof(PnLSummaryRow.symbol), true)
|
||||
, new SortingCriteria(nameof(PnLSummaryRow.security_text), true)
|
||||
});
|
||||
}
|
||||
pnl_view.CollectionChanged += Pnl_view_CollectionChanged;
|
||||
|
||||
//Account[] accounts =
|
||||
|
||||
|
||||
#region Create grid filters and load state
|
||||
#endregion
|
||||
|
||||
// setup the main Disposable and
|
||||
// hook up grid filters
|
||||
_dispose = Disposable.Create(pnl,
|
||||
DataGridFilterHelper.hook_filters(pnl_view,
|
||||
security_filter, pnl_filter,
|
||||
buy_quantity_filter, sell_quantity_filter,
|
||||
net_position_filter, weighted_average_buy_filter,
|
||||
weighted_average_sell_filter, pnl_price_filter,
|
||||
sod_position_filter, account_filter,
|
||||
exchange_string_filter, symbol_filter),
|
||||
account_filter,
|
||||
exchange_string_filter,
|
||||
symbol_filter,
|
||||
security_filter,
|
||||
start_position_filter,
|
||||
start_pnl_filter,
|
||||
day_long_quantity_filter,
|
||||
day_short_quantity_filter,
|
||||
day_pnl_filter,
|
||||
total_net_position_filter,
|
||||
total_pnl_filter,
|
||||
pnl_price_filter,
|
||||
weighted_average_buy_filter,
|
||||
weighted_average_sell_filter,
|
||||
security_type_filter),
|
||||
WorkLoop.Start("PAndL", _UPDATE_PERIOD, _calculate_pnl)
|
||||
);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing) {
|
||||
pnl_view.CollectionChanged -= Pnl_view_CollectionChanged;
|
||||
}
|
||||
|
||||
|
||||
private void Pnl_view_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) {
|
||||
_update_totals();
|
||||
}
|
||||
|
||||
private void pnl_calc_type_changed(pnl_calc_type oldval, pnl_calc_type newval) {
|
||||
_calculate_pnl();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing) {
|
||||
pnl_view.CollectionChanged -= Pnl_view_CollectionChanged;
|
||||
_selected_pnl_calc_type.Changed -= pnl_calc_type_changed;
|
||||
}
|
||||
|
||||
void _update_totals() {
|
||||
if (pnl_view != null) {
|
||||
total_pnl = pnl_view.Cast<PnLSummaryRow>().Where(r => !Double.IsNaN(r.pnl)).Sum(r => r.pnl);
|
||||
total_net_pos = pnl_view.Cast<PnLSummaryRow>().Sum(r => r.net_position);
|
||||
total_buy_qty = pnl_view.Cast<PnLSummaryRow>().Sum(r => r.buy_quantity);
|
||||
total_sell_qty = pnl_view.Cast<PnLSummaryRow>().Sum(r => r.sell_quantity);
|
||||
total_aggregate_pnl = pnl_view.Cast<PnLSummaryRow>().Where(r => !Double.IsNaN(r.total_pnl)).Sum(r => r.total_pnl);
|
||||
total_aggregate_net_pos = pnl_view.Cast<PnLSummaryRow>().Sum(r => r.total_net_position);
|
||||
total_aggregate_buy_qty = pnl_view.Cast<PnLSummaryRow>().Sum(r => r.day_long_quantity);
|
||||
total_aggregate_sell_qty = pnl_view.Cast<PnLSummaryRow>().Sum(r => r.day_short_quantity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,75 +124,113 @@ namespace mbts.spark {
|
||||
// position update in a while
|
||||
if (DateTime.Now.Subtract(_last_pnl_update_time) > _UPDATE_PERIOD) {
|
||||
foreach (var r in _pnl_data) {
|
||||
r.update_pnl(pnl_calc_type.waterfall);
|
||||
if (_selected_pnl_calc_type.Value != pnl_calc_type.none) {
|
||||
r.update_pnl(_selected_pnl_calc_type.Value);
|
||||
}
|
||||
}
|
||||
_update_totals();
|
||||
}
|
||||
}
|
||||
|
||||
private DateTime _last_pnl_update_time = DateTime.MinValue;
|
||||
private ObservableCollection<PnLSummaryRow> _pnl_data = new ObservableCollection<PnLSummaryRow>();
|
||||
#region Properties
|
||||
|
||||
public ICollectionView pnl_view { get; }
|
||||
|
||||
private double _total_pnl;
|
||||
public double total_pnl {
|
||||
public List<pnl_calc_type> pnl_calc_types { get; } = EnumHelper.get_values_except(pnl_calc_type.none, pnl_calc_type.waterfall, pnl_calc_type.bid_or_ask, pnl_calc_type.open, pnl_calc_type.close).ToList();
|
||||
|
||||
public pnl_calc_type selected_pnl_calc_type {
|
||||
get => _selected_pnl_calc_type.Value;
|
||||
set => _selected_pnl_calc_type.Value = value;
|
||||
}
|
||||
|
||||
public double total_aggregate_pnl {
|
||||
get {
|
||||
return _total_pnl;
|
||||
return _total_aggregate_pnl;
|
||||
}
|
||||
set {
|
||||
_total_pnl = value;
|
||||
RaisePropertyChanged(nameof(total_pnl));
|
||||
_total_aggregate_pnl = value;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private double _total_net_pos;
|
||||
public double total_net_pos {
|
||||
get { return _total_net_pos; }
|
||||
public double total_aggregate_net_pos {
|
||||
get { return _total_aggregate_net_pos; }
|
||||
set {
|
||||
_total_net_pos = value;
|
||||
RaisePropertyChanged(nameof(total_net_pos));
|
||||
_total_aggregate_net_pos = value;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private double _total_buy_qty;
|
||||
public double total_buy_qty {
|
||||
get { return _total_buy_qty; }
|
||||
public double total_aggregate_buy_qty {
|
||||
get { return _total_aggregate_buy_qty; }
|
||||
set {
|
||||
_total_buy_qty = value;
|
||||
RaisePropertyChanged(nameof(total_buy_qty));
|
||||
_total_aggregate_buy_qty = value;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private double _total_sell_qty;
|
||||
public double total_sell_qty {
|
||||
get { return _total_sell_qty; }
|
||||
public double total_aggregate_sell_qty {
|
||||
get { return _total_aggregate_sell_qty; }
|
||||
set {
|
||||
_total_sell_qty = value;
|
||||
RaisePropertyChanged(nameof(total_sell_qty));
|
||||
_total_aggregate_sell_qty = value;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public TimeSpan start_of_day {
|
||||
get { return _start_of_day_ts; }
|
||||
}
|
||||
|
||||
#region Filters
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, string> security_filter { get; } = new RegexFilterVM<PnLSummaryRow>(r => r.security_text, nameof(PnLSummaryRow.security_text));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, DoubleMinMaxState> pnl_filter { get; } = new DoubleMinMax<PnLSummaryRow>(r => r.pnl, nameof(PnLSummaryRow.pnl));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, DoubleMinMaxState> buy_quantity_filter { get; } = new DoubleMinMax<PnLSummaryRow>(r => r.buy_quantity, nameof(PnLSummaryRow.buy_quantity));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, DoubleMinMaxState> sell_quantity_filter { get; } = new DoubleMinMax<PnLSummaryRow>(r => r.sell_quantity, nameof(PnLSummaryRow.sell_quantity));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, DoubleMinMaxState> net_position_filter { get; } = new DoubleMinMax<PnLSummaryRow>(r => r.net_position, nameof(PnLSummaryRow.net_position));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, DoubleMinMaxState> weighted_average_buy_filter { get; } = new DoubleMinMax<PnLSummaryRow>(r => r.weighted_average_buy, nameof(PnLSummaryRow.weighted_average_buy));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, DoubleMinMaxState> weighted_average_sell_filter { get; } = new DoubleMinMax<PnLSummaryRow>(r => r.weighted_average_sell, nameof(PnLSummaryRow.weighted_average_sell));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, DoubleMinMaxState> pnl_price_filter { get; } = new DoubleMinMax<PnLSummaryRow>(r => r.pnl_price, nameof(PnLSummaryRow.pnl_price));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, DoubleMinMaxState> sod_position_filter { get; } = new DoubleMinMax<PnLSummaryRow>(r => r.sod_position, nameof(PnLSummaryRow.sod_position));
|
||||
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, string> account_filter { get; } = new RegexFilterVM<PnLSummaryRow>(r => r.account, nameof(PnLSummaryRow.account));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, string> exchange_string_filter { get; } = new RegexFilterVM<PnLSummaryRow>(r => r.exchange_string, nameof(PnLSummaryRow.exchange_string));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, string> symbol_filter { get; } = new RegexFilterVM<PnLSummaryRow>(r => r.symbol, nameof(PnLSummaryRow.symbol));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, string> security_filter { get; } = new RegexFilterVM<PnLSummaryRow>(r => r.security_text, nameof(PnLSummaryRow.security_text));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, IntMinMaxState> start_position_filter { get; } = new IntMinMax<PnLSummaryRow>(r => r.start_net_position, nameof(PnLSummaryRow.start_net_position));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, DoubleMinMaxState> start_pnl_filter { get; } = new DoubleMinMax<PnLSummaryRow>(r => r.start_pnl, nameof(PnLSummaryRow.start_pnl));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, UIntMinMaxState> day_long_quantity_filter { get; } = new UIntMinMax<PnLSummaryRow>(r => r.day_long_quantity, nameof(PnLSummaryRow.day_long_quantity));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, UIntMinMaxState> day_short_quantity_filter { get; } = new UIntMinMax<PnLSummaryRow>(r => r.day_short_quantity, nameof(PnLSummaryRow.day_short_quantity));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, DoubleMinMaxState> day_pnl_filter { get; } = new DoubleMinMax<PnLSummaryRow>(r => r.day_pnl, nameof(PnLSummaryRow.day_pnl));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, IntMinMaxState> total_net_position_filter { get; } = new IntMinMax<PnLSummaryRow>(r => r.total_net_position, nameof(PnLSummaryRow.total_net_position));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, DoubleMinMaxState> total_pnl_filter { get; } = new DoubleMinMax<PnLSummaryRow>(r => r.total_pnl, nameof(PnLSummaryRow.total_pnl));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, DoubleMinMaxState> pnl_price_filter { get; } = new DoubleMinMax<PnLSummaryRow>(r => r.pnl_price, nameof(PnLSummaryRow.pnl_price));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, DoubleMinMaxState> weighted_average_buy_filter { get; } = new DoubleMinMax<PnLSummaryRow>(r => r.weighted_average_buy, nameof(PnLSummaryRow.weighted_average_buy));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, DoubleMinMaxState> weighted_average_sell_filter { get; } = new DoubleMinMax<PnLSummaryRow>(r => r.weighted_average_sell, nameof(PnLSummaryRow.weighted_average_sell));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, SecurityType[]> security_type_filter { get; } = DiscreteValuesFilterVM.Create<PnLSummaryRow, SecurityType>(EnumHelper.get_values_except<SecurityType>(SecurityType.Option, SecurityType.Stock, SecurityType.Synthetic).ToArray(), r => r.security_type, nameof(PnLSummaryRow.security_type));
|
||||
|
||||
public FilterStates get_filters_states() => new FilterStates {
|
||||
account_regex = account_filter.get_state(),
|
||||
exchange_regex = exchange_string_filter.get_state(),
|
||||
symbol_regex = exchange_string_filter.get_state(),
|
||||
security_regex = security_filter.get_state(),
|
||||
start_net_pos = start_position_filter.get_state(),
|
||||
start_pnl = start_pnl_filter.get_state(),
|
||||
day_long_pos = day_long_quantity_filter.get_state(),
|
||||
day_short_pos = day_short_quantity_filter.get_state(),
|
||||
day_pnl = day_pnl_filter.get_state(),
|
||||
total_net_pos = total_net_position_filter.get_state(),
|
||||
total_pnl = total_pnl_filter.get_state(),
|
||||
pnl_price = pnl_price_filter.get_state(),
|
||||
security_types = security_type_filter.get_state()
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
public List<SortingCriteria> get_sorting_states() => pnl_view.GetSortingCriteria();
|
||||
|
||||
#endregion
|
||||
|
||||
readonly IBus _bus;
|
||||
readonly PAndLModel _model;
|
||||
static readonly TimeSpan _UPDATE_PERIOD = TimeSpan.FromSeconds(1);
|
||||
static readonly TimeSpan _POSITION_UPDATE_PERIOD = TimeSpan.FromMilliseconds(333);
|
||||
readonly Property<pnl_calc_type> _selected_pnl_calc_type;
|
||||
|
||||
double _total_aggregate_pnl, _total_aggregate_net_pos, _total_aggregate_buy_qty, _total_aggregate_sell_qty;
|
||||
TimeSpan _start_of_day_ts;
|
||||
DateTime _last_pnl_update_time = DateTime.MinValue;
|
||||
ObservableCollection<PnLSummaryRow> _pnl_data = new ObservableCollection<PnLSummaryRow>();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -18,19 +18,26 @@
|
||||
<Style x:Key="RightAlignedColumn" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="TextAlignment" Value="Right" />
|
||||
</Style>
|
||||
<convert:DoubleTextConverter x:Key="DoubleWithNaNHiddenConverter" Format="F2" />
|
||||
<convert:NumericSignBrushConverter x:Key="PnLBackgroundColorConverter" Opacity="0.33"/>
|
||||
<convert:DoubleTextConverter x:Key="DoubleWithNaNHiddenConverter" Format="N0" />
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<!-- Table -->
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<c:DataGridEx Grid.Row="0" Grid.Column="0" x:Name="_grid" IsReadOnly="True" BorderThickness="0" GridLinesVisibility="None" CanUserSortColumns="True" SelectionUnit="FullRow"
|
||||
<ComboBox Grid.Row="0"
|
||||
ItemsSource="{Binding pnl_calc_types}"
|
||||
SelectedItem="{Binding selected_pnl_calc_type}"
|
||||
ItemTemplate="{StaticResource EnumDescriptionTemplate}">
|
||||
</ComboBox>
|
||||
<c:DataGridEx Grid.Row="1" Grid.Column="0" x:Name="_grid" IsReadOnly="True" BorderThickness="0" GridLinesVisibility="None" CanUserSortColumns="True" SelectionUnit="FullRow"
|
||||
ItemsSource="{Binding pnl_view}" uc:DataGridBehavior.NotifyColumnsOfSorting="True">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn x:Name="account" Header="Account" Binding="{Binding account}" IsReadOnly="true"
|
||||
@@ -57,46 +64,78 @@
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.security_filter.open, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource RegexTemplate}"
|
||||
/>
|
||||
<DataGridTextColumn x:Name="sod" Header="SOD" Binding="{Binding sod_position}" IsReadOnly="true"
|
||||
<DataGridTextColumn x:Name="security_type" Header="Security Type" Binding="{Binding security_type}" IsReadOnly="true"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.security_type_filter, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.security_type_filter.active, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.security_type_filter.open, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource DiscreteValuesTemplate}"
|
||||
/>
|
||||
<DataGridTextColumn x:Name="start_net_pos" Header="Start Net Pos" Binding="{Binding start_net_position}" IsReadOnly="true"
|
||||
ElementStyle="{StaticResource RightAlignedColumn}"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.sod_position_filter, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.sod_position_filter.active, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.sod_position_filter.open, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.start_position_filter, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.start_position_filter.active, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.start_position_filter.open, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource DoubleMinMaxTemplate}"
|
||||
/>
|
||||
<DataGridTextColumn x:Name="buy_qty" Header="Buy Qty" Binding="{Binding buy_quantity}" IsReadOnly="true"
|
||||
<DataGridTextColumn x:Name="start_pnl" Header="Start PnL" IsReadOnly="true" SortMemberPath="start_pnl"
|
||||
Binding="{Binding start_pnl, Converter={StaticResource DoubleWithNaNHiddenConverter}}"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.start_pnl_filter, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.start_pnl_filter.active, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.start_pnl_filter.open, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource DoubleMinMaxTemplate}">
|
||||
<DataGridTextColumn.ElementStyle>
|
||||
<Style TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="TextAlignment" Value="Right" />
|
||||
<Setter Property="Background" Value="{Binding start_pnl, Converter={StaticResource PnLBackgroundColorConverter}}" />
|
||||
</Style>
|
||||
</DataGridTextColumn.ElementStyle>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn x:Name="day_long" Header="Day Long" Binding="{Binding day_long_quantity}" IsReadOnly="true"
|
||||
ElementStyle="{StaticResource RightAlignedColumn}"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.buy_quantity_filter, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.buy_quantity_filter.active, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.buy_quantity_filter.open, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.day_long_quantity_filter, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.day_long_quantity_filter.active, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.day_long_quantity_filter.open, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource DoubleMinMaxTemplate}"
|
||||
/>
|
||||
<DataGridTextColumn x:Name="sell_qty" Header="Sell Qty" Binding="{Binding sell_quantity}" IsReadOnly="true"
|
||||
<DataGridTextColumn x:Name="day_short" Header="Day Short" Binding="{Binding day_short_quantity}" IsReadOnly="true"
|
||||
ElementStyle="{StaticResource RightAlignedColumn}"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.sell_quantity_filter, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.sell_quantity_filter.active, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.sell_quantity_filter.open, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.day_short_quantity_filter, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.day_short_quantity_filter.active, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.day_short_quantity_filter.open, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource DoubleMinMaxTemplate}"
|
||||
/>
|
||||
<DataGridTextColumn x:Name="net_pos" Header="Net Pos" Binding="{Binding net_position}" IsReadOnly="true"
|
||||
ElementStyle="{StaticResource RightAlignedColumn}"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.net_position_filter, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.net_position_filter.active, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.net_position_filter.open, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource DoubleMinMaxTemplate}"
|
||||
/>
|
||||
<DataGridTextColumn x:Name="pnl" Header="PnL" IsReadOnly="true" SortMemberPath="pnl"
|
||||
ElementStyle="{StaticResource RightAlignedColumn}"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.pnl_filter, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.pnl_filter.active, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.pnl_filter.open, Source={uc:DirectReference _grid}}"
|
||||
<DataGridTextColumn x:Name="day_pnl" Header="Day PnL" IsReadOnly="true" SortMemberPath="day_pnl"
|
||||
Binding="{Binding day_pnl, Converter={StaticResource DoubleWithNaNHiddenConverter}}"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.day_pnl_filter, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.day_pnl_filter.active, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.day_pnl_filter.open, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource DoubleMinMaxTemplate}">
|
||||
<DataGridTextColumn.Binding>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
|
||||
<Binding Path="pnl"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</DataGridTextColumn.Binding>
|
||||
<DataGridTextColumn.ElementStyle>
|
||||
<Style TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="TextAlignment" Value="Right" />
|
||||
<Setter Property="Background" Value="{Binding day_pnl, Converter={StaticResource PnLBackgroundColorConverter}}" />
|
||||
</Style>
|
||||
</DataGridTextColumn.ElementStyle>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn x:Name="total_net_pos" Header="Total Net Pos" Binding="{Binding total_net_position}" IsReadOnly="true"
|
||||
ElementStyle="{StaticResource RightAlignedColumn}"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.total_net_position_filter, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.total_net_position_filter.active, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.total_net_position_filter.open, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource DoubleMinMaxTemplate}"
|
||||
/>
|
||||
<DataGridTextColumn x:Name="total_pnl" Header="Total PnL" IsReadOnly="true" SortMemberPath="total_pnl"
|
||||
Binding="{Binding total_pnl, Converter={StaticResource DoubleWithNaNHiddenConverter}}"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.total_pnl_filter, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.total_pnl_filter.active, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.total_pnl_filter.open, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource DoubleMinMaxTemplate}">
|
||||
<DataGridTextColumn.ElementStyle>
|
||||
<Style TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="TextAlignment" Value="Right" />
|
||||
<Setter Property="Background" Value="{Binding total_pnl, Converter={StaticResource PnLBackgroundColorConverter}}" />
|
||||
</Style>
|
||||
</DataGridTextColumn.ElementStyle>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn x:Name="pnl_price" Header="PnL Price" IsReadOnly="true" SortMemberPath="pnl_price"
|
||||
ElementStyle="{StaticResource RightAlignedColumn}"
|
||||
@@ -107,24 +146,17 @@
|
||||
<DataGridTextColumn.Binding>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
|
||||
<Binding Path="pnl_price"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
<Binding Path="security"/>
|
||||
</MultiBinding>
|
||||
</DataGridTextColumn.Binding>
|
||||
</DataGridTextColumn>
|
||||
<!-- we are hiding these until we have toggle on/off via settings
|
||||
<DataGridTextColumn x:Name="avg_buy" Header="AvgBuy" Binding="{Binding weighted_average_buy, Converter={StaticResource DoubleWithNaNHiddenConverter}}" IsReadOnly="true" SortMemberPath="weighted_average_buy"
|
||||
ElementStyle="{StaticResource RightAlignedColumn}"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.weighted_average_buy_filter, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.weighted_average_buy_filter.active, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.weighted_average_buy_filter.open, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource DoubleMinMaxTemplate}">
|
||||
<!--
|
||||
<DataGridTextColumn.Binding>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
|
||||
<Binding Path="weighted_average_buy"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</DataGridTextColumn.Binding>
|
||||
-->
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn x:Name="avg_sell" Header="AvgSell" Binding="{Binding weighted_average_sell, Converter={StaticResource DoubleWithNaNHiddenConverter}}" IsReadOnly="true" SortMemberPath="weighted_average_sell"
|
||||
ElementStyle="{StaticResource RightAlignedColumn}"
|
||||
@@ -132,26 +164,19 @@
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.weighted_average_sell_filter.active, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.weighted_average_sell_filter.open, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource DoubleMinMaxTemplate}">
|
||||
<!--
|
||||
<DataGridTextColumn.Binding>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
|
||||
<Binding Path="weighted_average_sell"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</DataGridTextColumn.Binding>
|
||||
-->
|
||||
</DataGridTextColumn>
|
||||
-->
|
||||
</DataGrid.Columns>
|
||||
</c:DataGridEx>
|
||||
<StatusBar Grid.Row="1" Grid.Column="0" DockPanel.Dock="Bottom">
|
||||
<StatusBar Grid.Row="2" Grid.Column="0" DockPanel.Dock="Bottom">
|
||||
<StatusBarItem DockPanel.Dock="Right">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="Total PnL: "/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding total_pnl, Converter={StaticResource DoubleWithNaNHiddenConverter}}" />
|
||||
<TextBlock Grid.Column="0" Text="Aggregate PnL: "/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding total_aggregate_pnl, Converter={StaticResource DoubleWithNaNHiddenConverter}}" />
|
||||
</Grid>
|
||||
</StatusBarItem>
|
||||
<StatusBarItem DockPanel.Dock="Right">
|
||||
@@ -163,8 +188,8 @@
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="Total Net Pos: "/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding total_net_pos}"/>
|
||||
<TextBlock Grid.Column="0" Text="Aggregate Net Pos: "/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding total_aggregate_net_pos}"/>
|
||||
</Grid>
|
||||
</StatusBarItem>
|
||||
<StatusBarItem DockPanel.Dock="Right">
|
||||
@@ -176,8 +201,8 @@
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="Total Sell Qty: "/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding total_sell_qty}"/>
|
||||
<TextBlock Grid.Column="0" Text="Aggregate Sell Qty: "/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding total_aggregate_sell_qty}"/>
|
||||
</Grid>
|
||||
</StatusBarItem>
|
||||
<StatusBarItem DockPanel.Dock="Right">
|
||||
@@ -189,8 +214,8 @@
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="Total Buy Qty: "/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding total_buy_qty}"/>
|
||||
<TextBlock Grid.Column="0" Text="Aggregate Buy Qty: "/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding total_aggregate_buy_qty}"/>
|
||||
</Grid>
|
||||
</StatusBarItem>
|
||||
<StatusBarItem />
|
||||
|
||||
@@ -14,26 +14,31 @@ namespace mbts.spark {
|
||||
internal class PAndLWindow : SparkWindow<PAndLWindowFactory.PAndLStartupData>, ISharedWindow {
|
||||
|
||||
const string _window_type = "PAndL", PNL_THREAD_ID = "PnL";
|
||||
PAndLWindowFactory.PAndLStartupData _state;
|
||||
PAndLWindowFactory.PAndLStartupData _startup_data;
|
||||
PAndLModel _model;
|
||||
PAndLView _view;
|
||||
PAndLVM _vm;
|
||||
PAndLWindowFactory _factory;
|
||||
public PAndLWindow(PAndLModel model, PAndLWindowFactory factory, IBus bus, PAndLWindowFactory.PAndLStartupData state, ImageSource Icon ) : base("P&L", bus, factory, SparkCommands.ShowPAndL.icon) {
|
||||
public PAndLWindow(PAndLModel model, PAndLWindowFactory factory, IBus bus, PAndLWindowFactory.PAndLStartupData startup_data, ImageSource Icon ) : base("P&L", bus, factory, SparkCommands.ShowPAndL.icon) {
|
||||
_model = model;
|
||||
_factory = factory;
|
||||
_state = state;
|
||||
_startup_data = startup_data;
|
||||
}
|
||||
|
||||
public override string window_type => _window_type;
|
||||
public override PAndLWindowFactory.PAndLStartupData SaveState => _state =
|
||||
new PAndLWindowFactory.PAndLStartupData();
|
||||
public override PAndLWindowFactory.PAndLStartupData SaveState => _startup_data =
|
||||
new PAndLWindowFactory.PAndLStartupData(
|
||||
_vm.get_sorting_states(),
|
||||
_vm.get_filters_states(),
|
||||
_vm.selected_pnl_calc_type,
|
||||
_vm.start_of_day
|
||||
);
|
||||
public override UIElement Content => _view;
|
||||
public override double MinWidth => 894;
|
||||
public string thread_id => PNL_THREAD_ID;
|
||||
|
||||
public void thread_init() {
|
||||
_vm = new PAndLVM(_bus, _model, _state);
|
||||
_vm = new PAndLVM(_bus, _model, _startup_data);
|
||||
_view = new PAndLView(_vm);
|
||||
_view.DataContext = _vm;
|
||||
}
|
||||
|
||||
Loaded 100 of 333 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user