Compare commits

..
15 Commits
368 changed files with 51492 additions and 7486 deletions

No files matched your search

-2
View File
@@ -28,5 +28,3 @@ 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
+6 -6
View File
@@ -33,12 +33,12 @@
"requireExactSource": false
},
{
"name": "SDK Launch",
"name": "FIX Launch",
"type": "clr",
"request": "launch",
"program": "${workspaceRoot}/build/Debug/bin/net471/spark_sdk.exe",
"args": ["md","GLBX","Future","CL", "CLZ9", "--user=bts", "--pass="],
"cwd": "${workspaceRoot}",
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
"args": ["fix","-f","spark_fix.cfg","-d"],
"cwd": "${workspaceRoot}/build/Debug/bin/net471/",
"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-packed.xll"],
"args": ["${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64.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","AuthToken"],
"args": ["test","Co2Tests"],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
"environment": [],
-13
View File
@@ -53,19 +53,6 @@ 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
-39
View File
@@ -1,43 +1,4 @@
# Release Notes
## 1.4.7
### Bug Fixes
- Corrected FIX tag 1028, ManualOrderIndicator, when orders are triggered by algos
## 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
+22
View File
@@ -23,6 +23,8 @@ 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
@@ -219,6 +221,26 @@ 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 -1
View File
@@ -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]
+48 -10
View File
@@ -17,22 +17,31 @@ using bts.api.user;
namespace bts {
[Export(typeof(IInitializable))]
class MDDaemon : bts.md.MDSource, IObserver<List<SecurityAndHandle>>, IInitializable, IDisposable {
class MDSource : IObserver<List<SecurityAndHandle>>, IInitializable, IDisposable {
readonly Lazy<IBus> _bus;
readonly Lazy<AuthTokenImmutable> _token;
readonly Lazy<AuthToken> _token;
readonly Lazy<SyntheticSecurityService> _ss;
readonly MDConsumer _consumer = new MDConsumer();
readonly Co2RelayControl _relay;
readonly Dictionary<SecurityKey, SecurityMD> _deleted_synthetics = new Dictionary<SecurityKey, SecurityMD>();
static uint get_naggle() => mbts.config.Configuration.get<bts.md.RelayOptions>().naggle_ms;
long _version = 0;
readonly ConcurrentDictionary<SecurityKey, SecurityMD> _exchange_sec_map = new ConcurrentDictionary<SecurityKey, SecurityMD>();
List<IDisposable> _dispose = new List<IDisposable>();
[ImportingConstructor]
public MDDaemon(Lazy<IBus> bus, Lazy<AuthTokenImmutable> token, Lazy<SyntheticSecurityService> ss) : base(get_naggle(), true) {
public MDSource(
Lazy<IBus> bus,
Lazy<AuthToken> token,
Lazy<SyntheticSecurityService> ss) {
_bus = bus;
_token = token;
_ss = ss;
_ss = ss;
_relay = new Co2RelayControl(_consumer);
_relay.preload_securities((co2.MDRequest.Types.Flags)0xff).ignore();
_dispose.Add(_consumer.on_security.Subscribe(this));
}
override public void initialize() {
public void initialize() {
_relay.set_token(_token.Value);
_dispose.Add(
_ss.Value.subscribei_metadata()
@@ -57,9 +66,6 @@ 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);
}
@@ -68,5 +74,37 @@ 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));
}
}
}
}
+4 -1
View File
@@ -13,7 +13,10 @@ using System.ComponentModel.Composition;
namespace Spark {
public class ShellServices {
[Export]
public ServiceContext context { get; } = new ServiceContext();
public static ServiceContext context { get; } = new ServiceContext();
[Export]
public UserService user_service => context.locate<UserService>();
[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());
Property<uint> _bid_direct_size, _bid_merged_size, _ask_direct_size, _ask_merged_size;
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,33 +45,28 @@ 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>(this, nameof(position), 0);
_position = new CellProperty<int>(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>(this, nameof(last_size), 0);
_last_size = new CellProperty<uint>(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>(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));
_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>();
//
_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>(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);
_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);
//
default_command = new DelegateCommand(_show_ladder);
bid_price_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.Bid));
@@ -105,15 +100,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.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 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 double ask_price => _ask_price.Value;
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 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 ulong volume => _volume.value;
public double settlement => _settlement.Value;
public double day_change => _day_change.Value;
@@ -149,9 +144,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, _bid_direct_size, _bid_merged_size, ref _bid_merged_size_text, nameof(bid_merged_size_text));
refresh_size(ref b, ref _bid_direct_size, ref _bid_merged_size, ref _bid_merged_size_text, nameof(bid_merged_size_text));
_ask_price.set(a.price);
refresh_size(ref a, _ask_direct_size, _ask_merged_size, ref _ask_merged_size_text, nameof(ask_merged_size_text));
refresh_size(ref a, ref _ask_direct_size, ref _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);
}
@@ -167,10 +162,10 @@ namespace mbts.spark {
if (dirty)
invalidated?.Invoke();
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);
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;
text = null;
dirty = true;
RaisePropertyChanged(text_prop);
@@ -210,28 +205,36 @@ namespace mbts.spark {
var order = _orders_model.factory.order(security);
switch (col) {
case MarketViewView.Columns.Bid:
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, 0u));
order.Side = Side.Sell;
_bus.post(SparkTags.ShowOrderTicketTag, (order, bid_price, 0u));
break;
case MarketViewView.Columns.BidMergedSize:
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_merged_size));
order.Side = Side.Sell;
_bus.post(SparkTags.ShowOrderTicketTag, (order, bid_price, bid_merged_size));
break;
case MarketViewView.Columns.BidDirectSize:
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_direct_size));
order.Side = Side.Sell;
_bus.post(SparkTags.ShowOrderTicketTag, (order, bid_price, bid_direct_size));
break;
case MarketViewView.Columns.BidImpliedSize:
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_implied_size));
order.Side = Side.Sell;
_bus.post(SparkTags.ShowOrderTicketTag, (order, bid_price, bid_implied_size));
break;
case MarketViewView.Columns.Ask:
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Buy), ask_price, 0u));
order.Side = Side.Buy;
_bus.post(SparkTags.ShowOrderTicketTag, (order, ask_price, 0u));
break;
case MarketViewView.Columns.AskMergedSize:
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Buy), ask_price, ask_merged_size));
order.Side = Side.Buy;
_bus.post(SparkTags.ShowOrderTicketTag, (order, ask_price, ask_merged_size));
break;
case MarketViewView.Columns.AskDirectSize:
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), ask_price, ask_direct_size));
order.Side = Side.Sell;
_bus.post(SparkTags.ShowOrderTicketTag, (order, ask_price, ask_direct_size));
break;
case MarketViewView.Columns.AskImpliedSize:
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), ask_price, ask_implied_size));
order.Side = Side.Sell;
_bus.post(SparkTags.ShowOrderTicketTag, (order, ask_price, ask_implied_size));
break;
case MarketViewView.Columns.Last:
_bus.post(SparkTags.ShowOrderTicketTag, (order, last_price, 0u));
@@ -263,15 +266,7 @@ 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(
// 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)
);
RaisePropertyChanged(nameof(working_bids_text), nameof(working_asks_text));
}
#endregion
@@ -97,11 +97,8 @@ namespace mbts.spark {
public IEnumerable<TradeInfo> read_trades() {
var copy = _securities;
foreach (var tm in _channel.read_trades(ref _cursor)) {
if (tm != null) {
if (copy.TryGetValue(tm.Key, out SecurityImmutable sec)) {
yield return new TradeInfo(sec, new TradeMessageImmutable(tm));
}
}
if (copy.TryGetValue(tm.Key, out SecurityImmutable sec))
yield return new TradeInfo(sec, 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.Value);
public async Task<uint?> create_synthetic(SecurityImmutable synthetic) {
var result = await _service.create_synthetic_async(synthetic);
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.SecurityImmutable) {
public Row(FillTuple fill_tuple, OrdersModel model) : base(fill_tuple.security_md.HasValue ? fill_tuple.security_md.Value.security : SecurityDataHelper.Defaults.Security) {
_fill = fill_tuple.fill;
_order = fill_tuple.order ?? OrderDataHelper.Defaults.OrderRecordImmutable;
_model = model;
@@ -61,12 +61,10 @@ 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 == null || state_settings.use_defaults ? null : state_settings,
_settings_hub, state_settings,
us => sanitize_ladder_settings(us.ladder),
(us, v) => us.with(sanitize_ladder_settings(v)));
var init = state_settings ?? settings_manager.default_settings;
init.use_defaults |= state_settings == null;
var vm = new LadderVM(_bus, model, settings_manager, init);
var vm = new LadderVM(_bus, model, settings_manager);
return new LadderView(vm);
});
string thread = Configuration.get<bts.config.toggles.LaddersOnThreads>().enabled ? $"Ladder{id:00}" : "";
@@ -14,8 +14,6 @@ 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.Value.CustomCase, id, display_name, model, security_md_source, cd.Value.Dynamic.DynamicOrderType);
if(cd.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic)
vm = OrderTypeVMFactory.create(cd.CustomCase, id, display_name, model, security_md_source, cd.Dynamic.DynamicOrderType);
else
vm = OrderTypeVMFactory.create(cd.Value.CustomCase, id, display_name, model, security_md_source);
vm.load_from(cd.Value);
vm = OrderTypeVMFactory.create(cd.CustomCase, id, display_name, model, security_md_source);
vm.load_from(cd);
return vm;
}
InlineUserProperty<IOrderTypeVM> get_order_type_vm_prop(InlineOrderProperties p) {
@@ -19,8 +19,7 @@ namespace mbts.spark {
var so = model.factory.spreader_order();
so.Security = s.CloneToMutable();
so.ExecutionStrategy = strategy.CloneToMutable();
var soi = new SpreaderOrderDefinitionImmutable(so);
return new InlineUserProperty<SpreaderOrderDefinitionImmutable>(soi, true);
return new InlineUserProperty<SpreaderOrderDefinitionImmutable>(so, true);
})), id, display_name) { }
public SpreaderOrderVM(
+4 -7
View File
@@ -93,13 +93,10 @@ namespace mbts.spark {
#endregion
#region Input
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;
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;
}
async Task<(uint? key, string error)> send_order_get_error(OrderDefinitionImmutable order, double price, uint size) {
-1
View File
@@ -8,7 +8,6 @@
<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,7 +3,6 @@ using bts.api.orderman;
using bts.shell;
using mbts.spark;
using mbts.ui;
using mbts;
using System;
using System.Collections.Generic;
@@ -218,7 +218,9 @@ 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(AuthService login, [Import("settings")] PersistenceService persist) {
public UISettingsFactory(UserService login, [Import("settings")] PersistenceService persist) {
_persist = persist;
_users = get_users(login);
}
async Task<User[]> get_users(AuthService login) {
async Task<User[]> get_users(UserService 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(uns(ns))
.subscribe_ns(new NamespaceSubArgs() { Ns = uns(ns) })
.Select(update =>
update.added
.Where(item => re.IsMatch(item.Key))
@@ -1,21 +1,16 @@
using mbts.ui.shell;
using System;
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(IRaiseNotifyPropertyChanged owner, string prop_name, T initial_value = default)
: this(owner, prop_name, t => t.ToString(), initial_value) { }
public CellProperty(T initial_value = default)
: this(t => t.ToString(), initial_value) { }
public CellProperty(IRaiseNotifyPropertyChanged owner, string prop_name, Func<T, string> format, T initial_value = default) {
_owner = owner;
_prop_name = prop_name;
public CellProperty(Func<T, string> format, T initial_value = default) {
_format = format;
value = initial_value;
}
@@ -29,7 +24,6 @@ namespace mbts.spark {
if (!value.Equals(this.value)) {
OnNext(value);
dirty = true;
_owner.raise_notify_property_changed(_prop_name);
}
}
@@ -34,11 +34,9 @@ namespace bts.view.controls {
private void any_button_click(object sender, RoutedEventArgs e) {
Close();
}
internal static void Show(ErrorReportService srv, ErrorReportImmutable? report) {
if (report.HasValue) {
var window = new SupportRequestWindow(new SupportRequestVM(srv, report.Value));
window.Show();
}
internal static void Show(ErrorReportService srv, ErrorReport report) {
var window = new SupportRequestWindow(new SupportRequestVM(srv, report));
window.Show();
}
}
@@ -1,31 +0,0 @@
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();
}
}
}
+65 -41
View File
@@ -1,29 +1,39 @@
using bts;
using bts.api;
using bts.api.core;
using bts.api.persistence;
using bts.api.user;
using mbts.config;
using System;
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.api.persistence;
using bts;
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(ServiceContext context) {
public LoginContainer(UserService user, [Import("settings")] PersistenceService persist) {
var model = LoginModel.instance;
model.provide_services(context);
model.provide_services(user, persist);
// await_login pumps the dispatcher but doesn't allow control to return to MEF until the username and workspace are available
model.await_login();
(UserName,UserId,Token) = model.get_user();
var u = model.get_user();
UserName = u.name;
UserId = u.id;
Token = u.token;
Workspace = model.get_workspace();
model.Dispose();
}
@@ -34,19 +44,21 @@ namespace mbts.ui.shell {
[Export("userid")]
public uint UserId { get; private set; }
[Export]
public AuthTokenImmutable Token { get; private set; }
public AuthToken Token { get; private set; }
[Export("workspace")]
public string Workspace { get; private set; }
}
public struct User {
public User(string name, uint id) {
public User(string name, uint id, AuthToken token) {
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
@@ -54,10 +66,11 @@ 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;
ServiceContext _ctx;
UserService _logon;
DispatcherFrame _frame = new DispatcherFrame();
private LoginModel() {
@@ -69,14 +82,13 @@ 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 (string, uint, AuthTokenImmutable) get_user() {
Log.assert_d(!string.IsNullOrEmpty(_ctx.user));
return (_ctx.user, _ctx.user_id, _ctx.token.Value);
public User get_user() {
Log.assert_d(!string.IsNullOrEmpty(_user.name));
return _user;
}
public string get_workspace() {
return _workspace ?? "";
}
@@ -100,13 +112,13 @@ namespace mbts.ui.shell {
}
// Wait for the hub to create the login doodad
_ctx = await _hub_src.Task;
(var users, var persist) = await _hub_src.Task;
_logon = users;
// Populate the UI
_enum_workspace = new PreloginWorkspaceEnumerator(_ctx.locate<PersistenceService>("settings"));
var auth = _ctx.locate<AuthService>();
var response = await auth.get_users_async(new GetUsersRequest());
_enum_workspace = new PreloginWorkspaceEnumerator(persist);
var response = await _logon.get_users_async(new GetUsersRequest());
if (response.success) {
Users = response.value.Users.Select(x => new User(x.Login,x.Id)).OrderBy(u => u.name).ToArray();
Users = response.value.Users.Select(x => new User(x.Login,x.Id, null)).OrderBy(u => u.name).ToArray();
}
else {
ErrorMessage = "Logon: " + response.error_message;
@@ -117,37 +129,49 @@ namespace mbts.ui.shell {
#endregion
public async Task<LoginResult> do_login(string username, string workspace, string password) {
public async Task<(bool success,string info)> do_login(string username, string workspace, string password) {
if (Users.Select(u => u.name).Contains(username)) {
var result = await _ctx.login(username, password);
if (result.success) {
_workspace = workspace;
on_user_authenticated(_ctx.user, _ctx.token);
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);
}
return result;
}
return new LoginResult("Invalid username");
return (false, "Invalid username");
}
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);
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);
_frame.Continue = false;
}
public void await_login() {
if (!_ctx.token.HasValue) {
if (string.IsNullOrEmpty(_user.name)) {
Dispatcher.PushFrame(_frame);
}
}
#region provide hub
internal void provide_services(ServiceContext ctx) {
_hub_src.TrySetResult(ctx);
public void provide_services(UserService u, PersistenceService p) {
_hub_src.TrySetResult((u,p));
}
private TaskCompletionSource<ServiceContext> _hub_src = new TaskCompletionSource<ServiceContext>();
private TaskCompletionSource<(UserService, PersistenceService)> _hub_src = new TaskCompletionSource<(UserService, PersistenceService)>();
#endregion
#region Dispose
@@ -138,15 +138,14 @@ namespace mbts.ui.shell
if (selected_workspace == "New workspace...") {
selected_workspace = null;
}
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;
(bool success, string message) = await _model.do_login(Name, selected_workspace, Password);
if (success) { VisualState = "Loading"; }
if (!success && !string.IsNullOrEmpty(message)) {
WarningMessage = message;
VisualState = "BadPassword";
}
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);
else if (!string.IsNullOrEmpty(message)) {
BTSMessageBox.Show(message, "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<AuthService>();
var users = sc.locate<UserService>();
var persist = sc.locate<PersistenceService>("settings");
var user_list = users.get_users(new GetUsersRequest());
var user_names = user_list.Users.Select(u => u.Login);
@@ -6,7 +6,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace mbts {
namespace mbts.spark {
struct SecurityMD : IEquatable<SecurityMD> {
public SecurityImmutable security { get; }
public MDConsumerHandle md { get; }
@@ -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,14 +177,8 @@ namespace mbts.spark {
public static RegionalTickFactors get(ContractSpecification 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(ContractSpecificationImmutable spec) =>
_exchange_ticks.GetOrAdd(new SpecKey(spec), _create_rtf);
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.FirstOrDefault(c => c.column_type == ColumnType.Order), width_adjustment);
lgh._adjust_column_width(lgh.columns.First(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.FirstOrDefault(c => c.column_type == ColumnType.Bid), width_adjustment);
lgh._adjust_column_width(lgh.columns.First(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.FirstOrDefault(c => c.column_type == ColumnType.Price), width_adjustment);
lgh._adjust_column_width(lgh.columns.First(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.FirstOrDefault(c => c.column_type == ColumnType.Ask), width_adjustment);
lgh._adjust_column_width(lgh.columns.First(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.FirstOrDefault(c => c.column_type == ColumnType.Volume), (double)newval - (double)oldval),
(o, oldval, newval) => o._adjust_column_width(o.columns.First(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.FirstOrDefault(c => c.column_type == ColumnType.Last), (double)newval - (double)oldval),
(o, oldval, newval) => o._adjust_column_width(o.columns.First(c => c.column_type == ColumnType.Last), (double)newval - (double)oldval),
50u);
public uint LastColumnWidth {
@@ -327,8 +327,6 @@ 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;
@@ -664,10 +662,22 @@ 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;
}
/// 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();
//
_settings = s;
_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);
@@ -688,19 +698,18 @@ namespace mbts.spark {
// Renders the grid lines and column backgrounds
_visualHost.AddVisualChild(_gridvis);
// Orders column
SetCurrentValue(orders_column_widthProperty, s.orders_column.width);
orders_column_width = 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: 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.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.Volume:
SetCurrentValue(VolumeColumnWidthProperty, col.settings.width);
VolumeColumnWidth = col.settings.width;
_volume_container.mark_dirty();
break;
}
@@ -8,7 +8,6 @@ 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!
@@ -180,34 +179,6 @@ 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
@@ -245,26 +216,7 @@ 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(),
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,
size_display = MarketSizeDisplay.get_default()
};
}
}
@@ -326,54 +278,6 @@ 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 &&
@@ -398,27 +302,7 @@ 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) &&
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;
size_display.safe_equals(other.size_display);
public override bool Equals(object obj) => Equals(obj as LadderSettings);
@@ -446,46 +330,7 @@ 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),
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;
}
}
EqualityComparer.safe_get_hash_code(size_display));
}
static class LadderSettingsExtensions {
@@ -500,22 +345,5 @@ 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,7 +17,6 @@ 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(),
@@ -33,35 +32,14 @@ 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, Func<double> get_left_panel_width, Func<double> get_zoom_factor) {
public LadderSettingsVM(bool is_synthetic) {
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;
@@ -130,28 +108,6 @@ 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) {
@@ -172,27 +128,6 @@ 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() {
@@ -214,25 +149,6 @@ 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;
}
@@ -360,19 +276,6 @@ 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,7 +140,6 @@
<Setter Property="VerticalContentAlignment" Value="Top"/>
</Style>
</TabControl.ItemContainerStyle>
<!-- Fonts and Colors Tab -->
<TabItem Header="Fonts and Colors" VerticalAlignment="Stretch">
<Grid>
<Grid.RowDefinitions>
@@ -233,7 +232,6 @@
</WrapPanel>
</Grid>
</TabItem>
<!-- Market Break Lines Tab -->
<TabItem Header="Market Break Lines" VerticalAlignment="Stretch">
<Grid>
<Grid.RowDefinitions>
@@ -352,7 +350,6 @@
</ItemsControl>
</Grid>
</TabItem>
<!-- Market Size Tab -->
<TabItem Header="Market Size Display">
<Grid DataContext="{Binding size_display}">
<Grid.RowDefinitions>
@@ -443,7 +440,6 @@
</GroupBox>
</Grid>
</TabItem>
<!-- Volume Display Tab -->
<TabItem Header="Volume Display" VerticalAlignment="Stretch" Visibility="{Binding is_synthetic, Converter={StaticResource FalseVisibilityConverter}}">
<Grid>
<Grid.RowDefinitions>
@@ -561,7 +557,6 @@
</WrapPanel>
</Grid>
</TabItem>
<!-- Size Presets Tab-->
<TabItem Header="Size Presets" VerticalAlignment="Stretch">
<Grid HorizontalAlignment="Left">
<Grid.RowDefinitions>
@@ -597,63 +592,6 @@
</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>
+14 -80
View File
@@ -20,24 +20,18 @@ 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, _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<double> _high, _low, _change, _open, _settlement, _position;
readonly Property<ulong> _volume;
readonly Property<bool> _auto_clear, _show_day, _show_high, _show_low, _show_open, _show_settle, _show_volume, _show_account,
_show_tif, _show_order_type, _show_account_position, _show_size_preset, _show_auto_clear, _show_clear, _show_cancel_buys,
_show_cancel_sells, _show_cancel_all, _show_zoom;
readonly Property<bool> _auto_clear;
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, LadderSettings init) {
public LadderVM(IBus bus, LadderModel model, IWindowSettingsManager<LadderSettings> settings_manager) {
_bus = bus;
_model = model;
_equality = TickUtils.tick_equality_comparer(_model.security.ContractSpec);
@@ -50,23 +44,13 @@ 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), _transparent_status_brush);
_status_brush = backing(nameof(status_background), (Brush)BTSColors.UI.spark_menu_background);
// 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());
@@ -74,31 +58,7 @@ 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);
@@ -107,11 +67,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, () => left_panel_width, () => zoom_factor);
s => {
var svm = new LadderSettingsVM(security.ContractCase == Security.ContractOneofCase.Synthetic);
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}'"
@@ -242,7 +202,7 @@ namespace mbts.spark {
await Task.Delay(TimeSpan.FromSeconds(5));
if (_status.Value == a.message) {
_status.set("");
_status_brush.set(_transparent_status_brush);
_status_brush.set(BTSColors.UI.spark_menu_background);
}
}
}
@@ -273,41 +233,16 @@ 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() {
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;
// 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;
}
public void key_down(KeyEventArgs e) {
@@ -364,7 +299,6 @@ namespace mbts.spark {
_change.set(mid - md.settlement_price);
}
_volume.set(volume);
_misc_time = _model.md.misc_timestamp;
}
}
+164 -266
View File
@@ -1,20 +1,16 @@
<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 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.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
@@ -31,13 +27,6 @@
</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}"/>
@@ -45,7 +34,6 @@
<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}">
@@ -69,8 +57,8 @@
</Border>
</Grid>
</Border>
<Canvas MinWidth="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=MinWidth, Mode=OneTime}">
<Thumb x:Name="PART_HeaderGripper" DragDelta="Thumb_DragDelta">
<Canvas>
<Thumb x:Name="PART_HeaderGripper">
<Thumb.Style>
<Style TargetType="{x:Type Thumb}">
<Setter Property="Canvas.Right" Value="-9"/>
@@ -135,7 +123,7 @@
<Style x:Key="LadderMenuButton" TargetType="Button">
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Margin" Value="2,0,2,5"/>
<Setter Property="Margin" Value="5,0,5,5"/>
<Setter Property="Padding" Value="2"/>
</Style>
<Style x:Key="CancelBuys" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
@@ -256,7 +244,8 @@
Background="{mbts:BTSColor UI.Button.normal_background}"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="True"
Margin="2,0,2,10">
VerticalAlignment="Bottom"
Margin="0,0,0,12">
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"
@@ -274,166 +263,108 @@
</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.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.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.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0" Grid.RowSpan="2" VerticalScrollBarVisibility="Hidden">
<Grid RowSpan="2" Background="{mbts:BTSColor UI.spark_menu_background}">
<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">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition/>
<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"/>
</Grid.RowDefinitions>
<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
<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
DataContext="{Binding order_properties_vm}"
Margin="2"
ExpandDirection="Left"
@@ -441,25 +372,19 @@
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}}"
/>
<!-- Position into qty button -->
<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>
<!-- Qty textbox -->
<TextBox
<Button.ToolTip>
<TextBlock Text="{Binding order_properties_vm.selected_account, StringFormat='{}{0} total position (click to copy to size)'}"/>
</Button.ToolTip>
</Button>
<TextBox
mbts:TextBoxBehavior.InputFilter="{mbts:RegexInputFilter Regex={x:Static Member=Integer, MemberType={mbts:Nested txt:TextUtils.RegexFilters}}}"
mbts:TextBoxBehavior.IncrementDecrementOnMouseClick="True"
HorizontalContentAlignment="Right"
@@ -467,64 +392,42 @@
Text="{Binding size_calculator.value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ContextMenu="{x:Null}"
/>
<!-- 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>
<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>
<Style TargetType="controls:Badge">
<Setter Property="Background" Value="{Binding background}" />
<Setter Property="BorderBrush" Value="{Binding border}" />
@@ -564,7 +467,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"
@@ -580,19 +483,14 @@
<Pen Thickness="0" Brush="MediumPurple" />
</spark:RecenterCountdown.PieStroke>
</spark:RecenterCountdown>
<!-- 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>
<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,14 +3,10 @@ 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 {
partial class LadderView : UserControl {
readonly double _max_zoom, _min_zoom;
public partial class LadderView : UserControl {
internal LadderView(LadderVM vm) {
InitializeComponent();
_column_headers_presenter.Columns = new GridViewColumnCollection();
@@ -19,87 +15,12 @@ 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) {
@@ -119,31 +40,31 @@ namespace mbts.spark {
switch (type) {
case LadderGridHost.ColumnType.Order:
header = "Orders";
width_prop = nameof(LadderVM.orders_column_width);
width_prop = nameof(LadderGridHost.orders_column_width);
break;
case LadderGridHost.ColumnType.Bid:
header = "Bid";
width_prop = nameof(LadderVM.bid_column_width);
width_prop = nameof(LadderGridHost.bid_column_width);
break;
case LadderGridHost.ColumnType.Price:
header = "Price";
width_prop = nameof(LadderVM.price_column_width);
width_prop = nameof(LadderGridHost.price_column_width);
break;
case LadderGridHost.ColumnType.Ask:
header = "Ask";
width_prop = nameof(LadderVM.ask_column_width);
width_prop = nameof(LadderGridHost.ask_column_width);
break;
case LadderGridHost.ColumnType.Volume:
header = "Volume";
width_prop = nameof(LadderVM.volume_column_width);
width_prop = nameof(LadderGridHost.VolumeColumnWidth);
break;
case LadderGridHost.ColumnType.Last:
header = "Last";
width_prop = nameof(LadderVM.last_column_width);
width_prop = nameof(LadderGridHost.LastColumnWidth);
break;
default: throw ExceptionBuilder.Argument.unexpected_case(type, nameof(type));
}
return _create(host, header, $"DataContext.{width_prop}");
return _create(host, header, width_prop);
}
static GridViewColumn _create(LadderGridHost host, string header, string width_prop) {
@@ -156,6 +77,5 @@ 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 CellProperty<double> _pnl_price, _weighted_average_buy, _weighted_average_sell;
readonly PnLPriceCell _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 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);
_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);
_fills = fills;
// set the initial pnl and pnl_price when there has been a
@@ -193,13 +193,19 @@ namespace mbts.spark {
class PnLDoubleBrushCell : CellProperty<double> {
Brush _background;
public PnLDoubleBrushCell(PnLSummaryRow owner, string prop) : base(owner, prop, d => double.IsNaN(d) ? string.Empty : d.ToString("N2"), double.NaN) =>
public PnLDoubleBrushCell(PnLSummaryRow owner, string prop) : base(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,8 +165,7 @@ namespace mbts.spark.utilities {
}
#endregion
public OrderDefinitionImmutable order(SecurityImmutable security) =>
new OrderDefinitionImmutable(order(security, _admin_model.get_default_account()));
public OrderDefinition order(SecurityImmutable security) => order(security, _admin_model.get_default_account());
public OrderDefinition order(SecurityImmutable security, AccountTuple? account) {
var rv = new OrderDefinition {
SecurityKey = security.Id,
@@ -17,9 +17,7 @@ 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, Func<T, T> merge_with_current = null) {
if (merge_with_current == null)
merge_with_current = s => s;
public WindowSettingsDialogVM(IWindowSettingsManager<T> settings_manager, Func<UserSettings, T> get_factory_settings, string settings_file_ext, Func<T, TVM> vm_factory) {
// 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);
@@ -61,14 +59,15 @@ 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 = result;
settings_manager.instance_settings = instance_vm.get_result();
} else {
settings_manager.instance_settings = null;
// copy the current instance settings and persist
if (!result.safe_equals(settings_manager.default_settings))
settings_manager.save_default_settings(result).ignore();
var my_default = instance_vm.get_result();
if (!my_default.safe_equals(settings_manager.default_settings))
settings_manager.save_default_settings(my_default).ignore();
}
}
@@ -3,4 +3,5 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("spark_core")]
[assembly: InternalsVisibleTo("sparkcmd")]
[assembly: InternalsVisibleTo("spark_test")]
[assembly: InternalsVisibleTo("spark_fix")]
+2 -2
View File
@@ -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, string qualifier, raw_subscription_callback cb, IntPtr extra);
internal static extern IntPtr api_client_subscribe(IntPtr client, string method_and_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, string qualifier);
internal static extern IntPtr api_server_create_table(IntPtr server, string method_and_qualifier);
[DllImport("libspark")]
internal static extern void api_server_update_table(IntPtr table, [In] StreamUpdate[] changes, long change_count);
[DllImport("libspark")]
+9
View File
@@ -17,6 +17,15 @@ 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 {
+61 -61
View File
@@ -1,68 +1,68 @@
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 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,7 +35,6 @@ 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>>();
@@ -57,99 +56,60 @@ namespace bts.api.core {
return tcs.Task;
}
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;
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);
}
else {
// Not authorized. return empty stream
unsubscribe_temp_sub();
observer.OnCompleted();
return System.Reactive.Disposables.Disposable.Empty;
else if (event_type == ApiNativeMethods.EventType.TABLE_CREATED) {
if (!first_update) {
observer.OnNext(the_update.MarkAsReset());
}
first_update = false;
}
}
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);
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);
}
}
// Authorized. Stream events
unsubscribe_temp_sub();
return SubscribeNative(method, qualifier, parser, converter, observer);
};
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}");
// }
});
});
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) {
+2 -2
View File
@@ -5,7 +5,7 @@ using System.Threading.Tasks;
namespace bts.api.core {
internal abstract class ClientChannel : IDisposable
{
protected readonly ServiceContext _context;
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>(int method_id, string method, string qualifier, MessageParser<TParse> parser, Func<TParse,TOut> converter=null)
public abstract IObservable<RawUpdate<TOut>> subscribe<TParse,TOut>(string method_and_qualifier, MessageParser<TParse> parser, Func<TParse,TOut> converter=null)
where TParse : IMessage<TParse>;
public abstract bool is_sync_call_ok();
+1 -6
View File
@@ -37,12 +37,7 @@ namespace bts.api.core
public T value {
get {
if (success) return _result;
else {
if (typeof(T).IsValueType) { throw new ApiException(error_message); }
else {
return default(T);// always null
}
}
else throw new ApiException(error_message);
}
}
public bool success { get { return _code == ApiError.Success; } }
+2 -2
View File
@@ -34,8 +34,8 @@ namespace bts.api.core {
}
public Action<RequestHeader, Stream, Action<byte[]>> on_request;
public IntPtr create_table(string method, string qualifier) {
return ApiNativeMethods.api_server_create_table(_server, method, qualifier);
public IntPtr create_table(string method_and_qualifier) {
return ApiNativeMethods.api_server_create_table(_server, method_and_qualifier);
}
public void Dispose() {
+3 -43
View File
@@ -4,14 +4,12 @@ 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);
}
@@ -24,6 +22,7 @@ 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 {
@@ -32,38 +31,6 @@ 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) {
@@ -85,11 +52,4 @@ 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; }
}
}
+23
View File
@@ -0,0 +1,23 @@
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;
}
}
}
@@ -2,7 +2,7 @@
using System;
using System.Threading.Tasks;
namespace bts.utils
namespace bts
{
public class AsyncLazy<T> : Lazy<Task<T>>
{
@@ -11,15 +11,6 @@ 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;
}
}
-59
View File
@@ -1,59 +0,0 @@
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()));
}
}
}
-61
View File
@@ -1,61 +0,0 @@
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;
}
}
}
-40
View File
@@ -1,40 +0,0 @@
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;
}
}
}
-67
View File
@@ -1,67 +0,0 @@
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;
}
}
}
-90
View File
@@ -1,90 +0,0 @@
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));
}
}
}
}
+271 -184
View File
@@ -11,224 +11,108 @@ using System.Reactive.Subjects;
using bts.api;
using bts.api.core;
using bts.co2;
using mbts;
using SecurityKey = System.UInt32;
namespace bts.md
namespace bts.api.md
{
public class MarketDataClient : IDisposable
using security_key = System.UInt32;
public class MarketDataClient : IDisposable
{
static uint naggle_ms = 100;
readonly static bts.md.MDSource _subscription = new MDSource(naggle_ms);
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)
class Data
{
_default_pump_ms = default_pump_ms;
if (token != null)
set_auth_token(token);
_disp.Add(_subscription.on_securities.Subscribe( md =>
{
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 long last_update_time;
public ChannelUpdate data;
}
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);
}
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);
IObservable<IEnumerable<T>> _subscribe<T>(Func<SecurityImmutable, bool> filter,
TimeSpan? update_interval = null, IScheduler scheduler = null) where T : IMDData, IMDUpdate, new()
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()
{
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);
_all_products = _md.Value.request_products(new VoidMessage()).Products.ToList();
}
public IObservable<IEnumerable<IMDBook>> subscribe_book(IEnumerable<SecurityKey> ids, TimeSpan? update_interval = null,
IScheduler scheduler = null)
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)
{
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 =>
if (!_venue_cache.TryGetValue(exchange, out md))
{
_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);
}
class Slot<T> where T : IMDData, IMDUpdate, new()
{
public Slot(SecurityMD md)
{
secmd = md;
last_update_time = 0;
data = new T();
_venue_cache[exchange] = md = new MarketDataClient(exchange, token);
return true;
}
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;
});
});
return false;
}
public void Dispose()
public MarketDataClient(bts.api.Venue exchange = Venue.Unset, user.AuthToken token = null)
{
_disp.ForEach(x => x.Dispose());
_venue = exchange;
_subscription = load_request(exchange, token);
}
#region security search
public SecurityImmutable? lookup(SecurityKey key)
public void set_auth_token(user.AuthToken toke)
{
if (_data.TryGetValue(key, out SecurityMD value))
return value.security;
_subscription.Token = toke?.Token ?? string.Empty;
}
MDRequest load_request(bts.api.Venue exchange, user.AuthToken token)
{
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;
else
return null;
}
public bool lookup_security_desc(SecurityKey key, out string security_desc)
public bool lookup_security_desc(security_key key, out string security_desc)
{
security_desc = null;
var sec = lookup(key);
if (sec.HasValue)
{
security_desc = sec.Value.glbx_desc();
return true;
}
return false;
return _security_key_to_desc.TryGetValue(key, out security_desc);
}
public bool lookup_security_key_from_desc(string security_desc, out SecurityKey key)
public bool lookup_security_key_from_desc(string security_desc, out security_key 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;
return _security_desc_to_key.TryGetValue(security_desc, out key);
}
IEnumerable<SecurityMD> find(Func<SecurityImmutable, bool> filter)
public Security find_security(Func<Security, bool> filter)
{
Func<SecurityMD, bool> filt = (SecurityMD secmd) => filter(secmd.security);
return _data.Values.Where(filt);
return _data.Values.Select(x => x.data.Security).Where(xx => filter(xx)).FirstOrDefault();
}
public SecurityImmutable? find_security(Func<SecurityImmutable, bool> filter)
public async Task<Security> find_security_async(Func<Security, bool> filter, TimeSpan? timeout = null)
{
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);
if(!timeout.HasValue) {
timeout = TimeSpan.FromMilliseconds(int.MaxValue-1);
}
CancellationTokenSource src = new CancellationTokenSource(timeout.Value);
SecurityImmutable? security = await Task.Run<SecurityImmutable?>(async () =>
Security security = await Task.Run<Security>(async () =>
{
SecurityImmutable? sec = null;
Security sec = null;
do
{
sec = find_security(filter);
@@ -238,11 +122,214 @@ namespace bts.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);
}
#endregion
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();
}
}
}
-24
View File
@@ -1,24 +0,0 @@
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} ";
}
}
}
@@ -3,7 +3,7 @@ using System.Collections.Generic;
using static bts.api.Security;
namespace bts.api {
struct FutureLeg {
public 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.Value.Future);
static string _format(SecurityImmutable security) {
switch (security.ContractCase) {
case ContractOneofCase.Future: return format_future(security.Symbol, security.glbx_desc(), security.Future);
case ContractOneofCase.Spread:
if (security?.HasGlbx == true) {
if (security.HasGlbx) {
// 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 {
internal static class SecurityHelper {
public 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,10 +66,24 @@ namespace bts.api {
public static readonly IComparer<SecurityImmutable> InstanceImmutable = Comparer<SecurityImmutable>.Create(Compare);
public static int Compare(Security x, Security y) {
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));
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);
}
public static int Compare(SecurityImmutable x, SecurityImmutable y) {
+3 -22
View File
@@ -1,34 +1,15 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace mbts
namespace bts
{
static class APIExtensions
public 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) { }
}
}
}
+4 -2
View File
@@ -33,7 +33,8 @@ namespace bts {
Latency,
DataFlow,
Plugin,
ClientSdk
ClientSdk,
Fix
// Please keep this list in sync with logging.h/logging.cpp
}
@@ -75,8 +76,9 @@ 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")]
@@ -13,9 +13,14 @@ 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);
IMessage result = (IMessage)new T();
result.MergeFrom(stream);
return (T)result;
if (typeof(IMessage).IsAssignableFrom(typeof(T))) {
IMessage result = (IMessage)new T();
result.MergeFrom(stream);
return (T)result;
}
else {
return ProtoBuf.Serializer.Deserialize<T>(stream);
}
}
[DllImport("libspark")]
@@ -76,7 +81,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, byte[] token, long token_size);
internal unsafe static extern IntPtr set_relay_token(IntPtr relay, string user_name);
[DllImport("libspark")]
internal unsafe static extern void post_on_scan_security_thread(Action callback);
@@ -14,16 +14,17 @@ namespace mbts.co2
{
MDConsumer _consumer;
IntPtr _relay;
public Co2RelayControl(MDConsumer consumer, uint naggle_ms) {
public Co2RelayControl(MDConsumer consumer) {
_consumer = consumer;
Log.assert(consumer.native_pointer() != IntPtr.Zero);
_relay = Co2NativeMethods.start_private_relay(consumer.native_pointer(), naggle_ms);
_relay =
Co2NativeMethods.start_private_relay(consumer.native_pointer(),
config.Configuration.get<bts.md.RelayOptions>().naggle_ms);
Log.assert(_relay != IntPtr.Zero);
}
public void set_token(AuthTokenImmutable token) {
var buffer = token.Token.ToByteArray();
Co2NativeMethods.set_relay_token(_relay, buffer, buffer.LongLength);
public void set_token(AuthToken token) {
Co2NativeMethods.set_relay_token(_relay, token.Token);
}
public async Task preload_securities(MDRequest.Types.Flags flags=MDRequest.Types.Flags.Security) {
File renamed without changes.
@@ -9,7 +9,6 @@ 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.
+38 -88
View File
@@ -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)
_assert_remove(_tree, n);
_tree.Remove(n);
node = new Node(item, children);
} else node = new Node(item, _comparer);
var pk = parent(item);
@@ -84,10 +84,13 @@ namespace bts.collections {
if (!_index.TryGetValue(k, out Node node)) {
return false;
} else {
_replace(node, replace);
var newval = replace(node.value);
Log.assert(id(newval).Equals(k));
_replace(node, newval);
return true;
}
}
public void Remove(T item) => Remove(id(item));
@@ -182,97 +185,44 @@ namespace bts.collections {
}
}
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.
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)) {
_tree.Add(node);
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.
} 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));
}
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,6 +6,9 @@ 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;
@@ -24,6 +27,15 @@ 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); }
@@ -57,7 +69,5 @@ namespace mbts {
}
}
}
}
}
@@ -17,7 +17,7 @@ namespace bts.api.orderman
{
public partial class OrderService_Excel
{
readonly static Lazy<user.AuthService> _users = new Lazy<user.AuthService>(() => Locator.locate<user.AuthService>(), true);
readonly static Lazy<user.UserService> _users = new Lazy<user.UserService>(() => Locator.locate<user.UserService>(), 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());
var admin = await _admin.Value.get_filtered_admin_data_async(new FilteredAdminDataRequest { Token = login_token });
if (admin.success)
{
foreach (var acc in admin.value.Accounts)
@@ -1,22 +1,21 @@
using System;
using bts.md;
namespace bts.excel.rtd
{
class BookMDFormatter : IExcelFormatter
{
readonly IMDBook _update;
public BookMDFormatter(IMDBook upd)
readonly co2.ChannelUpdate _update;
public BookMDFormatter(co2.ChannelUpdate upd)
{
_update = upd;
}
public object[,] format(bool include_headers = false)
public static double format_md(long mantissa, int exponent)
{
return format(_update, include_headers);
return mantissa * Math.Pow(10, exponent);
}
static public object[,] format(IMDBook update, bool include_headers = false)
public object[,] format(bool include_headers = false)
{
const int cols = 5;
const int rows = 30;
@@ -25,21 +24,21 @@ namespace bts.excel.rtd
for (int j = 0; j < cols; j++)
ret[i, j] = string.Empty;
if (update != null)
if (_update.Direct != null)
{
int idx = 0;
for (int j = update.asks.Count - 1; j >= 0 && idx < rows; j--)
for (int j = _update.Direct.Asks.Count - 1; j >= 0 && idx < rows; j--)
{
var ask = update.asks[j];
ret[idx, 1] = ask.price;
ret[idx, 2] = ask.size;
var ask = _update.Direct.Asks[j];
ret[idx, 1] = format_md(ask.Mantissa, ask.Exponent);
ret[idx, 2] = ask.Size;
idx++;
}
for (int j = 0; j < update.bids.Count && idx < rows; j++)
for (int j = 0; j < _update.Direct.Bids.Count && idx < rows; j++)
{
var bid = update.bids[j];
ret[idx, 1] = bid.price;
ret[idx, 0] = bid.size;
var bid = _update.Direct.Bids[j];
ret[idx, 1] = format_md(bid.Mantissa, bid.Exponent);
ret[idx, 0] = bid.Size;
idx++;
}
}
@@ -1,5 +1,4 @@
using System;
using bts.md;
namespace bts.excel.rtd
{
@@ -7,7 +6,7 @@ namespace bts.excel.rtd
{
public static IExcelFormatter make_formatter(object data)
{
if(data is IMDBook upd)
if(data is co2.ChannelUpdate upd)
return new BookMDFormatter(upd);
else
{
@@ -1,35 +0,0 @@
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,22 +1,16 @@
using System;
using bts.md;
namespace bts.excel.rtd
{
class MiscMDFormatter : IExcelFormatter
{
readonly IMDMisc _update;
public MiscMDFormatter(IMDMisc upd)
readonly co2.ChannelUpdate _update;
public MiscMDFormatter(co2.ChannelUpdate 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,22 +1,17 @@
using System;
using bts.md;
namespace bts.excel.rtd
{
class TopOfBookMDFormatter : IExcelFormatter
{
readonly IMDBook _update;
public TopOfBookMDFormatter(IMDBook upd)
readonly co2.ChannelUpdate _update;
public TopOfBookMDFormatter(co2.ChannelUpdate 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;
@@ -25,22 +20,22 @@ namespace bts.excel.rtd
for (int j = 0; j < cols; j++)
ret[i, j] = string.Empty;
if (update != null)
if (_update.Direct != null)
{
int idx = 0;
for (int j = 0; j < update.asks.Count && idx < rows; j++)
for (int j = 0; j < _update.Direct.Asks.Count && idx < rows; j++)
{
var ask = update.asks[j];
ret[idx, 2] = ask.price;
ret[idx, 3] = ask.size;
var ask = _update.Direct.Asks[j];
ret[idx, 2] = BookMDFormatter.format_md(ask.Mantissa, ask.Exponent);
ret[idx, 3] = ask.Size;
idx++;
}
idx = 0;
for (int j = 0; j < update.bids.Count && idx < rows; j++)
for (int j = 0; j < _update.Direct.Bids.Count && idx < rows; j++)
{
var bid = update.bids[j];
ret[idx, 1] = bid.price;
ret[idx, 0] = bid.size;
var bid = _update.Direct.Bids[j];
ret[idx, 1] = BookMDFormatter.format_md(bid.Mantissa, bid.Exponent);
ret[idx, 0] = bid.Size;
idx++;
}
}
@@ -1,95 +1,86 @@
using System;
using bts.md;
namespace bts.excel.rtd
{
class TopicMDFormatter : IExcelFormatter
{
readonly IMDCombined _update;
readonly co2.ChannelUpdate _update;
readonly string _topic;
public TopicMDFormatter(IMDCombined upd, string topic)
public TopicMDFormatter(co2.ChannelUpdate 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];
if(update == null || string.IsNullOrEmpty(topic))
return retval;
switch (topic.ToLower())
switch (_topic.ToLower())
{
case "bid":
case "bidprice":
retval[0, 0] = update.bids?[0]?.price;
retval[0, 0] = BookMDFormatter.format_md(_update?.Direct?.Bids?[0]?.Mantissa ?? 0, _update?.Direct?.Bids?[0]?.Exponent ?? 0);
break;
case "ask":
case "askprice":
retval[0, 0] = update.asks?[0]?.price;
retval[0, 0] = BookMDFormatter.format_md(_update?.Direct?.Asks?[0]?.Mantissa ?? 0, _update?.Direct?.Asks?[0]?.Exponent ?? 0);
break;
case "bidsize":
case "bidqty":
retval[0, 0] = update.bids?[0]?.size;
retval[0, 0] = _update?.Direct?.Bids?[0]?.Size;
break;
case "asksize":
case "askqty":
retval[0, 0] = update.asks?[0]?.size;
retval[0, 0] = _update?.Direct?.Asks?[0]?.Size;
break;
case "impliedbid":
case "impliedbidprice":
retval[0, 0] = update.bids?[0]?.price; //TODO is this the first price with impsz > 0?
retval[0, 0] = BookMDFormatter.format_md(_update?.Implied?.Bids?[0]?.Mantissa ?? 0, _update?.Implied?.Bids?[0]?.Exponent ?? 0);
break;
case "impliedask":
case "impliedaskprice":
retval[0, 0] = update.asks?[0]?.price;
retval[0, 0] = BookMDFormatter.format_md(_update?.Implied?.Asks?[0]?.Mantissa ?? 0, _update?.Implied?.Asks?[0]?.Exponent ?? 0);
break;
case "impliedbidsize":
case "impliedbidqty":
retval[0, 0] = update.bids?[0]?.implied_size;
retval[0, 0] = _update?.Implied?.Bids?[0]?.Size;
break;
case "impliedasksize":
case "impliedaskqty":
retval[0, 0] = update.asks?[0]?.implied_size;
retval[0, 0] = _update?.Implied?.Asks?[0]?.Size;
break;
case "settlement":
case "settlementprice":
retval[0, 0] = update.settlement_price;
retval[0, 0] = BookMDFormatter.format_md(_update?.Misc?.Settlement?.Mantissa ?? 0, _update?.Misc?.Settlement?.Exponent ?? 0);
break;
case "previoussettlement":
case "previoussettlementprice":
retval[0, 0] = update.previous_settlement_price;
retval[0, 0] = BookMDFormatter.format_md(_update?.Misc?.PreviousSettlementPrice?.Mantissa ?? 0, _update?.Misc?.PreviousSettlementPrice?.Exponent ?? 0);
break;
case "open":
case "openprice":
retval[0, 0] = update.open_price;
retval[0, 0] = _update?.Misc?.OpenPrice;
break;
case "close":
case "closeprice":
retval[0, 0] = update.close_price;
retval[0, 0] = _update?.Misc?.ClosePrice;
break;
case "high":
case "highprice":
retval[0, 0] = update.session_high;
retval[0, 0] = _update?.Misc?.SessionHigh;
break;
case "low":
case "lowprice":
retval[0, 0] = update.session_low;
retval[0, 0] = _update?.Misc?.SessionLow;
break;
case "last":
case "lastprice":
retval[0, 0] = update?.last?.price;
retval[0, 0] = BookMDFormatter.format_md(_update?.LastTrade?.Mantissa ?? 0, _update?.LastTrade?.Exponent ?? 0);
break;
case "lastsize":
case "lastqty":
retval[0, 0] = update?.last?.size;
retval[0, 0] = _update?.LastTrade?.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.md;
using bts.api.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 res = await Locator.login(login, password);
if (res.success && Locator.token.HasValue)
var toke = await _auth.login(login, password);
foreach (var ven in MarketDataClient.cached_exchanges)
{
_md.set_auth_token(Locator.token);
handle.SetResult("Login Successful");
MarketDataClient.create_or_get(ven, out MarketDataClient md);
md?.set_auth_token(toke);
}
else
handle.SetResult("Login Error: Could not log in check user/pass " + res.reason);
handle.SetResult(toke?.Token ?? "Could not log in check user/pass");
}
catch(Exception ex)
{
@@ -34,10 +34,12 @@ namespace bts.excel.rtd
}
}
static MarketDataClient _md = new MarketDataClient();
public static MarketDataClient market_data(api.Venue exchange)
{
return _md;
var newc = MarketDataClient.create_or_get(exchange, out MarketDataClient md);
if(newc)
md.run_pump_async(TimeSpan.FromMilliseconds(333));
return md;
}
static public object spark_lookup_security_desc(string exchange, long security_key)
@@ -49,7 +51,7 @@ namespace bts.excel.rtd
return desc ?? string.Empty;
}
static IObservable<IEnumerable<object>> md_observable(string contract, string symbol, string security_type, string exchange)
static IObservable<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;
@@ -58,18 +60,18 @@ namespace bts.excel.rtd
Venue ven;
SecurityType sectype;
if (!Utilities.parse_venue(exchange, out ven))
return Observable.Return(new [] {"Can't Parse Exchange: " + exchange ?? string.Empty});
return Observable.Return("Can't Parse Exchange: " + exchange ?? string.Empty);
if (!Utilities.parse_security_type(security_type, out sectype))
return Observable.Return(new [] {"Can't Parse Security Type: " + security_type ?? string.Empty});
return Observable.Return("Can't Parse Security Type: " + security_type ?? string.Empty);
if (string.IsNullOrEmpty(symbol))
return Observable.Return(new []{"Can't Parse Symbol: " + symbol ?? string.Empty});
return Observable.Return("Can't Parse Symbol: " + symbol ?? string.Empty);
if (string.IsNullOrEmpty(contract))
return Observable.Return(new []{"Can't Parse Contract: " + contract ?? string.Empty});
return Observable.Return("Can't Parse Contract: " + contract ?? string.Empty);
Func<SecurityImmutable, bool> where = (x => x.Exchange == ven && x.Symbol == symbol && x.security_type() == sectype &&
x.Glbx.SecurityDesc == contract);
Product prod = new Product() { Exchange = ven, Type = sectype, Symbol = symbol };
Func<Security, bool> filter = (x => x.Glbx.SecurityDesc == contract); //TODO GLBX
var stream = market_data(ven).subscribe_combined(where);
var stream = market_data(ven).subscribe(prod).Where(x => filter(x.Security));
return stream;
}
@@ -83,11 +85,10 @@ 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(data =>
() => md_observable(contract, symbol, security_type, exchange).Select(x =>
{
var x = data.FirstOrDefault();
if (x != null && x is IMDBook upd)
return BookMDFormatter.format(upd);
if (x is co2.ChannelUpdate upd)
return new BookMDFormatter(upd).format();
return x;
}));
// security_type = string.IsNullOrEmpty(security_type) ? SecurityType.Future.ToString() : security_type;
@@ -110,33 +111,13 @@ 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(data =>
() => md_observable(contract, symbol, security_type, exchange).Select(x =>
{
var x = data.FirstOrDefault();
if (x != null && x is IMDBook upd)
return TopOfBookMDFormatter.format(upd);
if (x is co2.ChannelUpdate upd)
return new TopOfBookMDFormatter(upd).format();
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(
@@ -147,17 +128,16 @@ 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(data =>
() => md_observable(contract, symbol, security_type, exchange).Select(x =>
{
var x = data.FirstOrDefault();
if (x != null && x is IMDMisc upd)
return MiscMDFormatter.format(upd);
if (x is co2.ChannelUpdate upd)
return new MiscMDFormatter(upd).format();
return x;
}));
}
[ExcelFunction(Category = "BTS", Description = "Subscribe to BTS Spark topic based market data")]
[ExcelFunction(Category = "BTS", Description = "Subscribe to BTS Spark top of book 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,
@@ -167,14 +147,12 @@ 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(data =>
() => md_observable(contract, symbol, security_type, exchange).Select(x =>
{
var x = data.FirstOrDefault();
if (x != null && x is IMDCombined upd)
return TopicMDFormatter.format(upd, topic);
if (x is co2.ChannelUpdate upd)
return new TopicMDFormatter(upd, topic).format();
return x;
}));
}
}
}
Binary file not shown.
+55
View File
@@ -0,0 +1,55 @@
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;
}
}
}
}
+106
View File
@@ -0,0 +1,106 @@
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;
}
}
}
+585
View File
@@ -0,0 +1,585 @@
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>]&nbsp;[<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>]&nbsp;[<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>]&nbsp;[<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>]&nbsp;[<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>]&nbsp;[<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>]&nbsp;[<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>]&nbsp;[<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>]&nbsp;[<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>]&nbsp;[<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>&nbsp;<A HREF='/refreshSessions'>REFRESH</A>&nbsp;<A HREF='/enableSessions'>ENABLE</A>&nbsp;<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>]&nbsp;[<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;
}
}
}
@@ -0,0 +1,2 @@
using System.Runtime.CompilerServices;
+2
View File
@@ -0,0 +1,2 @@
# BTS Spark FIX Adapter
@@ -0,0 +1,281 @@
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;
}
}
}
@@ -0,0 +1,99 @@
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);
}
}
}
}
+359
View File
@@ -0,0 +1,359 @@
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);
}
}
}
+42
View File
@@ -0,0 +1,42 @@
#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
+41
View File
@@ -0,0 +1,41 @@
<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