Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
930f3b23b8 | ||
|
|
d3fb52246c | ||
|
|
e173fe5515 | ||
|
|
dbe02e44fd | ||
|
|
7453540140 | ||
|
|
9830dcbe31 | ||
|
|
69bc734b6a | ||
|
|
2e69966d44 | ||
|
|
714dfd5038 | ||
|
|
6dfe9b3892 | ||
|
|
15e557a209 | ||
|
|
8075bafb86 | ||
|
|
95ae7b780e | ||
|
|
7e68dc4ff7 | ||
|
|
879963b828 | ||
|
|
5320e16088 | ||
|
|
7429c42de2 | ||
|
|
9147b4605a | ||
|
|
6fc8a6c7fe | ||
|
|
b537c0c5bd | ||
|
|
400ae269e3 | ||
|
|
30cd749d7c | ||
|
|
efbf45167c | ||
|
|
913b81b8b6 | ||
|
|
606d37744f | ||
|
|
8a4d3d4c97 | ||
|
|
7cbf0dc683 | ||
|
|
e05ce79abf | ||
|
|
2ea95e7938 | ||
|
|
65c4c23b22 | ||
|
|
54dde449ce | ||
|
|
c206d38982 | ||
|
|
72a1bddbd2 | ||
|
|
8ee63d7b8e | ||
|
|
73cd8d3f26 | ||
|
|
86a87c501c | ||
|
|
4705368097 | ||
|
|
034aaa5b98 | ||
|
|
cbbd678112 | ||
|
|
d62a2ea9d4 | ||
|
|
6259f8b03f | ||
|
|
3866e63a70 | ||
|
|
877e0192a1 | ||
|
|
6d3b7305df | ||
|
|
aa975ea1c5 | ||
|
|
49445c6871 | ||
|
|
ef7c3c70da | ||
|
|
404a193068 | ||
|
|
42a59c9c92 | ||
|
|
809bd16e99 | ||
|
|
04446df8cc | ||
|
|
d998d3ccbb | ||
|
|
62892f1fb9 | ||
|
|
8e75c80e02 | ||
|
|
1a2a82eda3 | ||
|
|
71147d038c | ||
|
|
c3292d2f60 | ||
|
|
e749a24a77 | ||
|
|
b706c36edb | ||
|
|
7823972bfd | ||
|
|
f958ee19ad | ||
|
|
63cbd74744 | ||
|
|
fc78475037 | ||
|
|
3ef42a453a | ||
|
|
469acd23a5 | ||
|
|
d2a0efebd3 | ||
|
|
5cde9d55a7 | ||
|
|
ddd08cb036 | ||
|
|
5bb064197f | ||
|
|
2417c6b57f | ||
|
|
a1addee1b3 | ||
|
|
2e1bd765f6 | ||
|
|
d108ff3151 | ||
|
|
0f7cae6de7 | ||
|
|
1ff84f26d2 | ||
|
|
dbd8a7d742 | ||
|
|
e45123fd76 | ||
|
|
4924a873a2 | ||
|
|
f0ba6b72fc | ||
|
|
5e6a0799d2 | ||
|
|
bea84c03f7 | ||
|
|
869faa5c6c | ||
|
|
141c28ed78 | ||
|
|
4d9bce82ab | ||
|
|
8428423ff9 | ||
|
|
fa18cf5aba | ||
|
|
de11e00d89 | ||
|
|
8feab27332 | ||
|
|
8c26ffd5d3 | ||
|
|
dad7d030bc | ||
|
|
7e7af8f606 | ||
|
|
d0647fb8d9 | ||
|
|
bb2564fb0a | ||
|
|
6b0e8b6600 | ||
|
|
9e768b7a24 | ||
|
|
a3774c8d01 | ||
|
|
59a88f76a8 | ||
|
|
24d8929e88 | ||
|
|
dcaae15e28 | ||
|
|
a94d753fdc | ||
|
|
bbd95db97b | ||
|
|
a048874734 | ||
|
|
6b24745ae8 | ||
|
|
a981965fd9 | ||
|
|
c4910f011f | ||
|
|
0c2593cb20 | ||
|
|
37b4657023 | ||
|
|
a608b5b99d | ||
|
|
b1fb6ed06a | ||
|
|
c88db07474 | ||
|
|
dbea159fa9 | ||
|
|
b33b671342 | ||
|
|
70570164c7 | ||
|
|
9338328b1f | ||
|
|
70db5ceb10 | ||
|
|
e07d602cf5 | ||
|
|
077505509a | ||
|
|
fd85f0507c | ||
|
|
24f0b48f9d | ||
|
|
30ed5b296e | ||
|
|
b7a620d30e | ||
|
|
dcee2930f5 | ||
|
|
1b2aeee069 | ||
|
|
c4393637e7 | ||
|
|
04dadc46d5 | ||
|
|
b64cd3b031 | ||
|
|
5e1c4143db | ||
|
|
ba4b6aa1ff | ||
|
|
ad8e803a8c | ||
|
|
601eec3ddb | ||
|
|
38edcb27f1 | ||
|
|
e1b638a4b6 | ||
|
|
b80fe1e748 | ||
|
|
0419d084f4 | ||
|
|
f26faa5676 | ||
|
|
09d4d1f907 | ||
|
|
bdbf7e53e8 | ||
|
|
9c5ddba7f7 | ||
|
|
91dab2c841 | ||
|
|
8cfca8e40e | ||
|
|
9cb619d374 | ||
|
|
fba8233c63 | ||
|
|
631158ebbc | ||
|
|
5f12e2f1b2 | ||
|
|
807f8b5876 | ||
|
|
e6a9c7e433 | ||
|
|
096787d710 | ||
|
|
0468fe4f4d | ||
|
|
69b75c5f5e | ||
|
|
5626614e2d | ||
|
|
67d0a0351a | ||
|
|
97925a82d4 | ||
|
|
0bdd2dfca0 | ||
|
|
a0c572ef2e | ||
|
|
b5c24880a1 | ||
|
|
31c8a50200 | ||
|
|
0219dd2013 | ||
|
|
91cb215b09 | ||
|
|
4446ef0f57 | ||
|
|
41d662b3ba | ||
|
|
05d0eee8c7 | ||
|
|
112a5703e0 | ||
|
|
955b9d1fec | ||
|
|
d652646e93 | ||
|
|
86199755f5 | ||
|
|
d68156f214 | ||
|
|
895bf3be6a | ||
|
|
860132469f | ||
|
|
e1568c1e67 | ||
|
|
f4ee9434cc | ||
|
|
3bbef9f328 | ||
|
|
d3043e9a19 | ||
|
|
c6b494db16 | ||
|
|
e8f797739c | ||
|
|
fc5e4039b2 | ||
|
|
d6c1285ada | ||
|
|
a5c0c95e4e | ||
|
|
308c5e5579 | ||
|
|
67b5aa7f15 | ||
|
|
70bcf3ff0d | ||
|
|
5dfca66665 | ||
|
|
5e1fd41935 | ||
|
|
ef00559f1e |
No files matched your search
@@ -28,3 +28,5 @@ src/server/sparkd/orderman/perf_analysis/trace_data/
|
||||
*.fsx.lock
|
||||
src/lib/orderman/perf_analysis/logs/latency_data/
|
||||
trace_data/
|
||||
/src/lib/orderman/perf_analysis/last_pretty_log.csv
|
||||
/src/lib/orderman/perf_analysis/log.csv
|
||||
Vendored
+6
-6
@@ -33,12 +33,12 @@
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "FIX Launch",
|
||||
"name": "SDK Launch",
|
||||
"type": "clr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
|
||||
"args": ["fix","-f","spark_fix.cfg","-d"],
|
||||
"cwd": "${workspaceRoot}/build/Debug/bin/net471/",
|
||||
"program": "${workspaceRoot}/build/Debug/bin/net471/spark_sdk.exe",
|
||||
"args": ["md","GLBX","Future","CL", "CLZ9", "--user=bts", "--pass="],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
@@ -48,7 +48,7 @@
|
||||
"type": "clr",
|
||||
"request": "launch",
|
||||
"program": "C:/Program Files/Microsoft Office/Office14/EXCEL.EXE",
|
||||
"args": ["${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64.xll"],
|
||||
"args": ["${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64-packed.xll"],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
@@ -209,7 +209,7 @@
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
|
||||
"args": ["test","Co2Tests"],
|
||||
"args": ["test","AuthToken"],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
|
||||
"environment": [],
|
||||
|
||||
@@ -53,6 +53,19 @@ implementation phase developers should reach out to Pedro for UI
|
||||
reviews, architecture and design concerns as well as specification
|
||||
trade off concerns.
|
||||
|
||||
* Verification. Once code is complete think carefully about the best way
|
||||
to verify that it is good before it goes to production. We don't have a
|
||||
dedicated QA cycle to flush out bugs so it's important that a change be
|
||||
fully tested before it's marked "Ready to Merge." For any non-trivial
|
||||
change consider enlisting a 'test buddy' to help review the code and
|
||||
think of test cases that you may not have considered. Stability from
|
||||
release to release is especially critical right now as we develop a
|
||||
reputation in the futures world.
|
||||
- Code Reviews
|
||||
- Unit tests
|
||||
- System tests
|
||||
- Manual tests
|
||||
|
||||
* Merge. Once a project is considered complete by developers, a PR
|
||||
that includes Pedro should be created (unless, as mentioned above, an
|
||||
early PR was already created in the development phase). Whether the PR
|
||||
|
||||
@@ -1,4 +1,39 @@
|
||||
# Release Notes
|
||||
## 1.4.6
|
||||
### Ladder Usability Improvements
|
||||
- The ladder can now be zoomed in/out according to a user's preference
|
||||
- The zoom slider can be enabled from Ladder Form -> Settings button -> Left Pane tab -> check Zoom Slider checkbox
|
||||
- The zoom functionality can also be performed via hotkey and mouse wheel combinations
|
||||
- Zoom In = CTRL + Mouse wheel up
|
||||
- Zoom Out = CTRL + Mouse wheel down
|
||||
- Reset zoom = CTRL + Mouse wheel click or double click on the zoom slider
|
||||
### Exchange simulator
|
||||
- Improved the exchange simulator to track real market data more closely
|
||||
### Bug Fixes
|
||||
- Fixed a UI crash in some cases when attempting to sort the order book by time
|
||||
|
||||
## 1.4.5
|
||||
- Fixed entitlement validation for ICS spreads
|
||||
- Tuned performance logging
|
||||
|
||||
## 1.4.4
|
||||
|
||||
### Performance Improvements
|
||||
- Reduced performance logging activity
|
||||
|
||||
## 1.4.3
|
||||
|
||||
### Ladder Usability Improvements
|
||||
- Controls found in Left Pane of the ladder can now be hidden via the checkboxes found under Ladder Form -> Settings button -> Left Pane tab
|
||||
- The Left Pane is now scrollable with the mouse wheel when there isn't enough vertical space to accomodate the entire form
|
||||
- The Left Pane can now grown and shrunk horizontally via the splitter between the left pane and the main ladder area
|
||||
### Performance Improvements
|
||||
- Requoting and hedging latency has been reduced by up to 30%
|
||||
### Software Development Kit (SDK) Improvements
|
||||
- The API now enforces authentication
|
||||
### Bug Fixes
|
||||
- The Market View form now honors filters when new securities are added
|
||||
|
||||
## 1.4.2
|
||||
|
||||
### User Interface Improvements
|
||||
|
||||
+1
-1
Submodule docker/linux updated: 8ee9692597...f6d8d59860.
+1
-1
Submodule externals updated: 5f21fd13e7...69f8639235.
@@ -23,8 +23,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_test", "src\client\sp
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_excel", "src\client\spark_excel\spark_excel.csproj", "{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_fix", "src\client\spark_fix\spark_fix.csproj", "{D8031C13-7BEA-430A-BB60-40927D15CFC3}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -221,26 +219,6 @@ Global
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x64.Build.0 = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x86.ActiveCfg = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x86.Build.0 = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Debug|x64.Build.0 = Debug|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|Any CPU.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|Any CPU.Build.0 = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|x64.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|x64.Build.0 = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|x86.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|x86.Build.0 = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Release|x64.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Release|x64.Build.0 = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Release|x86.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|Any CPU.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|Any CPU.Build.0 = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|x64.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|x64.Build.0 = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|x86.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|x86.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using bts;
|
||||
using bts.api.admin;
|
||||
using bts.api.core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using bts.api.core;
|
||||
|
||||
namespace mbts.spark {
|
||||
[Export]
|
||||
|
||||
@@ -17,31 +17,22 @@ using bts.api.user;
|
||||
|
||||
namespace bts {
|
||||
[Export(typeof(IInitializable))]
|
||||
class MDSource : IObserver<List<SecurityAndHandle>>, IInitializable, IDisposable {
|
||||
class MDDaemon : bts.md.MDSource, IObserver<List<SecurityAndHandle>>, IInitializable, IDisposable {
|
||||
readonly Lazy<IBus> _bus;
|
||||
readonly Lazy<AuthToken> _token;
|
||||
readonly Lazy<AuthTokenImmutable> _token;
|
||||
readonly Lazy<SyntheticSecurityService> _ss;
|
||||
readonly MDConsumer _consumer = new MDConsumer();
|
||||
readonly Co2RelayControl _relay;
|
||||
readonly Dictionary<SecurityKey, SecurityMD> _deleted_synthetics = new Dictionary<SecurityKey, SecurityMD>();
|
||||
long _version = 0;
|
||||
readonly ConcurrentDictionary<SecurityKey, SecurityMD> _exchange_sec_map = new ConcurrentDictionary<SecurityKey, SecurityMD>();
|
||||
List<IDisposable> _dispose = new List<IDisposable>();
|
||||
|
||||
static uint get_naggle() => mbts.config.Configuration.get<bts.md.RelayOptions>().naggle_ms;
|
||||
|
||||
[ImportingConstructor]
|
||||
public MDSource(
|
||||
Lazy<IBus> bus,
|
||||
Lazy<AuthToken> token,
|
||||
Lazy<SyntheticSecurityService> ss) {
|
||||
public MDDaemon(Lazy<IBus> bus, Lazy<AuthTokenImmutable> token, Lazy<SyntheticSecurityService> ss) : base(get_naggle(), true) {
|
||||
_bus = bus;
|
||||
_token = token;
|
||||
_ss = ss;
|
||||
_relay = new Co2RelayControl(_consumer);
|
||||
_relay.preload_securities((co2.MDRequest.Types.Flags)0xff).ignore();
|
||||
_dispose.Add(_consumer.on_security.Subscribe(this));
|
||||
_ss = ss;
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
override public void initialize() {
|
||||
_relay.set_token(_token.Value);
|
||||
_dispose.Add(
|
||||
_ss.Value.subscribei_metadata()
|
||||
@@ -66,6 +57,9 @@ namespace bts {
|
||||
// Post full dictionary of syntheics here
|
||||
bus.post(SparkTags.SyntheticstrategiesTag, t);
|
||||
}));
|
||||
_dispose.Add( this.on_securities.Subscribe( (md) => {
|
||||
bus.post(SparkTags.SecuritiesTag, md);
|
||||
}));
|
||||
Task.Run(run);
|
||||
}
|
||||
|
||||
@@ -74,37 +68,5 @@ namespace bts {
|
||||
|
||||
public int priority => 55;
|
||||
IBus bus => _bus.Value;
|
||||
|
||||
public void OnCompleted() { }
|
||||
public void OnError(Exception error) => Log.Md.error(error.ToString());
|
||||
public void OnNext(List<SecurityAndHandle> values) {
|
||||
if (values.Count > 0) {
|
||||
foreach (var v in values) {
|
||||
var smd = new SecurityMD(v.security, v.handle);
|
||||
_exchange_sec_map.AddOrUpdate(smd.security.Id, smd, (_, __) => smd);
|
||||
}
|
||||
_version += 1;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() => Disposable.dispose(_dispose);
|
||||
async Task run() {
|
||||
long run_version = 0;
|
||||
while (true) {
|
||||
var exch = _exchange_sec_map;
|
||||
long current_version = _version;
|
||||
if (current_version > run_version) {
|
||||
run_version = current_version;
|
||||
try {
|
||||
Log.Md.info($"MDSource publishing {exch.Count} securities");
|
||||
bus.post(SparkTags.SecuritiesTag, exch);
|
||||
} catch (Exception e) {
|
||||
Log.Md.error($"Error in publish_securities: {e}");
|
||||
}
|
||||
}
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,7 @@ using System.ComponentModel.Composition;
|
||||
namespace Spark {
|
||||
public class ShellServices {
|
||||
[Export]
|
||||
public static ServiceContext context { get; } = new ServiceContext();
|
||||
|
||||
[Export]
|
||||
public UserService user_service => context.locate<UserService>();
|
||||
public ServiceContext context { get; } = new ServiceContext();
|
||||
|
||||
[Export("settings")]
|
||||
public PersistenceService settings_service => context.locate<PersistenceService>("settings");
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace mbts.spark {
|
||||
readonly CellProperty<string> _working_bids_text, _working_asks_text;
|
||||
readonly MyOrderSummarizer _order_summarizer = new MyOrderSummarizer();
|
||||
readonly MDDisplayHelper.SizeFormatter _size_formatter = MDDisplayHelper.get_formatter(MarketSizeDisplay.get_default());
|
||||
uint _bid_direct_size, _bid_merged_size, _ask_direct_size, _ask_merged_size;
|
||||
Property<uint> _bid_direct_size, _bid_merged_size, _ask_direct_size, _ask_merged_size;
|
||||
string _bid_merged_size_text, _ask_merged_size_text;
|
||||
ulong _bbo_ref, _last_ref;
|
||||
|
||||
@@ -45,28 +45,33 @@ namespace mbts.spark {
|
||||
_security_text = smd.security.ToString();
|
||||
_security_key_text = security_key.ToString();
|
||||
string format_price(double price) => security.ContractSpec.format(price);
|
||||
_position = new CellProperty<int>(0);
|
||||
_position = new CellProperty<int>(this, nameof(position), 0);
|
||||
_bid_price = backing(nameof(bid_price), double.NaN);
|
||||
_ask_price = backing(nameof(ask_price), double.NaN);
|
||||
_last_price = backing(nameof(last_price), double.NaN);
|
||||
_last_size = new CellProperty<uint>(0);
|
||||
_last_size = new CellProperty<uint>(this, nameof(last_size), 0);
|
||||
_day_change = backing(nameof(day_change), double.NaN);
|
||||
_session_low = backing(nameof(session_low), double.NaN);
|
||||
_session_high = backing(nameof(session_high), double.NaN);
|
||||
_settlement = backing(nameof(settlement), double.NaN);
|
||||
_volume = new CellProperty<ulong>(0);
|
||||
_inside_bids = new CellProperty<bool>();
|
||||
_inside_asks = new CellProperty<bool>();
|
||||
_working_bid_volume = new CellProperty<int>();
|
||||
_working_ask_volume = new CellProperty<int>();
|
||||
_volume = new CellProperty<ulong>(this, nameof(volume), 0);
|
||||
_inside_bids = new CellProperty<bool>(this, nameof(inside_bids));
|
||||
_inside_asks = new CellProperty<bool>(this, nameof(inside_asks));
|
||||
_working_bid_volume = new CellProperty<int>(this, nameof(working_bid_volume));
|
||||
_working_ask_volume = new CellProperty<int>(this, nameof(working_ask_volume));
|
||||
//
|
||||
_bid_direct_size = backing(nameof(bid_direct_size), default(uint));
|
||||
_bid_merged_size = backing(nameof(bid_merged_size), default(uint));
|
||||
_ask_direct_size = backing(nameof(ask_direct_size), default(uint));
|
||||
_ask_merged_size = backing(nameof(ask_merged_size), default(uint));
|
||||
//
|
||||
_working_orders = new ObservableFilterCollection<OrderRecordImmutable>(_orders, o => o.is_active());
|
||||
_working_bids = new ObservableFilterCollection<OrderRecordImmutable>(_working_orders, o => o.Def.Side == Side.Buy);
|
||||
_working_asks = new ObservableFilterCollection<OrderRecordImmutable>(_working_orders, o => o.Def.Side == Side.Sell);
|
||||
_working_best_bid = new CellProperty<double>(format_price, double.NaN);
|
||||
_working_best_ask = new CellProperty<double>(format_price, double.NaN);
|
||||
_working_bids_text = new CellProperty<string>(string.Empty);
|
||||
_working_asks_text = new CellProperty<string>(string.Empty);
|
||||
_working_best_bid = new CellProperty<double>(this, nameof(working_best_bid), format_price, double.NaN);
|
||||
_working_best_ask = new CellProperty<double>(this, nameof(working_best_ask), format_price, double.NaN);
|
||||
_working_bids_text = new CellProperty<string>(this, nameof(working_bids_text), string.Empty);
|
||||
_working_asks_text = new CellProperty<string>(this, nameof(working_asks_text), string.Empty);
|
||||
//
|
||||
default_command = new DelegateCommand(_show_ladder);
|
||||
bid_price_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.Bid));
|
||||
@@ -100,15 +105,15 @@ namespace mbts.spark {
|
||||
public double last_price => _last_price.Value;
|
||||
public uint last_size => _last_size.value;
|
||||
public double bid_price => _bid_price.Value;
|
||||
public uint bid_direct_size => _bid_direct_size;
|
||||
public uint bid_implied_size => _bid_merged_size - _bid_direct_size;
|
||||
public uint bid_merged_size => _bid_merged_size;
|
||||
public string bid_merged_size_text => _bid_merged_size_text ?? (_bid_merged_size_text = _size_formatter(_bid_merged_size, bid_implied_size));
|
||||
public uint bid_direct_size => _bid_direct_size.Value;
|
||||
public uint bid_implied_size => _bid_merged_size.Value - _bid_direct_size.Value;
|
||||
public uint bid_merged_size => _bid_merged_size.Value;
|
||||
public string bid_merged_size_text => _bid_merged_size_text ?? (_bid_merged_size_text = _size_formatter(_bid_merged_size.Value, bid_implied_size));
|
||||
public double ask_price => _ask_price.Value;
|
||||
public uint ask_direct_size => _ask_direct_size;
|
||||
public uint ask_implied_size => _ask_merged_size - _ask_direct_size;
|
||||
public uint ask_merged_size => _ask_merged_size;
|
||||
public string ask_merged_size_text => _ask_merged_size_text ?? (_ask_merged_size_text = _size_formatter(_ask_merged_size, ask_implied_size));
|
||||
public uint ask_direct_size => _ask_direct_size.Value;
|
||||
public uint ask_implied_size => _ask_merged_size.Value - _ask_direct_size.Value;
|
||||
public uint ask_merged_size => _ask_merged_size.Value;
|
||||
public string ask_merged_size_text => _ask_merged_size_text ?? (_ask_merged_size_text = _size_formatter(_ask_merged_size.Value, ask_implied_size));
|
||||
public ulong volume => _volume.value;
|
||||
public double settlement => _settlement.Value;
|
||||
public double day_change => _day_change.Value;
|
||||
@@ -144,9 +149,9 @@ namespace mbts.spark {
|
||||
bool dirty = false;
|
||||
if (md.md.get_bbo(ref _bbo_ref, out NativePriceSize b, out NativePriceSize a)) {
|
||||
_bid_price.set(b.price);
|
||||
refresh_size(ref b, ref _bid_direct_size, ref _bid_merged_size, ref _bid_merged_size_text, nameof(bid_merged_size_text));
|
||||
refresh_size(ref b, _bid_direct_size, _bid_merged_size, ref _bid_merged_size_text, nameof(bid_merged_size_text));
|
||||
_ask_price.set(a.price);
|
||||
refresh_size(ref a, ref _ask_direct_size, ref _ask_merged_size, ref _ask_merged_size_text, nameof(ask_merged_size_text));
|
||||
refresh_size(ref a, _ask_direct_size, _ask_merged_size, ref _ask_merged_size_text, nameof(ask_merged_size_text));
|
||||
_inside_bids.on_next_distinct(_bid_price.Value >= _working_best_bid.value, ref dirty);
|
||||
_inside_asks.on_next_distinct(_ask_price.Value <= _working_best_ask.value, ref dirty);
|
||||
}
|
||||
@@ -162,10 +167,10 @@ namespace mbts.spark {
|
||||
if (dirty)
|
||||
invalidated?.Invoke();
|
||||
|
||||
void refresh_size(ref NativePriceSize nps, ref uint direct, ref uint merged, ref string text, string text_prop) {
|
||||
if (nps.size != merged || nps.direct_size != direct) {
|
||||
merged = nps.size;
|
||||
direct = nps.direct_size;
|
||||
void refresh_size(ref NativePriceSize nps, Property<uint> direct, Property<uint> merged, ref string text, string text_prop) {
|
||||
if (nps.size != merged.Value || nps.direct_size != direct.Value) {
|
||||
merged.set(nps.size);
|
||||
direct.set(nps.direct_size);
|
||||
text = null;
|
||||
dirty = true;
|
||||
RaisePropertyChanged(text_prop);
|
||||
@@ -205,36 +210,28 @@ namespace mbts.spark {
|
||||
var order = _orders_model.factory.order(security);
|
||||
switch (col) {
|
||||
case MarketViewView.Columns.Bid:
|
||||
order.Side = Side.Sell;
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, bid_price, 0u));
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, 0u));
|
||||
break;
|
||||
case MarketViewView.Columns.BidMergedSize:
|
||||
order.Side = Side.Sell;
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, bid_price, bid_merged_size));
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_merged_size));
|
||||
break;
|
||||
case MarketViewView.Columns.BidDirectSize:
|
||||
order.Side = Side.Sell;
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, bid_price, bid_direct_size));
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_direct_size));
|
||||
break;
|
||||
case MarketViewView.Columns.BidImpliedSize:
|
||||
order.Side = Side.Sell;
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, bid_price, bid_implied_size));
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_implied_size));
|
||||
break;
|
||||
case MarketViewView.Columns.Ask:
|
||||
order.Side = Side.Buy;
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, ask_price, 0u));
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Buy), ask_price, 0u));
|
||||
break;
|
||||
case MarketViewView.Columns.AskMergedSize:
|
||||
order.Side = Side.Buy;
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, ask_price, ask_merged_size));
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Buy), ask_price, ask_merged_size));
|
||||
break;
|
||||
case MarketViewView.Columns.AskDirectSize:
|
||||
order.Side = Side.Sell;
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, ask_price, ask_direct_size));
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), ask_price, ask_direct_size));
|
||||
break;
|
||||
case MarketViewView.Columns.AskImpliedSize:
|
||||
order.Side = Side.Sell;
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, ask_price, ask_implied_size));
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), ask_price, ask_implied_size));
|
||||
break;
|
||||
case MarketViewView.Columns.Last:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, last_price, 0u));
|
||||
@@ -266,7 +263,15 @@ namespace mbts.spark {
|
||||
_working_best_ask.on_next_distinct(_order_summarizer.best_working_ask, ref dirty);
|
||||
_inside_bids.on_next_distinct(_order_summarizer.best_working_bid >= bid_price, ref dirty);
|
||||
_inside_asks.on_next_distinct(_order_summarizer.best_working_ask <= ask_price, ref dirty);
|
||||
RaisePropertyChanged(nameof(working_bids_text), nameof(working_asks_text));
|
||||
|
||||
RaisePropertyChanged(
|
||||
// Implied might not be ever set, so it probably does not RaisePropertyChanged either
|
||||
// This might be part of the filter funk?
|
||||
//nameof(bid_implied_size),
|
||||
//nameof(ask_implied_size),
|
||||
nameof(working_bids_text),
|
||||
nameof(working_asks_text)
|
||||
);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -97,8 +97,11 @@ namespace mbts.spark {
|
||||
public IEnumerable<TradeInfo> read_trades() {
|
||||
var copy = _securities;
|
||||
foreach (var tm in _channel.read_trades(ref _cursor)) {
|
||||
if (copy.TryGetValue(tm.Key, out SecurityImmutable sec))
|
||||
yield return new TradeInfo(sec, tm);
|
||||
if (tm != null) {
|
||||
if (copy.TryGetValue(tm.Key, out SecurityImmutable sec)) {
|
||||
yield return new TradeInfo(sec, new TradeMessageImmutable(tm));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,8 +53,8 @@ namespace mbts.spark {
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<uint?> create_synthetic(SecurityImmutable synthetic) {
|
||||
var result = await _service.create_synthetic_async(synthetic);
|
||||
public async Task<uint?> create_synthetic(SecurityImmutable? synthetic) {
|
||||
var result = await _service.create_synthetic_async(synthetic.Value);
|
||||
return _error.handle_error(result, "Error creating synthetic: {0}", r => r.Error, e => e == CreateResult.Types.Error.Ok, r => new uint?(r.Key), () => null).result;
|
||||
}
|
||||
|
||||
|
||||
@@ -294,9 +294,9 @@ namespace mbts.spark {
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
static string _deduce_symbol(SecurityImmutable security) {
|
||||
if (!string.IsNullOrWhiteSpace(security.Symbol)) return security.Symbol;
|
||||
if (security.Synthetic.Legs.Count() > 0) return security.Synthetic.Legs.First().Security.Symbol;
|
||||
static string _deduce_symbol(SecurityImmutable? security) {
|
||||
if (!string.IsNullOrWhiteSpace(security?.Symbol)) return security?.Symbol;
|
||||
if (security?.Synthetic.Legs.Count() > 0) return security?.Synthetic.Legs.First().Security.Symbol;
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
|
||||
@@ -178,7 +178,7 @@ namespace mbts.spark {
|
||||
} else return model.on_fill_tuples;
|
||||
}
|
||||
|
||||
static IConnectableObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> _get_observable(IBus bus, OrdersModel model, IObservable<StartupData> state) =>
|
||||
static IConnectableObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> _get_observable(IBus bus, OrdersModel model, IObservable<StartupData> state) =>
|
||||
state.SelectManyInSeries(s => _get_observable(bus, model, s));
|
||||
|
||||
public class Row : SecurityRow, IEquatable<Row>, IRowVM {
|
||||
@@ -186,7 +186,7 @@ namespace mbts.spark {
|
||||
readonly FillImmutable _fill;
|
||||
readonly OrderRecordImmutable _order;
|
||||
|
||||
public Row(FillTuple fill_tuple, OrdersModel model) : base(fill_tuple.security_md.HasValue ? fill_tuple.security_md.Value.security : SecurityDataHelper.Defaults.Security) {
|
||||
public Row(FillTuple fill_tuple, OrdersModel model) : base(fill_tuple.security_md.HasValue ? fill_tuple.security_md.Value.security : SecurityDataHelper.Defaults.SecurityImmutable) {
|
||||
_fill = fill_tuple.fill;
|
||||
_order = fill_tuple.order ?? OrderDataHelper.Defaults.OrderRecordImmutable;
|
||||
_model = model;
|
||||
|
||||
@@ -61,10 +61,12 @@ namespace bts.shell {
|
||||
var view = new Lazy<UIElement>(() => {
|
||||
var model = new LadderModel(md, _orders_model, _md_auxiliary_model);
|
||||
var settings_manager = new WindowSettingsManager<LadderSettings>(
|
||||
_settings_hub, state_settings,
|
||||
_settings_hub, state_settings == null || state_settings.use_defaults ? null : state_settings,
|
||||
us => sanitize_ladder_settings(us.ladder),
|
||||
(us, v) => us.with(sanitize_ladder_settings(v)));
|
||||
var vm = new LadderVM(_bus, model, settings_manager);
|
||||
var init = state_settings ?? settings_manager.default_settings;
|
||||
init.use_defaults |= state_settings == null;
|
||||
var vm = new LadderVM(_bus, model, settings_manager, init);
|
||||
return new LadderView(vm);
|
||||
});
|
||||
string thread = Configuration.get<bts.config.toggles.LaddersOnThreads>().enabled ? $"Ladder{id:00}" : "";
|
||||
|
||||
@@ -14,6 +14,8 @@ namespace mbts.spark {
|
||||
class OrderBookRowCollection : ObservableCollection<OrderBookRowVM> {
|
||||
static readonly IEqualityComparer<INode<OrderBookRowVM>> _node_equality_comparer = EqualityComparer.Create<INode<OrderBookRowVM>, uint>(n => n.value.order_key);
|
||||
readonly Func<OrderRecordTuple, OrderBookRowVM> _row_factory;
|
||||
/// Critical: Any and all row mutations <see cref="OrderBookRowVM"/> must be executed through <see cref="SortedTree{T, TKey}.TryReplace(TKey, Func{T, T})"/>.
|
||||
/// Mutations can change the sort-position of a row, and <see cref="_tree"/> will be broken if its elements aren't properly sorted (it relies heavily on binary-search logic).
|
||||
SortedTree<OrderBookRowVM, uint> _tree;
|
||||
IComparer<OrderBookRowVM> _comparer;
|
||||
Func<OrderRecordTuple, bool> _filter;
|
||||
|
||||
@@ -139,13 +139,13 @@ namespace mbts.spark {
|
||||
tif => time_in_forces.Contains(tif));
|
||||
_dispose = Disposable.Create(() => _order_type.Changed -= on_selected_order_type_changed);
|
||||
// local methods
|
||||
IOrderTypeVM create_order_type_vm(CustomOrderDefinitionImmutable cd, string id, string display_name) {
|
||||
IOrderTypeVM create_order_type_vm(CustomOrderDefinitionImmutable? cd, string id, string display_name) {
|
||||
IOrderTypeVM vm = null;
|
||||
if(cd.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic)
|
||||
vm = OrderTypeVMFactory.create(cd.CustomCase, id, display_name, model, security_md_source, cd.Dynamic.DynamicOrderType);
|
||||
if(cd?.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic)
|
||||
vm = OrderTypeVMFactory.create(cd.Value.CustomCase, id, display_name, model, security_md_source, cd.Value.Dynamic.DynamicOrderType);
|
||||
else
|
||||
vm = OrderTypeVMFactory.create(cd.CustomCase, id, display_name, model, security_md_source);
|
||||
vm.load_from(cd);
|
||||
vm = OrderTypeVMFactory.create(cd.Value.CustomCase, id, display_name, model, security_md_source);
|
||||
vm.load_from(cd.Value);
|
||||
return vm;
|
||||
}
|
||||
InlineUserProperty<IOrderTypeVM> get_order_type_vm_prop(InlineOrderProperties p) {
|
||||
|
||||
@@ -19,7 +19,8 @@ namespace mbts.spark {
|
||||
var so = model.factory.spreader_order();
|
||||
so.Security = s.CloneToMutable();
|
||||
so.ExecutionStrategy = strategy.CloneToMutable();
|
||||
return new InlineUserProperty<SpreaderOrderDefinitionImmutable>(so, true);
|
||||
var soi = new SpreaderOrderDefinitionImmutable(so);
|
||||
return new InlineUserProperty<SpreaderOrderDefinitionImmutable>(soi, true);
|
||||
})), id, display_name) { }
|
||||
|
||||
public SpreaderOrderVM(
|
||||
|
||||
@@ -93,10 +93,13 @@ namespace mbts.spark {
|
||||
#endregion
|
||||
|
||||
#region Input
|
||||
public async Task<uint?> send_order(OrderDefinitionImmutable order, double price, uint size) {
|
||||
var result = (await send_order_get_error(order, price, size));
|
||||
_activity.on_send_order(order, price, size, result.key, result.error);
|
||||
return result.key;
|
||||
public async Task<uint?> send_order(OrderDefinitionImmutable? order, double price, uint size) {
|
||||
if (order.HasValue) {
|
||||
var result = (await send_order_get_error(order.Value, price, size));
|
||||
_activity.on_send_order(order.Value, price, size, result.key, result.error);
|
||||
return result.key;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
async Task<(uint? key, string error)> send_order_get_error(OrderDefinitionImmutable order, double price, uint size) {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Platforms>x64</Platforms>
|
||||
<RuntimeIdentifiers>win7-x64</RuntimeIdentifiers>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<LanguageTargets>$(MSBuildExtensionsPath)\$(VisualStudioVersion)\Bin\Microsoft.CSharp.targets</LanguageTargets>
|
||||
<ApplicationIcon>logo.ico</ApplicationIcon>
|
||||
|
||||
@@ -3,6 +3,7 @@ using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
@@ -218,9 +218,7 @@ namespace mbts
|
||||
private Func<T, T, int> _comparer_func;
|
||||
public DelegateComparer(Func<T, T, int> comparer_func) { _comparer_func = comparer_func; }
|
||||
public int Compare(T x, T y) { return _comparer_func(x, y); }
|
||||
}
|
||||
|
||||
public static void ignore(this Task t) { }
|
||||
}
|
||||
|
||||
public static Google.Protobuf.WellKnownTypes.Timestamp ToGoogleTime(this DateTime self) {
|
||||
return Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(self);
|
||||
|
||||
@@ -22,12 +22,12 @@ namespace mbts.ui.settings
|
||||
readonly PersistenceService _persist;
|
||||
|
||||
[ImportingConstructor]
|
||||
public UISettingsFactory(UserService login, [Import("settings")] PersistenceService persist) {
|
||||
public UISettingsFactory(AuthService login, [Import("settings")] PersistenceService persist) {
|
||||
_persist = persist;
|
||||
_users = get_users(login);
|
||||
}
|
||||
|
||||
async Task<User[]> get_users(UserService login) {
|
||||
async Task<User[]> get_users(AuthService login) {
|
||||
var users = await login.get_users_async(null);
|
||||
if (users.success) return users.value.Users.ToArray();
|
||||
else return new User[0];
|
||||
|
||||
@@ -120,7 +120,7 @@ namespace mbts.persist
|
||||
public IObservable<IEnumerable<PersistResult<T>>> subscribe<T>(string ns, string pattern) {
|
||||
var re = new Regex(pattern);
|
||||
return _persist
|
||||
.subscribe_ns(new NamespaceSubArgs() { Ns = uns(ns) })
|
||||
.subscribe_ns(uns(ns))
|
||||
.Select(update =>
|
||||
update.added
|
||||
.Where(item => re.IsMatch(item.Key))
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
using System;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
|
||||
namespace mbts.spark {
|
||||
class CellProperty<T> : ISubject<T, (T oldval, T newval)> {
|
||||
readonly Func<T, string> _format;
|
||||
readonly IRaiseNotifyPropertyChanged _owner;
|
||||
readonly string _prop_name;
|
||||
string _text;
|
||||
|
||||
public CellProperty(T initial_value = default)
|
||||
: this(t => t.ToString(), initial_value) { }
|
||||
public CellProperty(IRaiseNotifyPropertyChanged owner, string prop_name, T initial_value = default)
|
||||
: this(owner, prop_name, t => t.ToString(), initial_value) { }
|
||||
|
||||
public CellProperty(Func<T, string> format, T initial_value = default) {
|
||||
public CellProperty(IRaiseNotifyPropertyChanged owner, string prop_name, Func<T, string> format, T initial_value = default) {
|
||||
_owner = owner;
|
||||
_prop_name = prop_name;
|
||||
_format = format;
|
||||
value = initial_value;
|
||||
}
|
||||
@@ -24,6 +29,7 @@ namespace mbts.spark {
|
||||
if (!value.Equals(this.value)) {
|
||||
OnNext(value);
|
||||
dirty = true;
|
||||
_owner.raise_notify_property_changed(_prop_name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,9 +34,11 @@ namespace bts.view.controls {
|
||||
private void any_button_click(object sender, RoutedEventArgs e) {
|
||||
Close();
|
||||
}
|
||||
internal static void Show(ErrorReportService srv, ErrorReport report) {
|
||||
var window = new SupportRequestWindow(new SupportRequestVM(srv, report));
|
||||
window.Show();
|
||||
internal static void Show(ErrorReportService srv, ErrorReportImmutable? report) {
|
||||
if (report.HasValue) {
|
||||
var window = new SupportRequestWindow(new SupportRequestVM(srv, report.Value));
|
||||
window.Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.converters {
|
||||
class ResponsiveTextConverter : IValueConverter {
|
||||
|
||||
public string ShortText { get; set; }
|
||||
public string LongText { get; set; }
|
||||
public double Threshold { get; set; }
|
||||
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
if (value is double _input) {
|
||||
if (_input < Threshold) {
|
||||
return ShortText;
|
||||
} else {
|
||||
return LongText;
|
||||
}
|
||||
}
|
||||
return LongText;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using mbts.config;
|
||||
using mbts.ui.native;
|
||||
using mbts.utils;
|
||||
using bts.co2;
|
||||
using bts.api.user;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.persistence;
|
||||
using bts;
|
||||
using bts.api.user;
|
||||
using mbts.config;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace mbts.ui.shell
|
||||
{
|
||||
namespace mbts.ui.shell {
|
||||
// The LoginContainer participates in the startup Import/Export dependency system
|
||||
// Since the login form should not depend on *anything* it will be shown before this is created
|
||||
// Once this is created it access the global LoginModel and retrieves the username
|
||||
internal class LoginContainer
|
||||
{
|
||||
[ImportingConstructor]
|
||||
public LoginContainer(UserService user, [Import("settings")] PersistenceService persist) {
|
||||
public LoginContainer(ServiceContext context) {
|
||||
var model = LoginModel.instance;
|
||||
model.provide_services(user, persist);
|
||||
model.provide_services(context);
|
||||
// await_login pumps the dispatcher but doesn't allow control to return to MEF until the username and workspace are available
|
||||
model.await_login();
|
||||
var u = model.get_user();
|
||||
UserName = u.name;
|
||||
UserId = u.id;
|
||||
Token = u.token;
|
||||
(UserName,UserId,Token) = model.get_user();
|
||||
Workspace = model.get_workspace();
|
||||
model.Dispose();
|
||||
}
|
||||
@@ -44,21 +34,19 @@ namespace mbts.ui.shell
|
||||
[Export("userid")]
|
||||
public uint UserId { get; private set; }
|
||||
[Export]
|
||||
public AuthToken Token { get; private set; }
|
||||
public AuthTokenImmutable Token { get; private set; }
|
||||
|
||||
[Export("workspace")]
|
||||
public string Workspace { get; private set; }
|
||||
}
|
||||
|
||||
public struct User {
|
||||
public User(string name, uint id, AuthToken token) {
|
||||
public User(string name, uint id) {
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
this.token = token;
|
||||
}
|
||||
public readonly string name;
|
||||
public readonly UInt32 id;
|
||||
public readonly AuthToken token;
|
||||
}
|
||||
|
||||
public class LoginModel : IDisposable
|
||||
@@ -66,11 +54,10 @@ namespace mbts.ui.shell
|
||||
// Singleton to help bootstrap the system
|
||||
private static LoginModel _instance = new LoginModel();
|
||||
public static LoginModel instance { get { return _instance; } }
|
||||
private User _user;
|
||||
private string _workspace;
|
||||
private PreloginWorkspaceEnumerator _enum_workspace;
|
||||
|
||||
UserService _logon;
|
||||
|
||||
ServiceContext _ctx;
|
||||
DispatcherFrame _frame = new DispatcherFrame();
|
||||
|
||||
private LoginModel() {
|
||||
@@ -82,13 +69,14 @@ namespace mbts.ui.shell
|
||||
public string ErrorMessage { get; private set; }
|
||||
public bool HasFatalError { get; set; }
|
||||
public User[] Users { get; private set; }
|
||||
|
||||
|
||||
|
||||
|
||||
// The final username
|
||||
public User get_user() {
|
||||
Log.assert_d(!string.IsNullOrEmpty(_user.name));
|
||||
return _user;
|
||||
public (string, uint, AuthTokenImmutable) get_user() {
|
||||
Log.assert_d(!string.IsNullOrEmpty(_ctx.user));
|
||||
return (_ctx.user, _ctx.user_id, _ctx.token.Value);
|
||||
}
|
||||
|
||||
public string get_workspace() {
|
||||
return _workspace ?? "";
|
||||
}
|
||||
@@ -112,13 +100,13 @@ namespace mbts.ui.shell
|
||||
}
|
||||
|
||||
// Wait for the hub to create the login doodad
|
||||
(var users, var persist) = await _hub_src.Task;
|
||||
_logon = users;
|
||||
_ctx = await _hub_src.Task;
|
||||
// Populate the UI
|
||||
_enum_workspace = new PreloginWorkspaceEnumerator(persist);
|
||||
var response = await _logon.get_users_async(new GetUsersRequest());
|
||||
_enum_workspace = new PreloginWorkspaceEnumerator(_ctx.locate<PersistenceService>("settings"));
|
||||
var auth = _ctx.locate<AuthService>();
|
||||
var response = await auth.get_users_async(new GetUsersRequest());
|
||||
if (response.success) {
|
||||
Users = response.value.Users.Select(x => new User(x.Login,x.Id, null)).OrderBy(u => u.name).ToArray();
|
||||
Users = response.value.Users.Select(x => new User(x.Login,x.Id)).OrderBy(u => u.name).ToArray();
|
||||
}
|
||||
else {
|
||||
ErrorMessage = "Logon: " + response.error_message;
|
||||
@@ -129,49 +117,37 @@ namespace mbts.ui.shell
|
||||
|
||||
#endregion
|
||||
|
||||
public async Task<(bool success,string info)> do_login(string username, string workspace, string password) {
|
||||
public async Task<LoginResult> do_login(string username, string workspace, string password) {
|
||||
if (Users.Select(u => u.name).Contains(username)) {
|
||||
var response = await _logon.authenticate_login_async(new AuthenticateLoginRequest() {
|
||||
Login = username,
|
||||
Password = password ??""
|
||||
});
|
||||
if (response.success) {
|
||||
if (response.value.IsValid) {
|
||||
_workspace = workspace;
|
||||
set_authenticated_user(new User(username, response.value.UserId, response.value.Token));
|
||||
return (true, "");
|
||||
}
|
||||
else {
|
||||
return (false, response.value.Reason);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return (false, response.error_message);
|
||||
var result = await _ctx.login(username, password);
|
||||
if (result.success) {
|
||||
_workspace = workspace;
|
||||
on_user_authenticated(_ctx.user, _ctx.token);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return (false, "Invalid username");
|
||||
return new LoginResult("Invalid username");
|
||||
}
|
||||
|
||||
private void set_authenticated_user(User user) {
|
||||
Log.assert(!string.IsNullOrEmpty(user.name));
|
||||
Log.assert(Users.Select(u => u.name).Contains(user.name));
|
||||
_user = user;
|
||||
Environment.SetEnvironmentVariable("BTS_USER", user.name);
|
||||
private void on_user_authenticated(string username, AuthTokenImmutable? token) {
|
||||
Log.assert(!string.IsNullOrEmpty(username));
|
||||
Log.assert(Users.Select(u => u.name).Contains(username));
|
||||
Environment.SetEnvironmentVariable("BTS_USER", username);
|
||||
_frame.Continue = false;
|
||||
}
|
||||
|
||||
public void await_login() {
|
||||
if (string.IsNullOrEmpty(_user.name)) {
|
||||
if (!_ctx.token.HasValue) {
|
||||
Dispatcher.PushFrame(_frame);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region provide hub
|
||||
public void provide_services(UserService u, PersistenceService p) {
|
||||
_hub_src.TrySetResult((u,p));
|
||||
internal void provide_services(ServiceContext ctx) {
|
||||
_hub_src.TrySetResult(ctx);
|
||||
}
|
||||
private TaskCompletionSource<(UserService, PersistenceService)> _hub_src = new TaskCompletionSource<(UserService, PersistenceService)>();
|
||||
private TaskCompletionSource<ServiceContext> _hub_src = new TaskCompletionSource<ServiceContext>();
|
||||
#endregion
|
||||
|
||||
#region Dispose
|
||||
|
||||
@@ -138,14 +138,15 @@ namespace mbts.ui.shell
|
||||
if (selected_workspace == "New workspace...") {
|
||||
selected_workspace = null;
|
||||
}
|
||||
(bool success, string message) = await _model.do_login(Name, selected_workspace, Password);
|
||||
if (success) { VisualState = "Loading"; }
|
||||
if (!success && !string.IsNullOrEmpty(message)) {
|
||||
WarningMessage = message;
|
||||
var result = await _model.do_login(Name, selected_workspace, Password);
|
||||
if (result.success) { VisualState = "Loading"; }
|
||||
if (!result.success && !string.IsNullOrEmpty(result.reason)) {
|
||||
WarningMessage = result.reason;
|
||||
VisualState = "BadPassword";
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(message)) {
|
||||
BTSMessageBox.Show(message, "Login Message", BTSMessageBoxButtons.OK, MessageBoxImage.Information);
|
||||
else if (!string.IsNullOrEmpty(result.reason)) {
|
||||
// Login was successful but we have a message for the user
|
||||
BTSMessageBox.Show(result.reason, "Login Message", BTSMessageBoxButtons.OK, MessageBoxImage.Information);
|
||||
}
|
||||
}
|
||||
private void OnCancel(object unused) {
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace bts.settings {
|
||||
public class SettingsManagerTool {
|
||||
public static void run() {
|
||||
var sc = new ServiceContext();
|
||||
var users = sc.locate<UserService>();
|
||||
var users = sc.locate<AuthService>();
|
||||
var persist = sc.locate<PersistenceService>("settings");
|
||||
var user_list = users.get_users(new GetUsersRequest());
|
||||
var user_names = user_list.Users.Select(u => u.Login);
|
||||
|
||||
@@ -123,7 +123,7 @@ namespace mbts.spark {
|
||||
return intpart + floatpart;
|
||||
}
|
||||
|
||||
public static RegionalTickFactors get_regional_ticks(ContractSpecificationImmutable spec) =>
|
||||
public static RegionalTickFactors get_regional_ticks(ContractSpecificationImmutable? spec) =>
|
||||
TickStore.get(spec);
|
||||
|
||||
public static RegionalTickFactors get_regional_ticks(SecurityImmutable security) =>
|
||||
@@ -177,8 +177,14 @@ namespace mbts.spark {
|
||||
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(ContractSpecificationImmutable? spec) {
|
||||
if (spec.HasValue) {
|
||||
return _exchange_ticks.GetOrAdd(new SpecKey(spec.Value), _create_rtf);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static RegionalTickFactors get(SecurityImmutable security) =>
|
||||
security.HasContractSpec ? get(security.ContractSpec) : default_rtf;
|
||||
|
||||
@@ -110,7 +110,7 @@ namespace mbts.spark {
|
||||
{
|
||||
var lgh = source as LadderGridHost;
|
||||
double width_adjustment = (double)(uint) e.NewValue - (double)(uint) e.OldValue;
|
||||
lgh._adjust_column_width(lgh.columns.First(c => c.column_type == ColumnType.Order), width_adjustment);
|
||||
lgh._adjust_column_width(lgh.columns.FirstOrDefault(c => c.column_type == ColumnType.Order), width_adjustment);
|
||||
}
|
||||
|
||||
public uint bid_column_width
|
||||
@@ -126,7 +126,7 @@ namespace mbts.spark {
|
||||
{
|
||||
var lgh = source as LadderGridHost;
|
||||
double width_adjustment = (double)(uint) e.NewValue - (double)(uint) e.OldValue;
|
||||
lgh._adjust_column_width(lgh.columns.First(c => c.column_type == ColumnType.Bid), width_adjustment);
|
||||
lgh._adjust_column_width(lgh.columns.FirstOrDefault(c => c.column_type == ColumnType.Bid), width_adjustment);
|
||||
}
|
||||
|
||||
public uint price_column_width
|
||||
@@ -142,7 +142,7 @@ namespace mbts.spark {
|
||||
{
|
||||
var lgh = source as LadderGridHost;
|
||||
double width_adjustment = (double)(uint) e.NewValue - (double)(uint) e.OldValue;
|
||||
lgh._adjust_column_width(lgh.columns.First(c => c.column_type == ColumnType.Price), width_adjustment);
|
||||
lgh._adjust_column_width(lgh.columns.FirstOrDefault(c => c.column_type == ColumnType.Price), width_adjustment);
|
||||
}
|
||||
|
||||
|
||||
@@ -159,14 +159,14 @@ namespace mbts.spark {
|
||||
{
|
||||
var lgh = source as LadderGridHost;
|
||||
double width_adjustment = (double)(uint) e.NewValue - (double)(uint) e.OldValue;
|
||||
lgh._adjust_column_width(lgh.columns.First(c => c.column_type == ColumnType.Ask), width_adjustment);
|
||||
lgh._adjust_column_width(lgh.columns.FirstOrDefault(c => c.column_type == ColumnType.Ask), width_adjustment);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty VolumeColumnWidthProperty =
|
||||
WPFHelper.create_dp<uint, LadderGridHost>(
|
||||
nameof(VolumeColumnWidth),
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||
(o, oldval, newval) => o._adjust_column_width(o.columns.First(c => c.column_type == ColumnType.Volume), (double)newval - (double)oldval),
|
||||
(o, oldval, newval) => o._adjust_column_width(o.columns.FirstOrDefault(c => c.column_type == ColumnType.Volume), (double)newval - (double)oldval),
|
||||
50u);
|
||||
|
||||
public uint VolumeColumnWidth {
|
||||
@@ -178,7 +178,7 @@ namespace mbts.spark {
|
||||
WPFHelper.create_dp<uint, LadderGridHost>(
|
||||
nameof(LastColumnWidth),
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||
(o, oldval, newval) => o._adjust_column_width(o.columns.First(c => c.column_type == ColumnType.Last), (double)newval - (double)oldval),
|
||||
(o, oldval, newval) => o._adjust_column_width(o.columns.FirstOrDefault(c => c.column_type == ColumnType.Last), (double)newval - (double)oldval),
|
||||
50u);
|
||||
|
||||
public uint LastColumnWidth {
|
||||
@@ -327,6 +327,8 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
void _adjust_column_width(Column column, double adjustment) {
|
||||
if (column == null)
|
||||
return;
|
||||
int new_col_width;
|
||||
switch (column.column_type) {
|
||||
case ColumnType.Order: new_col_width = (int)orders_column_width; break;
|
||||
@@ -662,22 +664,10 @@ namespace mbts.spark {
|
||||
} else {
|
||||
init = false;
|
||||
prev_row_height = _settings.row_height;
|
||||
// This is a general issue with default-settings:
|
||||
// "Not every property in the settings object should be shared across windows."
|
||||
// Column widths are one such property (they are changed from the actual Ladder window, not the settings dialog).
|
||||
// Below is a hack which prevents overwriting current column widths; until we come up with a better solution this will suffice.
|
||||
// TODO: fix this by moving such properties from the settings proto into a separate proto (or come up with a better workaround/solution)
|
||||
copy_col_width(v => v.orders_column);
|
||||
copy_col_width(v => v.bid_column);
|
||||
copy_col_width(v => v.price_column);
|
||||
copy_col_width(v => v.ask_column);
|
||||
copy_col_width(v => v.volume_column.general);
|
||||
copy_col_width(v => v.last_column);
|
||||
void copy_col_width(Func<LadderSettings, LadderColumnSettings> select_col) =>
|
||||
select_col(s).width = select_col(_settings).width;
|
||||
}
|
||||
//
|
||||
_settings = s;
|
||||
/// The viewmodel will always have the most up-to-date settings, so ignore <see cref="s"/> and just get it from <see cref="_vm"/>.
|
||||
/// This is a workaround for the global settings-defaults issue: when defaults are updated, some properties are inadvertantly overwritten, such as column-widths, etc.
|
||||
_settings = _vm.get_save_state();
|
||||
_bid_vis_tup.glyph_context = glyph_context(s.bid_column);
|
||||
_ask_vis_tup.glyph_context = glyph_context(s.ask_column);
|
||||
_price_vis_tup.glyph_context = glyph_context(s.price_column);
|
||||
@@ -698,18 +688,19 @@ namespace mbts.spark {
|
||||
// Renders the grid lines and column backgrounds
|
||||
_visualHost.AddVisualChild(_gridvis);
|
||||
// Orders column
|
||||
orders_column_width = s.orders_column.width;
|
||||
SetCurrentValue(orders_column_widthProperty, s.orders_column.width);
|
||||
_ordershost.update_settings(_price_list, _price_comparer, s.row_height, s.orders_column);
|
||||
foreach (var col in columns) {
|
||||
if (col.visual_tup != null) {
|
||||
if (col.settings.show) {
|
||||
_visualHost.AddVisualChild(col.visual_tup.visual);
|
||||
switch (col.column_type) {
|
||||
case ColumnType.Bid: bid_column_width = col.settings.width; break;
|
||||
case ColumnType.Price: price_column_width = col.settings.width; break;
|
||||
case ColumnType.Ask: ask_column_width = col.settings.width; break;
|
||||
case ColumnType.Bid: SetCurrentValue(bid_column_widthProperty, col.settings.width); break;
|
||||
case ColumnType.Price: SetCurrentValue(price_column_widthProperty, col.settings.width); break;
|
||||
case ColumnType.Ask: SetCurrentValue(ask_column_widthProperty, col.settings.width); break;
|
||||
case ColumnType.Last: SetCurrentValue(LastColumnWidthProperty, col.settings.width); break;
|
||||
case ColumnType.Volume:
|
||||
VolumeColumnWidth = col.settings.width;
|
||||
SetCurrentValue(VolumeColumnWidthProperty, col.settings.width);
|
||||
_volume_container.mark_dirty();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Windows;
|
||||
using bts.proto;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace mbts.spark {
|
||||
// IMPORTANT: BE SURE TO ADD ANY NEW PROPERTIES TO THE EQUALS FUNCTION!
|
||||
@@ -179,6 +180,34 @@ namespace mbts.spark {
|
||||
// Settings are liable to change in the near future, so an internal IEqualityComparer simply won't do as it risks becoming out of date.
|
||||
[ProtoContract]
|
||||
class LadderSettings : IEquatable<LadderSettings> {
|
||||
const double _default_order_entry_panel_width = 110.0;
|
||||
const double _default_zoom_factor = 1.0;
|
||||
|
||||
// Default proto constructor
|
||||
// Added this so that when clients upgrade to a version with new settings,
|
||||
// the default values are applied correctly; thx to John for this tip
|
||||
public LadderSettings() {
|
||||
day_visible = true;
|
||||
high_visible = true;
|
||||
low_visible = true;
|
||||
open_visible = true;
|
||||
settle_visible = true;
|
||||
volume_visible = true;
|
||||
account_visible = true;
|
||||
tif_visible = true;
|
||||
order_type_visible = true;
|
||||
account_position_visible = true;
|
||||
size_presets_visible = true;
|
||||
cancel_buys_visible = true;
|
||||
cancel_sells_visible = true;
|
||||
cancel_all_visible = true;
|
||||
auto_clear_visible = true;
|
||||
clear_visible = true;
|
||||
order_entry_panel_initial_width = _default_order_entry_panel_width;
|
||||
zoom_factor = _default_zoom_factor;
|
||||
use_defaults = true;
|
||||
}
|
||||
|
||||
public static LadderSettings Default {
|
||||
get {
|
||||
LadderColumnSettings
|
||||
@@ -216,7 +245,26 @@ namespace mbts.spark {
|
||||
session_high_line = new LadderMarketBreakLine { color = BTSColors.MarketData.session_high, show = true },
|
||||
session_range_fill = new LadderMarketBreakLine { color = BTSColors.MarketData.session_range_fill, show = true },
|
||||
alternate_order_size = 0,
|
||||
size_display = MarketSizeDisplay.get_default()
|
||||
size_display = MarketSizeDisplay.get_default(),
|
||||
day_visible = true,
|
||||
high_visible = true,
|
||||
low_visible = true,
|
||||
open_visible = true,
|
||||
settle_visible = true,
|
||||
volume_visible = true,
|
||||
account_visible = true,
|
||||
tif_visible = true,
|
||||
order_type_visible = true,
|
||||
account_position_visible = true,
|
||||
size_presets_visible = true,
|
||||
cancel_buys_visible = true,
|
||||
cancel_sells_visible = true,
|
||||
cancel_all_visible = true,
|
||||
auto_clear_visible = true,
|
||||
clear_visible = true,
|
||||
order_entry_panel_initial_width = _default_order_entry_panel_width,
|
||||
zoom_slider_visible = false,
|
||||
zoom_factor = _default_zoom_factor,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -278,6 +326,54 @@ namespace mbts.spark {
|
||||
[ProtoMember(26)]
|
||||
public MarketSizeDisplay size_display { get; set; }
|
||||
|
||||
// order entry panel items show/hide
|
||||
[ProtoMember(27), DefaultValue(true)]
|
||||
public bool day_visible { get; set; }
|
||||
[ProtoMember(28), DefaultValue(true)]
|
||||
public bool high_visible { get; set; }
|
||||
[ProtoMember(29), DefaultValue(true)]
|
||||
public bool low_visible { get; set; }
|
||||
[ProtoMember(30), DefaultValue(true)]
|
||||
public bool open_visible { get; set; }
|
||||
[ProtoMember(31), DefaultValue(true)]
|
||||
public bool settle_visible { get; set; }
|
||||
[ProtoMember(32), DefaultValue(true)]
|
||||
public bool volume_visible { get; set; }
|
||||
[ProtoMember(33), DefaultValue(true)]
|
||||
public bool account_visible { get; set; }
|
||||
[ProtoMember(34), DefaultValue(true)]
|
||||
public bool tif_visible { get; set; }
|
||||
[ProtoMember(35), DefaultValue(true)]
|
||||
public bool order_type_visible { get; set; }
|
||||
[ProtoMember(36), DefaultValue(true)]
|
||||
public bool account_position_visible { get; set; }
|
||||
[ProtoMember(37), DefaultValue(true)]
|
||||
public bool size_presets_visible { get; set; }
|
||||
[ProtoMember(38), DefaultValue(true)]
|
||||
public bool cancel_buys_visible { get; set; }
|
||||
[ProtoMember(39), DefaultValue(true)]
|
||||
public bool cancel_sells_visible { get; set; }
|
||||
[ProtoMember(40), DefaultValue(true)]
|
||||
public bool cancel_all_visible { get; set; }
|
||||
[ProtoMember(41), DefaultValue(true)]
|
||||
public bool auto_clear_visible { get; set; }
|
||||
[ProtoMember(42), DefaultValue(true)]
|
||||
public bool clear_visible { get; set; }
|
||||
//
|
||||
[ProtoMember(43), DefaultValue(_default_order_entry_panel_width)]
|
||||
public double order_entry_panel_initial_width { get; set; }
|
||||
|
||||
[ProtoMember(44), DefaultValue(true)]
|
||||
public bool use_defaults { get; set; }
|
||||
|
||||
// the zoom slider is the first new widget on the panel
|
||||
// since our initial general release, so its enablement
|
||||
// will be opt-in (default not visible)
|
||||
[ProtoMember(45)]
|
||||
public bool zoom_slider_visible { get; set; }
|
||||
[ProtoMember(46), DefaultValue(_default_zoom_factor)]
|
||||
public double zoom_factor { get; set; }
|
||||
|
||||
public bool Equals(LadderSettings other) =>
|
||||
other != null &&
|
||||
row_height == other.row_height &&
|
||||
@@ -302,7 +398,27 @@ namespace mbts.spark {
|
||||
session_high_line.safe_equals(other.session_high_line) &&
|
||||
session_range_fill.safe_equals(other.session_range_fill) &&
|
||||
volume_column.safe_equals(other.volume_column) &&
|
||||
size_display.safe_equals(other.size_display);
|
||||
size_display.safe_equals(other.size_display) &&
|
||||
day_visible == other.day_visible &&
|
||||
high_visible == other.high_visible &&
|
||||
low_visible == other.low_visible &&
|
||||
open_visible == other.open_visible &&
|
||||
settle_visible == other.settle_visible &&
|
||||
volume_visible == other.volume_visible &&
|
||||
account_visible == other.account_visible &&
|
||||
tif_visible == other.tif_visible &&
|
||||
order_type_visible == other.order_type_visible &&
|
||||
account_position_visible == other.account_position_visible &&
|
||||
size_presets_visible == other.size_presets_visible &&
|
||||
cancel_buys_visible == other.cancel_buys_visible &&
|
||||
cancel_sells_visible == other.cancel_sells_visible &&
|
||||
cancel_all_visible == other.cancel_all_visible &&
|
||||
auto_clear_visible == other.auto_clear_visible &&
|
||||
clear_visible == other.clear_visible &&
|
||||
order_entry_panel_initial_width == other.order_entry_panel_initial_width &&
|
||||
zoom_slider_visible == other.zoom_slider_visible &&
|
||||
zoom_factor == other.zoom_factor &&
|
||||
use_defaults == other.use_defaults;
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as LadderSettings);
|
||||
|
||||
@@ -330,7 +446,46 @@ namespace mbts.spark {
|
||||
EqualityComparer.safe_get_hash_code(session_high_line),
|
||||
EqualityComparer.safe_get_hash_code(session_range_fill),
|
||||
EqualityComparer.safe_get_hash_code(volume_column),
|
||||
EqualityComparer.safe_get_hash_code(size_display));
|
||||
EqualityComparer.safe_get_hash_code(size_display),
|
||||
day_visible.GetHashCode(),
|
||||
high_visible.GetHashCode(),
|
||||
low_visible.GetHashCode(),
|
||||
open_visible.GetHashCode(),
|
||||
settle_visible.GetHashCode(),
|
||||
volume_visible.GetHashCode(),
|
||||
account_visible.GetHashCode(),
|
||||
tif_visible.GetHashCode(),
|
||||
order_type_visible.GetHashCode(),
|
||||
account_position_visible.GetHashCode(),
|
||||
size_presets_visible.GetHashCode(),
|
||||
cancel_buys_visible.GetHashCode(),
|
||||
cancel_sells_visible.GetHashCode(),
|
||||
cancel_all_visible.GetHashCode(),
|
||||
auto_clear_visible.GetHashCode(),
|
||||
clear_visible.GetHashCode(),
|
||||
order_entry_panel_initial_width.GetHashCode(),
|
||||
zoom_slider_visible.GetHashCode(),
|
||||
zoom_factor.GetHashCode(),
|
||||
use_defaults.GetHashCode());
|
||||
|
||||
public struct WindowSpecifics {
|
||||
public readonly double order_entry_panel_width, zoom_factor;
|
||||
public readonly uint orders_column_width, bid_column_width, price_column_width, ask_column_width, last_column_width, volume_column_width;
|
||||
|
||||
public WindowSpecifics(LadderSettings s)
|
||||
: this(s.order_entry_panel_initial_width, s.orders_column.width, s.bid_column.width, s.price_column.width, s.ask_column.width, s.last_column.width, s.volume_column.general.width, s.zoom_factor) { }
|
||||
|
||||
public WindowSpecifics(double order_entry_panel_width, uint orders_column_width, uint bid_column_width, uint price_column_width, uint ask_column_width, uint last_column_width, uint volume_column_width, double zoom_factor) {
|
||||
this.order_entry_panel_width = order_entry_panel_width;
|
||||
this.orders_column_width = orders_column_width;
|
||||
this.bid_column_width = bid_column_width;
|
||||
this.price_column_width = price_column_width;
|
||||
this.ask_column_width = ask_column_width;
|
||||
this.last_column_width = last_column_width;
|
||||
this.volume_column_width = volume_column_width;
|
||||
this.zoom_factor = zoom_factor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class LadderSettingsExtensions {
|
||||
@@ -345,5 +500,22 @@ namespace mbts.spark {
|
||||
|
||||
public static IEnumerable<LadderColumnSettings> visible_columns(this LadderSettings settings) =>
|
||||
settings.all_columns().Where(c => c.show);
|
||||
|
||||
public static LadderSettings.WindowSpecifics get_window_specifics(this LadderSettings settings) =>
|
||||
new LadderSettings.WindowSpecifics(settings);
|
||||
|
||||
public static LadderSettings with_window_specifics(this LadderSettings source, LadderSettings.WindowSpecifics specifics) {
|
||||
// must use clone bc we are applying settings that are specific to each individual LadderWindow, and should not be shared among all ladders (even if it is default settings).
|
||||
source = SparkUtils.clone_proto(source);
|
||||
source.order_entry_panel_initial_width = specifics.order_entry_panel_width;
|
||||
source.orders_column.width = specifics.orders_column_width;
|
||||
source.bid_column.width = specifics.bid_column_width;
|
||||
source.price_column.width = specifics.price_column_width;
|
||||
source.ask_column.width = specifics.ask_column_width;
|
||||
source.last_column.width = specifics.last_column_width;
|
||||
source.volume_column.general.width = specifics.volume_column_width;
|
||||
source.zoom_factor = specifics.zoom_factor;
|
||||
return source;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ namespace mbts.spark {
|
||||
readonly Property<LadderSettings.BadgeStyle> _badge_style;
|
||||
readonly Property<uint> _alternate_order_size;
|
||||
readonly DelegateProperty<double> _row_height;
|
||||
Func<double> _get_order_entry_panel_width, _get_zoom_factor;
|
||||
LadderSettings _current;
|
||||
LadderColumnSettingsVM
|
||||
_orders = new OrdersColumnSettingsVM(),
|
||||
@@ -32,14 +33,35 @@ namespace mbts.spark {
|
||||
_session_low_mkt_line = new LadderMarketBreakLineVM("Session Low", 2, 1),
|
||||
_session_high_mkt_line = new LadderMarketBreakLineVM("Session High", 2, 1, false),
|
||||
_session_range_fill = new LadderMarketBreakLineVM("Session Range", 0, 0, false);
|
||||
OrderEntryPanelItemVisibilityVM
|
||||
_zoom_slider_visbility = new OrderEntryPanelItemVisibilityVM("Zoom Slider"),
|
||||
_day_visibility = new OrderEntryPanelItemVisibilityVM("Day Change"),
|
||||
_high_visibility = new OrderEntryPanelItemVisibilityVM("High"),
|
||||
_low_visibility = new OrderEntryPanelItemVisibilityVM("Low"),
|
||||
_open_visibility = new OrderEntryPanelItemVisibilityVM("Open"),
|
||||
_settle_visibility = new OrderEntryPanelItemVisibilityVM("Settle"),
|
||||
_volume_visibility = new OrderEntryPanelItemVisibilityVM("Volume"),
|
||||
_account_visibility = new OrderEntryPanelItemVisibilityVM("Account"),
|
||||
_tif_visibility = new OrderEntryPanelItemVisibilityVM("Time In Force"),
|
||||
_order_type_visibility = new OrderEntryPanelItemVisibilityVM("Order Type"),
|
||||
_account_position_visibility = new OrderEntryPanelItemVisibilityVM("Account Position"),
|
||||
_size_preset_visibility = new OrderEntryPanelItemVisibilityVM("Size Presets"),
|
||||
_auto_clear_visibility = new OrderEntryPanelItemVisibilityVM("Auto Clear"),
|
||||
_clear_visibility = new OrderEntryPanelItemVisibilityVM("Clear"),
|
||||
_cancel_buys_visibility = new OrderEntryPanelItemVisibilityVM("Cancel Buys"),
|
||||
_cancel_sells_visibility = new OrderEntryPanelItemVisibilityVM("Cancel Sells"),
|
||||
_cancel_all_visibility = new OrderEntryPanelItemVisibilityVM("Cancel All");
|
||||
|
||||
public LadderSettingsVM(bool is_synthetic) {
|
||||
public LadderSettingsVM(bool is_synthetic, Func<double> get_left_panel_width, Func<double> get_zoom_factor) {
|
||||
this.is_synthetic = is_synthetic;
|
||||
size_presets = new ObservableEditableCollection<IMutableValueWrapperVM<uint>>(_create_size_preset_vm);
|
||||
size_presets.CollectionChanged += (s, e) => RaisePropertyChanged(nameof(size_presets_text));
|
||||
_badge_style = backing<LadderSettings.BadgeStyle>(nameof(badge_style));
|
||||
_badge_style.Changed += _badge_style_Changed;
|
||||
_alternate_order_size = backing<uint>(nameof(alternate_order_size));
|
||||
_get_order_entry_panel_width = get_left_panel_width;
|
||||
_get_zoom_factor = get_zoom_factor;
|
||||
// be sure to initialize backings before this measure_row_height call
|
||||
_row_height = backing(nameof(row_height), () => LadderRender.measure_row_height(get_result()));
|
||||
_orders.font_size_changed += on_font_size_changed;
|
||||
_bid.font_size_changed += on_font_size_changed;
|
||||
@@ -108,6 +130,28 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<OrderEntryPanelItemVisibilityVM> order_entry_panel_items {
|
||||
get {
|
||||
yield return _zoom_slider_visbility;
|
||||
yield return _day_visibility;
|
||||
yield return _high_visibility;
|
||||
yield return _low_visibility;
|
||||
yield return _open_visibility;
|
||||
yield return _settle_visibility;
|
||||
yield return _volume_visibility;
|
||||
yield return _account_visibility;
|
||||
yield return _tif_visibility;
|
||||
yield return _order_type_visibility;
|
||||
yield return _account_position_visibility;
|
||||
yield return _size_preset_visibility;
|
||||
yield return _clear_visibility;
|
||||
yield return _auto_clear_visibility;
|
||||
yield return _cancel_buys_visibility;
|
||||
yield return _cancel_sells_visibility;
|
||||
yield return _cancel_all_visibility;
|
||||
}
|
||||
}
|
||||
|
||||
public string key => GridOrLadderSettingsSelectorKeys.Ladder;
|
||||
|
||||
public void load(LadderSettings settings) {
|
||||
@@ -128,6 +172,27 @@ namespace mbts.spark {
|
||||
_session_low_mkt_line.assign_from(_current.session_low_line);
|
||||
_session_high_mkt_line.assign_from(_current.session_high_line);
|
||||
_session_range_fill.assign_from(_current.session_range_fill);
|
||||
_zoom_slider_visbility.show = settings.zoom_slider_visible;
|
||||
_day_visibility.show = settings.day_visible;
|
||||
_high_visibility.show = settings.high_visible;
|
||||
_low_visibility.show = settings.low_visible;
|
||||
_open_visibility.show = settings.open_visible;
|
||||
_settle_visibility.show = settings.settle_visible;
|
||||
_volume_visibility.show = settings.volume_visible;
|
||||
_account_visibility.show = settings.account_visible;
|
||||
_tif_visibility.show = settings.tif_visible;
|
||||
_order_type_visibility.show = settings.order_type_visible;
|
||||
_account_position_visibility.show = settings.account_position_visible;
|
||||
_size_preset_visibility.show = settings.size_presets_visible;
|
||||
_clear_visibility.show = settings.clear_visible;
|
||||
_auto_clear_visibility.show = settings.auto_clear_visible;
|
||||
_cancel_buys_visibility.show = settings.cancel_buys_visible;
|
||||
_cancel_sells_visibility.show = settings.cancel_sells_visible;
|
||||
_cancel_all_visibility.show = settings.cancel_all_visible;
|
||||
// do not read the order entry panel width here from settings
|
||||
// because it could have changed from the UI
|
||||
// there might be a better way of doing this, like how
|
||||
// does the widths of the ladder grid column pull this off?
|
||||
}
|
||||
|
||||
public LadderSettings get_result() {
|
||||
@@ -149,6 +214,25 @@ namespace mbts.spark {
|
||||
clone.session_high_line = _session_high_mkt_line.get_settings();
|
||||
clone.session_range_fill = _session_range_fill.get_settings();
|
||||
clone.row_height = LadderRender.measure_row_height(clone);
|
||||
clone.day_visible = _day_visibility.show;
|
||||
clone.high_visible = _high_visibility.show;
|
||||
clone.low_visible = _low_visibility.show;
|
||||
clone.open_visible = _open_visibility.show;
|
||||
clone.settle_visible = _settle_visibility.show;
|
||||
clone.volume_visible = _volume_visibility.show;
|
||||
clone.account_visible = _account_visibility.show;
|
||||
clone.tif_visible = _tif_visibility.show;
|
||||
clone.order_type_visible = _order_type_visibility.show;
|
||||
clone.account_position_visible = _account_position_visibility.show;
|
||||
clone.size_presets_visible = _size_preset_visibility.show;
|
||||
clone.auto_clear_visible = _auto_clear_visibility.show;
|
||||
clone.clear_visible = _clear_visibility.show;
|
||||
clone.cancel_buys_visible = _cancel_buys_visibility.show;
|
||||
clone.cancel_sells_visible = _cancel_sells_visibility.show;
|
||||
clone.cancel_all_visible = _cancel_all_visibility.show;
|
||||
clone.order_entry_panel_initial_width = _get_order_entry_panel_width();
|
||||
clone.zoom_slider_visible = _zoom_slider_visbility.show;
|
||||
clone.zoom_factor = _get_zoom_factor();
|
||||
return clone;
|
||||
}
|
||||
|
||||
@@ -276,6 +360,19 @@ namespace mbts.spark {
|
||||
};
|
||||
}
|
||||
|
||||
public class OrderEntryPanelItemVisibilityVM : ViewModelBase {
|
||||
readonly Property<bool> _show;
|
||||
|
||||
public OrderEntryPanelItemVisibilityVM(string name) {
|
||||
this.name = name;
|
||||
_show = backing(nameof(show), true);
|
||||
}
|
||||
|
||||
public string name { get; }
|
||||
public bool show { get => _show.Value; set => _show.Value = value; }
|
||||
|
||||
}
|
||||
|
||||
public class VolumeColumnVM : ViewModelBase {
|
||||
readonly DelegateProperty<bool> _any_visible;
|
||||
public VolumeColumnVM() {
|
||||
|
||||
@@ -140,6 +140,7 @@
|
||||
<Setter Property="VerticalContentAlignment" Value="Top"/>
|
||||
</Style>
|
||||
</TabControl.ItemContainerStyle>
|
||||
<!-- Fonts and Colors Tab -->
|
||||
<TabItem Header="Fonts and Colors" VerticalAlignment="Stretch">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
@@ -232,6 +233,7 @@
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<!-- Market Break Lines Tab -->
|
||||
<TabItem Header="Market Break Lines" VerticalAlignment="Stretch">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
@@ -350,6 +352,7 @@
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<!-- Market Size Tab -->
|
||||
<TabItem Header="Market Size Display">
|
||||
<Grid DataContext="{Binding size_display}">
|
||||
<Grid.RowDefinitions>
|
||||
@@ -440,6 +443,7 @@
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<!-- Volume Display Tab -->
|
||||
<TabItem Header="Volume Display" VerticalAlignment="Stretch" Visibility="{Binding is_synthetic, Converter={StaticResource FalseVisibilityConverter}}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
@@ -557,6 +561,7 @@
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<!-- Size Presets Tab-->
|
||||
<TabItem Header="Size Presets" VerticalAlignment="Stretch">
|
||||
<Grid HorizontalAlignment="Left">
|
||||
<Grid.RowDefinitions>
|
||||
@@ -592,6 +597,63 @@
|
||||
</Button>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<!-- Order Entry Panel Item Control Visbility -->
|
||||
<TabItem Header="Left Panel" VerticalAlignment="Stretch">
|
||||
<Grid HorizontalAlignment="Left" Grid.IsSharedSizeScope="True">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ItemsControl Grid.Row="0" ItemsSource="{Binding order_entry_panel_items}" Grid.IsSharedSizeScope="True">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Columns="2" Focusable="False"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="A" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Margin="0,5,5,0">
|
||||
<CheckBox Content="{Binding name}" FlowDirection="RightToLeft" IsChecked="{Binding show}">
|
||||
<CheckBox.Resources>
|
||||
<Style TargetType="{x:Type Path}">
|
||||
<Setter Property="FlowDirection" Value="LeftToRight" />
|
||||
</Style>
|
||||
</CheckBox.Resources>
|
||||
</CheckBox>
|
||||
</Border>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<!--
|
||||
<ItemsControl ItemsSource="{Binding order_entry_panel_items}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Columns="2" Focusable="False"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="52"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" VerticalAlignment="Center" Text="{Binding name}"/>
|
||||
<CheckBox Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding show}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
-->
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
</TabControl>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -20,18 +20,24 @@ using System.Windows.Media;
|
||||
|
||||
namespace mbts.spark {
|
||||
class LadderVM : DisposableViewModelBase, IWillLetYouKnowWhenIDispose, IKeyListener {
|
||||
static readonly Brush _transparent_status_brush = BTSColors.UI.transparent;
|
||||
|
||||
readonly IBus _bus;
|
||||
readonly LadderModel _model;
|
||||
readonly Property<double> _high, _low, _change, _open, _settlement, _position;
|
||||
readonly Property<double> _high, _low, _change, _open, _settlement, _position, _left_panel_width, _zoom_factor;
|
||||
//readonly Property<double> _window_width, _window_height;
|
||||
readonly Property<uint> _orders_column_width, _bid_column_width, _price_column_width, _ask_column_width, _last_column_width, _volume_column_width;
|
||||
readonly Property<ulong> _volume;
|
||||
readonly Property<bool> _auto_clear;
|
||||
readonly Property<bool> _auto_clear, _show_day, _show_high, _show_low, _show_open, _show_settle, _show_volume, _show_account,
|
||||
_show_tif, _show_order_type, _show_account_position, _show_size_preset, _show_auto_clear, _show_clear, _show_cancel_buys,
|
||||
_show_cancel_sells, _show_cancel_all, _show_zoom;
|
||||
readonly Property<Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
|
||||
IWindowSettingsManager<LadderSettings> _settings_manager;
|
||||
ulong _misc_time = 0;
|
||||
IEqualityComparer<double> _equality;
|
||||
Dictionary<double, LadderOrderVM> _slot_price_vs_orders;
|
||||
|
||||
public LadderVM(IBus bus, LadderModel model, IWindowSettingsManager<LadderSettings> settings_manager) {
|
||||
public LadderVM(IBus bus, LadderModel model, IWindowSettingsManager<LadderSettings> settings_manager, LadderSettings init) {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
_equality = TickUtils.tick_equality_comparer(_model.security.ContractSpec);
|
||||
@@ -44,13 +50,23 @@ namespace mbts.spark {
|
||||
_change = backing(nameof(change), double.NaN);
|
||||
_open = backing(nameof(open), double.NaN);
|
||||
_settlement = backing(nameof(settlement), double.NaN);
|
||||
// init window-specific settings: these properties should not observe settings updates; they are independent from the settings dialog, so it doesn't make sense that the settings dialog should cause them to change.
|
||||
var ws = init.get_window_specifics();
|
||||
_orders_column_width = backing(nameof(orders_column_width), ws.orders_column_width);
|
||||
_bid_column_width = backing(nameof(bid_column_width), ws.bid_column_width);
|
||||
_price_column_width = backing(nameof(price_column_width), ws.price_column_width);
|
||||
_ask_column_width = backing(nameof(ask_column_width), ws.ask_column_width);
|
||||
_last_column_width = backing(nameof(last_column_width), ws.last_column_width);
|
||||
_volume_column_width = backing(nameof(volume_column_width), ws.volume_column_width);
|
||||
_left_panel_width = backing(nameof(left_panel_width), ws.order_entry_panel_width);
|
||||
_zoom_factor = backing(nameof(zoom_factor), ws.zoom_factor);
|
||||
// remember: account_positions needs to subscribe to selected_account; both the sub and account_positions need to be disposed
|
||||
var account_positions = new KeyDrivenObservableSeries<string, double>(a => _model.order_gateway.security_total_position(security.Id, a));
|
||||
_position = backing(nameof(position), account_positions.OnDispatcher());
|
||||
_volume = backing(nameof(volume), 0ul);
|
||||
_auto_clear = backing(nameof(auto_clear), false);
|
||||
_status = backing(nameof(status_message), "");
|
||||
_status_brush = backing(nameof(status_background), (Brush)BTSColors.UI.spark_menu_background);
|
||||
_status_brush = backing(nameof(status_background), _transparent_status_brush);
|
||||
// brush props
|
||||
_bid_background = brush_prop(nameof(bid_background), s => s.bid_column.background.rgb_brush());
|
||||
_bid_foreground = brush_prop(nameof(bid_foreground), s => s.bid_column.foreground.rgb_brush());
|
||||
@@ -58,7 +74,31 @@ namespace mbts.spark {
|
||||
_ask_foreground = brush_prop(nameof(ask_foreground), s => s.ask_column.foreground.rgb_brush());
|
||||
Property<Brush> brush_prop(string name, Func<LadderSettings, Brush> get_brush) =>
|
||||
backing(name, settings_manager.subscribe_current().OnDispatcher(), get_brush);
|
||||
// bool visibility props
|
||||
_show_day = bool_prop(nameof(show_day), s => s.day_visible);
|
||||
_show_high = bool_prop(nameof(show_high), s => s.high_visible);
|
||||
_show_low = bool_prop(nameof(show_low), s => s.low_visible);
|
||||
_show_open = bool_prop(nameof(show_open), s => s.open_visible);
|
||||
_show_settle = bool_prop(nameof(show_settle), s => s.settle_visible);
|
||||
_show_volume = bool_prop(nameof(show_volume), s => s.volume_visible);
|
||||
_show_account = bool_prop(nameof(show_account), s => s.account_visible);
|
||||
_show_tif = bool_prop(nameof(show_tif), s => s.tif_visible);
|
||||
_show_order_type = bool_prop(nameof(show_order_type), s => s.order_type_visible);
|
||||
_show_account_position = bool_prop(nameof(show_account_position), s => s.account_position_visible);
|
||||
_show_size_preset = bool_prop(nameof(show_size_preset), s => s.size_presets_visible);
|
||||
_show_auto_clear = bool_prop(nameof(show_auto_clear), s => s.auto_clear_visible);
|
||||
_show_clear = bool_prop(nameof(show_clear), s => s.clear_visible);
|
||||
_show_cancel_buys = bool_prop(nameof(show_cancel_buys), s => s.cancel_buys_visible);
|
||||
_show_cancel_sells = bool_prop(nameof(show_cancel_sells), s => s.cancel_sells_visible);
|
||||
_show_cancel_all = bool_prop(nameof(show_cancel_all), s => s.cancel_all_visible);
|
||||
_show_zoom = bool_prop(nameof(show_zoom), s => s.zoom_slider_visible);
|
||||
|
||||
Property<bool> bool_prop(string name, Func<LadderSettings, bool> get_bool) =>
|
||||
backing(name, settings_manager.subscribe_current().OnDispatcher(), get_bool);
|
||||
|
||||
//
|
||||
//_window_height = backing(nameof(window_height), settings_manager.subscribe_current().OnDispatcher().Select(s => s.zoom_factor), (scale) => scale * 600);
|
||||
//_window_width = backing(nameof(window_width), settings_manager.subscribe_current().OnDispatcher().Select(s => s.zoom_factor), (scale) => scale * 420);
|
||||
position_click_command = new DelegateCommand(() => size_calculator.value = DoubleHelper.safe_convert_to_uint(Math.Abs(position)));
|
||||
send_order_command = new DelegateCommand<(double price, Side? side, bool size_from_alternate)>(send_order);
|
||||
cancel_command = new AsyncCommand<LadderModel.CancelType>(_model.cancel_orders, true);
|
||||
@@ -67,11 +107,11 @@ namespace mbts.spark {
|
||||
settings_command = new DialogCommand(() => {
|
||||
var vm = new WindowSettingsDialogVM<LadderSettings, LadderSettingsVM>(
|
||||
settings_manager, s => s.ladder, UserSettings.FileExtensions.LADDER,
|
||||
s => {
|
||||
var svm = new LadderSettingsVM(security.ContractCase == Security.ContractOneofCase.Synthetic);
|
||||
s => {
|
||||
var svm = new LadderSettingsVM(security.ContractCase == Security.ContractOneofCase.Synthetic, () => left_panel_width, () => zoom_factor);
|
||||
svm.load(s);
|
||||
return svm;
|
||||
});
|
||||
}, s => s.with_window_specifics(get_save_state().get_window_specifics()));
|
||||
var dialog = new ui.spark.SimpleGridSettingsDialog {
|
||||
DataContext = vm,
|
||||
Title = $"Ladder Settings - '{security}'"
|
||||
@@ -202,7 +242,7 @@ namespace mbts.spark {
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
if (_status.Value == a.message) {
|
||||
_status.set("");
|
||||
_status_brush.set(BTSColors.UI.spark_menu_background);
|
||||
_status_brush.set(_transparent_status_brush);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,16 +273,41 @@ namespace mbts.spark {
|
||||
public Brush ask_background => _ask_background.Value;
|
||||
public Brush ask_foreground => _ask_foreground.Value;
|
||||
public DialogCommand settings_command { get; }
|
||||
public bool show_day { get => _show_day.Value; set => _show_day.set(value); }
|
||||
public bool show_high { get => _show_high.Value; set => _show_high.set(value); }
|
||||
public bool show_low { get => _show_low.Value; set => _show_low.set(value); }
|
||||
public bool show_open { get => _show_open.Value; set => _show_open.set(value); }
|
||||
public bool show_settle { get => _show_settle.Value; set => _show_settle.set(value); }
|
||||
public bool show_volume { get => _show_volume.Value; set => _show_volume.set(value); }
|
||||
public bool show_account { get => _show_account.Value; set => _show_account.set(value); }
|
||||
public bool show_tif { get => _show_tif.Value; set => _show_tif.set(value); }
|
||||
public bool show_order_type { get => _show_order_type.Value; set => _show_order_type.set(value); }
|
||||
public bool show_account_position { get => _show_account_position.Value; set => _show_account_position.set(value); }
|
||||
public bool show_size_preset { get => _show_size_preset.Value; set => _show_size_preset.set(value); }
|
||||
public bool show_auto_clear { get => _show_auto_clear.Value; set => _show_auto_clear.set(value); }
|
||||
public bool show_clear { get => _show_clear.Value; set => _show_clear.set(value); }
|
||||
public bool show_cancel_buys { get => _show_cancel_buys.Value; set => _show_cancel_buys.set(value); }
|
||||
public bool show_cancel_sells { get => _show_cancel_sells.Value; set => _show_cancel_sells.set(value); }
|
||||
public bool show_cancel_all { get => _show_cancel_all.Value; set => _show_cancel_all.set(value); }
|
||||
public bool show_zoom { get => _show_zoom.Value; set => _show_zoom.set(value); }
|
||||
public double left_panel_width { get => _left_panel_width.Value; set => _left_panel_width.set(value); }
|
||||
public double zoom_factor { get => _zoom_factor.Value; set => _zoom_factor.set(value); }
|
||||
//public double window_height { get => _window_height.Value; set => _window_height.set(value); }
|
||||
//public double window_width { get => _window_width.Value; set => _window_width.set(value); }
|
||||
public uint orders_column_width { get => _orders_column_width.Value; set => _orders_column_width.set(value); }
|
||||
public uint bid_column_width { get => _bid_column_width.Value; set => _bid_column_width.set(value); }
|
||||
public uint price_column_width { get => _price_column_width.Value; set => _price_column_width.set(value); }
|
||||
public uint ask_column_width { get => _ask_column_width.Value; set => _ask_column_width.set(value); }
|
||||
public uint last_column_width { get => _last_column_width.Value; set => _last_column_width.set(value); }
|
||||
public uint volume_column_width { get => _volume_column_width.Value; set => _volume_column_width.set(value); }
|
||||
internal LadderModel model => _model;
|
||||
internal IObservable<LadderSettings> settings => _settings_manager.subscribe_current();
|
||||
|
||||
public LadderSettings get_save_state() {
|
||||
// TODO: save an actual OrderDefinition to preserve order params.
|
||||
if (_settings_manager.instance_settings == null)
|
||||
return null;
|
||||
var settings_clone = SparkUtils.clone_proto(_settings_manager.instance_settings);
|
||||
settings_clone.default_order_type = order_properties_vm.selected_order_type?.order_type ?? settings_clone.default_order_type;
|
||||
return settings_clone;
|
||||
var state = _settings_manager.current_settings();
|
||||
state = state.with_window_specifics(new LadderSettings.WindowSpecifics(left_panel_width, orders_column_width, bid_column_width, price_column_width, ask_column_width, last_column_width, volume_column_width, zoom_factor));
|
||||
state.use_defaults = _settings_manager.instance_settings == null;
|
||||
return state;
|
||||
}
|
||||
|
||||
public void key_down(KeyEventArgs e) {
|
||||
@@ -299,6 +364,7 @@ namespace mbts.spark {
|
||||
_change.set(mid - md.settlement_price);
|
||||
}
|
||||
_volume.set(volume);
|
||||
_misc_time = _model.md.misc_timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
<UserControl x:Class="mbts.spark.LadderView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mbts="clr-namespace:mbts.ui"
|
||||
xmlns:controls="clr-namespace:mbts.ui.controls"
|
||||
xmlns:spark="clr-namespace:mbts.spark"
|
||||
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
|
||||
xmlns:converters="clr-namespace:mbts.ui.converters"
|
||||
xmlns:txt="clr-namespace:mbts.ui.utility.text"
|
||||
Height="600"
|
||||
Width="420"
|
||||
Background="{x:Static mbts:BTSColors.common_background_brush}"
|
||||
TextElement.FontSize="{x:Static mbts:FontSizes.Small}">
|
||||
<UserControl
|
||||
x:Class="mbts.spark.LadderView"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mbts="clr-namespace:mbts.ui"
|
||||
xmlns:controls="clr-namespace:mbts.ui.controls"
|
||||
xmlns:spark="clr-namespace:mbts.spark"
|
||||
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
|
||||
xmlns:converters="clr-namespace:mbts.ui.converters"
|
||||
xmlns:txt="clr-namespace:mbts.ui.utility.text"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
Height="600"
|
||||
Width="420"
|
||||
Background="{x:Static mbts:BTSColors.common_background_brush}"
|
||||
TextElement.FontSize="{x:Static mbts:FontSizes.Small}"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
@@ -27,6 +31,13 @@
|
||||
</converters:BooleanToObjectConverter>
|
||||
<converters:EnumCharacterCasingConverter x:Key="casing_converter"/>
|
||||
<converters:DoubleTextConverter x:Key="PositionTextConverter" Format="0" NaNText="-"/>
|
||||
<converters:GridLengthConverter x:Key="GridLengthPixelConverter" UnitType="Pixel" />
|
||||
<converters:ResponsiveTextConverter x:Key="DayTextConverter" LongText="Day:" ShortText="D:" Threshold="99" />
|
||||
<converters:ResponsiveTextConverter x:Key="HighTextConverter" LongText="High:" ShortText="H:" Threshold="99" />
|
||||
<converters:ResponsiveTextConverter x:Key="LowTextConverter" LongText="Low:" ShortText="L:" Threshold="99" />
|
||||
<converters:ResponsiveTextConverter x:Key="OpenTextConverter" LongText="Open:" ShortText="O:" Threshold="99" />
|
||||
<converters:ResponsiveTextConverter x:Key="SettleTextConverter" LongText="Settle:" ShortText="S:" Threshold="99" />
|
||||
<converters:ResponsiveTextConverter x:Key="VolumeTextConverter" LongText="Volume:" ShortText="V:" Threshold="99" />
|
||||
<Style TargetType="GridViewColumnHeader">
|
||||
<Setter Property="Background" Value="{mbts:BTSColor header}"/>
|
||||
<Setter Property="Foreground" Value="{mbts:BTSColor header_text}"/>
|
||||
@@ -34,6 +45,7 @@
|
||||
<Setter Property="BorderThickness" Value="0,0,1,0"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
<Setter Property="Height" Value="23" />
|
||||
<Setter Property="MinWidth" Value="30" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type GridViewColumnHeader}">
|
||||
@@ -57,8 +69,8 @@
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Canvas>
|
||||
<Thumb x:Name="PART_HeaderGripper">
|
||||
<Canvas MinWidth="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=MinWidth, Mode=OneTime}">
|
||||
<Thumb x:Name="PART_HeaderGripper" DragDelta="Thumb_DragDelta">
|
||||
<Thumb.Style>
|
||||
<Style TargetType="{x:Type Thumb}">
|
||||
<Setter Property="Canvas.Right" Value="-9"/>
|
||||
@@ -123,7 +135,7 @@
|
||||
<Style x:Key="LadderMenuButton" TargetType="Button">
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="Margin" Value="5,0,5,5"/>
|
||||
<Setter Property="Margin" Value="2,0,2,5"/>
|
||||
<Setter Property="Padding" Value="2"/>
|
||||
</Style>
|
||||
<Style x:Key="CancelBuys" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
|
||||
@@ -244,8 +256,7 @@
|
||||
Background="{mbts:BTSColor UI.Button.normal_background}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
SnapsToDevicePixels="True"
|
||||
VerticalAlignment="Bottom"
|
||||
Margin="0,0,0,12">
|
||||
Margin="2,0,2,10">
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
@@ -263,108 +274,166 @@
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<sys:Double x:Key="MaxZoomFactor">1.5</sys:Double>
|
||||
<sys:Double x:Key="MinZoomFactor">0.5</sys:Double>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<AdornerDecorator>
|
||||
<Grid x:Name="_root">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="112"/>
|
||||
<ColumnDefinition Width="2"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid RowSpan="2" Background="{mbts:BTSColor UI.spark_menu_background}">
|
||||
<Grid.LayoutTransform>
|
||||
<ScaleTransform CenterX="0" CenterY="0"
|
||||
ScaleX="{Binding ElementName=_ui_scaler, Path=Value}"
|
||||
ScaleY="{Binding ElementName=_ui_scaler, Path=Value}"
|
||||
/>
|
||||
</Grid.LayoutTransform>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{Binding left_panel_width, Mode=TwoWay, Converter={StaticResource GridLengthPixelConverter}}"
|
||||
MinWidth="60" MaxWidth="150"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Margin="5,0">
|
||||
<StackPanel.Resources>
|
||||
<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
</Style>
|
||||
</StackPanel.Resources>
|
||||
<Grid Margin="8,4,0,0">
|
||||
<ScrollViewer Grid.Row="0" Grid.RowSpan="2" VerticalScrollBarVisibility="Hidden">
|
||||
<Grid RowSpan="2" Background="{mbts:BTSColor UI.spark_menu_background}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="6"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Grid.Row="0" Text="Day"/>
|
||||
<TextBlock Grid.Column="0" Grid.Row="2" Text="High"/>
|
||||
<TextBlock Grid.Column="0" Grid.Row="4" Text="Low"/>
|
||||
<TextBlock Grid.Column="0" Grid.Row="6" Text="Open"/>
|
||||
<TextBlock Grid.Column="0" Grid.Row="8" Text="Settle"/>
|
||||
<TextBlock Grid.Column="0" Grid.Row="10" Text="Volume"/>
|
||||
|
||||
<TextBlock Grid.Column="2" Grid.Row="0" Foreground="{Binding change, Converter={StaticResource NumericSignBrushConverter}}" FontWeight="Bold">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="change"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Column="2" Grid.Row="2" Foreground="{mbts:BTSColor UI.default_text}" FontWeight="Bold">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="high"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Column="2" Grid.Row="4" Foreground="{mbts:BTSColor UI.default_text}" FontWeight="Bold">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="low"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Column="2" Grid.Row="6" FontWeight="Bold">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="open"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Column="2" Grid.Row="8" FontWeight="Bold">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="settlement"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Column="2" Grid.Row="10" Text="{Binding volume, Mode=OneWay}" FontWeight="Bold" />
|
||||
</Grid>
|
||||
<ComboBox DataContext="{Binding order_properties_vm}" Margin="2,10,2,2" ItemsSource="{Binding accounts}" SelectedItem="{Binding selected_account}"/>
|
||||
<ComboBox DataContext="{Binding order_properties_vm}" ItemsSource="{Binding time_in_forces}" SelectedItem="{Binding selected_time_in_force}" Margin="2">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={StaticResource casing_converter}, ConverterParameter=Upper}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<uag:OrderDetailsPopupControl
|
||||
<StackPanel Margin="2,0,5,0">
|
||||
<StackPanel.Resources>
|
||||
<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
</Style>
|
||||
</StackPanel.Resources>
|
||||
<StackPanel x:Name="_slider_stack_panel" Margin="5,5,5,0">
|
||||
<Slider x:Name="_ui_scaler" Value="{Binding zoom_factor}"
|
||||
Minimum="{StaticResource MinZoomFactor}"
|
||||
Maximum="{StaticResource MaxZoomFactor}"
|
||||
Width="{Binding Path=ActualWidth, ElementName=_slider_stack_panel}"
|
||||
Visibility="{Binding show_zoom, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
</StackPanel>
|
||||
<Grid Margin="2,4,2,0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="6"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- Day -->
|
||||
<TextBlock Grid.Column="0" Grid.Row="0"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource DayTextConverter}}"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding show_day, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<TextBlock Grid.Column="2" Grid.Row="0" FontWeight="Bold"
|
||||
Margin="0,0,0,4"
|
||||
Foreground="{Binding change, Converter={StaticResource NumericSignBrushConverter}}"
|
||||
Visibility="{Binding show_day, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="change"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<!-- High -->
|
||||
<TextBlock Grid.Column="0" Grid.Row="1"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource HighTextConverter}}"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding show_high, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<TextBlock Grid.Column="2" Grid.Row="1" Foreground="{mbts:BTSColor UI.default_text}" FontWeight="Bold"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding show_high, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="high"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<!-- Low -->
|
||||
<TextBlock Grid.Column="0" Grid.Row="2"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource LowTextConverter}}"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding show_low, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<TextBlock Grid.Column="2" Grid.Row="2" Foreground="{mbts:BTSColor UI.default_text}" FontWeight="Bold"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding show_low, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="low"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<!-- Open -->
|
||||
<TextBlock Grid.Column="0" Grid.Row="3"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource OpenTextConverter}}"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding show_open, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<TextBlock Grid.Column="2" Grid.Row="3" FontWeight="Bold"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding show_open, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="open"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<!-- Settle -->
|
||||
<TextBlock Grid.Column="0" Grid.Row="4"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource SettleTextConverter}}"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding show_settle, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<TextBlock Grid.Column="2" Grid.Row="4" FontWeight="Bold"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding show_settle, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="settlement"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<!-- Volume -->
|
||||
<TextBlock Grid.Column="0" Grid.Row="5"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource VolumeTextConverter}}"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding show_volume, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<TextBlock Grid.Column="2" Grid.Row="5" Text="{Binding volume, Mode=OneWay}" FontWeight="Bold"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding show_volume, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
</Grid>
|
||||
<!-- Account combobox -->
|
||||
<ComboBox DataContext="{Binding order_properties_vm}" Margin="2,10,2,2"
|
||||
ItemsSource="{Binding accounts}" SelectedItem="{Binding selected_account}"
|
||||
Visibility="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}},
|
||||
Path = DataContext.show_account,
|
||||
Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<!-- TIF combobox-->
|
||||
<ComboBox DataContext="{Binding order_properties_vm}" ItemsSource="{Binding time_in_forces}"
|
||||
SelectedItem="{Binding selected_time_in_force}" Margin="2"
|
||||
Visibility="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}},
|
||||
Path = DataContext.show_tif,
|
||||
Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={StaticResource casing_converter}, ConverterParameter=Upper}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<!-- Order Type combobox -->
|
||||
<uag:OrderDetailsPopupControl
|
||||
DataContext="{Binding order_properties_vm}"
|
||||
Margin="2"
|
||||
ExpandDirection="Left"
|
||||
@@ -372,19 +441,25 @@
|
||||
IsOpen="{Binding selected_order_type.editable_properties_open, FallbackValue=False}"
|
||||
ItemsSource="{Binding order_types}"
|
||||
SelectedItem="{Binding selected_order_type}"
|
||||
Visibility="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}},
|
||||
Path = DataContext.show_order_type,
|
||||
Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
/>
|
||||
<Button
|
||||
<!-- Position into qty button -->
|
||||
<Button
|
||||
Margin="2"
|
||||
Command="{Binding position_click_command}"
|
||||
Content="{Binding position, Converter={StaticResource PositionTextConverter}}"
|
||||
FontWeight="Bold"
|
||||
Foreground="{Binding position, Converter={StaticResource NumericSignBrushConverter}}"
|
||||
Visibility="{Binding show_account_position, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
>
|
||||
<Button.ToolTip>
|
||||
<TextBlock Text="{Binding order_properties_vm.selected_account, StringFormat='{}{0} total position (click to copy to size)'}"/>
|
||||
</Button.ToolTip>
|
||||
</Button>
|
||||
<TextBox
|
||||
<Button.ToolTip>
|
||||
<TextBlock Text="{Binding order_properties_vm.selected_account, StringFormat='{}{0} total position (click to copy to size)'}"/>
|
||||
</Button.ToolTip>
|
||||
</Button>
|
||||
<!-- Qty textbox -->
|
||||
<TextBox
|
||||
mbts:TextBoxBehavior.InputFilter="{mbts:RegexInputFilter Regex={x:Static Member=Integer, MemberType={mbts:Nested txt:TextUtils.RegexFilters}}}"
|
||||
mbts:TextBoxBehavior.IncrementDecrementOnMouseClick="True"
|
||||
HorizontalContentAlignment="Right"
|
||||
@@ -392,42 +467,64 @@
|
||||
Text="{Binding size_calculator.value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
ContextMenu="{x:Null}"
|
||||
/>
|
||||
<ItemsControl ItemsSource="{Binding size_calculator.buttons}" Focusable="False">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Columns="2" Focusable="False"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Margin="2" Command="{Binding Path=DataContext.size_calculator.click, RelativeSource={RelativeSource AncestorType=ItemsControl}}" CommandParameter="{Binding}" Content="{Binding}" Focusable="False"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<Button Margin="2" HorizontalAlignment="Stretch" Command="{Binding size_calculator.clear}">Clear</Button>
|
||||
<!-- reset to defaults after order -->
|
||||
<CheckBox Margin="2" IsChecked="{Binding auto_clear}">Auto Clear</CheckBox>
|
||||
</StackPanel>
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Margin="0,17,0,0" VerticalAlignment="Top">
|
||||
<Button Command="{Binding cancel_command}" CommandParameter="Bids" Style="{StaticResource CancelBuys}"/>
|
||||
<Button Command="{Binding cancel_command}" CommandParameter="Asks" Style="{StaticResource CancelSells}"/>
|
||||
<Button Command="{Binding cancel_command}" CommandParameter="All" Style="{StaticResource CancelAll}"/>
|
||||
</StackPanel>
|
||||
<Button Grid.Row="1" HorizontalContentAlignment="Stretch" Command="{Binding settings_command}" Style="{StaticResource SettingsButton}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Border Grid.Column="2" HorizontalAlignment="Stretch">
|
||||
<GridViewHeaderRowPresenter x:Name="_column_headers_presenter" AllowsColumnReorder="False"/>
|
||||
</Border>
|
||||
<spark:LadderGridHost x:Name="_ladder" Grid.Column="2" Grid.Row="1"
|
||||
Command="{Binding send_order_command}"
|
||||
Orders="{Binding orders}">
|
||||
<spark:LadderGridHost.Resources>
|
||||
<!-- Qty quick buttons -->
|
||||
<ItemsControl ItemsSource="{Binding size_calculator.buttons}" Focusable="False"
|
||||
Visibility="{Binding show_size_preset, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Columns="2" Focusable="False"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Margin="2" Command="{Binding Path=DataContext.size_calculator.click, RelativeSource={RelativeSource AncestorType=ItemsControl}}" CommandParameter="{Binding}" Content="{Binding}" Focusable="False"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<!-- Clear button -->
|
||||
<Button Margin="2" HorizontalAlignment="Stretch" Command="{Binding size_calculator.clear}"
|
||||
Visibility="{Binding show_clear, Converter={StaticResource BooleanToVisibilityConverter}}">Clear</Button>
|
||||
<!-- Auto Clear button - reset to defaults after order -->
|
||||
<CheckBox Margin="2" IsChecked="{Binding auto_clear}"
|
||||
Visibility="{Binding show_auto_clear, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
Auto Clear
|
||||
</CheckBox>
|
||||
<StackPanel Margin="0,17,0,0" VerticalAlignment="Top">
|
||||
<!-- Cancel Buttons -->
|
||||
<Button Command="{Binding cancel_command}" CommandParameter="Bids" Style="{StaticResource CancelBuys}"
|
||||
Visibility="{Binding show_cancel_buys, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<Button Command="{Binding cancel_command}" CommandParameter="Asks" Style="{StaticResource CancelSells}"
|
||||
Visibility="{Binding show_cancel_sells, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<Button Command="{Binding cancel_command}" CommandParameter="All" Style="{StaticResource CancelAll}"
|
||||
Visibility="{Binding show_cancel_all, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="1" Margin="0,17,0,0" VerticalAlignment="Bottom">
|
||||
<!-- Settings Button -->
|
||||
<Button Command="{Binding settings_command}" Style="{StaticResource SettingsButton}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
<!-- Ladder Column Headers -->
|
||||
<Border Grid.Column="2" HorizontalAlignment="Stretch">
|
||||
<GridViewHeaderRowPresenter x:Name="_column_headers_presenter" AllowsColumnReorder="False" />
|
||||
</Border>
|
||||
<!-- LadderGridHost -->
|
||||
<spark:LadderGridHost
|
||||
x:Name="_ladder"
|
||||
Grid.Column="2"
|
||||
Grid.Row="1"
|
||||
Command="{Binding send_order_command}"
|
||||
Orders="{Binding orders}"
|
||||
orders_column_width="{Binding orders_column_width, Mode=TwoWay}"
|
||||
bid_column_width="{Binding bid_column_width, Mode=TwoWay}"
|
||||
price_column_width="{Binding price_column_width, Mode=TwoWay}"
|
||||
ask_column_width="{Binding ask_column_width, Mode=TwoWay}"
|
||||
LastColumnWidth="{Binding last_column_width, Mode=TwoWay}"
|
||||
VolumeColumnWidth="{Binding volume_column_width, Mode=TwoWay}"
|
||||
>
|
||||
<spark:LadderGridHost.Resources>
|
||||
<Style TargetType="controls:Badge">
|
||||
<Setter Property="Background" Value="{Binding background}" />
|
||||
<Setter Property="BorderBrush" Value="{Binding border}" />
|
||||
@@ -467,7 +564,7 @@
|
||||
</Style>
|
||||
</spark:LadderGridHost.Resources>
|
||||
</spark:LadderGridHost>
|
||||
<spark:RecenterCountdown Grid.Column="2" Grid.Row="1" Margin="0,0,18,0"
|
||||
<spark:RecenterCountdown Grid.Column="2" Grid.Row="1" Margin="0,0,18,0"
|
||||
VerticalAlignment="Bottom" HorizontalAlignment="Right"
|
||||
Width="30" Height="30"
|
||||
Mode="Pie" Delay="0:0:10"
|
||||
@@ -483,14 +580,19 @@
|
||||
<Pen Thickness="0" Brush="MediumPurple" />
|
||||
</spark:RecenterCountdown.PieStroke>
|
||||
</spark:RecenterCountdown>
|
||||
|
||||
<Button x:Name="_status" Grid.ColumnSpan="4" Grid.Row="1" VerticalAlignment="Bottom" HorizontalAlignment="Left" Command="{Binding status_click_command}">
|
||||
<Button.Template>
|
||||
<ControlTemplate>
|
||||
<TextBlock Text="{Binding status_message}" Background="{Binding status_background}" Padding="5,0" />
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
</Grid>
|
||||
<!-- Since the GridSplitter will share a cell with other elements,
|
||||
we add it in toward the end, but we still want the ladder status
|
||||
message to have the topmost Z order
|
||||
-->
|
||||
<GridSplitter Grid.Column="0" Grid.RowSpan="2" Width="5" />
|
||||
<!-- Ladder Status message -->
|
||||
<Button x:Name="_status" Grid.ColumnSpan="4" Grid.Row="1" VerticalAlignment="Bottom" HorizontalAlignment="Left" Command="{Binding status_click_command}">
|
||||
<Button.Template>
|
||||
<ControlTemplate>
|
||||
<TextBlock Text="{Binding status_message}" Background="{Binding status_background}" Padding="5,0" />
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</UserControl>
|
||||
@@ -3,10 +3,14 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
public partial class LadderView : UserControl {
|
||||
partial class LadderView : UserControl {
|
||||
readonly double _max_zoom, _min_zoom;
|
||||
|
||||
internal LadderView(LadderVM vm) {
|
||||
InitializeComponent();
|
||||
_column_headers_presenter.Columns = new GridViewColumnCollection();
|
||||
@@ -15,12 +19,87 @@ namespace mbts.spark {
|
||||
this.vm = vm;
|
||||
DataContext = vm;
|
||||
DataContextChanged += datacontext_changed;
|
||||
if (TryFindResource("MaxZoomFactor") is double found_max) {
|
||||
_max_zoom = found_max;
|
||||
} else {
|
||||
// is there a better way of not specifying the constant twice ?
|
||||
// once in the resource dictionary here and again in the xaml, seems like there has to be a better way
|
||||
_max_zoom = 1.5;
|
||||
}
|
||||
// ditto
|
||||
if (TryFindResource("MinZoomFactor") is double found_min) {
|
||||
_min_zoom = found_min;
|
||||
} else {
|
||||
_min_zoom = 0.5;
|
||||
}
|
||||
_ui_scaler.MouseDoubleClick += _ui_scaler_MouseDoubleClick;
|
||||
this.PreviewMouseWheel += LadderView_PreviewMouseWheel;
|
||||
this.PreviewMouseDown += LadderView_PreviewMouseDown;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// (Left|Right) CTRL + Wheel to zoom
|
||||
// This is neat b/c the user doesn't even need to have the slider visible to zoom
|
||||
private void LadderView_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e) {
|
||||
base.OnPreviewMouseWheel(e);
|
||||
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) {
|
||||
var delta = (e.Delta > 0) ? 0.1 : -0.1;
|
||||
var proposed_scaler_value = _ui_scaler.Value + delta;
|
||||
// Here we enforce the same contraints that are on the gui slider regarding min and max
|
||||
if (_min_zoom < proposed_scaler_value && proposed_scaler_value < _max_zoom) {
|
||||
_ui_scaler.Value = proposed_scaler_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// (Left|Right) CTRL + Wheel Click to reset
|
||||
private void LadderView_PreviewMouseDown(object sender, MouseButtonEventArgs e) {
|
||||
base.OnPreviewMouseDown(e);
|
||||
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) {
|
||||
if (e.MiddleButton == MouseButtonState.Pressed) {
|
||||
_ui_scaler_MouseDoubleClick(_ui_scaler, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Double click on scaler itself to reset
|
||||
private void _ui_scaler_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e) {
|
||||
if (sender is Slider _the_slider) {
|
||||
_the_slider.Value = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void datacontext_changed(object sender, DependencyPropertyChangedEventArgs e) {
|
||||
this.vm = DataContext as LadderVM;
|
||||
}
|
||||
|
||||
// I found this on StackOverflow (and the XAML) and thought it was a neat
|
||||
// way of enforcing a MinWidth on the columns. This is so that the columns
|
||||
// cannot be resized to nothing with the mouse.
|
||||
private void Thumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e) {
|
||||
if (sender != null) {
|
||||
DependencyObject parent = sender as DependencyObject;
|
||||
if (parent != null) {
|
||||
try {
|
||||
do {
|
||||
parent = System.Windows.Media.VisualTreeHelper.GetParent(parent as DependencyObject);
|
||||
} while (parent.GetType() != typeof(Canvas));
|
||||
|
||||
Canvas canvas = parent as Canvas;
|
||||
if (canvas != null) {
|
||||
if (canvas.ActualWidth + e.HorizontalChange < canvas.MinWidth) {
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal LadderVM vm { get; private set; }
|
||||
|
||||
protected override void OnDragOver(DragEventArgs e) {
|
||||
@@ -40,31 +119,31 @@ namespace mbts.spark {
|
||||
switch (type) {
|
||||
case LadderGridHost.ColumnType.Order:
|
||||
header = "Orders";
|
||||
width_prop = nameof(LadderGridHost.orders_column_width);
|
||||
width_prop = nameof(LadderVM.orders_column_width);
|
||||
break;
|
||||
case LadderGridHost.ColumnType.Bid:
|
||||
header = "Bid";
|
||||
width_prop = nameof(LadderGridHost.bid_column_width);
|
||||
width_prop = nameof(LadderVM.bid_column_width);
|
||||
break;
|
||||
case LadderGridHost.ColumnType.Price:
|
||||
header = "Price";
|
||||
width_prop = nameof(LadderGridHost.price_column_width);
|
||||
width_prop = nameof(LadderVM.price_column_width);
|
||||
break;
|
||||
case LadderGridHost.ColumnType.Ask:
|
||||
header = "Ask";
|
||||
width_prop = nameof(LadderGridHost.ask_column_width);
|
||||
width_prop = nameof(LadderVM.ask_column_width);
|
||||
break;
|
||||
case LadderGridHost.ColumnType.Volume:
|
||||
header = "Volume";
|
||||
width_prop = nameof(LadderGridHost.VolumeColumnWidth);
|
||||
width_prop = nameof(LadderVM.volume_column_width);
|
||||
break;
|
||||
case LadderGridHost.ColumnType.Last:
|
||||
header = "Last";
|
||||
width_prop = nameof(LadderGridHost.LastColumnWidth);
|
||||
width_prop = nameof(LadderVM.last_column_width);
|
||||
break;
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(type, nameof(type));
|
||||
}
|
||||
return _create(host, header, width_prop);
|
||||
return _create(host, header, $"DataContext.{width_prop}");
|
||||
}
|
||||
|
||||
static GridViewColumn _create(LadderGridHost host, string header, string width_prop) {
|
||||
@@ -77,5 +156,6 @@ namespace mbts.spark {
|
||||
return gvc;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace mbts.spark {
|
||||
class PnLSummaryRow : ViewModelBase, IEquatable<PnLSummaryRow>, DIYGrid.IRowVM {
|
||||
static readonly SignedDataStore<Brush> _brushes;
|
||||
readonly PnLDoubleBrushCell _start_pnl, _day_pnl, _total_pnl;
|
||||
readonly PnLPriceCell _pnl_price, _weighted_average_buy, _weighted_average_sell;
|
||||
readonly CellProperty<double> _pnl_price, _weighted_average_buy, _weighted_average_sell;
|
||||
(string account, Venue? exchange, string symbol, SecurityImmutable? security, SecurityMD? sec_md, SecurityType? sec_type) key;
|
||||
IEnumerable<FillImmutable> _fills;
|
||||
double _driver;
|
||||
@@ -38,9 +38,9 @@ namespace mbts.spark {
|
||||
day_short_quantity = day_sell_quant;
|
||||
total_net_position = net_pos;
|
||||
start_net_position = sod_pos;
|
||||
_pnl_price = new PnLPriceCell(this, nameof(pnl_price));
|
||||
_weighted_average_buy = new PnLPriceCell(this, nameof(weighted_average_buy), wavg_buy);
|
||||
_weighted_average_sell = new PnLPriceCell(this, nameof(weighted_average_sell), wavg_sell);
|
||||
_pnl_price = new CellProperty<double>(this, nameof(pnl_price), price => this.security.spec_or_default().format(price), 0);
|
||||
_weighted_average_buy = new CellProperty<double>(this, nameof(weighted_average_buy), price => this.security.spec_or_default().format(price), wavg_buy);
|
||||
_weighted_average_sell = new CellProperty<double>(this, nameof(weighted_average_sell), price => this.security.spec_or_default().format(price), wavg_sell);
|
||||
_fills = fills;
|
||||
|
||||
// set the initial pnl and pnl_price when there has been a
|
||||
@@ -193,19 +193,13 @@ namespace mbts.spark {
|
||||
class PnLDoubleBrushCell : CellProperty<double> {
|
||||
Brush _background;
|
||||
|
||||
public PnLDoubleBrushCell(PnLSummaryRow owner, string prop) : base(d => double.IsNaN(d) ? string.Empty : d.ToString("N2"), double.NaN) =>
|
||||
public PnLDoubleBrushCell(PnLSummaryRow owner, string prop) : base(owner, prop, d => double.IsNaN(d) ? string.Empty : d.ToString("N2"), double.NaN) =>
|
||||
_value_set += _ => {
|
||||
_background = null;
|
||||
owner.RaisePropertyChanged(prop);
|
||||
};
|
||||
|
||||
public Brush background => _background ?? (_background = _brushes.get(value));
|
||||
}
|
||||
|
||||
class PnLPriceCell : CellProperty<double> {
|
||||
public PnLPriceCell(PnLSummaryRow owner, string prop, double init = 0)
|
||||
: base(price => owner.security.spec_or_default().format(price), init) =>
|
||||
_value_set += _ => owner.RaisePropertyChanged(prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,7 +165,8 @@ namespace mbts.spark.utilities {
|
||||
}
|
||||
#endregion
|
||||
|
||||
public OrderDefinition order(SecurityImmutable security) => order(security, _admin_model.get_default_account());
|
||||
public OrderDefinitionImmutable order(SecurityImmutable security) =>
|
||||
new OrderDefinitionImmutable(order(security, _admin_model.get_default_account()));
|
||||
public OrderDefinition order(SecurityImmutable security, AccountTuple? account) {
|
||||
var rv = new OrderDefinition {
|
||||
SecurityKey = security.Id,
|
||||
|
||||
@@ -17,7 +17,9 @@ namespace mbts.spark {
|
||||
readonly DelegateProperty<IEnumerable<DialogSkirtItem>> _left_skirt_items;
|
||||
readonly List<IDisposable> _resources = new List<IDisposable>(2); // initialize with 2 capacity bc the only potential disposables are the instance/default vms
|
||||
|
||||
public WindowSettingsDialogVM(IWindowSettingsManager<T> settings_manager, Func<UserSettings, T> get_factory_settings, string settings_file_ext, Func<T, TVM> vm_factory) {
|
||||
public WindowSettingsDialogVM(IWindowSettingsManager<T> settings_manager, Func<UserSettings, T> get_factory_settings, string settings_file_ext, Func<T, TVM> vm_factory, Func<T, T> merge_with_current = null) {
|
||||
if (merge_with_current == null)
|
||||
merge_with_current = s => s;
|
||||
// by default, each new window will use their own instance settings
|
||||
_use_default = backing(nameof(use_default), settings_manager.instance_settings == null);
|
||||
default_vm = vm_factory(settings_manager.default_settings);
|
||||
@@ -59,15 +61,14 @@ namespace mbts.spark {
|
||||
_resources.Add(dispose_dvm);
|
||||
// local methods
|
||||
void apply() {
|
||||
var result = merge_with_current(instance_vm.get_result());
|
||||
if (!use_default) {
|
||||
settings_manager.instance_settings = instance_vm.get_result();
|
||||
settings_manager.instance_settings = result;
|
||||
} else {
|
||||
settings_manager.instance_settings = null;
|
||||
|
||||
// copy the current instance settings and persist
|
||||
var my_default = instance_vm.get_result();
|
||||
if (!my_default.safe_equals(settings_manager.default_settings))
|
||||
settings_manager.save_default_settings(my_default).ignore();
|
||||
if (!result.safe_equals(settings_manager.default_settings))
|
||||
settings_manager.save_default_settings(result).ignore();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File renamed without changes.
+4
-9
@@ -13,14 +13,9 @@ namespace mbts.co2
|
||||
{
|
||||
internal static unsafe T deserialize<T>(byte* buffer, ulong length) where T : new() {
|
||||
var stream = new System.IO.UnmanagedMemoryStream(buffer, (long)length);
|
||||
if (typeof(IMessage).IsAssignableFrom(typeof(T))) {
|
||||
IMessage result = (IMessage)new T();
|
||||
result.MergeFrom(stream);
|
||||
return (T)result;
|
||||
}
|
||||
else {
|
||||
return ProtoBuf.Serializer.Deserialize<T>(stream);
|
||||
}
|
||||
IMessage result = (IMessage)new T();
|
||||
result.MergeFrom(stream);
|
||||
return (T)result;
|
||||
}
|
||||
|
||||
[DllImport("libspark")]
|
||||
@@ -81,7 +76,7 @@ namespace mbts.co2
|
||||
[DllImport("libspark")]
|
||||
internal unsafe static extern IntPtr add_relay_symbol(IntPtr relay, UserSymbol symbol, bts.co2.MDRequest.Types.Flags flags);
|
||||
[DllImport("libspark")]
|
||||
internal unsafe static extern IntPtr set_relay_token(IntPtr relay, string user_name);
|
||||
internal unsafe static extern IntPtr set_relay_token(IntPtr relay, byte[] token, long token_size);
|
||||
|
||||
[DllImport("libspark")]
|
||||
internal unsafe static extern void post_on_scan_security_thread(Action callback);
|
||||
+5
-6
@@ -14,17 +14,16 @@ namespace mbts.co2
|
||||
{
|
||||
MDConsumer _consumer;
|
||||
IntPtr _relay;
|
||||
public Co2RelayControl(MDConsumer consumer) {
|
||||
public Co2RelayControl(MDConsumer consumer, uint naggle_ms) {
|
||||
_consumer = consumer;
|
||||
Log.assert(consumer.native_pointer() != IntPtr.Zero);
|
||||
_relay =
|
||||
Co2NativeMethods.start_private_relay(consumer.native_pointer(),
|
||||
config.Configuration.get<bts.md.RelayOptions>().naggle_ms);
|
||||
_relay = Co2NativeMethods.start_private_relay(consumer.native_pointer(), naggle_ms);
|
||||
Log.assert(_relay != IntPtr.Zero);
|
||||
}
|
||||
|
||||
public void set_token(AuthToken token) {
|
||||
Co2NativeMethods.set_relay_token(_relay, token.Token);
|
||||
public void set_token(AuthTokenImmutable token) {
|
||||
var buffer = token.Token.ToByteArray();
|
||||
Co2NativeMethods.set_relay_token(_relay, buffer, buffer.LongLength);
|
||||
}
|
||||
|
||||
public async Task preload_securities(MDRequest.Types.Flags flags=MDRequest.Types.Flags.Security) {
|
||||
File renamed without changes.
@@ -9,6 +9,7 @@ using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using security_key = System.UInt32;
|
||||
using bts;
|
||||
|
||||
namespace mbts.co2
|
||||
{
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -3,5 +3,4 @@ using System.Runtime.CompilerServices;
|
||||
[assembly: InternalsVisibleTo("spark_core")]
|
||||
[assembly: InternalsVisibleTo("sparkcmd")]
|
||||
[assembly: InternalsVisibleTo("spark_test")]
|
||||
[assembly: InternalsVisibleTo("spark_fix")]
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace bts.api.core {
|
||||
internal static extern void api_client_send(IntPtr nchannel, byte[] buffer, int length, raw_pb_callback p, IntPtr extra);
|
||||
|
||||
[DllImport("libspark")]
|
||||
internal static extern IntPtr api_client_subscribe(IntPtr client, string method_and_qualifier, raw_subscription_callback cb, IntPtr extra);
|
||||
internal static extern IntPtr api_client_subscribe(IntPtr client, string method, string qualifier, raw_subscription_callback cb, IntPtr extra);
|
||||
[DllImport("libspark")]
|
||||
internal static extern void api_client_unsubscribe(IntPtr client, IntPtr subscription);
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace bts.api.core {
|
||||
}
|
||||
|
||||
[DllImport("libspark")]
|
||||
internal static extern IntPtr api_server_create_table(IntPtr server, string method_and_qualifier);
|
||||
internal static extern IntPtr api_server_create_table(IntPtr server, string method, string qualifier);
|
||||
[DllImport("libspark")]
|
||||
internal static extern void api_server_update_table(IntPtr table, [In] StreamUpdate[] changes, long change_count);
|
||||
[DllImport("libspark")]
|
||||
|
||||
@@ -17,15 +17,6 @@ namespace bts.api.core
|
||||
|
||||
internal abstract class ApiServer : IApiServer
|
||||
{
|
||||
static JsonFormatter _formatter = new JsonFormatter(new JsonFormatter.Settings(true));
|
||||
static Regex _fmt_cleanup = new Regex(@"[\s_+]", RegexOptions.Compiled | RegexOptions.Multiline);
|
||||
public static string format_qualifier<T>(string method, T arg) where T : IMessage {
|
||||
if (arg != null) {
|
||||
string arg_fmt = _fmt_cleanup.Replace(_formatter.Format(arg), "");
|
||||
return method + arg_fmt;
|
||||
}
|
||||
return method;
|
||||
}
|
||||
public abstract void dispatch(RequestHeader header, Stream request, Action<byte[]> on_response);
|
||||
|
||||
protected void respond<T>(int request_id, Task<T> task, Action<byte[]> on_response) where T : IMessage, IImmutableMessage {
|
||||
|
||||
@@ -1,68 +1,68 @@
|
||||
using Google.Protobuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.api {
|
||||
internal static class ApiUtils {
|
||||
|
||||
|
||||
public static byte[] serialize<T>(this RequestHeader header, T body) where T : IImmutableMessage {
|
||||
int body_size = body?.CalculateSize() ?? 0;
|
||||
int header_size = header.CalculateSize() + 1; // 1 byte header length
|
||||
var buffer = new byte[header_size+body_size];
|
||||
using bts.api.core;
|
||||
using Google.Protobuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.api {
|
||||
internal static class ApiUtils {
|
||||
|
||||
public static byte[] serialize<T>(this RequestHeader header, T body) where T : IImmutableMessage {
|
||||
int body_size = body?.CalculateSize() ?? 0;
|
||||
int header_size = header.CalculateSize() + 1; // 1 byte header length
|
||||
var buffer = new byte[header_size + body_size];
|
||||
using (var stream = new MemoryStream(buffer)) {
|
||||
checked { stream.WriteByte((byte)header_size); }
|
||||
header.WriteTo(stream);
|
||||
if (body != null) body.WriteTo(stream);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public struct DeserializeResult<THeader,TBody> {
|
||||
public THeader header;
|
||||
public TBody body;
|
||||
}
|
||||
|
||||
private static DeserializeResult<THeader, TBody> mkresult<THeader,TBody>(THeader header, TBody body) {
|
||||
return new DeserializeResult<THeader, TBody>() { header = header, body = body }; }
|
||||
|
||||
public static unsafe DeserializeResult<THeader, TBody> deserialize<THeader,TBody>(byte* buffer, long buffer_length) where THeader : IMessage,new() where TBody : class,IMessage,new() {
|
||||
var header_size = buffer[0];
|
||||
Debug.Assert(header_size <= buffer_length);
|
||||
THeader header = new THeader();
|
||||
header.MergeFrom(new UnmanagedMemoryStream(buffer+1, header_size - 1));
|
||||
TBody body = new TBody();
|
||||
if (buffer_length > header_size) {
|
||||
body.MergeFrom(new UnmanagedMemoryStream(buffer + header_size, buffer_length - header_size));
|
||||
}
|
||||
return mkresult(header, body);
|
||||
}
|
||||
|
||||
public static byte[] serialize<T>(this ResponseHeader header, T body) where T : IImmutableMessage {
|
||||
int header_size = 1 + header.CalculateSize();
|
||||
int body_size = body == null ? 0 : body.CalculateSize();
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public struct DeserializeResult<THeader, TBody> {
|
||||
public THeader header;
|
||||
public TBody body;
|
||||
}
|
||||
|
||||
private static DeserializeResult<THeader, TBody> mkresult<THeader, TBody>(THeader header, TBody body) {
|
||||
return new DeserializeResult<THeader, TBody>() { header = header, body = body }; }
|
||||
|
||||
public static unsafe DeserializeResult<THeader, TBody> deserialize<THeader, TBody>(byte* buffer, long buffer_length) where THeader : IMessage, new() where TBody : class, IMessage, new() {
|
||||
var header_size = buffer[0];
|
||||
Debug.Assert(header_size <= buffer_length);
|
||||
THeader header = new THeader();
|
||||
header.MergeFrom(new UnmanagedMemoryStream(buffer + 1, header_size - 1));
|
||||
TBody body = new TBody();
|
||||
if (buffer_length > header_size) {
|
||||
body.MergeFrom(new UnmanagedMemoryStream(buffer + header_size, buffer_length - header_size));
|
||||
}
|
||||
return mkresult(header, body);
|
||||
}
|
||||
|
||||
public static byte[] serialize<T>(this ResponseHeader header, T body) where T : IImmutableMessage {
|
||||
int header_size = 1 + header.CalculateSize();
|
||||
int body_size = body == null ? 0 : body.CalculateSize();
|
||||
var buffer = new byte[header_size + body_size];
|
||||
using (var stream = new MemoryStream(buffer)) {
|
||||
checked { stream.WriteByte((byte)header_size); }
|
||||
header.WriteTo(stream);
|
||||
if (body != null) body.WriteTo(stream);
|
||||
using (var stream = new MemoryStream(buffer)) {
|
||||
checked { stream.WriteByte((byte)header_size); }
|
||||
header.WriteTo(stream);
|
||||
if (body != null) body.WriteTo(stream);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
public static byte[] serialize(this ResponseHeader header) {
|
||||
int header_size = 1 + header.CalculateSize();
|
||||
var buffer = new byte[header_size];
|
||||
using (var stream = new MemoryStream(buffer)) {
|
||||
checked { stream.WriteByte((byte)header_size); }
|
||||
header.WriteTo(stream);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public static byte[] serialize(this ResponseHeader header) {
|
||||
int header_size = 1 + header.CalculateSize();
|
||||
var buffer = new byte[header_size];
|
||||
using (var stream = new MemoryStream(buffer)) {
|
||||
checked { stream.WriteByte((byte)header_size); }
|
||||
header.WriteTo(stream);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ namespace bts.api.core {
|
||||
var header = new RequestHeader() {
|
||||
Method = method_id,
|
||||
RequestId = id,
|
||||
Token = _context.token.HasValue ? new AuthToken() { Token = _context.token?.Token } : null
|
||||
};
|
||||
var buffer = header.serialize(request);
|
||||
var tcs = new TaskCompletionSource<Result<Response>>();
|
||||
@@ -56,60 +57,99 @@ namespace bts.api.core {
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public unsafe override IObservable<RawUpdate<TOut>> subscribe<TParse,TOut>(string method_and_qualifier, MessageParser<TParse> parser, Func<TParse,TOut> converter) {
|
||||
var result = Observable.Create<RawUpdate<TOut>>(observer => {
|
||||
bool first_update = true;
|
||||
// Memory optimization. There is only one update per subscription.
|
||||
RawUpdate<TOut> the_update = new RawUpdate<TOut>();
|
||||
ApiNativeMethods.raw_subscription_callback cb = (event_type, changes, change_count, unused) => {
|
||||
the_update.Clear();
|
||||
try {
|
||||
if (event_type == ApiNativeMethods.EventType.TABLE_UPDATED) {
|
||||
foreach (var change in changes) {
|
||||
switch (change.type) {
|
||||
case ApiNativeMethods.ChangeType.REMOVE:
|
||||
the_update.removed_keys.Add(change.key);
|
||||
break;
|
||||
case ApiNativeMethods.ChangeType.REPLACE:
|
||||
the_update.changed_items.Add(converter(parser.ParseFrom(new UnmanagedMemoryStream(change.buffer, change.buffer_len))));
|
||||
the_update.changed_keys.Add(change.key);
|
||||
break;
|
||||
case ApiNativeMethods.ChangeType.CREATE:
|
||||
the_update.added_items.Add(converter(parser.ParseFrom(new UnmanagedMemoryStream(change.buffer, change.buffer_len))));
|
||||
the_update.added_keys.Add(change.key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
observer.OnNext(the_update);
|
||||
public override IObservable<RawUpdate<TOut>> subscribe<TParse,TOut>(int method_id, string method, string qualifier, MessageParser<TParse> parser, Func<TParse,TOut> converter) {
|
||||
var result = Observable.Create<RawUpdate<TOut>>(async observer => {
|
||||
IntPtr temp_subscription = IntPtr.Zero;
|
||||
void unsubscribe_temp_sub() {
|
||||
// Unsubscribe late to keep qb refcount > 0 continuously
|
||||
if (temp_subscription != IntPtr.Zero) ApiNativeMethods.api_client_unsubscribe(_client, temp_subscription);
|
||||
}
|
||||
while (true) {
|
||||
var response = await check_stream_access(method_id, qualifier);
|
||||
if (response.success) {
|
||||
if (response.value.Authorized) {
|
||||
// success. Break loop and subscribe
|
||||
break;
|
||||
}
|
||||
else if (event_type == ApiNativeMethods.EventType.TABLE_CREATED) {
|
||||
if (!first_update) {
|
||||
observer.OnNext(the_update.MarkAsReset());
|
||||
}
|
||||
first_update = false;
|
||||
else {
|
||||
// Not authorized. return empty stream
|
||||
unsubscribe_temp_sub();
|
||||
observer.OnCompleted();
|
||||
return System.Reactive.Disposables.Disposable.Empty;
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
ApiNativeMethods.api_client_log_error($"Exception in subscription callback for sub: '{method_and_qualifier}' \n--\n '{e}'");
|
||||
// Sleep to let the log message get asyncronously written to disk
|
||||
Thread.Sleep(100);
|
||||
Environment.Exit(-1);
|
||||
else {
|
||||
// Wait for stream to fire once before trying again
|
||||
unsubscribe_temp_sub();
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
ApiNativeMethods.raw_subscription_callback cb = (event_type, changes, change_count, unused) => {
|
||||
tcs.TrySetResult(true);
|
||||
};
|
||||
temp_subscription = ApiNativeMethods.api_client_subscribe(_client, method, qualifier, cb, IntPtr.Zero);
|
||||
await tcs.Task;
|
||||
GC.KeepAlive(cb);
|
||||
}
|
||||
};
|
||||
var subscription = ApiNativeMethods.api_client_subscribe(_client, method_and_qualifier, cb, IntPtr.Zero);
|
||||
return FinalizeableAnonymousDisposable.Create((disposing) => {
|
||||
ApiNativeMethods.api_client_unsubscribe(_client, subscription);
|
||||
subscription = IntPtr.Zero;
|
||||
GC.KeepAlive(cb);
|
||||
cb = null;
|
||||
// Cannot (ever) call logging from the finalizer thread
|
||||
// if (!disposing) {
|
||||
// ApiNativeMethods.api_client_log_error($"Subscription has been terminated because the returned IDisposable has been finalized. Subscription: {method_and_qualifier}");
|
||||
// }
|
||||
});
|
||||
}
|
||||
|
||||
// Authorized. Stream events
|
||||
unsubscribe_temp_sub();
|
||||
return SubscribeNative(method, qualifier, parser, converter, observer);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
private Task<Result<StreamAccessResponse>> check_stream_access(int method_id, string qualifier) {
|
||||
var request = new StreamAccessRequest() { Qualifier = qualifier ?? "" };
|
||||
return send<StreamAccessRequest, StreamAccessResponse>(method_id, request);
|
||||
}
|
||||
|
||||
private unsafe IDisposable SubscribeNative<TParse, TOut>(string method, string qualifier, MessageParser<TParse> parser, Func<TParse, TOut> converter, IObserver<RawUpdate<TOut>> observer) where TParse : IMessage<TParse> {
|
||||
bool first_update = true;
|
||||
// Memory optimization. There is only one update per subscription.
|
||||
RawUpdate<TOut> the_update = new RawUpdate<TOut>();
|
||||
ApiNativeMethods.raw_subscription_callback cb = (event_type, changes, change_count, unused) => {
|
||||
the_update.Clear();
|
||||
try {
|
||||
if (event_type == ApiNativeMethods.EventType.TABLE_UPDATED) {
|
||||
foreach (var change in changes) {
|
||||
switch (change.type) {
|
||||
case ApiNativeMethods.ChangeType.REMOVE:
|
||||
the_update.removed_keys.Add(change.key);
|
||||
break;
|
||||
case ApiNativeMethods.ChangeType.REPLACE:
|
||||
the_update.changed_items.Add(converter(parser.ParseFrom(new UnmanagedMemoryStream(change.buffer, change.buffer_len))));
|
||||
the_update.changed_keys.Add(change.key);
|
||||
break;
|
||||
case ApiNativeMethods.ChangeType.CREATE:
|
||||
the_update.added_items.Add(converter(parser.ParseFrom(new UnmanagedMemoryStream(change.buffer, change.buffer_len))));
|
||||
the_update.added_keys.Add(change.key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
observer.OnNext(the_update);
|
||||
}
|
||||
else if (event_type == ApiNativeMethods.EventType.TABLE_CREATED) {
|
||||
if (!first_update) {
|
||||
observer.OnNext(the_update.MarkAsReset());
|
||||
}
|
||||
first_update = false;
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
ApiNativeMethods.api_client_log_error($"Exception in subscription callback for sub: '{method}/{qualifier}' \n--\n '{e}'");
|
||||
// Sleep to let the log message get asyncronously written to disk
|
||||
Thread.Sleep(100);
|
||||
Environment.Exit(-1);
|
||||
}
|
||||
};
|
||||
var subscription = ApiNativeMethods.api_client_subscribe(_client, method, qualifier, cb, IntPtr.Zero);
|
||||
return FinalizeableAnonymousDisposable.Create((disposing) => {
|
||||
ApiNativeMethods.api_client_unsubscribe(_client, subscription);
|
||||
subscription = IntPtr.Zero;
|
||||
GC.KeepAlive(cb);
|
||||
cb = null;
|
||||
});
|
||||
}
|
||||
|
||||
private class FinalizeableAnonymousDisposable : IDisposable {
|
||||
private Action<bool> _action;
|
||||
public FinalizeableAnonymousDisposable(Action<bool> dispose) {
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.Threading.Tasks;
|
||||
namespace bts.api.core {
|
||||
internal abstract class ClientChannel : IDisposable
|
||||
{
|
||||
readonly ServiceContext _context;
|
||||
protected readonly ServiceContext _context;
|
||||
|
||||
public ClientChannel(ServiceContext context) {
|
||||
_context = context;
|
||||
@@ -13,7 +13,7 @@ namespace bts.api.core {
|
||||
public abstract void Dispose();
|
||||
public abstract Task<Result<Response>> send<Request, Response>(int method_id, Request request)
|
||||
where Request: IImmutableMessage where Response : class,IMessage,new();
|
||||
public abstract IObservable<RawUpdate<TOut>> subscribe<TParse,TOut>(string method_and_qualifier, MessageParser<TParse> parser, Func<TParse,TOut> converter=null)
|
||||
public abstract IObservable<RawUpdate<TOut>> subscribe<TParse,TOut>(int method_id, string method, string qualifier, MessageParser<TParse> parser, Func<TParse,TOut> converter=null)
|
||||
where TParse : IMessage<TParse>;
|
||||
|
||||
public abstract bool is_sync_call_ok();
|
||||
|
||||
@@ -37,7 +37,12 @@ namespace bts.api.core
|
||||
public T value {
|
||||
get {
|
||||
if (success) return _result;
|
||||
else throw new ApiException(error_message);
|
||||
else {
|
||||
if (typeof(T).IsValueType) { throw new ApiException(error_message); }
|
||||
else {
|
||||
return default(T);// always null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool success { get { return _code == ApiError.Success; } }
|
||||
|
||||
@@ -34,8 +34,8 @@ namespace bts.api.core {
|
||||
}
|
||||
public Action<RequestHeader, Stream, Action<byte[]>> on_request;
|
||||
|
||||
public IntPtr create_table(string method_and_qualifier) {
|
||||
return ApiNativeMethods.api_server_create_table(_server, method_and_qualifier);
|
||||
public IntPtr create_table(string method, string qualifier) {
|
||||
return ApiNativeMethods.api_server_create_table(_server, method, qualifier);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
|
||||
@@ -4,12 +4,14 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using bts.api.user;
|
||||
|
||||
namespace bts.api.core
|
||||
{
|
||||
namespace bts.api.core {
|
||||
public class ServiceContext : IDisposable
|
||||
{
|
||||
IntPtr _qbus;
|
||||
AuthToken _token;
|
||||
|
||||
public ServiceContext() {
|
||||
_qbus = ApiNativeMethods.api_create_quickbus(null, 0);
|
||||
}
|
||||
@@ -22,7 +24,6 @@ namespace bts.api.core
|
||||
Type[] arg_types = new[] { typeof(ClientChannel) };
|
||||
ConstructorInfo ctor = typeof(T).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).First(ci => ci.GetParameters().SingleOrDefault()?.ParameterType == typeof(ClientChannel));
|
||||
return (T)ctor.Invoke(new object[] { channel });
|
||||
//return (T)Activator.CreateInstance(typeof(T), channel);
|
||||
}
|
||||
|
||||
internal ServerChannel publish<T>(T service, string service_type, string qualifier) where T : IApiServer {
|
||||
@@ -31,6 +32,38 @@ namespace bts.api.core
|
||||
return ch;
|
||||
}
|
||||
|
||||
public AuthTokenImmutable? token => _token ?? (AuthTokenImmutable?)null;
|
||||
public string user { get; private set; }
|
||||
public uint user_id { get; private set; }
|
||||
public bool is_authenticated => token.HasValue;
|
||||
|
||||
public async Task<LoginResult> login(string username, string password) {
|
||||
var auth_service = locate<AuthService>();
|
||||
var result = await auth_service.authenticate_login_async(new AuthenticateLoginRequest() {
|
||||
Login = username ?? "",
|
||||
Password = password ?? ""
|
||||
});
|
||||
if (result.success) {
|
||||
if (result.value.IsValid) {
|
||||
_token = result.value.Token;
|
||||
user = username;
|
||||
user_id = result.value.UserId;
|
||||
return new LoginResult();
|
||||
}
|
||||
else {
|
||||
return new LoginResult(result.value.Reason);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return new LoginResult(result.error_message);
|
||||
}
|
||||
}
|
||||
public void login(AuthTokenImmutable token) {
|
||||
this.user = "";
|
||||
this.user_id = 0;
|
||||
this._token = token.CloneToMutable();
|
||||
}
|
||||
|
||||
#region IDisposable Support
|
||||
|
||||
protected virtual void Dispose(bool disposing) {
|
||||
@@ -52,4 +85,11 @@ namespace bts.api.core
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
public class LoginResult {
|
||||
public LoginResult() { success = true; }
|
||||
public LoginResult(string reason) { success = false; this.reason = reason; }
|
||||
public bool success { get; }
|
||||
public string reason { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
|
||||
namespace bts.api
|
||||
{
|
||||
public class Authenticator
|
||||
{
|
||||
readonly static Lazy<user.UserService> _auth = new Lazy<user.UserService>(() => Locator.locate<user.UserService>(), true);
|
||||
user.AuthToken _token = null;
|
||||
public Authenticator() { }
|
||||
|
||||
public user.AuthToken last_token => _token;
|
||||
public async Task<user.AuthToken> login(string login, string password)
|
||||
{
|
||||
var res = await _auth.Value.authenticate_login_async(new user.AuthenticateLoginRequest { Login = login, Password = password });
|
||||
if (res.success)
|
||||
_token = res.value.Token;
|
||||
return _token;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using bts.api;
|
||||
using mbts;
|
||||
using mbts.co2;
|
||||
|
||||
namespace bts.md
|
||||
{
|
||||
interface IMDUpdate
|
||||
{
|
||||
bool update(SecurityMD smd, ref ulong last_update_time);
|
||||
}
|
||||
|
||||
public interface IMDData
|
||||
{
|
||||
SecurityImmutable security { get; }
|
||||
}
|
||||
|
||||
public interface IMDBook : IMDData
|
||||
{
|
||||
List<PriceSize> bids { get; }
|
||||
List<PriceSize> asks { get; }
|
||||
}
|
||||
|
||||
class MDBook : IMDBook, IMDUpdate
|
||||
{
|
||||
public MDBook()
|
||||
{
|
||||
}
|
||||
public SecurityImmutable security { get; private set; }
|
||||
|
||||
public List<PriceSize> bids { get; private set; }
|
||||
public List<PriceSize> asks { get; private set; }
|
||||
|
||||
bool IMDUpdate.update(SecurityMD smd, ref ulong last_update_time)
|
||||
{
|
||||
this.security = smd.security;
|
||||
NativePriceSize[] bids = null, asks = null;
|
||||
if (smd.md.get_depth(ref last_update_time, ref bids, ref asks, NativePriceHelpers.COCONUT_MAX_DEPTH))
|
||||
{
|
||||
this.bids = bids.Select(x => new PriceSize(x.size, x.direct_size, x.implied_size, x.price)).ToList();
|
||||
this.asks = asks.Select(x => new PriceSize(x.size, x.direct_size, x.implied_size, x.price)).ToList();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return security.ToString() + Environment.NewLine +
|
||||
"Bid BidSz BidISz" + Environment.NewLine +
|
||||
string.Join("\n", bids.Select(x => x.ToString())) + Environment.NewLine +
|
||||
"Ask AskSz AskISz" + Environment.NewLine +
|
||||
string.Join("\n", asks.Select(x => x.ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using bts.api;
|
||||
using mbts;
|
||||
|
||||
namespace bts.md
|
||||
{
|
||||
public interface IMDCombined : IMDData, IMDMisc, IMDLast, IMDBook
|
||||
{
|
||||
}
|
||||
|
||||
class MDCombined : IMDCombined, IMDUpdate
|
||||
{
|
||||
MDBook _book = new MDBook();
|
||||
MDLast _last = new MDLast();
|
||||
MDMisc _misc = new MDMisc();
|
||||
public MDCombined()
|
||||
{
|
||||
}
|
||||
public SecurityImmutable security { get; private set; }
|
||||
|
||||
public ulong total_volume => _misc.total_volume;
|
||||
public double settlement_price => _misc.settlement_price;
|
||||
public DateTime settlement_date => _misc.settlement_date;
|
||||
public double previous_settlement_price => _misc.previous_settlement_price;
|
||||
public DateTime previous_settlement_date => _misc.previous_settlement_date;
|
||||
|
||||
public double open_price => _misc.open_price;
|
||||
public DateTime open_date => _misc.open_date;
|
||||
public double close_price => _misc.close_price;
|
||||
public DateTime close_date => _misc.close_date;
|
||||
public double session_high => _misc.session_high;
|
||||
public double session_low => _misc.session_low;
|
||||
public double fixing_price => _misc.fixing_price;
|
||||
|
||||
public PriceSize last => _last.last;
|
||||
public ulong total_volume2 => _last.total_volume;
|
||||
public DateTime last_time => _last.last_time;
|
||||
|
||||
public List<PriceSize> bids => _book.bids;
|
||||
public List<PriceSize> asks => _book.asks;
|
||||
|
||||
|
||||
bool IMDUpdate.update(SecurityMD smd, ref ulong last_update_time)
|
||||
{
|
||||
this.security = smd.security;
|
||||
var bl = last_update_time;
|
||||
bool b = ((IMDUpdate)_book).update(smd, ref bl);
|
||||
|
||||
var ll = last_update_time;
|
||||
bool l = ((IMDUpdate)_last).update(smd, ref ll);
|
||||
|
||||
var ml = last_update_time;
|
||||
bool m = ((IMDUpdate)_misc).update(smd, ref ml);
|
||||
|
||||
last_update_time = new[] { bl, ll, ml }.Max();
|
||||
return b || l || m;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using bts.api;
|
||||
using mbts;
|
||||
using mbts.co2;
|
||||
|
||||
namespace bts.md
|
||||
{
|
||||
public interface IMDLast : IMDData
|
||||
{
|
||||
PriceSize last{ get; }
|
||||
ulong total_volume { get; }
|
||||
DateTime last_time { get; }
|
||||
}
|
||||
|
||||
class MDLast : IMDLast, IMDUpdate
|
||||
{
|
||||
public MDLast()
|
||||
{
|
||||
}
|
||||
public SecurityImmutable security { get; private set; }
|
||||
|
||||
public PriceSize last { get; private set; }
|
||||
public ulong total_volume { get; private set; }
|
||||
public DateTime last_time { get; private set; }
|
||||
|
||||
bool IMDUpdate.update(SecurityMD smd, ref ulong last_update_time)
|
||||
{
|
||||
this.security = smd.security;
|
||||
NativePriceSize x; DateTime ts; ulong vol;
|
||||
if (smd.md.get_last_trade(ref last_update_time, out x, out ts, out vol))
|
||||
{
|
||||
last = new PriceSize(x.size, x.direct_size, x.implied_size, x.price);
|
||||
total_volume = vol;
|
||||
last_time = ts;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using bts.api;
|
||||
using mbts;
|
||||
|
||||
namespace bts.md
|
||||
{
|
||||
public interface IMDMisc : IMDData
|
||||
{
|
||||
ulong total_volume { get; }
|
||||
double settlement_price { get; }
|
||||
DateTime settlement_date { get; }
|
||||
double previous_settlement_price { get; }
|
||||
DateTime previous_settlement_date { get; }
|
||||
|
||||
double open_price { get; }
|
||||
DateTime open_date { get; }
|
||||
double close_price { get; }
|
||||
DateTime close_date { get; }
|
||||
double session_high { get; }
|
||||
double session_low { get; }
|
||||
double fixing_price { get; }
|
||||
}
|
||||
|
||||
class MDMisc : IMDMisc, IMDUpdate
|
||||
{
|
||||
public MDMisc()
|
||||
{
|
||||
}
|
||||
public SecurityImmutable security { get; private set; }
|
||||
|
||||
public ulong total_volume { get; private set; }
|
||||
public double settlement_price { get; private set; }
|
||||
public DateTime settlement_date { get; private set; }
|
||||
public double previous_settlement_price { get; private set; }
|
||||
public DateTime previous_settlement_date { get; private set; }
|
||||
|
||||
public double open_price { get; private set; }
|
||||
public DateTime open_date { get; private set; }
|
||||
public double close_price { get; private set; }
|
||||
public DateTime close_date { get; private set; }
|
||||
public double session_high { get; private set; }
|
||||
public double session_low { get; private set; }
|
||||
public double fixing_price { get; private set; }
|
||||
|
||||
bool IMDUpdate.update(SecurityMD smd, ref ulong last_update_time)
|
||||
{
|
||||
this.security = smd.security;
|
||||
if (smd.md.misc_timestamp > last_update_time)
|
||||
{
|
||||
total_volume = smd.md.total_volume;
|
||||
settlement_price = smd.md.settlement_price;
|
||||
settlement_date = smd.md.settlement_date;
|
||||
previous_settlement_price = smd.md.previous_settlement_price;
|
||||
previous_settlement_date = smd.md.previous_settlement_date;
|
||||
|
||||
open_price = smd.md.open_price;
|
||||
open_date = smd.md.open_date;
|
||||
close_price = smd.md.close_price;
|
||||
close_date = smd.md.close_date;
|
||||
session_high = smd.md.session_high;
|
||||
session_low = smd.md.session_low;
|
||||
fixing_price = smd.md.fixing_price;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
using bts.api;
|
||||
using bts.api.synthetic;
|
||||
using mbts;
|
||||
using mbts.co2;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using SecurityKey = System.UInt32;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using bts.api.user;
|
||||
using System.Reactive.Subjects;
|
||||
|
||||
namespace bts.md
|
||||
{
|
||||
class MDSource : IObserver<List<SecurityAndHandle>>, IDisposable
|
||||
{
|
||||
protected readonly MDConsumer _consumer = new MDConsumer();
|
||||
protected readonly Co2RelayControl _relay;
|
||||
protected long _version = 0;
|
||||
protected readonly ConcurrentDictionary<SecurityKey, SecurityMD> _exchange_sec_map = new ConcurrentDictionary<SecurityKey, SecurityMD>();
|
||||
protected List<IDisposable> _dispose = new List<IDisposable>();
|
||||
|
||||
public MDSource(uint naggle_ms, bool defer_init = false)
|
||||
{
|
||||
_relay = new Co2RelayControl(_consumer, naggle_ms);
|
||||
_relay.preload_securities((co2.MDRequest.Types.Flags)0xff).ignore();
|
||||
_dispose.Add(_consumer.on_security.Subscribe(this));
|
||||
if(!defer_init)
|
||||
initialize();
|
||||
}
|
||||
|
||||
readonly BehaviorSubject<ConcurrentDictionary<SecurityKey, SecurityMD>> _on_securities =
|
||||
new BehaviorSubject<ConcurrentDictionary<SecurityKey, SecurityMD>>( new ConcurrentDictionary<SecurityKey, SecurityMD>() );
|
||||
public IObservable<ConcurrentDictionary<SecurityKey, SecurityMD>> on_securities => _on_securities;
|
||||
|
||||
virtual public void initialize()
|
||||
{
|
||||
Task.Run(run);
|
||||
}
|
||||
|
||||
public void set_token(AuthTokenImmutable token)
|
||||
{
|
||||
_relay.set_token(token);
|
||||
}
|
||||
|
||||
public void OnCompleted() { }
|
||||
public void OnError(Exception error) => Log.Md.error(error.ToString());
|
||||
public void OnNext(List<SecurityAndHandle> values)
|
||||
{
|
||||
if (values.Count > 0)
|
||||
{
|
||||
foreach (var v in values)
|
||||
{
|
||||
var smd = new SecurityMD(new SecurityImmutable(v.security), v.handle);
|
||||
_exchange_sec_map.AddOrUpdate(smd.security.Id, smd, (_, __) => smd);
|
||||
}
|
||||
_version += 1;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() => Disposable.dispose(_dispose);
|
||||
async protected Task run()
|
||||
{
|
||||
long run_version = 0;
|
||||
while (true)
|
||||
{
|
||||
var exch = _exchange_sec_map;
|
||||
long current_version = _version;
|
||||
if (current_version > run_version)
|
||||
{
|
||||
run_version = current_version;
|
||||
try
|
||||
{
|
||||
Log.Md.info($"MDSource publishing {exch.Count} securities");
|
||||
_on_securities.OnNext(exch);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Md.error($"Error in publish_securities: {e}");
|
||||
}
|
||||
}
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -11,108 +11,224 @@ using System.Reactive.Subjects;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.co2;
|
||||
using mbts;
|
||||
using SecurityKey = System.UInt32;
|
||||
|
||||
namespace bts.api.md
|
||||
namespace bts.md
|
||||
{
|
||||
using security_key = System.UInt32;
|
||||
public class MarketDataClient : IDisposable
|
||||
|
||||
public class MarketDataClient : IDisposable
|
||||
{
|
||||
class Data
|
||||
{
|
||||
public long last_update_time;
|
||||
public ChannelUpdate data;
|
||||
}
|
||||
static uint naggle_ms = 100;
|
||||
readonly static bts.md.MDSource _subscription = new MDSource(naggle_ms);
|
||||
|
||||
const UInt32 _all_flags = (uint)(
|
||||
MDRequest.Types.Flags.Security |
|
||||
MDRequest.Types.Flags.Bbo |
|
||||
MDRequest.Types.Flags.Direct |
|
||||
MDRequest.Types.Flags.Implied |
|
||||
MDRequest.Types.Flags.LastTrade);
|
||||
|
||||
readonly ConcurrentDictionary<security_key, Data> _data = new ConcurrentDictionary<security_key, Data>();
|
||||
readonly ConcurrentDictionary<security_key, string> _security_key_to_desc= new ConcurrentDictionary<security_key, string>();
|
||||
readonly ConcurrentDictionary<string, security_key> _security_desc_to_key = new ConcurrentDictionary<string, security_key>();
|
||||
readonly MDRequest _subscription;
|
||||
readonly Venue _venue;
|
||||
|
||||
readonly static Lazy<MarketDataRelay> _md = new Lazy<MarketDataRelay>(() => Locator.locate<MarketDataRelay>(), true);
|
||||
readonly static ConcurrentDictionary<api.Venue, MarketDataClient> _venue_cache = new ConcurrentDictionary<api.Venue, api.md.MarketDataClient>();
|
||||
static readonly List<Product> _all_products;
|
||||
static MarketDataClient()
|
||||
readonly ConcurrentDictionary<SecurityKey, SecurityMD> _data = new ConcurrentDictionary<SecurityKey, SecurityMD>();
|
||||
readonly List<IDisposable> _disp = new List<IDisposable>();
|
||||
readonly uint _default_pump_ms;
|
||||
public MarketDataClient(uint default_pump_ms = 250, AuthTokenImmutable? token = null)
|
||||
{
|
||||
_all_products = _md.Value.request_products(new VoidMessage()).Products.ToList();
|
||||
}
|
||||
_default_pump_ms = default_pump_ms;
|
||||
if (token != null)
|
||||
set_auth_token(token);
|
||||
|
||||
public static IEnumerable<api.Venue> cached_exchanges => _venue_cache.Keys;
|
||||
public static bool create_or_get(api.Venue exchange, out MarketDataClient md, user.AuthToken token = null)
|
||||
{
|
||||
if (!_venue_cache.TryGetValue(exchange, out md))
|
||||
_disp.Add(_subscription.on_securities.Subscribe( md =>
|
||||
{
|
||||
_venue_cache[exchange] = md = new MarketDataClient(exchange, token);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
foreach (var entry in md)
|
||||
_data.AddOrUpdate(entry.Key, entry.Value, (_, __) => entry.Value);
|
||||
}));
|
||||
}
|
||||
public IEnumerable<SecurityImmutable> securities => _data.Values.Select(x => x.security);
|
||||
public IObservable<IEnumerable<SecurityImmutable>> on_securities
|
||||
{
|
||||
get { return _subscription.on_securities.Select(x => x.Values.Select(xx => xx.security)); }
|
||||
}
|
||||
|
||||
public void set_auth_token(AuthTokenImmutable? toke)
|
||||
{
|
||||
if(toke.HasValue)
|
||||
_subscription.set_token(toke.Value);
|
||||
}
|
||||
public IObservable< IEnumerable<IMDBook> > subscribe_book(Func<SecurityImmutable, bool> filter,
|
||||
TimeSpan? update_interval = null, IScheduler scheduler = null)
|
||||
{
|
||||
return _subscribe<MDBook>(filter, update_interval, scheduler);
|
||||
}
|
||||
public IObservable< IEnumerable<IMDLast> > subscribe_last(Func<SecurityImmutable, bool> filter,
|
||||
TimeSpan? update_interval = null, IScheduler scheduler = null)
|
||||
{
|
||||
return _subscribe<MDLast>(filter, update_interval, scheduler);
|
||||
}
|
||||
public IObservable< IEnumerable<IMDMisc> > subscribe_misc(Func<SecurityImmutable, bool> filter,
|
||||
TimeSpan? update_interval = null, IScheduler scheduler = null)
|
||||
{
|
||||
return _subscribe<MDMisc>(filter, update_interval, scheduler);
|
||||
}
|
||||
public IObservable< IEnumerable<IMDCombined> > subscribe_combined(Func<SecurityImmutable, bool> filter,
|
||||
TimeSpan? update_interval = null, IScheduler scheduler = null)
|
||||
{
|
||||
return _subscribe<MDCombined>(filter, update_interval, scheduler);
|
||||
}
|
||||
|
||||
IObservable<IEnumerable<T>> _subscribe<T>(Func<SecurityImmutable, bool> filter,
|
||||
TimeSpan? update_interval = null, IScheduler scheduler = null) where T : IMDData, IMDUpdate, new()
|
||||
{
|
||||
List<Slot<T>> slots = new List<Slot<T>>(); int data_size = 0;
|
||||
return timer_pump<IEnumerable<T>>((last_timer_tick, observer) =>
|
||||
{
|
||||
//if the number of securities has changed, or this is the first call
|
||||
if (data_size != _data.Count)
|
||||
{
|
||||
slots = _data.Values.Where(x => filter(x.security)).Select(xx => new Slot<T>(xx)).ToList();
|
||||
data_size = _data.Count;
|
||||
}
|
||||
List<T> updates = new List<T>();
|
||||
foreach (var slot in slots)
|
||||
{
|
||||
bool did_update = slot.data.update(slot.secmd, ref slot.last_update_time);
|
||||
//if (slot.last_update_time > last_timer_tick)
|
||||
if(did_update)
|
||||
updates.Add(slot.data);
|
||||
}
|
||||
if(updates.Count > 0)
|
||||
observer.OnNext(updates);
|
||||
},
|
||||
update_interval, scheduler);
|
||||
}
|
||||
|
||||
public IObservable<IEnumerable<IMDBook>> subscribe_book(IEnumerable<SecurityKey> ids, TimeSpan? update_interval = null,
|
||||
IScheduler scheduler = null)
|
||||
{
|
||||
return _subscribe<MDBook>(ids, update_interval, scheduler);
|
||||
}
|
||||
public IObservable<IEnumerable<IMDLast>> subscribe_last(IEnumerable<SecurityKey> ids, TimeSpan? update_interval = null,
|
||||
IScheduler scheduler = null)
|
||||
{
|
||||
return _subscribe<MDLast>(ids, update_interval, scheduler);
|
||||
}
|
||||
public IObservable<IEnumerable<IMDMisc>> subscribe_misc(IEnumerable<SecurityKey> ids, TimeSpan? update_interval = null,
|
||||
IScheduler scheduler = null)
|
||||
{
|
||||
return _subscribe<MDMisc>(ids, update_interval, scheduler);
|
||||
}
|
||||
public IObservable<IEnumerable<IMDCombined>> subscribe_combined(IEnumerable<SecurityKey> ids, TimeSpan? update_interval = null,
|
||||
IScheduler scheduler = null)
|
||||
{
|
||||
return _subscribe<MDCombined>(ids, update_interval, scheduler);
|
||||
}
|
||||
|
||||
IObservable<IEnumerable<T>> _subscribe<T>(IEnumerable<SecurityKey> ids, TimeSpan? update_interval = null,
|
||||
IScheduler scheduler = null) where T : IMDData, IMDUpdate, new()
|
||||
{
|
||||
List<Slot<T>> slots = ids.Where(ii => _data.TryGetValue(ii, out SecurityMD _)).Select(x =>
|
||||
{
|
||||
_data.TryGetValue(x, out SecurityMD smd);
|
||||
return new Slot<T>(smd);
|
||||
}).ToList();
|
||||
return timer_pump<IEnumerable<T>>((last_timer_tick, observer) =>
|
||||
{
|
||||
List<T> updates = new List<T>();
|
||||
foreach (var slot in slots)
|
||||
{
|
||||
bool did_update = slot.data.update(slot.secmd, ref slot.last_update_time);
|
||||
//if (slot.last_update_time > last_timer_tick)
|
||||
if(did_update)
|
||||
updates.Add(slot.data);
|
||||
}
|
||||
if(updates.Count > 0)
|
||||
observer.OnNext(updates);
|
||||
},
|
||||
update_interval, scheduler);
|
||||
}
|
||||
|
||||
public MarketDataClient(bts.api.Venue exchange = Venue.Unset, user.AuthToken token = null)
|
||||
class Slot<T> where T : IMDData, IMDUpdate, new()
|
||||
{
|
||||
_venue = exchange;
|
||||
_subscription = load_request(exchange, token);
|
||||
public Slot(SecurityMD md)
|
||||
{
|
||||
secmd = md;
|
||||
last_update_time = 0;
|
||||
data = new T();
|
||||
}
|
||||
public SecurityMD secmd;
|
||||
public ulong last_update_time;
|
||||
public T data;
|
||||
}
|
||||
|
||||
IObservable<T> timer_pump<T>(Action<ulong, IObserver<T>> tick, TimeSpan? interval, IScheduler scheduler = null)
|
||||
{
|
||||
if(!interval.HasValue)
|
||||
interval = TimeSpan.FromMilliseconds(_default_pump_ms);
|
||||
ulong last_update_time = 0;
|
||||
return Observable.Create<T>(observer =>
|
||||
{
|
||||
return Observable.Timer(interval.Value, interval.Value, scheduler ?? Scheduler.Default).Subscribe(_ =>
|
||||
{
|
||||
tick(last_update_time, observer);
|
||||
last_update_time = (ulong)DateTime.UtcNow.Ticks;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_disp.ForEach(x => x.Dispose());
|
||||
}
|
||||
|
||||
public void set_auth_token(user.AuthToken toke)
|
||||
{
|
||||
_subscription.Token = toke?.Token ?? string.Empty;
|
||||
}
|
||||
#region security search
|
||||
|
||||
MDRequest load_request(bts.api.Venue exchange, user.AuthToken token)
|
||||
public SecurityImmutable? lookup(SecurityKey key)
|
||||
{
|
||||
var req = new MDRequest() { Token = token?.Token ?? string.Empty } ;
|
||||
var prods = _all_products.Where(x=>x.Exchange == exchange || exchange == bts.api.Venue.Unset);
|
||||
foreach(var prod in prods)
|
||||
req.Channels.Add(new SecurityChannel() { Exchange = prod.Exchange, Type = prod.Type, Symbol = prod.Symbol, Flags = 0 });
|
||||
return req;
|
||||
}
|
||||
|
||||
public IEnumerable<SecurityImmutable> get_securities()
|
||||
{
|
||||
return _data.Values.Select(x => new SecurityImmutable(x.data.Security));
|
||||
}
|
||||
|
||||
public SecurityImmutable? lookup(security_key key)
|
||||
{
|
||||
Data value;
|
||||
if(_data.TryGetValue(key, out value))
|
||||
return value.data.Security;
|
||||
if (_data.TryGetValue(key, out SecurityMD value))
|
||||
return value.security;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool lookup_security_desc(security_key key, out string security_desc)
|
||||
public bool lookup_security_desc(SecurityKey key, out string security_desc)
|
||||
{
|
||||
return _security_key_to_desc.TryGetValue(key, out security_desc);
|
||||
security_desc = null;
|
||||
var sec = lookup(key);
|
||||
if (sec.HasValue)
|
||||
{
|
||||
security_desc = sec.Value.glbx_desc();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool lookup_security_key_from_desc(string security_desc, out security_key key)
|
||||
public bool lookup_security_key_from_desc(string security_desc, out SecurityKey key)
|
||||
{
|
||||
return _security_desc_to_key.TryGetValue(security_desc, out key);
|
||||
key = 0;
|
||||
var fnd = find(x => x.glbx_desc() == security_desc).TryFirst();
|
||||
if (fnd.HasValue)
|
||||
{
|
||||
key = fnd.Value.security.Id;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Security find_security(Func<Security, bool> filter)
|
||||
IEnumerable<SecurityMD> find(Func<SecurityImmutable, bool> filter)
|
||||
{
|
||||
return _data.Values.Select(x => x.data.Security).Where(xx => filter(xx)).FirstOrDefault();
|
||||
Func<SecurityMD, bool> filt = (SecurityMD secmd) => filter(secmd.security);
|
||||
return _data.Values.Where(filt);
|
||||
}
|
||||
|
||||
public async Task<Security> find_security_async(Func<Security, bool> filter, TimeSpan? timeout = null)
|
||||
public SecurityImmutable? find_security(Func<SecurityImmutable, bool> filter)
|
||||
{
|
||||
if(!timeout.HasValue) {
|
||||
timeout = TimeSpan.FromMilliseconds(int.MaxValue-1);
|
||||
var fnd = find(filter).TryFirst();
|
||||
return fnd?.security;
|
||||
}
|
||||
|
||||
public async Task<SecurityImmutable?> find_security_async(Func<SecurityImmutable, bool> filter, TimeSpan? timeout = null)
|
||||
{
|
||||
if (!timeout.HasValue)
|
||||
{
|
||||
timeout = TimeSpan.FromMilliseconds(10000);
|
||||
}
|
||||
CancellationTokenSource src = new CancellationTokenSource(timeout.Value);
|
||||
Security security = await Task.Run<Security>(async () =>
|
||||
SecurityImmutable? security = await Task.Run<SecurityImmutable?>(async () =>
|
||||
{
|
||||
Security sec = null;
|
||||
SecurityImmutable? sec = null;
|
||||
do
|
||||
{
|
||||
sec = find_security(filter);
|
||||
@@ -122,214 +238,11 @@ namespace bts.api.md
|
||||
while (sec == null);
|
||||
return sec;
|
||||
}, src.Token);
|
||||
|
||||
|
||||
return security ?? throw new KeyNotFoundException("Can't find security for filter"); ;
|
||||
}
|
||||
|
||||
public IObservable<ChannelUpdate> subscribe(Product product,
|
||||
TimeSpan? update_interval = null, IScheduler scheduler = null)
|
||||
{
|
||||
return subscribe(product, _all_flags, update_interval, scheduler);
|
||||
}
|
||||
|
||||
|
||||
public IObservable<ChannelUpdate> subscribe(Product product, uint flags,
|
||||
TimeSpan? update_interval = null, IScheduler scheduler = null)
|
||||
{
|
||||
check_subscriptions(flags, x =>
|
||||
(x.Exchange == product.Exchange && x.Type == product.Type && x.Symbol == product.Symbol));
|
||||
return _subscribe(
|
||||
x => (x.Exchange == product.Exchange && ((SecurityImmutable)x).security_type() == product.Type && x.Symbol == product.Symbol),
|
||||
update_interval,
|
||||
scheduler);
|
||||
}
|
||||
|
||||
IObservable<ChannelUpdate> _subscribe(Func<Security, bool> filter,
|
||||
TimeSpan? update_interval = null, IScheduler scheduler = null)
|
||||
{
|
||||
Data[] slots = new Data[0]; long local_security_count = 0;
|
||||
return timer_pump<Data>((last_update, observer) =>
|
||||
{
|
||||
//if the number of securities has changed, or this is the first call
|
||||
if (slots.Length == 0 || local_security_count != _data.Count)
|
||||
slots = _data.Values.Where(xx => filter(xx.data.Security)).ToArray();
|
||||
foreach (var slot in slots)
|
||||
{
|
||||
Data data;
|
||||
if (_data.TryGetValue(slot.data.Security.Id, out data))
|
||||
{
|
||||
if (data.last_update_time > last_update)
|
||||
observer.OnNext(data);
|
||||
}
|
||||
}
|
||||
},
|
||||
update_interval, scheduler).Select(x => x.data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Poll for updates for a security on a specified update_interval, on the specified scheduler with default flags
|
||||
/// </summary>
|
||||
/// <param name="security"></param>
|
||||
/// <param name="update_interval"></param>
|
||||
/// <param name="scheduler"></param>
|
||||
/// <returns></returns>
|
||||
public IObservable<ChannelUpdate> subscribe(Security security,
|
||||
TimeSpan? update_interval = null, IScheduler scheduler = null)
|
||||
{
|
||||
return subscribe(security, _all_flags, update_interval, scheduler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Poll for updates for a security on a specified update_interval, on the specified scheduler
|
||||
/// </summary>
|
||||
/// <param name="security"></param>
|
||||
/// <param name="flags"></param>
|
||||
/// <param name="update_interval"></param>
|
||||
/// <param name="scheduler"></param>
|
||||
/// <returns></returns>
|
||||
public IObservable<ChannelUpdate> subscribe(Security security, uint flags,
|
||||
TimeSpan? update_interval = null, IScheduler scheduler = null)
|
||||
{
|
||||
check_subscriptions(flags, x =>
|
||||
(x.Exchange == security.Exchange && x.Type == ((SecurityImmutable)security).security_type() && security.Symbol == security.Symbol));
|
||||
return timer_pump<Data>( (last_update, observer) =>
|
||||
{
|
||||
Data data;
|
||||
if (_data.TryGetValue(security.Id, out data))
|
||||
{
|
||||
if (data.last_update_time > last_update)
|
||||
observer.OnNext(data);
|
||||
}
|
||||
},
|
||||
update_interval, scheduler).Select(x => x.data);
|
||||
}
|
||||
|
||||
IObservable<T> timer_pump<T>(Action<long, IObserver<T>> tick, TimeSpan? interval, IScheduler scheduler = null)
|
||||
{
|
||||
if(!interval.HasValue)
|
||||
interval = TimeSpan.FromMilliseconds(25);
|
||||
long last_update_time = 0;
|
||||
return Observable.Create<T>(observer =>
|
||||
{
|
||||
return Observable.Timer(interval.Value, interval.Value, scheduler ?? Scheduler.Default).Subscribe(_ =>
|
||||
{
|
||||
tick(last_update_time, observer);
|
||||
last_update_time = DateTime.UtcNow.Ticks;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bool check_subscriptions(uint flags, Func<SecurityChannel, bool> selector)
|
||||
{
|
||||
foreach (var sub in _subscription.Channels)
|
||||
{
|
||||
if (selector(sub))
|
||||
{
|
||||
sub.Flags |= flags;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start pumping market data for all subscribed channels and securities. Call this without await
|
||||
/// </summary>
|
||||
/// <param name="interval"></param>
|
||||
/// <returns></returns>
|
||||
public async Task start_pump(TimeSpan? interval = null)
|
||||
{
|
||||
await _start_pump(interval);
|
||||
}
|
||||
|
||||
async Task _start_pump(TimeSpan? interval = null, int num_pumps = -1)
|
||||
{
|
||||
var rep = await _md.Value.request_names_async(new VoidMessage());
|
||||
int count = 0;
|
||||
do
|
||||
{
|
||||
foreach (var s in rep.value.Securities)
|
||||
{
|
||||
foreach (var ss in s.Names)
|
||||
{
|
||||
_security_desc_to_key[ss.Value] = ss.Key;
|
||||
_security_key_to_desc[ss.Key] = ss.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (!rep.success && count++ < 5);
|
||||
count = 0;
|
||||
while (num_pumps < 0 || count++ < num_pumps)
|
||||
{
|
||||
var reply = await _md.Value.requestmd_async(_subscription);
|
||||
if (reply.success)
|
||||
{
|
||||
foreach (var channel in reply.value.Channels)
|
||||
{
|
||||
long now = DateTime.UtcNow.Ticks;
|
||||
foreach (var update in channel.Updates)
|
||||
{
|
||||
Data data;
|
||||
if (_data.TryGetValue(update.Security.Id, out data))
|
||||
{
|
||||
//mergefrom concats repeated fields
|
||||
if (update.Direct != null)
|
||||
data.data.Direct = null;
|
||||
//this can tear, but the book won't tear, so maybe it's ok
|
||||
data.data.MergeFrom(update);
|
||||
data.last_update_time = now;
|
||||
}
|
||||
else
|
||||
{
|
||||
_data[update.Security.Id] = new Data() { last_update_time = now, data = update };
|
||||
}
|
||||
}
|
||||
}
|
||||
await Task.Delay(interval ?? TimeSpan.FromMilliseconds(100), _cts.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Task _pump_task;
|
||||
public void run_pump(TimeSpan? interval = null)
|
||||
{
|
||||
if (_pump_task == null)
|
||||
{
|
||||
_pump_task = this.start_pump(interval)
|
||||
.ContinueWith(ex =>
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(ex == null, ex.ToString());
|
||||
}
|
||||
, TaskContinuationOptions.OnlyOnFaulted);
|
||||
}
|
||||
}
|
||||
|
||||
public async void run_pump_async(TimeSpan? interval = null)
|
||||
{
|
||||
await this.start_pump(interval);
|
||||
}
|
||||
|
||||
public void pump_once()
|
||||
{
|
||||
if (_pump_task == null)
|
||||
{
|
||||
_pump_task = this._start_pump(null, 1)
|
||||
.ContinueWith(ex =>
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(ex == null, ex.ToString());
|
||||
}
|
||||
, TaskContinuationOptions.OnlyOnFaulted);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
readonly CancellationTokenSource _cts = new CancellationTokenSource();
|
||||
public void Dispose()
|
||||
{
|
||||
_cts.Cancel();
|
||||
_cts.Dispose();
|
||||
if (_md.IsValueCreated)
|
||||
_md.Value.Dispose();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using bts.api;
|
||||
using mbts.co2;
|
||||
|
||||
namespace bts.md
|
||||
{
|
||||
public class PriceSize
|
||||
{
|
||||
public uint size { get; }
|
||||
public uint direct_size { get; }
|
||||
public uint implied_size { get; }
|
||||
public double price { get; }
|
||||
public PriceSize(uint sz, uint direct_sz, uint implied_sz, double prx)
|
||||
{
|
||||
size = sz;
|
||||
direct_size = direct_sz;
|
||||
implied_size = implied_sz;
|
||||
price = prx;
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{price} {size} {implied_size} ";
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -6,7 +6,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.spark {
|
||||
namespace mbts {
|
||||
struct SecurityMD : IEquatable<SecurityMD> {
|
||||
public SecurityImmutable security { get; }
|
||||
public MDConsumerHandle md { get; }
|
||||
@@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
using static bts.api.Security;
|
||||
|
||||
namespace bts.api {
|
||||
public struct FutureLeg {
|
||||
struct FutureLeg {
|
||||
public readonly Venue exchange;
|
||||
public readonly string symbol, exchange_desc;
|
||||
public readonly FutureContractImmutable future;
|
||||
@@ -37,17 +37,17 @@ namespace bts.api {
|
||||
$"{symbol}{DateFormatter.Format(future.ExpirationYear, future.ExpirationMonth, future.ExpirationDay, DateFormat.MonthCodeLastDigitYear)}" :
|
||||
exch_desc;
|
||||
|
||||
static string _format(SecurityImmutable security) {
|
||||
switch (security.ContractCase) {
|
||||
case ContractOneofCase.Future: return format_future(security.Symbol, security.glbx_desc(), security.Future);
|
||||
static string _format(SecurityImmutable? security) {
|
||||
switch (security?.ContractCase) {
|
||||
case ContractOneofCase.Future: return format_future(security?.Symbol, security?.glbx_desc(), security.Value.Future);
|
||||
case ContractOneofCase.Spread:
|
||||
if (security.HasGlbx) {
|
||||
if (security?.HasGlbx == true) {
|
||||
// In case we ever bring it back, pretty printed spread names can be found here:
|
||||
// GlbxSecurityFormatter.FormatSpread(security, out string result)
|
||||
return security.Glbx.SecurityDesc;
|
||||
return security?.Glbx.SecurityDesc;
|
||||
}
|
||||
return string.Join("", _format_future_legs(SecurityHelper.get_flat_future_legs(security.Spread.Legs)));
|
||||
case ContractOneofCase.Synthetic: return security.Synthetic.Name;
|
||||
return string.Join("", _format_future_legs(SecurityHelper.get_flat_future_legs(security?.Spread.Legs)));
|
||||
case ContractOneofCase.Synthetic: return security?.Synthetic.Name;
|
||||
default: return "Unknown-security";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ using System.Linq;
|
||||
using static bts.api.Security;
|
||||
|
||||
namespace bts.api {
|
||||
public static class SecurityHelper {
|
||||
internal static class SecurityHelper {
|
||||
public static IEnumerable<FutureLeg> get_flat_future_legs(SecurityImmutable sec) {
|
||||
switch (sec.ContractCase) {
|
||||
case ContractOneofCase.Spread: return _get_flat_future_legs(sec.Spread.Legs, 1);
|
||||
@@ -66,24 +66,10 @@ namespace bts.api {
|
||||
public static readonly IComparer<SecurityImmutable> InstanceImmutable = Comparer<SecurityImmutable>.Create(Compare);
|
||||
|
||||
public static int Compare(Security x, Security y) {
|
||||
if (x.ContractCase == ContractOneofCase.Future) return y.ContractCase == ContractOneofCase.Future ? Compare(x.Symbol, x.Future, y.Symbol, y.Future) : -1;
|
||||
else if (y.ContractCase == ContractOneofCase.Future) return 1;
|
||||
var xlegs = get_flat_future_legs(x).ToList();
|
||||
var ylegs = get_flat_future_legs(y).ToList();
|
||||
int max = Math.Min(xlegs.Count, ylegs.Count);
|
||||
for (int i = 0; i < max; i++) {
|
||||
int rv = Compare(xlegs[i].symbol, xlegs[i].future, ylegs[i].symbol, ylegs[i].future);
|
||||
if (rv != 0) return rv;
|
||||
}
|
||||
for (int i = 0; i < max; i++) {
|
||||
int rv = xlegs[i].quantity.CompareTo(ylegs[i].quantity);
|
||||
if (rv != 0) return rv;
|
||||
}
|
||||
for (int i = 0; i < max; i++) {
|
||||
int rv = xlegs[i].exchange.CompareTo(ylegs[i].exchange);
|
||||
if (rv != 0) return rv;
|
||||
}
|
||||
return xlegs.Count.CompareTo(ylegs.Count);
|
||||
if (ReferenceEquals(x, y)) return 0; // x ==y
|
||||
if (x == null) return 1; // y > x
|
||||
if (y == null) return -1; // x > y
|
||||
return Compare(new SecurityImmutable(x), new SecurityImmutable(y));
|
||||
}
|
||||
|
||||
public static int Compare(SecurityImmutable x, SecurityImmutable y) {
|
||||
|
||||
@@ -1,15 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts
|
||||
namespace mbts
|
||||
{
|
||||
public static class APIExtensions
|
||||
static class APIExtensions
|
||||
{
|
||||
public static T? TryFirst<T>(this IEnumerable<T> src) where T : struct
|
||||
{
|
||||
foreach (var v in src)
|
||||
return v;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static DateTime lldatetime(this ulong ll)
|
||||
{
|
||||
// Warning: Do not remove these consts, or try to simplify this method.
|
||||
// System.DateTime() requires bits 63-64 to be empty in the "DateTime(long ticks)" constructor.
|
||||
// The only way to set the DateTimeKind is through the "DateTime(long ticks, DateTimeKind kind)" constructor.
|
||||
// (Yes, it is annoying. Internally, System.DateTime stores the DateTimeKind in bits 63-64 - in the exact format we already have!!!)
|
||||
const ulong ticks_mask = 0x3FFFFFFFFFFFFFFF;
|
||||
const int kind_shift = 62;
|
||||
return new DateTime((long)(ll & ticks_mask), (DateTimeKind)(ll >> kind_shift));
|
||||
}
|
||||
|
||||
public static string fmt(this string input, params object[] vals)
|
||||
{
|
||||
return string.Format(input, vals);
|
||||
}
|
||||
|
||||
public static void ignore(this Task t) { }
|
||||
}
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -2,7 +2,7 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts
|
||||
namespace bts.utils
|
||||
{
|
||||
public class AsyncLazy<T> : Lazy<Task<T>>
|
||||
{
|
||||
@@ -11,6 +11,15 @@ namespace bts.api
|
||||
public static T locate<T>(string qualifier="") where T : class, IApiClient
|
||||
{
|
||||
return _context.Value.locate<T>(qualifier);
|
||||
}
|
||||
static public async Task<LoginResult> login(string username, string password)
|
||||
{
|
||||
return await _context.Value.login(username, password);
|
||||
}
|
||||
static public void login_with_token(AuthTokenImmutable token)
|
||||
{
|
||||
_context.Value.login(token);
|
||||
}
|
||||
static public AuthTokenImmutable? token => _context.Value.token;
|
||||
}
|
||||
}
|
||||
@@ -33,8 +33,7 @@ namespace bts {
|
||||
Latency,
|
||||
DataFlow,
|
||||
Plugin,
|
||||
ClientSdk,
|
||||
Fix
|
||||
ClientSdk
|
||||
// Please keep this list in sync with logging.h/logging.cpp
|
||||
}
|
||||
|
||||
@@ -76,9 +75,8 @@ namespace bts {
|
||||
internal static Logger Assert => new Logger(Subsystem.Assert);
|
||||
internal static Logger Ui => new Logger(Subsystem.Ui);
|
||||
internal static Logger Testing => new Logger(Subsystem.Testing);
|
||||
internal static Logger DataFlow => new Logger(Subsystem.DataFlow);
|
||||
internal static Logger DataFlow => new Logger(Subsystem.DataFlow);
|
||||
public static Logger ClientSdk => new Logger(Subsystem.ClientSdk);
|
||||
internal static Logger FIX => new Logger(Subsystem.Fix);
|
||||
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
|
||||
File renamed without changes.
@@ -61,7 +61,7 @@ namespace bts.collections {
|
||||
if (_nursery.TryPopValue(k, out SortedList<Node> children)) {
|
||||
// remove orphans from _tree (orphan-nodes must reside in the main _tree until parents arrive)
|
||||
foreach (var n in children)
|
||||
_tree.Remove(n);
|
||||
_assert_remove(_tree, n);
|
||||
node = new Node(item, children);
|
||||
} else node = new Node(item, _comparer);
|
||||
var pk = parent(item);
|
||||
@@ -84,13 +84,10 @@ namespace bts.collections {
|
||||
if (!_index.TryGetValue(k, out Node node)) {
|
||||
return false;
|
||||
} else {
|
||||
var newval = replace(node.value);
|
||||
Log.assert(id(newval).Equals(k));
|
||||
_replace(node, newval);
|
||||
_replace(node, replace);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Remove(T item) => Remove(id(item));
|
||||
|
||||
@@ -185,44 +182,97 @@ namespace bts.collections {
|
||||
}
|
||||
}
|
||||
|
||||
void _replace(Node node, T newval) {
|
||||
TKey pk = parent(newval);
|
||||
TKey old_pk = parent(node.value);
|
||||
if (pk.Equals(old_pk)) {
|
||||
SortedList<Node> node_list;
|
||||
if (pk.Equals(_root_id))
|
||||
node_list = _tree;
|
||||
else if (_index.TryGetValue(pk, out Node pnode)) {
|
||||
node_list = pnode.children;
|
||||
} else if (!_nursery.TryGetValue(pk, out node_list)) {
|
||||
throw new KeyNotFoundException($"Replace was called with a parent-key ({pk}) that was not found in _index or _nursery; item-key was ({id(newval)}).");
|
||||
}
|
||||
node_list.Remove(node);
|
||||
node.value = newval;
|
||||
node_list.Add(node);
|
||||
} else {
|
||||
if (old_pk.Equals(_root_id)) {
|
||||
_tree.Remove(node);
|
||||
} else {
|
||||
if (_index.TryGetValue(old_pk, out Node pnode))
|
||||
pnode.children.Remove(node);
|
||||
else {
|
||||
_remove_from_nursery(old_pk, node);
|
||||
}
|
||||
}
|
||||
node.value = newval;
|
||||
if (pk.Equals(_root_id)) {
|
||||
void _replace(Node node, T new_value) =>
|
||||
_replace(node, _ => new_value);
|
||||
|
||||
void _replace(Node node, Func<T, T> replace_value) {
|
||||
/// STEPS (outline)
|
||||
/// 1) remove node with current value
|
||||
/// 2) replace node.value
|
||||
/// 3) re-insert node with new value
|
||||
|
||||
/// Implementation (details):
|
||||
/// there are 3 scenarios in which step 1 (remove node...) may occur
|
||||
/// a) node is a root node
|
||||
/// - remove it from <see cref="_tree"/>.
|
||||
/// b) node is a child node who's parent is present
|
||||
/// - remove it from the parent <see cref="Node.children"/>
|
||||
/// c) the node is a child node who's parent is NOT present
|
||||
/// - remove it from <see cref="_nursery"/> AND <see cref="_tree"/>.
|
||||
/// In each scenario, after the node is removed, the new parent will be assigned (<see cref="new_pk"/>).
|
||||
/// If the parent has NOT changed, assign the node back to the same list(s), then return.
|
||||
/// If the parent HAS changed, determine the list(s) in which the node needs to be inserted, insert the node, then return.
|
||||
TKey old_pk = parent(node.value), new_pk;
|
||||
if (old_pk.Equals(_root_id)) {
|
||||
// a)
|
||||
_assert_remove(_tree, node);
|
||||
_assert_replace(ref node, replace_value);
|
||||
new_pk = parent(node.value);
|
||||
if (new_pk.Equals(old_pk)) {
|
||||
// the parent has NOT changed; re-insert the node, then return.
|
||||
_tree.Add(node);
|
||||
} else if (_index.TryGetValue(pk, out Node pnode)) {
|
||||
pnode.children.Add(node);
|
||||
} else {
|
||||
if (!_nursery.TryGetValue(pk, out SortedList<Node> nursery_list)) {
|
||||
_nursery.Add(pk, nursery_list = new SortedList<Node>(_comparer));
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if (_index.TryGetValue(old_pk, out Node old_pnode)) {
|
||||
// b)
|
||||
_assert_remove(old_pnode.children, node);
|
||||
_assert_replace(ref node, replace_value);
|
||||
new_pk = parent(node.value);
|
||||
if (new_pk.Equals(old_pk)) {
|
||||
// the parent has NOT changed; re-insert the node, then return.
|
||||
old_pnode.children.Add(node);
|
||||
return;
|
||||
}
|
||||
} else if (_nursery.TryGetValue(old_pk, out SortedList<Node> nursery_list)) {
|
||||
// c)
|
||||
_assert_remove(nursery_list, node);
|
||||
_assert_remove(_tree, node);
|
||||
_assert_replace(ref node, replace_value);
|
||||
new_pk = parent(node.value);
|
||||
if (new_pk.Equals(old_pk)) {
|
||||
// the parent has NOT changed; re-insert the node, then return.
|
||||
nursery_list.Add(node);
|
||||
_tree.Add(node);
|
||||
return;
|
||||
} else {
|
||||
// the parent HAS changed; if the old nursery_list is empty, remove it.
|
||||
if (nursery_list.Count == 0)
|
||||
_nursery.Remove(old_pk);
|
||||
}
|
||||
} else {
|
||||
// error)
|
||||
throw new InvalidOperationException($"Unknown parent key '{old_pk}'.");
|
||||
}
|
||||
// the parent HAS changed. Determine the list(s) in which the node needs to inserted.
|
||||
if (new_pk.Equals(_root_id)) {
|
||||
_tree.Add(node);
|
||||
} else if (_index.TryGetValue(new_pk, out Node new_pnode)) {
|
||||
new_pnode.children.Add(node);
|
||||
} else {
|
||||
if (!_nursery.TryGetValue(new_pk, out SortedList<Node> new_nursery_list)) {
|
||||
_nursery.Add(new_pk, new_nursery_list = new SortedList<Node>(_comparer));
|
||||
}
|
||||
new_nursery_list.Add(node);
|
||||
_tree.Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to removes the node from the list <see cref="SortedList{T}.Remove"/> and throws an exception if it does not succeed.
|
||||
/// </summary>
|
||||
static void _assert_remove(SortedList<Node> list, Node node) {
|
||||
if (!list.Remove(node))
|
||||
throw new InvalidOperationException("Failed to remove node.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces <see cref="Node.value"/> using the specified delegate, and throws an exception if the new value's id is different from the old value's id.
|
||||
/// </summary>
|
||||
void _assert_replace(ref Node node, Func<T, T> replace_value) {
|
||||
TKey old_key = id(node.value);
|
||||
node.value = replace_value(node.value);
|
||||
if (!id(node.value).Equals(old_key))
|
||||
throw new InvalidOperationException("Replace op changed Node's id.");
|
||||
}
|
||||
|
||||
void _remove_from_nursery(TKey pk, Node node) {
|
||||
|
||||
@@ -6,9 +6,6 @@ using System.Text;
|
||||
|
||||
namespace mbts {
|
||||
public static class GlobalExtensions {
|
||||
public static string fmt(this string input, params object[] vals) {
|
||||
return string.Format(input, vals);
|
||||
}
|
||||
public static string Repeat(this string s, uint count) {
|
||||
if (count == 0) return "";
|
||||
if (count == 1) return s;
|
||||
@@ -27,15 +24,6 @@ namespace mbts {
|
||||
return range.Skip(index).First();
|
||||
}
|
||||
|
||||
public static DateTime lldatetime(this ulong ll) {
|
||||
// Warning: Do not remove these consts, or try to simplify this method.
|
||||
// System.DateTime() requires bits 63-64 to be empty in the "DateTime(long ticks)" constructor.
|
||||
// The only way to set the DateTimeKind is through the "DateTime(long ticks, DateTimeKind kind)" constructor.
|
||||
// (Yes, it is annoying. Internally, System.DateTime stores the DateTimeKind in bits 63-64 - in the exact format we already have!!!)
|
||||
const ulong ticks_mask = 0x3FFFFFFFFFFFFFFF;
|
||||
const int kind_shift = 62;
|
||||
return new DateTime((long)(ll & ticks_mask), (DateTimeKind)(ll >> kind_shift));
|
||||
}
|
||||
|
||||
//this overloaded declaration, (as opposed to one function with comp = null) allows observable_collection.sync_with to be passed into functions as a parameterless Action
|
||||
public static void sync_with<T>(this IList<T> dest, IEnumerable<T> src) { dest.sync_with(src, EqualityComparer<T>.Default); }
|
||||
@@ -69,5 +57,7 @@ namespace mbts {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ namespace bts.api.orderman
|
||||
{
|
||||
public partial class OrderService_Excel
|
||||
{
|
||||
readonly static Lazy<user.UserService> _users = new Lazy<user.UserService>(() => Locator.locate<user.UserService>(), true);
|
||||
readonly static Lazy<user.AuthService> _users = new Lazy<user.AuthService>(() => Locator.locate<user.AuthService>(), true);
|
||||
readonly static Lazy<admin.AdminService> _admin = new Lazy<admin.AdminService>(() => Locator.locate<admin.AdminService>(), true);
|
||||
|
||||
[ExcelFunction(Category = "BTS", Description = "Get All Accessible Accounts")]
|
||||
@@ -26,7 +26,7 @@ namespace bts.api.orderman
|
||||
try
|
||||
{
|
||||
List<string> accts = new List<string>();
|
||||
var admin = await _admin.Value.get_filtered_admin_data_async(new FilteredAdminDataRequest { Token = login_token });
|
||||
var admin = await _admin.Value.get_filtered_admin_data_async(new FilteredAdminDataRequest());
|
||||
if (admin.success)
|
||||
{
|
||||
foreach (var acc in admin.value.Accounts)
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
using System;
|
||||
using bts.md;
|
||||
|
||||
namespace bts.excel.rtd
|
||||
{
|
||||
class BookMDFormatter : IExcelFormatter
|
||||
{
|
||||
readonly co2.ChannelUpdate _update;
|
||||
public BookMDFormatter(co2.ChannelUpdate upd)
|
||||
readonly IMDBook _update;
|
||||
public BookMDFormatter(IMDBook upd)
|
||||
{
|
||||
_update = upd;
|
||||
}
|
||||
|
||||
public static double format_md(long mantissa, int exponent)
|
||||
public object[,] format(bool include_headers = false)
|
||||
{
|
||||
return mantissa * Math.Pow(10, exponent);
|
||||
return format(_update, include_headers);
|
||||
}
|
||||
|
||||
public object[,] format(bool include_headers = false)
|
||||
static public object[,] format(IMDBook update, bool include_headers = false)
|
||||
{
|
||||
const int cols = 5;
|
||||
const int rows = 30;
|
||||
@@ -24,21 +25,21 @@ namespace bts.excel.rtd
|
||||
for (int j = 0; j < cols; j++)
|
||||
ret[i, j] = string.Empty;
|
||||
|
||||
if (_update.Direct != null)
|
||||
if (update != null)
|
||||
{
|
||||
int idx = 0;
|
||||
for (int j = _update.Direct.Asks.Count - 1; j >= 0 && idx < rows; j--)
|
||||
for (int j = update.asks.Count - 1; j >= 0 && idx < rows; j--)
|
||||
{
|
||||
var ask = _update.Direct.Asks[j];
|
||||
ret[idx, 1] = format_md(ask.Mantissa, ask.Exponent);
|
||||
ret[idx, 2] = ask.Size;
|
||||
var ask = update.asks[j];
|
||||
ret[idx, 1] = ask.price;
|
||||
ret[idx, 2] = ask.size;
|
||||
idx++;
|
||||
}
|
||||
for (int j = 0; j < _update.Direct.Bids.Count && idx < rows; j++)
|
||||
for (int j = 0; j < update.bids.Count && idx < rows; j++)
|
||||
{
|
||||
var bid = _update.Direct.Bids[j];
|
||||
ret[idx, 1] = format_md(bid.Mantissa, bid.Exponent);
|
||||
ret[idx, 0] = bid.Size;
|
||||
var bid = update.bids[j];
|
||||
ret[idx, 1] = bid.price;
|
||||
ret[idx, 0] = bid.size;
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using bts.md;
|
||||
|
||||
namespace bts.excel.rtd
|
||||
{
|
||||
@@ -6,7 +7,7 @@ namespace bts.excel.rtd
|
||||
{
|
||||
public static IExcelFormatter make_formatter(object data)
|
||||
{
|
||||
if(data is co2.ChannelUpdate upd)
|
||||
if(data is IMDBook upd)
|
||||
return new BookMDFormatter(upd);
|
||||
else
|
||||
{
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using bts.md;
|
||||
|
||||
namespace bts.excel.rtd
|
||||
{
|
||||
class LastMDFormatter : IExcelFormatter
|
||||
{
|
||||
readonly IMDLast _update;
|
||||
public LastMDFormatter(IMDLast upd)
|
||||
{
|
||||
_update = upd;
|
||||
}
|
||||
|
||||
public object[,] format(bool include_headers = false)
|
||||
{
|
||||
return format(_update, include_headers);
|
||||
}
|
||||
|
||||
static public object[,] format(IMDLast update, bool include_headers = false)
|
||||
{
|
||||
const int cols = 3;
|
||||
const int rows = 1;
|
||||
var ret = new object[rows, cols];
|
||||
|
||||
if (update != null)
|
||||
{
|
||||
ret[0, 0] = update.last?.price;
|
||||
ret[0, 1] = update.last?.size;
|
||||
ret[0, 1] = update.last_time;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,16 +1,22 @@
|
||||
using System;
|
||||
using bts.md;
|
||||
|
||||
namespace bts.excel.rtd
|
||||
{
|
||||
class MiscMDFormatter : IExcelFormatter
|
||||
{
|
||||
readonly co2.ChannelUpdate _update;
|
||||
public MiscMDFormatter(co2.ChannelUpdate upd)
|
||||
readonly IMDMisc _update;
|
||||
public MiscMDFormatter(IMDMisc upd)
|
||||
{
|
||||
_update = upd;
|
||||
}
|
||||
|
||||
public object[,] format(bool include_headers = false)
|
||||
{
|
||||
return format(_update, include_headers);
|
||||
}
|
||||
|
||||
static public object[,] format(IMDMisc update, bool include_headers = false)
|
||||
{
|
||||
return new object[0, 0];
|
||||
}
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
|
||||
using System;
|
||||
using bts.md;
|
||||
|
||||
namespace bts.excel.rtd
|
||||
{
|
||||
class TopOfBookMDFormatter : IExcelFormatter
|
||||
{
|
||||
readonly co2.ChannelUpdate _update;
|
||||
public TopOfBookMDFormatter(co2.ChannelUpdate upd)
|
||||
readonly IMDBook _update;
|
||||
public TopOfBookMDFormatter(IMDBook upd)
|
||||
{
|
||||
_update = upd;
|
||||
}
|
||||
|
||||
public object[,] format(bool include_headers = false)
|
||||
{
|
||||
return format(_update, include_headers);
|
||||
}
|
||||
|
||||
static public object[,] format(IMDBook update, bool include_headers = false)
|
||||
{
|
||||
const int cols = 4;
|
||||
const int rows = 1;
|
||||
@@ -20,22 +25,22 @@ namespace bts.excel.rtd
|
||||
for (int j = 0; j < cols; j++)
|
||||
ret[i, j] = string.Empty;
|
||||
|
||||
if (_update.Direct != null)
|
||||
if (update != null)
|
||||
{
|
||||
int idx = 0;
|
||||
for (int j = 0; j < _update.Direct.Asks.Count && idx < rows; j++)
|
||||
for (int j = 0; j < update.asks.Count && idx < rows; j++)
|
||||
{
|
||||
var ask = _update.Direct.Asks[j];
|
||||
ret[idx, 2] = BookMDFormatter.format_md(ask.Mantissa, ask.Exponent);
|
||||
ret[idx, 3] = ask.Size;
|
||||
var ask = update.asks[j];
|
||||
ret[idx, 2] = ask.price;
|
||||
ret[idx, 3] = ask.size;
|
||||
idx++;
|
||||
}
|
||||
idx = 0;
|
||||
for (int j = 0; j < _update.Direct.Bids.Count && idx < rows; j++)
|
||||
for (int j = 0; j < update.bids.Count && idx < rows; j++)
|
||||
{
|
||||
var bid = _update.Direct.Bids[j];
|
||||
ret[idx, 1] = BookMDFormatter.format_md(bid.Mantissa, bid.Exponent);
|
||||
ret[idx, 0] = bid.Size;
|
||||
var bid = update.bids[j];
|
||||
ret[idx, 1] = bid.price;
|
||||
ret[idx, 0] = bid.size;
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,86 +1,95 @@
|
||||
|
||||
using System;
|
||||
using bts.md;
|
||||
|
||||
namespace bts.excel.rtd
|
||||
{
|
||||
class TopicMDFormatter : IExcelFormatter
|
||||
{
|
||||
readonly co2.ChannelUpdate _update;
|
||||
readonly IMDCombined _update;
|
||||
readonly string _topic;
|
||||
public TopicMDFormatter(co2.ChannelUpdate upd, string topic)
|
||||
public TopicMDFormatter(IMDCombined upd, string topic)
|
||||
{
|
||||
_update = upd;
|
||||
_topic = topic;
|
||||
}
|
||||
|
||||
public object[,] format(bool include_headers = false)
|
||||
{
|
||||
return format(_update, _topic, include_headers);
|
||||
}
|
||||
|
||||
static public object[,] format(IMDCombined update, string topic, bool include_headers = false)
|
||||
{
|
||||
var retval = new object[1, 1];
|
||||
switch (_topic.ToLower())
|
||||
if(update == null || string.IsNullOrEmpty(topic))
|
||||
return retval;
|
||||
|
||||
switch (topic.ToLower())
|
||||
{
|
||||
case "bid":
|
||||
case "bidprice":
|
||||
retval[0, 0] = BookMDFormatter.format_md(_update?.Direct?.Bids?[0]?.Mantissa ?? 0, _update?.Direct?.Bids?[0]?.Exponent ?? 0);
|
||||
retval[0, 0] = update.bids?[0]?.price;
|
||||
break;
|
||||
case "ask":
|
||||
case "askprice":
|
||||
retval[0, 0] = BookMDFormatter.format_md(_update?.Direct?.Asks?[0]?.Mantissa ?? 0, _update?.Direct?.Asks?[0]?.Exponent ?? 0);
|
||||
retval[0, 0] = update.asks?[0]?.price;
|
||||
break;
|
||||
case "bidsize":
|
||||
case "bidqty":
|
||||
retval[0, 0] = _update?.Direct?.Bids?[0]?.Size;
|
||||
retval[0, 0] = update.bids?[0]?.size;
|
||||
break;
|
||||
case "asksize":
|
||||
case "askqty":
|
||||
retval[0, 0] = _update?.Direct?.Asks?[0]?.Size;
|
||||
retval[0, 0] = update.asks?[0]?.size;
|
||||
break;
|
||||
case "impliedbid":
|
||||
case "impliedbidprice":
|
||||
retval[0, 0] = BookMDFormatter.format_md(_update?.Implied?.Bids?[0]?.Mantissa ?? 0, _update?.Implied?.Bids?[0]?.Exponent ?? 0);
|
||||
retval[0, 0] = update.bids?[0]?.price; //TODO is this the first price with impsz > 0?
|
||||
break;
|
||||
case "impliedask":
|
||||
case "impliedaskprice":
|
||||
retval[0, 0] = BookMDFormatter.format_md(_update?.Implied?.Asks?[0]?.Mantissa ?? 0, _update?.Implied?.Asks?[0]?.Exponent ?? 0);
|
||||
retval[0, 0] = update.asks?[0]?.price;
|
||||
break;
|
||||
case "impliedbidsize":
|
||||
case "impliedbidqty":
|
||||
retval[0, 0] = _update?.Implied?.Bids?[0]?.Size;
|
||||
retval[0, 0] = update.bids?[0]?.implied_size;
|
||||
break;
|
||||
case "impliedasksize":
|
||||
case "impliedaskqty":
|
||||
retval[0, 0] = _update?.Implied?.Asks?[0]?.Size;
|
||||
retval[0, 0] = update.asks?[0]?.implied_size;
|
||||
break;
|
||||
case "settlement":
|
||||
case "settlementprice":
|
||||
retval[0, 0] = BookMDFormatter.format_md(_update?.Misc?.Settlement?.Mantissa ?? 0, _update?.Misc?.Settlement?.Exponent ?? 0);
|
||||
retval[0, 0] = update.settlement_price;
|
||||
break;
|
||||
case "previoussettlement":
|
||||
case "previoussettlementprice":
|
||||
retval[0, 0] = BookMDFormatter.format_md(_update?.Misc?.PreviousSettlementPrice?.Mantissa ?? 0, _update?.Misc?.PreviousSettlementPrice?.Exponent ?? 0);
|
||||
retval[0, 0] = update.previous_settlement_price;
|
||||
break;
|
||||
case "open":
|
||||
case "openprice":
|
||||
retval[0, 0] = _update?.Misc?.OpenPrice;
|
||||
retval[0, 0] = update.open_price;
|
||||
break;
|
||||
case "close":
|
||||
case "closeprice":
|
||||
retval[0, 0] = _update?.Misc?.ClosePrice;
|
||||
retval[0, 0] = update.close_price;
|
||||
break;
|
||||
case "high":
|
||||
case "highprice":
|
||||
retval[0, 0] = _update?.Misc?.SessionHigh;
|
||||
retval[0, 0] = update.session_high;
|
||||
break;
|
||||
case "low":
|
||||
case "lowprice":
|
||||
retval[0, 0] = _update?.Misc?.SessionLow;
|
||||
retval[0, 0] = update.session_low;
|
||||
break;
|
||||
case "last":
|
||||
case "lastprice":
|
||||
retval[0, 0] = BookMDFormatter.format_md(_update?.LastTrade?.Mantissa ?? 0, _update?.LastTrade?.Exponent ?? 0);
|
||||
retval[0, 0] = update?.last?.price;
|
||||
break;
|
||||
case "lastsize":
|
||||
case "lastqty":
|
||||
retval[0, 0] = _update?.LastTrade?.Size;
|
||||
retval[0, 0] = update?.last?.size;
|
||||
break;
|
||||
}
|
||||
return retval;
|
||||
|
||||
@@ -7,26 +7,26 @@ using ExcelDna.Integration.Rtd;
|
||||
using ExcelDna.Registration.Utils;
|
||||
using System.Reactive.Linq;
|
||||
using bts.api;
|
||||
using bts.api.md;
|
||||
using bts.md;
|
||||
using bts.api.orderman;
|
||||
|
||||
namespace bts.excel.rtd
|
||||
{
|
||||
public class MDSubscribe
|
||||
{
|
||||
static Authenticator _auth = new Authenticator();
|
||||
[ExcelFunction(Category = "BTS", Description = "Log in to BTS Spark")]
|
||||
async public static void spark_login(string login, string password, ExcelAsyncHandle handle)
|
||||
{
|
||||
try
|
||||
{
|
||||
var toke = await _auth.login(login, password);
|
||||
foreach (var ven in MarketDataClient.cached_exchanges)
|
||||
var res = await Locator.login(login, password);
|
||||
if (res.success && Locator.token.HasValue)
|
||||
{
|
||||
MarketDataClient.create_or_get(ven, out MarketDataClient md);
|
||||
md?.set_auth_token(toke);
|
||||
_md.set_auth_token(Locator.token);
|
||||
handle.SetResult("Login Successful");
|
||||
}
|
||||
handle.SetResult(toke?.Token ?? "Could not log in check user/pass");
|
||||
else
|
||||
handle.SetResult("Login Error: Could not log in check user/pass " + res.reason);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
@@ -34,12 +34,10 @@ namespace bts.excel.rtd
|
||||
}
|
||||
}
|
||||
|
||||
static MarketDataClient _md = new MarketDataClient();
|
||||
public static MarketDataClient market_data(api.Venue exchange)
|
||||
{
|
||||
var newc = MarketDataClient.create_or_get(exchange, out MarketDataClient md);
|
||||
if(newc)
|
||||
md.run_pump_async(TimeSpan.FromMilliseconds(333));
|
||||
return md;
|
||||
return _md;
|
||||
}
|
||||
|
||||
static public object spark_lookup_security_desc(string exchange, long security_key)
|
||||
@@ -51,7 +49,7 @@ namespace bts.excel.rtd
|
||||
return desc ?? string.Empty;
|
||||
}
|
||||
|
||||
static IObservable<object> md_observable(string contract, string symbol, string security_type, string exchange)
|
||||
static IObservable<IEnumerable<object>> md_observable(string contract, string symbol, string security_type, string exchange)
|
||||
{
|
||||
security_type = string.IsNullOrEmpty(security_type) ? SecurityType.Future.ToString() : security_type;
|
||||
exchange = string.IsNullOrEmpty(exchange) ? Security.ExchangeDataOneofCase.Glbx.ToString() : exchange;
|
||||
@@ -60,18 +58,18 @@ namespace bts.excel.rtd
|
||||
Venue ven;
|
||||
SecurityType sectype;
|
||||
if (!Utilities.parse_venue(exchange, out ven))
|
||||
return Observable.Return("Can't Parse Exchange: " + exchange ?? string.Empty);
|
||||
return Observable.Return(new [] {"Can't Parse Exchange: " + exchange ?? string.Empty});
|
||||
if (!Utilities.parse_security_type(security_type, out sectype))
|
||||
return Observable.Return("Can't Parse Security Type: " + security_type ?? string.Empty);
|
||||
return Observable.Return(new [] {"Can't Parse Security Type: " + security_type ?? string.Empty});
|
||||
if (string.IsNullOrEmpty(symbol))
|
||||
return Observable.Return("Can't Parse Symbol: " + symbol ?? string.Empty);
|
||||
return Observable.Return(new []{"Can't Parse Symbol: " + symbol ?? string.Empty});
|
||||
if (string.IsNullOrEmpty(contract))
|
||||
return Observable.Return("Can't Parse Contract: " + contract ?? string.Empty);
|
||||
return Observable.Return(new []{"Can't Parse Contract: " + contract ?? string.Empty});
|
||||
|
||||
Product prod = new Product() { Exchange = ven, Type = sectype, Symbol = symbol };
|
||||
Func<Security, bool> filter = (x => x.Glbx.SecurityDesc == contract); //TODO GLBX
|
||||
Func<SecurityImmutable, bool> where = (x => x.Exchange == ven && x.Symbol == symbol && x.security_type() == sectype &&
|
||||
x.Glbx.SecurityDesc == contract);
|
||||
|
||||
var stream = market_data(ven).subscribe(prod).Where(x => filter(x.Security));
|
||||
var stream = market_data(ven).subscribe_combined(where);
|
||||
return stream;
|
||||
}
|
||||
|
||||
@@ -85,10 +83,11 @@ namespace bts.excel.rtd
|
||||
|
||||
var param = new object[] { contract, symbol, security_type, exchange };
|
||||
return ObservableRtdUtil.Observe(nameof(spark_md_book), param,
|
||||
() => md_observable(contract, symbol, security_type, exchange).Select(x =>
|
||||
() => md_observable(contract, symbol, security_type, exchange).Select(data =>
|
||||
{
|
||||
if (x is co2.ChannelUpdate upd)
|
||||
return new BookMDFormatter(upd).format();
|
||||
var x = data.FirstOrDefault();
|
||||
if (x != null && x is IMDBook upd)
|
||||
return BookMDFormatter.format(upd);
|
||||
return x;
|
||||
}));
|
||||
// security_type = string.IsNullOrEmpty(security_type) ? SecurityType.Future.ToString() : security_type;
|
||||
@@ -111,13 +110,33 @@ namespace bts.excel.rtd
|
||||
{
|
||||
var param = new object[] { contract, symbol, security_type, exchange };
|
||||
return ObservableRtdUtil.Observe(nameof(spark_md_top_of_book), param,
|
||||
() => md_observable(contract, symbol, security_type, exchange).Select(x =>
|
||||
() => md_observable(contract, symbol, security_type, exchange).Select(data =>
|
||||
{
|
||||
if (x is co2.ChannelUpdate upd)
|
||||
return new TopOfBookMDFormatter(upd).format();
|
||||
var x = data.FirstOrDefault();
|
||||
if (x != null && x is IMDBook upd)
|
||||
return TopOfBookMDFormatter.format(upd);
|
||||
return x;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
[ExcelFunction(Category = "BTS", Description = "Subscribe to BTS Spark last trade market data")]
|
||||
public static object spark_md_last(
|
||||
[ExcelArgument("Required. The specific contract, GCZ8 or ZNU8-ZNZ8, etc")] string contract,
|
||||
[ExcelArgument("Required. The symbol, GC or ZN, etc")] string symbol,
|
||||
[ExcelArgument("Optional. The type, Future or Spread, default = Future")] string security_type = null,
|
||||
[ExcelArgument("Optional. The exchange, default = GLBX")] string exchange = null)
|
||||
{
|
||||
var param = new object[] { contract, symbol, security_type, exchange };
|
||||
return ObservableRtdUtil.Observe(nameof(spark_md_last), param,
|
||||
() => md_observable(contract, symbol, security_type, exchange).Select(data =>
|
||||
{
|
||||
var x = data.FirstOrDefault();
|
||||
if (x != null && x is IMDLast upd)
|
||||
return LastMDFormatter.format(upd);
|
||||
return x;
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
[ExcelFunction(Category = "BTS", Description = "Subscribe to BTS Spark miscellaneous market data")]
|
||||
public static object spark_md_misc(
|
||||
@@ -128,16 +147,17 @@ namespace bts.excel.rtd
|
||||
{
|
||||
var param = new object[] { contract, symbol, security_type, exchange };
|
||||
return ObservableRtdUtil.Observe(nameof(spark_md_misc), param,
|
||||
() => md_observable(contract, symbol, security_type, exchange).Select(x =>
|
||||
() => md_observable(contract, symbol, security_type, exchange).Select(data =>
|
||||
{
|
||||
if (x is co2.ChannelUpdate upd)
|
||||
return new MiscMDFormatter(upd).format();
|
||||
var x = data.FirstOrDefault();
|
||||
if (x != null && x is IMDMisc upd)
|
||||
return MiscMDFormatter.format(upd);
|
||||
return x;
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
[ExcelFunction(Category = "BTS", Description = "Subscribe to BTS Spark top of book market data")]
|
||||
[ExcelFunction(Category = "BTS", Description = "Subscribe to BTS Spark topic based market data")]
|
||||
public static object spark_md(
|
||||
[ExcelArgument("Required. The 'Topic' string, 'bid', 'ask', etc")] string topic,
|
||||
[ExcelArgument("Required. The specific contract, GCZ8 or ZNU8-ZNZ8, etc")] string contract,
|
||||
@@ -147,12 +167,14 @@ namespace bts.excel.rtd
|
||||
{
|
||||
var param = new object[] { topic, contract, symbol, security_type, exchange };
|
||||
return ObservableRtdUtil.Observe(nameof(spark_md), param,
|
||||
() => md_observable(contract, symbol, security_type, exchange).Select(x =>
|
||||
() => md_observable(contract, symbol, security_type, exchange).Select(data =>
|
||||
{
|
||||
if (x is co2.ChannelUpdate upd)
|
||||
return new TopicMDFormatter(upd, topic).format();
|
||||
var x = data.FirstOrDefault();
|
||||
if (x != null && x is IMDCombined upd)
|
||||
return TopicMDFormatter.format(upd, topic);
|
||||
return x;
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -1,55 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using QuickFix;
|
||||
using QuickFix.Fields;
|
||||
|
||||
namespace bts.fix
|
||||
{
|
||||
public class FixInit
|
||||
{
|
||||
public static int start_acceptor(string settings_file, bool orders_allowed, string http_address = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
SessionSettings settings = new SessionSettings(settings_file);
|
||||
IApplication app = new FixRouter( (th, sess) =>
|
||||
{
|
||||
if(orders_allowed)
|
||||
return new OrderSession(th, sess);
|
||||
else
|
||||
return new DropCopySession(th, sess);
|
||||
});
|
||||
IMessageStoreFactory storeFactory = new FileStoreFactory(settings);
|
||||
ILogFactory logFactory = new FileLogFactory(settings);
|
||||
IAcceptor acceptor = new ThreadedSocketAcceptor(app, storeFactory, settings, logFactory);
|
||||
|
||||
acceptor.Start();
|
||||
HttpServer srv = null;
|
||||
if (!string.IsNullOrEmpty(http_address))
|
||||
{
|
||||
if(!http_address.StartsWith("http"))
|
||||
http_address = "http://127.0.0.1:5080/";
|
||||
srv = new HttpServer(http_address, settings);
|
||||
srv.Start();
|
||||
}
|
||||
Console.WriteLine("press <q> to quit");
|
||||
while (Console.Read() != 'q') { }
|
||||
acceptor.Stop();
|
||||
if(srv != null)
|
||||
srv.Stop();
|
||||
return 0;
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
bts.Log.FIX.fatal(e.ToString());
|
||||
Console.WriteLine("==FATAL ERROR==");
|
||||
Console.WriteLine(e.ToString());
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Text;
|
||||
|
||||
using QuickFix;
|
||||
using QuickFix.Fields;
|
||||
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.api.user;
|
||||
using bts.api.md;
|
||||
using bts.api.admin;
|
||||
|
||||
using security_key = System.UInt32;
|
||||
|
||||
namespace bts.fix
|
||||
{
|
||||
interface IFixRouter
|
||||
{
|
||||
IEnumerable<SecurityImmutable> get_securities();
|
||||
SecurityImmutable? lookup(security_key key);
|
||||
SecurityImmutable? lookup(string desc);
|
||||
}
|
||||
|
||||
class FixRouter : QuickFix.MessageCracker, QuickFix.IApplication, IFixRouter
|
||||
{
|
||||
readonly ConcurrentDictionary<SessionID, QuickFix.IApplication> _sessions = new ConcurrentDictionary<SessionID, IApplication>();
|
||||
readonly Func<IFixRouter, SessionID, IApplication> _factory;
|
||||
readonly MarketDataClient _mdglobal = new MarketDataClient();
|
||||
|
||||
public FixRouter(Func<IFixRouter, SessionID, IApplication> factory)
|
||||
{
|
||||
_factory = factory;
|
||||
_mdglobal.pump_once(); //get securities
|
||||
}
|
||||
|
||||
public IApplication session(SessionID sessionID)
|
||||
{
|
||||
IApplication app;
|
||||
if (!_sessions.TryGetValue(sessionID, out app))
|
||||
{
|
||||
app = _factory(this, sessionID);
|
||||
_sessions[sessionID] = app;
|
||||
}
|
||||
return app;
|
||||
}
|
||||
|
||||
public void FromAdmin(Message message, SessionID sessionID)
|
||||
{
|
||||
session(sessionID).FromAdmin(message, sessionID);
|
||||
}
|
||||
|
||||
public void FromApp(Message message, SessionID sessionID)
|
||||
{
|
||||
session(sessionID).FromApp(message, sessionID);
|
||||
}
|
||||
|
||||
public void OnCreate(SessionID sessionID)
|
||||
{
|
||||
session(sessionID).OnCreate(sessionID);
|
||||
}
|
||||
|
||||
public void OnLogon(SessionID sessionID)
|
||||
{
|
||||
session(sessionID).OnLogon(sessionID);
|
||||
}
|
||||
|
||||
public void OnLogout(SessionID sessionID)
|
||||
{
|
||||
session(sessionID).OnLogout(sessionID);
|
||||
}
|
||||
|
||||
public void ToAdmin(Message message, SessionID sessionID)
|
||||
{
|
||||
session(sessionID).ToAdmin(message, sessionID);
|
||||
}
|
||||
|
||||
public void ToApp(Message message, SessionID sessionID)
|
||||
{
|
||||
session(sessionID).ToApp(message, sessionID);
|
||||
}
|
||||
|
||||
IEnumerable<SecurityImmutable> IFixRouter.get_securities()
|
||||
{
|
||||
return _mdglobal.get_securities();
|
||||
}
|
||||
|
||||
SecurityImmutable? IFixRouter.lookup(uint key)
|
||||
{
|
||||
return _mdglobal.lookup(key);
|
||||
}
|
||||
|
||||
SecurityImmutable? IFixRouter.lookup(string desc)
|
||||
{
|
||||
if (_mdglobal.lookup_security_key_from_desc(desc, out uint key))
|
||||
return _mdglobal.lookup(key);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,585 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using QuickFix;
|
||||
|
||||
namespace bts.fix
|
||||
{
|
||||
public class HttpServer : IDisposable
|
||||
{
|
||||
private readonly HttpListener _httpListener;
|
||||
private Thread _connectionThread;
|
||||
private Boolean _running, _disposed;
|
||||
private readonly SessionSettings _sessionSettings;
|
||||
private StringBuilder _sbHtmlHeader;
|
||||
|
||||
|
||||
public HttpServer(string prefix, SessionSettings settings)
|
||||
{
|
||||
if (!HttpListener.IsSupported)
|
||||
{
|
||||
// Requires at least a Windows XP with Service Pack 2
|
||||
throw new NotSupportedException(
|
||||
"The Http Server cannot run on this operating system.");
|
||||
}
|
||||
|
||||
_httpListener = new HttpListener();
|
||||
_httpListener.Prefixes.Add(prefix);
|
||||
_sessionSettings = settings;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (!_httpListener.IsListening)
|
||||
{
|
||||
_httpListener.Start();
|
||||
_running = true;
|
||||
// Use a thread to listen to the Http requests
|
||||
_connectionThread = new Thread(ConnectionThreadStart);
|
||||
_connectionThread.Start();
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (_httpListener.IsListening)
|
||||
{
|
||||
_running = false;
|
||||
_httpListener.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
private void ConnectionThreadStart()
|
||||
{
|
||||
try
|
||||
{
|
||||
while (_running)
|
||||
{
|
||||
// Grab the context and pass it to the processor methods to handle it
|
||||
HttpListenerContext context = _httpListener.GetContext();
|
||||
HttpListenerRequest request = context.Request;
|
||||
HttpListenerResponse response = context.Response;
|
||||
string responseString;
|
||||
|
||||
_sbHtmlHeader = new StringBuilder(@"<HEAD><CENTER><TITLE>QuickFIX Engine Web Interface</TITLE><H1>QuickFIX Engine Web Interface</H1></CENTER>");
|
||||
_sbHtmlHeader.AppendFormat(@"<CENTER>[<A HREF='/'>HOME</A>] [<A HREF='{0}'>RELOAD</A>]</CENTER><HR/></HEADER><BODY>", request.Url.OriginalString);
|
||||
|
||||
switch (request.Url.AbsolutePath)
|
||||
{
|
||||
case "/":
|
||||
responseString = ProcessRoot(request);
|
||||
break;
|
||||
case "/session":
|
||||
responseString = SessionDetails(request);
|
||||
break;
|
||||
case "/resetSession":
|
||||
responseString = ResetSession(request);
|
||||
break;
|
||||
case "/resetSessions":
|
||||
responseString = ResetSessions(request);
|
||||
break;
|
||||
case "/refreshSession":
|
||||
responseString = RefreshSession(request);
|
||||
break;
|
||||
case "/refreshSessions":
|
||||
responseString = RefreshSessions(request);
|
||||
break;
|
||||
case "/enableSessions":
|
||||
responseString = EnableSessions(request);
|
||||
break;
|
||||
case "/disableSessions":
|
||||
responseString = DisableSessions(request);
|
||||
break;
|
||||
default:
|
||||
responseString = ProcessRoot(request);
|
||||
break;
|
||||
}
|
||||
|
||||
byte[] buffer = Encoding.UTF8.GetBytes(responseString);
|
||||
response.ContentLength64 = buffer.Length;
|
||||
Stream output = response.OutputStream;
|
||||
output.Write(buffer, 0, buffer.Length);
|
||||
output.Close();
|
||||
}
|
||||
}
|
||||
catch (HttpListenerException)
|
||||
{
|
||||
Console.WriteLine("HTTP server was shut down.");
|
||||
}
|
||||
}
|
||||
|
||||
private string DisableSessions(HttpListenerRequest request)
|
||||
{
|
||||
bool confirm = false;
|
||||
StringBuilder sbHtmlPageBody = _sbHtmlHeader;
|
||||
|
||||
if (request.QueryString["confirm"] != null)
|
||||
{
|
||||
if (Convert.ToInt16(request.QueryString["confirm"]) != 0)
|
||||
{
|
||||
confirm = true;
|
||||
foreach (SessionID session in _sessionSettings.GetSessions())
|
||||
{
|
||||
Session sessionDetails = Session.LookupSession(session);
|
||||
sessionDetails.Logout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (confirm)
|
||||
{
|
||||
sbHtmlPageBody = new StringBuilder();
|
||||
sbHtmlPageBody.AppendFormat(@"<HEAD><META http-equiv='refresh' content=2;URL={0} /><CENTER><TITLE>QuickFIX Engine Web Interface</TITLE><H1>QuickFIX Engine Web Interface</H1></CENTER>", "/");
|
||||
sbHtmlPageBody.AppendFormat("<CENTER><h2><A HREF='{0}'>Sessions</A> have been disabled</h2></CENTER>", "/");
|
||||
}
|
||||
else
|
||||
{
|
||||
sbHtmlPageBody.Append("<CENTER><h2>Are you sure you want to disable all sessions ?</h2></CENTER>");
|
||||
sbHtmlPageBody.AppendFormat("<CENTER>[<A HREF='{0}?confirm=1'>YES, disable sessions</A>] [<A HREF='/'>NO, do not disable sessions</A>]</CENTER>", request.Url.OriginalString);
|
||||
}
|
||||
return sbHtmlPageBody.ToString();
|
||||
}
|
||||
|
||||
private string EnableSessions(HttpListenerRequest request)
|
||||
{
|
||||
bool confirm = false;
|
||||
string urlOriginalString = request.Url.OriginalString;
|
||||
StringBuilder sbHtmlPageBody = _sbHtmlHeader;
|
||||
|
||||
if (request.QueryString["confirm"] != null)
|
||||
{
|
||||
if (Convert.ToInt16(request.QueryString["confirm"]) != 0)
|
||||
{
|
||||
confirm = true;
|
||||
foreach (SessionID session in _sessionSettings.GetSessions())
|
||||
{
|
||||
Session sessionDetails = Session.LookupSession(session);
|
||||
sessionDetails.Logon();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (confirm)
|
||||
{
|
||||
sbHtmlPageBody = new StringBuilder();
|
||||
sbHtmlPageBody.AppendFormat(@"<HEAD><META http-equiv='refresh' content=2;URL={0} /><CENTER><TITLE>QuickFIX Engine Web Interface</TITLE><H1>QuickFIX Engine Web Interface</H1></CENTER>", "/");
|
||||
sbHtmlPageBody.AppendFormat("<CENTER><h2><A HREF='{0}'>Sessions</A> have been enabled</h2></CENTER>", "/");
|
||||
}
|
||||
else
|
||||
{
|
||||
sbHtmlPageBody.Append("<CENTER><h2>Are you sure you want to enable all sessions ?</h2></CENTER>");
|
||||
sbHtmlPageBody.AppendFormat("<CENTER>[<A HREF='{0}?confirm=1'>YES, enable sessions</A>] [<A HREF='/'>NO, do not enable sessions</A>]</CENTER>", urlOriginalString);
|
||||
}
|
||||
return sbHtmlPageBody.ToString();
|
||||
}
|
||||
|
||||
private string RefreshSession(HttpListenerRequest request)
|
||||
{
|
||||
SessionID sessionId = new SessionID(request.QueryString["beginstring"], request.QueryString["sendercompid"], request.QueryString["targetcompid"]);
|
||||
Session sessionDetails = Session.LookupSession(sessionId);
|
||||
if (sessionDetails == null) throw new Exception("Session not found");
|
||||
StringBuilder sbHtmlPageBody = _sbHtmlHeader;
|
||||
bool confirm = false;
|
||||
string urlOriginalString = request.Url.OriginalString;
|
||||
|
||||
string url = "/session?" + GetParameterList(urlOriginalString);
|
||||
|
||||
if (request.QueryString["confirm"] != null)
|
||||
{
|
||||
if (Convert.ToInt16(request.QueryString["confirm"]) != 0)
|
||||
{
|
||||
confirm = true;
|
||||
sessionDetails.Refresh();
|
||||
url = RemoveQueryStringByKey(urlOriginalString, "confirm");
|
||||
}
|
||||
}
|
||||
|
||||
if (confirm)
|
||||
{
|
||||
sbHtmlPageBody = new StringBuilder();
|
||||
sbHtmlPageBody.AppendFormat(@"<HEAD><META http-equiv='refresh' content=2;URL=/session?{0} /><CENTER><TITLE>QuickFIX Engine Web Interface</TITLE><H1>QuickFIX Engine Web Interface</H1></CENTER>", GetParameterList(urlOriginalString));
|
||||
sbHtmlPageBody.AppendFormat(@"<CENTER>[<A HREF='/'>HOME</A>] [<A HREF='{0}'>RELOAD</A>]</CENTER><HR/></HEAD><BODY>", urlOriginalString);
|
||||
sbHtmlPageBody.AppendFormat("<CENTER><h2><A HREF='/session?{0}'>{1}</A> has been refreshed</h2></CENTER>", GetParameterList(url), sessionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
sbHtmlPageBody.AppendFormat("<CENTER><h2>Are you sure you want to refresh session <A HREF='{0}'>{1}</A>?</h2></CENTER>", url, sessionId);
|
||||
sbHtmlPageBody.AppendFormat("<CENTER>[<A HREF='{0}&confirm=1'>YES, refresh session</A>] [<A HREF='{0}'>NO, do not refresh session</A>]</CENTER>", url);
|
||||
}
|
||||
return sbHtmlPageBody.ToString();
|
||||
}
|
||||
|
||||
private string RefreshSessions(HttpListenerRequest request)
|
||||
{
|
||||
bool confirm = false;
|
||||
string urlOriginalString = request.Url.OriginalString;
|
||||
StringBuilder sbHtmlPageBody = _sbHtmlHeader;
|
||||
|
||||
if (request.QueryString["confirm"] != null)
|
||||
{
|
||||
if (Convert.ToInt16(request.QueryString["confirm"]) != 0)
|
||||
{
|
||||
confirm = true;
|
||||
foreach (SessionID session in _sessionSettings.GetSessions())
|
||||
{
|
||||
Session sessionDetails = Session.LookupSession(session);
|
||||
sessionDetails.Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (confirm)
|
||||
{
|
||||
sbHtmlPageBody = new StringBuilder();
|
||||
sbHtmlPageBody.AppendFormat(@"<HEAD><META http-equiv='refresh' content=2;URL={0} /><CENTER><TITLE>QuickFIX Engine Web Interface</TITLE><H1>QuickFIX Engine Web Interface</H1></CENTER>", "/");
|
||||
sbHtmlPageBody.AppendFormat("<CENTER><h2><A HREF='{0}'>Sessions</A> have been refreshed</h2></CENTER>", "/");
|
||||
}
|
||||
else
|
||||
{
|
||||
sbHtmlPageBody.Append("<CENTER><h2>Are you sure you want to refresh all sessions ?</h2></CENTER>");
|
||||
sbHtmlPageBody.AppendFormat("<CENTER>[<A HREF='{0}?confirm=1'>YES, refresh sessions</A>] [<A HREF='/'>NO, do not refresh sessions</A>]</CENTER>", urlOriginalString);
|
||||
}
|
||||
return sbHtmlPageBody.ToString();
|
||||
}
|
||||
|
||||
private string ResetSessions(HttpListenerRequest request)
|
||||
{
|
||||
bool confirm = false;
|
||||
string urlOriginalString = request.Url.OriginalString;
|
||||
StringBuilder sbHtmlPageBody = _sbHtmlHeader;
|
||||
|
||||
if (request.QueryString["confirm"] != null)
|
||||
{
|
||||
if (Convert.ToInt16(request.QueryString["confirm"]) != 0)
|
||||
{
|
||||
confirm = true;
|
||||
foreach (SessionID session in _sessionSettings.GetSessions())
|
||||
{
|
||||
Session sessionDetails = Session.LookupSession(session);
|
||||
sessionDetails.Reset("Reset from WebInterface");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (confirm)
|
||||
{
|
||||
sbHtmlPageBody = new StringBuilder();
|
||||
sbHtmlPageBody.AppendFormat(@"<HEAD><META http-equiv='refresh' content=2;URL={0} /><CENTER><TITLE>QuickFIX Engine Web Interface</TITLE><H1>QuickFIX Engine Web Interface</H1></CENTER>", "/");
|
||||
sbHtmlPageBody.AppendFormat("<CENTER><h2><A HREF='{0}'>Sessions</A> have been reset</h2></CENTER>", "/");
|
||||
}
|
||||
else
|
||||
{
|
||||
sbHtmlPageBody.Append("<CENTER><h2>Are you sure you want to reset all sessions ?</h2></CENTER>");
|
||||
sbHtmlPageBody.AppendFormat("<CENTER>[<A HREF='{0}?confirm=1'>YES, reset sessions</A>] [<A HREF='/'>NO, do not reset sessions</A>]</CENTER>", urlOriginalString);
|
||||
}
|
||||
return sbHtmlPageBody.ToString();
|
||||
}
|
||||
|
||||
private string ResetSession(HttpListenerRequest request)
|
||||
{
|
||||
SessionID sessionId = new SessionID(request.QueryString["beginstring"], request.QueryString["sendercompid"], request.QueryString["targetcompid"]);
|
||||
Session sessionDetails = Session.LookupSession(sessionId);
|
||||
if (sessionDetails == null) throw new Exception("Session not found");
|
||||
StringBuilder sbHtmlPageBody = _sbHtmlHeader;
|
||||
|
||||
bool confirm = false;
|
||||
string urlOriginalString = request.Url.OriginalString;
|
||||
|
||||
string url = "/session?" + GetParameterList(urlOriginalString);
|
||||
|
||||
if (request.QueryString["confirm"] != null)
|
||||
{
|
||||
if (Convert.ToInt16(request.QueryString["confirm"]) != 0)
|
||||
{
|
||||
confirm = true;
|
||||
sessionDetails.Reset("Reset from WebInterface");
|
||||
url = RemoveQueryStringByKey(urlOriginalString, "confirm");
|
||||
}
|
||||
}
|
||||
|
||||
if (confirm)
|
||||
{
|
||||
sbHtmlPageBody = new StringBuilder();
|
||||
sbHtmlPageBody.AppendFormat(@"<HEAD><META http-equiv='refresh' content=2;URL=/session?{0} /><CENTER><TITLE>QuickFIX Engine Web Interface</TITLE><H1>QuickFIX Engine Web Interface</H1></CENTER>", GetParameterList(urlOriginalString));
|
||||
sbHtmlPageBody.AppendFormat(@"<CENTER>[<A HREF='/'>HOME</A>] [<A HREF='{0}'>RELOAD</A>]</CENTER><HR/></HEAD><BODY>", urlOriginalString);
|
||||
sbHtmlPageBody.AppendFormat("<CENTER><h2><A HREF='/session?{0}'>{1}</A> has been reset</h2></CENTER>", GetParameterList(url), sessionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
sbHtmlPageBody.AppendFormat("<CENTER><h2>Are you sure you want to reset session <A HREF='{0}'>{1}</A>?</h2></CENTER>", url, sessionId);
|
||||
sbHtmlPageBody.AppendFormat("<CENTER>[<A HREF='{0}&confirm=1'>YES, reset session</A>] [<A HREF='{0}'>NO, do not reset session</A>]</CENTER>", url);
|
||||
}
|
||||
return sbHtmlPageBody.ToString();
|
||||
}
|
||||
|
||||
|
||||
private string ProcessRoot(HttpListenerRequest request)
|
||||
{
|
||||
StringBuilder sbHtmlPageBody = _sbHtmlHeader;
|
||||
|
||||
//Session count
|
||||
sbHtmlPageBody.AppendFormat("<CENTER><b><i>{0} Sessions managed by QuickFIX</b></i></CENTER><HR/>", _sessionSettings.GetSessions().Count);
|
||||
|
||||
//session management links
|
||||
sbHtmlPageBody.AppendFormat(@"<CENTER><A HREF='/resetSessions{0}'>RESET</A> <A HREF='/refreshSessions'>REFRESH</A> <A HREF='/enableSessions'>ENABLE</A> <A HREF='/disableSessions'>DISABLE</A></CENTER><HR/></HEADER><BODY>", GetParameterList(request.Url.OriginalString));
|
||||
|
||||
//Start the table generation
|
||||
sbHtmlPageBody.Append("<table id=\"sessionlist\" style=\"border-width:1; padding:2; width:100%\">");
|
||||
|
||||
sbHtmlPageBody.Append("<tr>");
|
||||
sbHtmlPageBody.Append(AddCell("Session", true));
|
||||
sbHtmlPageBody.Append(AddCell("Type", true));
|
||||
sbHtmlPageBody.Append(AddCell("Enabled", true));
|
||||
sbHtmlPageBody.Append(AddCell("Session Time", true));
|
||||
sbHtmlPageBody.Append(AddCell("Logged On", true));
|
||||
sbHtmlPageBody.Append(AddCell("Next Incoming", true));
|
||||
sbHtmlPageBody.Append(AddCell("Next Outgoing", true));
|
||||
sbHtmlPageBody.Append("</tr>");
|
||||
|
||||
foreach (SessionID session in _sessionSettings.GetSessions())
|
||||
{
|
||||
Session sessionDetails = Session.LookupSession(session);
|
||||
sbHtmlPageBody.Append("<tr>");
|
||||
sbHtmlPageBody.Append(AddCell(String.Format(
|
||||
"<a href=\"session?BeginString={0}&SenderCompID={1}&TargetCompID={2}\">{0}:{1}->{2}</a>",
|
||||
session.BeginString, session.SenderCompID, session.TargetCompID)));
|
||||
sbHtmlPageBody.Append(AddCell(sessionDetails.IsInitiator ? "initiator" : "acceptor"));
|
||||
sbHtmlPageBody.Append(AddCell(sessionDetails.IsEnabled ? "yes" : "no"));
|
||||
sbHtmlPageBody.Append(AddCell(sessionDetails.IsSessionTime ? "yes" : "no"));
|
||||
sbHtmlPageBody.Append(AddCell(sessionDetails.IsLoggedOn ? "yes" : "no"));
|
||||
sbHtmlPageBody.Append(AddCell(sessionDetails.NextTargetMsgSeqNum.ToString()));
|
||||
sbHtmlPageBody.Append(AddCell(sessionDetails.NextSenderMsgSeqNum.ToString()));
|
||||
sbHtmlPageBody.Append("</tr>");
|
||||
}
|
||||
|
||||
sbHtmlPageBody.Append("</table>");
|
||||
return sbHtmlPageBody.ToString();
|
||||
}
|
||||
|
||||
public string SessionDetails(HttpListenerRequest request)
|
||||
{
|
||||
SessionID sessionId = new SessionID(request.QueryString["beginstring"], request.QueryString["sendercompid"], request.QueryString["targetcompid"]);
|
||||
Session sessionDetails = Session.LookupSession(sessionId);
|
||||
if (sessionDetails == null) throw new Exception("Session not found");
|
||||
StringBuilder sbHtmlPageBody = _sbHtmlHeader;
|
||||
|
||||
string url = request.Url.OriginalString;
|
||||
string urlOriginalString = request.Url.OriginalString;
|
||||
|
||||
if (request.QueryString["enabled"] != null)
|
||||
{
|
||||
if (!Convert.ToBoolean(request.QueryString["enabled"]))
|
||||
sessionDetails.Logout();
|
||||
else
|
||||
sessionDetails.Logon();
|
||||
|
||||
url = RemoveQueryStringByKey(urlOriginalString, "Enabled");
|
||||
}
|
||||
|
||||
if (request.QueryString["next incoming"] != null)
|
||||
{
|
||||
int value = Convert.ToInt16(request.QueryString["next incoming"]);
|
||||
sessionDetails.NextTargetMsgSeqNum = value <= 0 ? 1 : value;
|
||||
url = RemoveQueryStringByKey(urlOriginalString, "next incoming");
|
||||
}
|
||||
|
||||
if (request.QueryString["Next Outgoing"] != null)
|
||||
{
|
||||
int value = Convert.ToInt16(request.QueryString["Next Outgoing"]);
|
||||
sessionDetails.NextSenderMsgSeqNum = value <= 0 ? 1 : value;
|
||||
url = RemoveQueryStringByKey(urlOriginalString, "Next Outgoing");
|
||||
}
|
||||
|
||||
if (request.QueryString["SendRedundantResendRequests"] != null)
|
||||
{
|
||||
sessionDetails.SendRedundantResendRequests = Convert.ToBoolean(request.QueryString["SendRedundantResendRequests"]);
|
||||
url = RemoveQueryStringByKey(urlOriginalString, "SendRedundantResendRequests");
|
||||
}
|
||||
|
||||
if (request.QueryString["CheckCompId"] != null)
|
||||
{
|
||||
sessionDetails.CheckCompID = Convert.ToBoolean(request.QueryString["CheckCompId"]);
|
||||
url = RemoveQueryStringByKey(urlOriginalString, "CheckCompId");
|
||||
}
|
||||
|
||||
if (request.QueryString["CheckLatency"] != null)
|
||||
{
|
||||
sessionDetails.CheckLatency = Convert.ToBoolean(request.QueryString["CheckLatency"]);
|
||||
url = RemoveQueryStringByKey(urlOriginalString, "CheckLatency");
|
||||
}
|
||||
|
||||
if (request.QueryString["MaxLatency"] != null)
|
||||
{
|
||||
int value = Convert.ToInt16(request.QueryString["MaxLatency"]);
|
||||
sessionDetails.MaxLatency = value <= 0 ? 1 : value;
|
||||
url = RemoveQueryStringByKey(urlOriginalString, "MaxLatency");
|
||||
}
|
||||
|
||||
if (request.QueryString["LogonTimeout"] != null)
|
||||
{
|
||||
int value = Convert.ToInt16(request.QueryString["LogonTimeout"]);
|
||||
sessionDetails.LogonTimeout = value <= 0 ? 1 : value;
|
||||
url = RemoveQueryStringByKey(urlOriginalString, "LogonTimeout");
|
||||
}
|
||||
|
||||
if (request.QueryString["LogoutTimeout"] != null)
|
||||
{
|
||||
int value = Convert.ToInt16(request.QueryString["LogoutTimeout"]);
|
||||
sessionDetails.LogoutTimeout = value <= 0 ? 1 : value;
|
||||
url = RemoveQueryStringByKey(urlOriginalString, "LogoutTimeout");
|
||||
}
|
||||
|
||||
if (request.QueryString["ResetOnLogon"] != null)
|
||||
{
|
||||
sessionDetails.ResetOnLogon = Convert.ToBoolean(request.QueryString["ResetOnLogon"]);
|
||||
url = RemoveQueryStringByKey(urlOriginalString, "ResetOnLogon");
|
||||
}
|
||||
|
||||
if (request.QueryString["ResetOnLogout"] != null)
|
||||
{
|
||||
sessionDetails.ResetOnLogout = Convert.ToBoolean(request.QueryString["ResetOnLogout"]);
|
||||
url = RemoveQueryStringByKey(urlOriginalString, "ResetOnLogout");
|
||||
}
|
||||
|
||||
if (request.QueryString["ResetOnDisconnect"] != null)
|
||||
{
|
||||
sessionDetails.ResetOnDisconnect = Convert.ToBoolean(request.QueryString["ResetOnDisconnect"]);
|
||||
url = RemoveQueryStringByKey(urlOriginalString, "ResetOnDisconnect");
|
||||
}
|
||||
|
||||
if (request.QueryString["RefreshOnLogon"] != null)
|
||||
{
|
||||
sessionDetails.RefreshOnLogon = Convert.ToBoolean(request.QueryString["RefreshOnLogon"]);
|
||||
url = RemoveQueryStringByKey(urlOriginalString, "RefreshOnLogon");
|
||||
}
|
||||
|
||||
if (request.QueryString["MillisecondsInTimestamp"] != null)
|
||||
{
|
||||
sessionDetails.MillisecondsInTimeStamp = Convert.ToBoolean(request.QueryString["MillisecondsInTimestamp"]);
|
||||
url = RemoveQueryStringByKey(urlOriginalString, "MillisecondsInTimestamp");
|
||||
}
|
||||
|
||||
if (request.QueryString["PersistMessages"] != null)
|
||||
{
|
||||
sessionDetails.PersistMessages = Convert.ToBoolean(request.QueryString["PersistMessages"]);
|
||||
url = RemoveQueryStringByKey(urlOriginalString, "PersistMessages");
|
||||
}
|
||||
|
||||
|
||||
sbHtmlPageBody.AppendFormat(@"<CENTER>{0}</CENTER><HR/>", sessionId);
|
||||
sbHtmlPageBody.AppendFormat(@"<CENTER>[<A HREF='/resetSession?{0}'>RESET</A>] [<A HREF='/refreshSession?{0}'>REFRESH</A>]</CENTER><HR/></HEADER><BODY>", GetParameterList(urlOriginalString));
|
||||
|
||||
sbHtmlPageBody.Append("<table id=\"session_details\" style=\"border-width:1; padding:2; width:100%\">");
|
||||
|
||||
sbHtmlPageBody.Append(AddRow("Enabled", sessionDetails.IsEnabled, url));
|
||||
sbHtmlPageBody.Append(AddRow("ConnectionType", sessionDetails.IsInitiator ? "initiator" : "acceptor"));
|
||||
sbHtmlPageBody.Append(AddRow("SessionTime", sessionDetails.IsSessionTime));
|
||||
sbHtmlPageBody.Append(AddRow("LoggedOn", sessionDetails.IsLoggedOn));
|
||||
sbHtmlPageBody.Append(AddRow("Next Incoming", sessionDetails.NextTargetMsgSeqNum, url));
|
||||
sbHtmlPageBody.Append(AddRow("Next Outgoing", sessionDetails.NextSenderMsgSeqNum, url));
|
||||
sbHtmlPageBody.Append(AddRow("SendRedundantResendRequests", sessionDetails.SendRedundantResendRequests, url));
|
||||
sbHtmlPageBody.Append(AddRow("CheckCompId", sessionDetails.CheckCompID, url));
|
||||
sbHtmlPageBody.Append(AddRow("CheckLatency", sessionDetails.CheckLatency, url));
|
||||
sbHtmlPageBody.Append(AddRow("MaxLatency", sessionDetails.MaxLatency, url));
|
||||
sbHtmlPageBody.Append(AddRow("LogonTimeout", sessionDetails.LogonTimeout, url));
|
||||
sbHtmlPageBody.Append(AddRow("LogoutTimeout", sessionDetails.LogoutTimeout, url));
|
||||
sbHtmlPageBody.Append(AddRow("ResetOnLogon", sessionDetails.ResetOnLogon, url));
|
||||
sbHtmlPageBody.Append(AddRow("ResetOnLogout", sessionDetails.ResetOnLogout, url));
|
||||
sbHtmlPageBody.Append(AddRow("ResetOnDisconnect", sessionDetails.ResetOnDisconnect, url));
|
||||
sbHtmlPageBody.Append(AddRow("RefreshOnLogon", sessionDetails.RefreshOnLogon, url));
|
||||
sbHtmlPageBody.Append(AddRow("MillisecondsInTimestamp", sessionDetails.MillisecondsInTimeStamp, url));
|
||||
sbHtmlPageBody.Append(AddRow("PersistMessages", sessionDetails.PersistMessages, url));
|
||||
|
||||
sbHtmlPageBody.Append("</table>");
|
||||
return sbHtmlPageBody.ToString();
|
||||
}
|
||||
|
||||
public static string RemoveQueryStringByKey(string url, string key)
|
||||
{
|
||||
var uri = new Uri(url);
|
||||
|
||||
// this gets all the query string key value pairs as a collection
|
||||
var newQueryString = HttpUtility.ParseQueryString(uri.Query);
|
||||
|
||||
// this removes the key if exists
|
||||
newQueryString.Remove(key);
|
||||
|
||||
// this gets the page path from root without QueryString
|
||||
string pagePathWithoutQueryString = uri.GetLeftPart(UriPartial.Path);
|
||||
|
||||
return newQueryString.Count > 0
|
||||
? $"{pagePathWithoutQueryString}?{newQueryString}"
|
||||
: pagePathWithoutQueryString;
|
||||
}
|
||||
|
||||
public static string GetParameterList(string url)
|
||||
{
|
||||
return HttpUtility.ParseQueryString((new Uri(url).Query)).ToString();
|
||||
}
|
||||
|
||||
private static string AddRow(string colName, bool value, string url = "")
|
||||
{
|
||||
string valueAsStr = value ? "yes" : "no";
|
||||
string innerHtml = url.Length > 0
|
||||
? $"<a href=\" {url}&{colName}={!value} \">toggle</a>"
|
||||
: "";
|
||||
return AddRow(colName, valueAsStr, innerHtml);
|
||||
}
|
||||
|
||||
private static string AddRow(string colName, int value, string url = "")
|
||||
{
|
||||
string innerHtml = $"<a href=\" {url}&{colName}={value - 10} \"> << </a>" +
|
||||
$"<a href=\" {url}&{colName}={value - 1} \"> < </a>" +
|
||||
" | " +
|
||||
$"<a href=\" {url}&{colName}={value + 1} \"> > </a>" +
|
||||
$"<a href=\" {url}&{colName}={value + 10} \"> >> </a>";
|
||||
return AddRow(colName, value.ToString(), innerHtml);
|
||||
}
|
||||
private static string AddRow(string colName, string value, string innerHtml = "")
|
||||
{
|
||||
StringBuilder row = new StringBuilder();
|
||||
row.Append("<tr>");
|
||||
row.Append(AddCell(colName));
|
||||
row.Append(AddCell(value));
|
||||
row.Append(AddCell(innerHtml));
|
||||
row.Append("</tr>");
|
||||
return row.ToString();
|
||||
}
|
||||
|
||||
private static string AddCell(string cellContent, bool header = false)
|
||||
{
|
||||
string entryType = header ? "th" : "td";
|
||||
return "<" + entryType + " align=\"left\">" + cellContent + "</" + entryType + ">";
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (disposing)
|
||||
{
|
||||
if (_running)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
if (_connectionThread != null)
|
||||
{
|
||||
_connectionThread.Abort();
|
||||
_connectionThread = null;
|
||||
}
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# BTS Spark FIX Adapter
|
||||
|
||||
@@ -1,281 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Text;
|
||||
|
||||
using QuickFix;
|
||||
using QuickFix.Fields;
|
||||
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.api.user;
|
||||
using bts.api.admin;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace bts.fix
|
||||
{
|
||||
class DropCopySession : SessionBase, QuickFix.IApplication
|
||||
{
|
||||
protected IDisposable _fillsdisp;
|
||||
protected IDisposable _ordersdisp;
|
||||
|
||||
|
||||
protected readonly ConcurrentDictionary<uint, OrderRecordImmutable> _order_cache = new ConcurrentDictionary<uint, OrderRecordImmutable>();
|
||||
protected readonly ConcurrentDictionary<uint, string> _fix_clorderids = new ConcurrentDictionary<uint, string>();
|
||||
public DropCopySession(IFixRouter router, SessionID sess) : base(router, sess)
|
||||
{
|
||||
}
|
||||
|
||||
override public void OnLogon(SessionID sessionID)
|
||||
{
|
||||
Log.assert_d(sessionID.Equals(_sess));
|
||||
var msg = sessionID.ToString() + " Logged on";
|
||||
log_info(msg);
|
||||
|
||||
subscribe_fills_and_orders(sessionID);
|
||||
}
|
||||
override public void OnLogout(SessionID sessionID)
|
||||
{
|
||||
Log.assert_d(sessionID.Equals(_sess));
|
||||
var msg = sessionID.ToString() + " Logged out";
|
||||
log_info(msg);
|
||||
|
||||
if (_ordersdisp != null)
|
||||
_ordersdisp.Dispose();
|
||||
if (_fillsdisp != null)
|
||||
_fillsdisp.Dispose();
|
||||
}
|
||||
|
||||
override public void FromApp(Message message, SessionID sessionID)
|
||||
{
|
||||
Log.assert_d(sessionID.Equals(_sess));
|
||||
MsgType type = Message.IdentifyType(message.ToString());
|
||||
switch (type.getValue())
|
||||
{
|
||||
case MsgType.ORDER_STATUS_REQUEST:
|
||||
case MsgType.SECURITYDEFINITIONREQUEST:
|
||||
log_info("IN: " + message);
|
||||
Crack(message, sessionID);
|
||||
break;
|
||||
default:
|
||||
log_info("UNSUPPORTED MESSAGE IN: " + type.getValue());
|
||||
throw new UnsupportedMessageType();
|
||||
}
|
||||
}
|
||||
|
||||
virtual public void OnMessage(QuickFix.FIX44.OrderStatusRequest req, SessionID s)
|
||||
{
|
||||
if (req.IsSetOrderID() && req.IsSetOrdStatusReqID())
|
||||
{
|
||||
uint oid = uint.Parse(req.OrderID.getValue());
|
||||
if (_order_cache.TryGetValue(oid, out OrderRecordImmutable order))
|
||||
{
|
||||
var ostat = build_order_status(s, order);
|
||||
ostat.OrdStatusReqID = new OrdStatusReqID(req.OrdStatusReqID.getValue());
|
||||
send(ostat, s);
|
||||
}
|
||||
else
|
||||
send_reject(s, "Can't find order " + oid, req);
|
||||
}
|
||||
else
|
||||
send_reject(s, "clorderid null", req);
|
||||
}
|
||||
|
||||
virtual public void OnMessage(QuickFix.FIX44.SecurityDefinitionRequest req, SessionID s)
|
||||
{
|
||||
if (req.IsSetSecurityReqID() && req.IsSetSecurityRequestType() &&
|
||||
req.SecurityRequestType.getValue() == SecurityRequestType.REQUEST_LIST_SECURITIES)
|
||||
{
|
||||
var secs = _router.get_securities();
|
||||
if (req.IsSetSymbol())
|
||||
secs = secs.Where(x => x.Symbol.ToLower() == req.Symbol.getValue().ToLower());
|
||||
if (req.IsSetSecurityType())
|
||||
secs = secs.Where(x => security_types_equal(x.security_type(), req.SecurityType));
|
||||
if (req.IsSetSecurityExchange())
|
||||
secs = secs.Where(x => exchanges_equal(x.Exchange, req.SecurityExchange));
|
||||
|
||||
var def = new QuickFix.FIX44.SecurityDefinition(req.SecurityReqID,
|
||||
new SecurityResponseID(Guid.NewGuid().ToString()),
|
||||
new SecurityResponseType(SecurityResponseType.LIST_OF_SECURITIES_RETURNED_PER_REQUEST));
|
||||
send(def, s);
|
||||
}
|
||||
else
|
||||
send_reject(s, "Can't parse secdef request", req);
|
||||
}
|
||||
|
||||
|
||||
|
||||
virtual protected void subscribe_fills_and_orders(SessionID sessionID)
|
||||
{
|
||||
|
||||
|
||||
var scheduler = TaskPoolScheduler.Default;
|
||||
var interval = TimeSpan.FromMilliseconds(500);
|
||||
var on_orders = _orderservice.subscribei_orders().Naggle(interval, scheduler).PermissionFilter(_adminobservable).Table().Cache();
|
||||
var on_fills = _orderservice.subscribei_fills().Naggle(interval, scheduler).PermissionFilter(_adminobservable).Table().Cache();
|
||||
|
||||
_ordersdisp = on_orders.Subscribe(o =>
|
||||
{
|
||||
foreach (var order in o.added)
|
||||
{
|
||||
var ostat = build_order_status(sessionID, order);
|
||||
_order_cache[order.Key] = order;
|
||||
send(ostat, sessionID);
|
||||
}
|
||||
foreach (var order in o.changed)
|
||||
{
|
||||
var ostat = build_order_status(sessionID, order, true);
|
||||
send(ostat, sessionID);
|
||||
}
|
||||
});
|
||||
|
||||
_fillsdisp = on_fills.Subscribe(f =>
|
||||
{
|
||||
foreach (var fill in f.added)
|
||||
{
|
||||
var fi = build_fill(sessionID, fill);
|
||||
send(fi, sessionID);
|
||||
}
|
||||
foreach (var fill in f.changed)
|
||||
{
|
||||
var fi = build_fill(sessionID, fill);
|
||||
send(fi, sessionID);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
virtual protected void process_order_state(in OrderStateImmutable state, ref uint filled, ref uint leaves, ref double avgprx,
|
||||
ref QuickFix.Fields.OrdStatus fstatus, ref NativeOrderStateImmutable? native)
|
||||
{
|
||||
filled = state.FilledQuantity;
|
||||
leaves = state.Quantity - state.FilledQuantity;
|
||||
avgprx = state.AvgPrice;
|
||||
fstatus = translate_status(state, ref native);
|
||||
}
|
||||
|
||||
protected bts.api.SecurityImmutable? resolve_execution_report_security(bts.api.SecurityImmutable? sec, QuickFix.FIX44.ExecutionReport report)
|
||||
{
|
||||
report.SecurityExchange = convert_exchange(sec.Value.Exchange);
|
||||
var sectype = sec.Value.security_type();
|
||||
report.SecurityType = convert_security_type(sectype);
|
||||
if (sec.Value.HasGlbx)
|
||||
report.SecurityDesc = new SecurityDesc(sec.Value.Glbx.SecurityDesc);
|
||||
|
||||
switch (sectype)
|
||||
{
|
||||
case bts.api.SecurityType.Future:
|
||||
report.MaturityMonthYear = new MaturityMonthYear(bts.api.SecurityHelper.expiration_date(sec.Value.Future).ToString("yyyyMMdd"));
|
||||
break;
|
||||
case bts.api.SecurityType.Option:
|
||||
throw new NotSupportedException("No options yet");
|
||||
case bts.api.SecurityType.Spread:
|
||||
//report.MaturityMonthYear = new MaturityMonthYear(bts.api.SecurityHelper.EarliestExpiration(sec.Value).ToString("yyyyMMdd"));
|
||||
//TODO
|
||||
//bug here need to kill MDCLIENT
|
||||
break;
|
||||
default:
|
||||
report.MaturityMonthYear = new MaturityMonthYear(bts.api.SecurityHelper.EarliestExpiration(sec.Value).ToString("yyyyMMdd"));
|
||||
break;
|
||||
}
|
||||
return sec;
|
||||
}
|
||||
|
||||
virtual protected QuickFix.FIX44.ExecutionReport build_fill(SessionID sess, in FillImmutable fill, bool change = false)
|
||||
{
|
||||
var exec = new QuickFix.Fields.ExecType(QuickFix.Fields.ExecType.FILL);
|
||||
QuickFix.Fields.OrdStatus status = new QuickFix.Fields.OrdStatus();
|
||||
uint filled = 0; uint leaves = 0; double prx = 0.0;
|
||||
OrderRecordImmutable order; NativeOrderStateImmutable? native = null;
|
||||
if (_order_cache.TryGetValue(fill.OrderKey, out order))
|
||||
{
|
||||
process_order_state(order.State, ref filled, ref leaves, ref prx, ref status, ref native);
|
||||
if (leaves > 0)
|
||||
exec = new QuickFix.Fields.ExecType(QuickFix.Fields.ExecType.PARTIAL_FILL);
|
||||
}
|
||||
if (change)
|
||||
exec = new QuickFix.Fields.ExecType(QuickFix.Fields.ExecType.TRADE_CORRECT);
|
||||
|
||||
var sec = _router.lookup(fill.SecurityKey);
|
||||
QuickFix.FIX44.ExecutionReport report = new QuickFix.FIX44.ExecutionReport(
|
||||
new OrderID(fill.OrderKey.ToString()),
|
||||
new ExecID(fill.FillKey.ToString()),
|
||||
exec,
|
||||
status,
|
||||
new Symbol(sec?.Symbol ?? string.Empty),
|
||||
new Side(fill.Side == bts.api.Side.Buy ? Side.BUY : Side.SELL),
|
||||
new LeavesQty(leaves),
|
||||
new CumQty(filled),
|
||||
new AvgPx(Convert.ToDecimal(prx)));
|
||||
|
||||
report.Account = new Account(fill.Account);
|
||||
report.LastQty = new LastQty(Convert.ToDecimal(fill.LastShares));
|
||||
report.LastPx = new LastPx(Convert.ToDecimal(fill.LastPrice));
|
||||
var ts = fill.HasTimestamp ? fill.Timestamp.ToDateTime() : DateTime.MinValue;
|
||||
report.TransactTime = new TransactTime(ts, true);
|
||||
report.TradeDate = new TradeDate(ts.ToString("yyyyMMdd"));
|
||||
sec = resolve_execution_report_security(sec, report);
|
||||
if (native.HasValue)
|
||||
report.SecondaryOrderID = new SecondaryOrderID(native.Value.ExchangeOrderId);
|
||||
|
||||
if (sec.HasValue && sec.Value.security_type() == bts.api.SecurityType.Spread ||
|
||||
sec.Value.security_type() == bts.api.SecurityType.Synthetic)
|
||||
{
|
||||
if (fill.FillKind == bts.api.orderman.Fill.Types.FillKind.Leg)
|
||||
report.MultiLegReportingType = new MultiLegReportingType('2');
|
||||
else
|
||||
report.MultiLegReportingType = new MultiLegReportingType('3');
|
||||
}
|
||||
return report;
|
||||
}
|
||||
|
||||
virtual protected QuickFix.FIX44.ExecutionReport build_order_status(SessionID sess, in OrderRecordImmutable order, bool change = false)
|
||||
{
|
||||
var exec = new QuickFix.Fields.ExecType(QuickFix.Fields.ExecType.NEW);
|
||||
if (_order_cache.TryGetValue(order.Key, out OrderRecordImmutable _))
|
||||
exec = new QuickFix.Fields.ExecType(QuickFix.Fields.ExecType.ORDER_STATUS);
|
||||
|
||||
QuickFix.Fields.OrdStatus status = new QuickFix.Fields.OrdStatus();
|
||||
uint filled = 0; uint leaves = 0; double prx = 0.0; NativeOrderStateImmutable? native = null;
|
||||
process_order_state(order.State, ref filled, ref leaves, ref prx, ref status, ref native);
|
||||
|
||||
var sec = _router.lookup(order.Def.SecurityKey);
|
||||
QuickFix.FIX44.ExecutionReport report = new QuickFix.FIX44.ExecutionReport(
|
||||
new OrderID(order.Key.ToString()),
|
||||
new ExecID("0"),
|
||||
exec,
|
||||
status,
|
||||
new Symbol(sec?.Symbol ?? string.Empty),
|
||||
new Side(order.Def.Side == bts.api.Side.Buy ? Side.BUY : Side.SELL),
|
||||
new LeavesQty(leaves),
|
||||
new CumQty(filled),
|
||||
new AvgPx(Convert.ToDecimal(prx)));
|
||||
|
||||
report.Account = new Account(order.Def.Account);
|
||||
report.OrderQty = new OrderQty(order.State.Quantity);
|
||||
report.Price = new Price(Convert.ToDecimal(order.State.Price));
|
||||
report.TimeInForce = new QuickFix.Fields.TimeInForce((char)order.Def.TimeInForce);
|
||||
var ts = order.State.LastChange.ToDateTime();
|
||||
report.TransactTime = new TransactTime(ts, true);
|
||||
if(!string.IsNullOrEmpty(order.State.LastMsg))
|
||||
report.Text = new Text(order.State.LastMsg);
|
||||
|
||||
sec = resolve_execution_report_security(sec, report);
|
||||
if (native.HasValue)
|
||||
{
|
||||
report.SecondaryOrderID = new SecondaryOrderID(native.Value.ExchangeOrderId);
|
||||
report.OrdType = new OrdType((char)native.Value.NativeType);
|
||||
}
|
||||
//TODO Big race here, we need a correlation id of some sort
|
||||
if (_fix_clorderids.TryGetValue(order.Key, out string clorderid))
|
||||
{
|
||||
report.ClOrdID = new ClOrdID(clorderid);
|
||||
}
|
||||
return report;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using QuickFix;
|
||||
using QuickFix.Fields;
|
||||
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
|
||||
namespace bts.fix
|
||||
{
|
||||
class OrderSession : DropCopySession, QuickFix.IApplication
|
||||
{
|
||||
public OrderSession(IFixRouter router, SessionID sess) : base(router, sess)
|
||||
{
|
||||
}
|
||||
|
||||
override public void FromApp(Message message, SessionID sessionID)
|
||||
{
|
||||
Log.assert_d(sessionID.Equals(_sess));
|
||||
MsgType type = Message.IdentifyType(message.ToString());
|
||||
switch (type.getValue())
|
||||
{
|
||||
case MsgType.ORDER_STATUS_REQUEST:
|
||||
case MsgType.SECURITYDEFINITIONREQUEST:
|
||||
|
||||
case MsgType.NEWORDERSINGLE:
|
||||
case MsgType.ORDERCANCELREPLACEREQUEST:
|
||||
case MsgType.ORDERCANCELREQUEST:
|
||||
log_info("IN: " + message);
|
||||
Crack(message, sessionID);
|
||||
break;
|
||||
default:
|
||||
log_info("UNSUPPORTED MESSAGE IN: " + type.getValue());
|
||||
throw new UnsupportedMessageType();
|
||||
}
|
||||
}
|
||||
|
||||
virtual public void OnMessage(QuickFix.FIX44.NewOrderSingle req, SessionID s)
|
||||
{
|
||||
Venue ven = convert_exchange(req.SecurityExchange);
|
||||
if(ven != Venue.Glbx)
|
||||
throw new IncorrectTagValue(req.SecurityExchange.Tag);
|
||||
|
||||
var sec = _router.lookup(req.Symbol.getValue());
|
||||
if(sec == null)
|
||||
sec = _router.lookup(req.SecurityDesc.getValue());
|
||||
if(!sec.HasValue)
|
||||
throw new IncorrectTagValue(req.SecurityDesc.Tag);
|
||||
|
||||
OrderDefinition def = new OrderDefinition()
|
||||
{
|
||||
Account = req.Account.getValue(),
|
||||
AccountId = lookup_account(req.Account.getValue()),
|
||||
SecurityKey = sec.Value.Id,
|
||||
Side = (api.Side)int.Parse(req.Side.getValue().ToString()),
|
||||
TimeInForce = (api.orderman.TimeInForce)int.Parse(req.TimeInForce.getValue().ToString()),
|
||||
User = _user_name,
|
||||
UserId = lookup_user(_user_name),
|
||||
ManualOrderIndicator = bts.api.orderman.ManualOrderIndicator.Yes,
|
||||
};
|
||||
|
||||
set_ordertype(def, req, out bool price_req);
|
||||
Create cr = new Create();
|
||||
if(price_req)
|
||||
cr.Price = Convert.ToDouble(req.Price.getValue());
|
||||
cr.NewOrder = def;
|
||||
cr.Quantity = Convert.ToUInt32(Math.Abs(req.OrderQty.getValue()));
|
||||
|
||||
_orderservice.create_order_async(cr);
|
||||
}
|
||||
|
||||
private void set_ordertype(OrderDefinition def, QuickFix.FIX44.NewOrderSingle req, out bool price_req)
|
||||
{
|
||||
price_req = false;
|
||||
switch (req.OrdType.getValue())
|
||||
{
|
||||
case '1':
|
||||
def.CustomDef = new CustomOrderDefinition { Market = new MarketOrderDefinition() };
|
||||
break;
|
||||
case '2':
|
||||
def.CustomDef = new CustomOrderDefinition { Limit = new LimitOrderDefinition() };
|
||||
price_req = true;
|
||||
break;
|
||||
case '3':
|
||||
def.CustomDef = new CustomOrderDefinition { Stop = new StopOrderDefinition() {StopPrice = Convert.ToDouble(req.StopPx.getValue()) } };
|
||||
break;
|
||||
case '4':
|
||||
def.CustomDef = new CustomOrderDefinition { StopLimit = new StopLimitOrderDefinition() { StopPrice = Convert.ToDouble(req.StopPx.getValue()) } };
|
||||
price_req = true;
|
||||
break;
|
||||
default:
|
||||
throw new IncorrectTagValue(req.OrdType.Tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,359 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Text;
|
||||
|
||||
using QuickFix;
|
||||
using QuickFix.Fields;
|
||||
|
||||
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.api.user;
|
||||
using bts.api.admin;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
|
||||
namespace bts.fix
|
||||
{
|
||||
abstract class SessionBase : QuickFix.MessageCracker, QuickFix.IApplication
|
||||
{
|
||||
protected static UserService _userservice = bts.api.Locator.locate<UserService>();
|
||||
protected static OrderService _orderservice = bts.api.Locator.locate<OrderService>();
|
||||
protected static AdminService _adminservice = bts.api.Locator.locate<AdminService>();
|
||||
protected static bts.metrics.ShellLatencyService _latencyservice = bts.api.Locator.locate<bts.metrics.ShellLatencyService>();
|
||||
|
||||
protected string _user_name;
|
||||
protected AuthToken _token;
|
||||
|
||||
protected IDisposable _admindisp;
|
||||
protected IObservable<AdminModelWrapper> _adminobservable;
|
||||
protected AdminDataImmutable? _admin_data;
|
||||
|
||||
protected readonly IFixRouter _router;
|
||||
protected readonly SessionID _sess;
|
||||
|
||||
public SessionBase(IFixRouter router, SessionID sess)
|
||||
{
|
||||
_router = router;
|
||||
_sess = sess;
|
||||
|
||||
_clockdisp = _latencyservice.subscribe_clock().Subscribe(x =>
|
||||
{
|
||||
var t = x.all.FirstOrDefault();
|
||||
if(t != null)
|
||||
_clock = t.Timestamp;
|
||||
});
|
||||
|
||||
_adminobservable = _adminservice.subscribei_admin_data().Select(x =>
|
||||
{
|
||||
return new AdminModelWrapper(x.all.TryFirst(), _user_name);
|
||||
});
|
||||
_admindisp = _adminobservable.Subscribe(x => _admin_data = x.admin_data);
|
||||
}
|
||||
|
||||
abstract public void FromApp(Message message, SessionID sessionID);
|
||||
abstract public void OnLogon(SessionID sessionID); //subscribe to your stuff
|
||||
abstract public void OnLogout(SessionID sessionID); //unsub
|
||||
|
||||
virtual public void ToApp(Message message, SessionID sessionID)
|
||||
{
|
||||
Log.assert_d(sessionID.Equals(_sess));
|
||||
log_info("OUT: " + message);
|
||||
}
|
||||
|
||||
readonly IDisposable _clockdisp; ulong _clock = 0;
|
||||
virtual public void FromAdmin(Message message, SessionID sessionID)
|
||||
{
|
||||
Log.assert_d(sessionID.Equals(_sess));
|
||||
|
||||
MsgType type = Message.IdentifyType(message.ToString());
|
||||
switch (type.getValue())
|
||||
{
|
||||
case MsgType.LOGON:
|
||||
{
|
||||
try
|
||||
{
|
||||
QuickFix.FIX44.Logon msg = (QuickFix.FIX44.Logon)message;
|
||||
_user_name = msg.Username.getValue();
|
||||
string pass = string.Empty;
|
||||
if(msg.IsSetPassword())
|
||||
pass = msg.Password.getValue();
|
||||
var resp = _userservice.authenticate_login(new AuthenticateLoginRequest {
|
||||
Login = msg.Username.getValue(), Password = pass});
|
||||
if (resp != null && resp.IsValid)
|
||||
_token = resp.Token;
|
||||
else
|
||||
throw new RejectLogon("Logon rejected " + resp?.Reason ?? string.Empty);
|
||||
}
|
||||
catch (RejectLogon)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new RejectLogon("Logon rejected " + ex.ToString());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MsgType.HEARTBEAT:
|
||||
{
|
||||
QuickFix.FIX44.Heartbeat hb = (QuickFix.FIX44.Heartbeat)message;
|
||||
handle_heartbeat(hb, sessionID);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
log_info("ADMININ: " + message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async protected void handle_heartbeat(QuickFix.FIX44.Heartbeat hb, SessionID sessionID)
|
||||
{
|
||||
var resp = await _latencyservice.report_async(new bts.metrics.Report { HostName = "BTSFIX_" + sessionID.ToString(),
|
||||
Timestamp = _clock, User = _user_name });
|
||||
if (resp.success)
|
||||
send(hb, sessionID);
|
||||
}
|
||||
|
||||
virtual public void ToAdmin(Message message, SessionID sessionID)
|
||||
{
|
||||
Log.assert_d(sessionID.Equals(_sess));
|
||||
log_info("ADMINOUT: " + message);
|
||||
}
|
||||
|
||||
virtual public void OnCreate(SessionID sessionID)
|
||||
{
|
||||
log_info("CREATE");
|
||||
}
|
||||
|
||||
virtual public void OnMessage(QuickFix.FIX44.BusinessMessageReject n, SessionID s)
|
||||
{
|
||||
//Log.assert(false, "biz reject wtf " + n.EncodedText.getValue());
|
||||
Log.FIX.warn(_sess.ToString() + " Business Reject " + n.BusinessRejectReason.getValue() + " " + n.Text.getValue());
|
||||
}
|
||||
virtual public void OnMessage(QuickFix.FIX44.News n, SessionID s)
|
||||
{
|
||||
log_info("news " + n.EncodedHeadline.getValue());
|
||||
}
|
||||
|
||||
|
||||
protected void log_info(string message, [CallerFilePath] string file = "", [CallerLineNumber] int line = 0)
|
||||
{
|
||||
Log.FIX.info(_sess.ToString() + " " + message, file, line);
|
||||
}
|
||||
|
||||
protected void log_error(string message, [CallerFilePath] string file = "", [CallerLineNumber] int line = 0)
|
||||
{
|
||||
Log.FIX.error(_sess.ToString() + " " + message, file, line);
|
||||
}
|
||||
|
||||
protected void send(Message mess, SessionID sess)
|
||||
{
|
||||
try
|
||||
{
|
||||
Session.SendToTarget(mess, sess);
|
||||
}
|
||||
catch (SessionNotFound ex)
|
||||
{
|
||||
log_error("==session not found exception!==");
|
||||
log_error(ex.ToString());
|
||||
Log.assert(false, "session not found " + sess);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log_error("==exception sending to " + sess + " " + ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void send_reject(SessionID sess, string text, Message refmessage = null)
|
||||
{
|
||||
var seq = new QuickFix.Fields.MsgSeqNum();
|
||||
QuickFix.FIX44.Reject rej = null;
|
||||
if (refmessage.Header.IsSetField(seq))
|
||||
rej = new QuickFix.FIX44.Reject(new QuickFix.Fields.RefSeqNum(seq.getValue()));
|
||||
else
|
||||
rej = new QuickFix.FIX44.Reject();
|
||||
rej.Text = new Text(text);
|
||||
send(rej, sess);
|
||||
}
|
||||
|
||||
protected bool security_types_equal(bts.api.SecurityType btstype, QuickFix.Fields.SecurityType fixtype)
|
||||
{
|
||||
return btstype == convert_security_type(fixtype);
|
||||
}
|
||||
|
||||
protected QuickFix.Fields.SecurityType convert_security_type(bts.api.SecurityType btstype)
|
||||
{
|
||||
switch (btstype)
|
||||
{
|
||||
case bts.api.SecurityType.Future:
|
||||
return new QuickFix.Fields.SecurityType("FUT");
|
||||
case bts.api.SecurityType.Option:
|
||||
return new QuickFix.Fields.SecurityType("OPT");
|
||||
case bts.api.SecurityType.Spread:
|
||||
return new QuickFix.Fields.SecurityType("MLEG");
|
||||
case bts.api.SecurityType.Stock:
|
||||
return new QuickFix.Fields.SecurityType("CS");
|
||||
case bts.api.SecurityType.Synthetic:
|
||||
return new QuickFix.Fields.SecurityType("SYNTH");
|
||||
default:
|
||||
return new QuickFix.Fields.SecurityType();
|
||||
}
|
||||
}
|
||||
|
||||
protected bts.api.SecurityType convert_security_type(QuickFix.Fields.SecurityType fixtype)
|
||||
{
|
||||
switch (fixtype.getValue())
|
||||
{
|
||||
case "FUT":
|
||||
return bts.api.SecurityType.Future;
|
||||
case "OPT":
|
||||
return bts.api.SecurityType.Option;
|
||||
case "MLEG":
|
||||
return bts.api.SecurityType.Spread;
|
||||
case "CS":
|
||||
return bts.api.SecurityType.Stock;
|
||||
case "SYNTH":
|
||||
return bts.api.SecurityType.Synthetic;
|
||||
default:
|
||||
return bts.api.SecurityType.Unset;
|
||||
}
|
||||
}
|
||||
|
||||
protected bool exchanges_equal(bts.api.Venue btstype, QuickFix.Fields.SecurityExchange fixtype)
|
||||
{
|
||||
return btstype == convert_exchange(fixtype);
|
||||
}
|
||||
|
||||
protected QuickFix.Fields.SecurityExchange convert_exchange(bts.api.Venue btstype)
|
||||
{
|
||||
switch (btstype)
|
||||
{
|
||||
case bts.api.Venue.Bts:
|
||||
return new QuickFix.Fields.SecurityExchange("BTS");
|
||||
case bts.api.Venue.Cboe:
|
||||
return new QuickFix.Fields.SecurityExchange("XCBO");
|
||||
case bts.api.Venue.Cboecfe:
|
||||
return new QuickFix.Fields.SecurityExchange("XCBF");
|
||||
case bts.api.Venue.Glbx:
|
||||
return new QuickFix.Fields.SecurityExchange("XCME");
|
||||
case bts.api.Venue.Ice:
|
||||
return new QuickFix.Fields.SecurityExchange("IEPA");
|
||||
case bts.api.Venue.Sim:
|
||||
return new QuickFix.Fields.SecurityExchange("SIM");
|
||||
default:
|
||||
return new QuickFix.Fields.SecurityExchange();
|
||||
}
|
||||
}
|
||||
|
||||
protected bts.api.Venue convert_exchange(QuickFix.Fields.SecurityExchange fixtype)
|
||||
{
|
||||
switch (fixtype.getValue())
|
||||
{
|
||||
case "BTS":
|
||||
return bts.api.Venue.Bts;
|
||||
case "XCBO":
|
||||
return bts.api.Venue.Cboe;
|
||||
case "XCBF":
|
||||
return bts.api.Venue.Cboecfe;
|
||||
case "XCME":
|
||||
case "XCBT":
|
||||
case "XCEC":
|
||||
case "XNYM":
|
||||
return bts.api.Venue.Glbx;
|
||||
case "IEPA":
|
||||
case "IFUS":
|
||||
case "IFEU":
|
||||
case "IFLL":
|
||||
return bts.api.Venue.Ice;
|
||||
case "SIM":
|
||||
return bts.api.Venue.Sim;
|
||||
default:
|
||||
return bts.api.Venue.Unset;
|
||||
}
|
||||
}
|
||||
|
||||
protected QuickFix.Fields.OrdStatus translate_status(in bts.api.orderman.OrderStateImmutable state,
|
||||
ref bts.api.orderman.NativeOrderStateImmutable? native)
|
||||
{
|
||||
native = null;
|
||||
if (state.HasMarket)
|
||||
{
|
||||
native = state.Market.NativeState;
|
||||
return new OrdStatus((char)native?.NativeStatus);
|
||||
}
|
||||
else if (state.HasLimit)
|
||||
{
|
||||
native = state.Limit.NativeState;
|
||||
return new OrdStatus((char)native?.NativeStatus);
|
||||
}
|
||||
else if (state.HasStop)
|
||||
{
|
||||
native = state.Stop.NativeState;
|
||||
return new OrdStatus((char)native?.NativeStatus);
|
||||
}
|
||||
else if (state.HasStopLimit)
|
||||
{
|
||||
native = state.StopLimit.NativeState;
|
||||
return new OrdStatus((char)native?.NativeStatus);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (state.Status)
|
||||
{
|
||||
case OrderStatus.Canceled:
|
||||
return new OrdStatus(OrdStatus.CANCELED);
|
||||
case OrderStatus.Completed:
|
||||
return new OrdStatus(OrdStatus.FILLED);
|
||||
case OrderStatus.Disconnected:
|
||||
return new OrdStatus(OrdStatus.STOPPED);
|
||||
case OrderStatus.Error:
|
||||
return new OrdStatus(OrdStatus.REJECTED);
|
||||
case OrderStatus.Inactive:
|
||||
return new OrdStatus(OrdStatus.PENDING_NEW);
|
||||
case OrderStatus.PendingWorking:
|
||||
return new OrdStatus(OrdStatus.PENDING_NEW);
|
||||
case OrderStatus.RejectedBySpark:
|
||||
return new OrdStatus(OrdStatus.REJECTED);
|
||||
case OrderStatus.Working:
|
||||
{
|
||||
if (state.FilledQuantity < state.Quantity)
|
||||
return new OrdStatus(OrdStatus.PARTIALLY_FILLED);
|
||||
else
|
||||
return new OrdStatus(OrdStatus.NEW);
|
||||
}
|
||||
default:
|
||||
return new OrdStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected uint lookup_account(string acct)
|
||||
{
|
||||
if (_admin_data.HasValue)
|
||||
{
|
||||
var ac = _admin_data.Value.Accounts.Where(x => x.Name == acct).TryFirst();
|
||||
if(ac.HasValue)
|
||||
return ac.Value.Id;
|
||||
}
|
||||
throw new IncorrectTagValue(new Account().Tag);
|
||||
}
|
||||
|
||||
protected uint lookup_user(string user)
|
||||
{
|
||||
if (_admin_data.HasValue)
|
||||
{
|
||||
var ac = _admin_data.Value.Users.Where(x => x.Name == user).TryFirst();
|
||||
if (ac.HasValue)
|
||||
return ac.Value.Id;
|
||||
}
|
||||
throw new IncorrectTagValue(new Username().Tag);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
#http://www.quickfixengine.org/quickfix/doc/html/configuration.html
|
||||
#https://www.onixs.biz/fix-dictionary.html
|
||||
|
||||
[DEFAULT]
|
||||
ConnectionType=acceptor
|
||||
SocketAcceptPort=5001
|
||||
SocketReuseAddress=Y
|
||||
StartTime=00:00:00
|
||||
EndTime=00:00:00
|
||||
FileLogPath=log
|
||||
UseDataDictionary=Y
|
||||
ResetOnLogon=Y
|
||||
ResetOnLogout=Y
|
||||
ResetOnDisconnect=Y
|
||||
|
||||
HeartBtInt=30
|
||||
LogonTimeout=30
|
||||
|
||||
SSLEnable=N
|
||||
# SSLEnable=Y
|
||||
# # It is recommended to install the certificate and refer to it by name instead of using filename + password
|
||||
# SSLCertificate=QuickFixn-TestServer.pfx
|
||||
# SSLCertificatePassword=QuickFixn-TestServer
|
||||
# # For production refer to certificate by name instead: SSLCertificate=CN=QuickFixn-TestServer
|
||||
# SSLCACertificate=QuickFixn-TestCA.cer
|
||||
# SSLCheckCertificateRevocation=N
|
||||
|
||||
|
||||
[SESSION]
|
||||
BeginString=FIX.4.4
|
||||
SenderCompID=SPARK
|
||||
TargetCompID=CLIENT1
|
||||
FileStorePath=store
|
||||
DataDictionary=./spec/fix/FIX44.xml
|
||||
|
||||
[SESSION]
|
||||
BeginString=FIX.4.4
|
||||
SenderCompID=SPARK
|
||||
TargetCompID=CLIENT2
|
||||
FileStorePath=store
|
||||
DataDictionary=./spec/fix/FIX44.xml
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0</TargetFrameworks>
|
||||
|
||||
<Authors>Blue Trading Systems</Authors>
|
||||
<Company>Blue Trading Systems</Company>
|
||||
<Product>Spark</Product>
|
||||
<Description />
|
||||
<Platforms>x64</Platforms>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
<LangVersion>7.2</LangVersion>
|
||||
<Version>1.0.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Google.Protobuf" Version="3.4.0" />
|
||||
<PackageReference Include="System.Reactive" Version="3.1.1" />
|
||||
<PackageReference Include="System.Reactive.PlatformServices" Version="3.1.1" />
|
||||
<PackageReference Include="NUnit" Version="3.10.1" />
|
||||
|
||||
<PackageReference Include="QuickFix.Net.NETCore" Version="1.8.1" />
|
||||
<PackageReference Include="QuickFix.Net.NETCore.FIX44" Version="1.8.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../spark_api/spark_api.csproj"/>
|
||||
<ProjectReference Include="../spark_core/spark_core.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="copy_fix" AfterTargets="PostBuildEvent" >
|
||||
<Copy SourceFiles="./spark_fix.cfg" DestinationFolder="$(OutputPath)\" SkipUnchangedFiles="true" />
|
||||
<Copy SourceFiles="./spec/fix/FIX44.xml" DestinationFolder="$(OutputPath)/spec/fix/" SkipUnchangedFiles="true" />
|
||||
</Target>
|
||||
|
||||
<Target Name="copy_fix471" AfterTargets="PostBuildEvent" >
|
||||
<Copy SourceFiles="./spark_fix.cfg" DestinationFolder="$(OutputPath)/../net471" SkipUnchangedFiles="true" />
|
||||
<Copy SourceFiles="./spec/fix/FIX44.xml" DestinationFolder="$(OutputPath)/../net471/spec/fix/" SkipUnchangedFiles="true" />
|
||||
</Target>
|
||||
</Project>
|
||||
Loaded 100 of 368 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user