Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6bb4c73a8 | ||
|
|
e43a2a4818 | ||
|
|
cde0f44b7d | ||
|
|
b07a1e2c74 | ||
|
|
8376fce5fe | ||
|
|
d9e5c846c2 | ||
|
|
65736c13ef | ||
|
|
071c28e925 | ||
|
|
8355e3e1be | ||
|
|
f9a6e0963c | ||
|
|
b2879837c6 | ||
|
|
0d03ddb009 | ||
|
|
a6dfa4ae80 | ||
|
|
e66ec82ba8 | ||
|
|
9ce5e971c6 | ||
|
|
3e7cc49ebb | ||
|
|
d07e68b6e2 | ||
|
|
619ece2313 | ||
|
|
1dda9566f3 | ||
|
|
3704d1ed16 | ||
|
|
a524ca02a5 | ||
|
|
3c97f2552b | ||
|
|
15b1d55ef1 | ||
|
|
4e42c9f01b | ||
|
|
76775f821a | ||
|
|
e7088f6b1c | ||
|
|
f2c3ff4be6 | ||
|
|
1fb42ade59 | ||
|
|
129650e9d4 | ||
|
|
170a44ac04 | ||
|
|
7e04f02327 | ||
|
|
abda2dbf97 | ||
|
|
ad1a45cf49 | ||
|
|
e9abdd0ff2 | ||
|
|
9254002318 | ||
|
|
596780b7f4 | ||
|
|
7ba061ac20 | ||
|
|
c01593d119 | ||
|
|
09e2a1764f | ||
|
|
a780668bce | ||
|
|
5b79aeaa14 | ||
|
|
049c006b5f | ||
|
|
6fdc344472 | ||
|
|
cb09b93553 | ||
|
|
21447d32e5 | ||
|
|
28b03e1cd2 | ||
|
|
a7acb89c15 | ||
|
|
35289710ba | ||
|
|
4677acfb39 | ||
|
|
ac05d0d69e | ||
|
|
25a9b6b433 | ||
|
|
b78713ad0d | ||
|
|
1f3d0e879c | ||
|
|
baad4dc68e | ||
|
|
ef7c0c7a30 | ||
|
|
3b3c4a2a35 | ||
|
|
ef9fc66a31 | ||
|
|
212d9c853b | ||
|
|
7815b18b6b | ||
|
|
05126074d4 | ||
|
|
3611d2c1f3 | ||
|
|
b5ad4b8104 | ||
|
|
43d7c0a2b5 | ||
|
|
12e361cec7 | ||
|
|
82c126362d |
No files matched your search
@@ -3,7 +3,6 @@ namespace mbts.spark {
|
||||
using bts.api;
|
||||
using bts.api.analytics;
|
||||
using bts.collections;
|
||||
using bts.reactive;
|
||||
using bts.utils;
|
||||
|
||||
using mbts;
|
||||
@@ -18,6 +17,7 @@ namespace mbts.spark {
|
||||
using Spark.Core;
|
||||
using Spark.Core.Collections;
|
||||
using Spark.Core.Disposables;
|
||||
using Spark.Core.Reactive;
|
||||
using Spark.ViewModel.Commands;
|
||||
|
||||
using System;
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
namespace mbts.spark {
|
||||
using bts;
|
||||
using bts.api;
|
||||
|
||||
using mbts;
|
||||
using mbts.analytics;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Collections;
|
||||
|
||||
// For each vs, it sends the vol surface calc request (_do_periodic_auto_fit)
|
||||
// and then commits the response
|
||||
public class SingleVolSurfaceAutoFitSource : IDisposable {
|
||||
private readonly ThreadedBlackboard _bb;
|
||||
private readonly int _vs_id;
|
||||
private readonly ITag<VolSurfaceInput> _vs_input_tag;
|
||||
private readonly ITag<VolSurfaceCalculationResponse> _vs_calc_response_tag;
|
||||
private DateTime _last_req_time = DateTime.MinValue;
|
||||
private TimeSpan _req_period = TimeSpan.MaxValue;
|
||||
private int _req_id = 0;
|
||||
public SingleVolSurfaceAutoFitSource(int vs_id, ThreadedBlackboard bb) {
|
||||
_bb = bb;
|
||||
_vs_id = vs_id;
|
||||
_vs_input_tag = SparkTags.VolSurfaceCommittedInputTag(vs_id);
|
||||
_vs_calc_response_tag = SparkTags.VolSurfaceAutoCalculationResponseTag(vs_id);
|
||||
_bb.subscribe(_vs_input_tag);
|
||||
_bb.subscribe(_vs_calc_response_tag);
|
||||
_bb.subscribe(SparkTags.CurrentUser);
|
||||
_bb.data_callback += _on_bb_data;
|
||||
_bb.timer_callback += _on_bb_timer;
|
||||
}
|
||||
|
||||
private void _on_bb_data(object tag, object data) {
|
||||
if (tag.Equals(_vs_input_tag))
|
||||
_possibly_autofit();
|
||||
else if (tag.Equals(_vs_calc_response_tag)) {
|
||||
VolSurfaceCalculationResponse calc_resp = data as VolSurfaceCalculationResponse;
|
||||
bts.Log.Gen.info($"SingleVolSurfaceAutoFitSource._on_bb_data vs_id: {_vs_id} " +
|
||||
$"received calculation response");
|
||||
_bb.post(SparkTags.VolSurfaceCommitTag(_vs_id),
|
||||
calc_resp.volsurface_calculation_output.volsurface_input);
|
||||
}
|
||||
}
|
||||
|
||||
private bool _user_has_vs_control() {
|
||||
VolSurfaceInput vs_input = _bb.read_state(_vs_input_tag);
|
||||
string current_user = _bb.read_state(SparkTags.CurrentUser).name;
|
||||
return vs_input != null &&
|
||||
vs_input.surface_settings.core_settings.vs_manager == current_user;
|
||||
}
|
||||
|
||||
private void _on_bb_timer() {
|
||||
_possibly_autofit();
|
||||
}
|
||||
|
||||
private void _possibly_autofit() {
|
||||
var auto_fit_period = _get_auto_fit_periodic();
|
||||
if (!auto_fit_period.HasValue)
|
||||
return;
|
||||
if (DateTime.Now - _last_req_time <= auto_fit_period)
|
||||
return;
|
||||
if (!_user_has_vs_control())
|
||||
return;
|
||||
// auto fit here
|
||||
bts.Log.Gen.info($"SingleVolSurfaceAutoFitSource vs_id: {_vs_id} requesting fit " +
|
||||
$"last_req_time: {_last_req_time} period: {auto_fit_period.Value}");
|
||||
_send_request();
|
||||
}
|
||||
|
||||
private Nullable<TimeSpan> _get_auto_fit_periodic() {
|
||||
VolSurfaceInput vs_input = _bb.read_state(_vs_input_tag);
|
||||
if (vs_input == null)
|
||||
return null;
|
||||
var fit_settings =
|
||||
VolSurfaceDataFunctions.get_market_fit_settings(vs_input.surface_settings);
|
||||
if (fit_settings == null)
|
||||
return null;
|
||||
var auto_fit_setting = fit_settings.vs_auto_fit_settings;
|
||||
if (auto_fit_setting == null)
|
||||
return null;
|
||||
if (auto_fit_setting.auto_fit_region == VolSurfaceEnums.VolCurveFitRegion.None)
|
||||
return null;
|
||||
var time_based_fit_setting = auto_fit_setting.time_based_settings;
|
||||
if (time_based_fit_setting == null)
|
||||
return null;
|
||||
if (!time_based_fit_setting.period_seconds.HasValue)
|
||||
return null;
|
||||
return TimeSpan.FromSeconds(time_based_fit_setting.period_seconds.Value);
|
||||
}
|
||||
|
||||
private void _send_request() {
|
||||
VolSurfaceInput vs_input = _bb.read_state(_vs_input_tag);
|
||||
if (vs_input == null)
|
||||
return;
|
||||
VolCurveMarketFitAction mf_action = new VolCurveMarketFitAction(
|
||||
VolCurveMarketFitAction.ActionType.FitWholeCurve, null, false);
|
||||
VolCurveAction vc_action = new VolCurveAction(mf_action);
|
||||
IDictionary<VolSurfaceExpiration, VolCurveAction> action_map =
|
||||
vs_input.surface_parameters.curve_parameters_map.ToDictionary(
|
||||
i => i.Item1,
|
||||
i => vc_action
|
||||
);
|
||||
VolSurfaceCalculationRequest req = new VolSurfaceCalculationRequest(vs_input,
|
||||
_req_id++, action_map, true, true);
|
||||
_last_req_time = DateTime.Now;
|
||||
_bb.post(SparkTags.VolSurfaceAutoCalculationRequestTag(_vs_id), req);
|
||||
}
|
||||
|
||||
|
||||
#region IDisposable
|
||||
public void Dispose() {
|
||||
_bb.data_callback -= _on_bb_data;
|
||||
_bb.timer_callback -= _on_bb_timer;
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
using bts.api.analytics;
|
||||
|
||||
using mbts.analytics;
|
||||
using mbts.ui;
|
||||
|
||||
using Spark.Core.Protobuf;
|
||||
|
||||
@@ -13,6 +14,7 @@
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
|
||||
// For a single VolSurface this class
|
||||
// 1. Listents to commmits and calls the service to persist data
|
||||
@@ -24,7 +26,6 @@
|
||||
private readonly ITag<VolSurfaceInput> _in_bb_committ_tag;
|
||||
private readonly AnalyticsService _analytics_service;
|
||||
private readonly HighPerformanceTimer _timer = new HighPerformanceTimer();
|
||||
private readonly string _user;
|
||||
private readonly List<IDisposable> _disposables = new List<IDisposable>();
|
||||
private IDictionary<UInt64, VolSurfaceInputSnapshot> _snp_shot_map = new
|
||||
Dictionary<UInt64, VolSurfaceInputSnapshot>();
|
||||
@@ -35,10 +36,10 @@
|
||||
volsurface_id));
|
||||
_volsurface_id = volsurface_id;
|
||||
_bb = threaded_bb;
|
||||
_user = "TEST_USER"; // TODO
|
||||
_in_bb_committ_tag = SparkTags.VolSurfaceCommitTag(volsurface_id);
|
||||
_analytics_service = analytics_service;
|
||||
_bb.subscribe(_in_bb_committ_tag);
|
||||
_bb.subscribe(SparkTags.CurrentUser);
|
||||
_bb.data_callback += _on_bb_data;
|
||||
var vs_sub = _analytics_service.subscribei_volsurfaces()
|
||||
.ObserveOn(SynchronizationContext.Current)
|
||||
@@ -54,8 +55,10 @@
|
||||
private void _on_vss(bts.api.core.Update<VolSurfaceEntryImmutable, UInt64> update) {
|
||||
IEnumerable<VolSurfaceEntryImmutable> all_vss = update.all;
|
||||
var this_vs_vss = all_vss.Where(vs => vs.VsId == _volsurface_id);
|
||||
bts.Log.Gen.info(String.Format("SingleVolSurfaceGateway._on_vss count:{0} vs_count:{1}",
|
||||
all_vss.Count(), this_vs_vss.Count()));
|
||||
bts.Log.Gen.info(
|
||||
$"SingleVolSurfaceGateway._on_vss vs_count: {this_vs_vss.Count()} " +
|
||||
$"all: {update.all.Count()} added: {update.added.Count()} " +
|
||||
$"updated: {update.changed.Count()} removed: {update.removed.Count()}");
|
||||
if (this_vs_vss.Count() == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -83,22 +86,39 @@
|
||||
static VolSurfaceInput _to_vs_input(VolSurfaceEntryImmutable vs) =>
|
||||
ProtobufSerializationHelper.deserialize<VolSurfaceInput>(vs.Volsurface.ToByteArray());
|
||||
|
||||
// private void _show_save_error(string message) {
|
||||
// BTSMessageBox.Show(message, "Vol Save Error", BTSMessageBoxButtons.OK);
|
||||
// }
|
||||
|
||||
private void _on_bb_data(object tag, object data) {
|
||||
if (_in_bb_committ_tag.Equals(tag)) {
|
||||
try {
|
||||
_timer.start();
|
||||
VolSurfaceInput vs_input = data as VolSurfaceInput;
|
||||
var user_tup = _bb.read_state(SparkTags.CurrentUser);
|
||||
string current_user = user_tup.name;
|
||||
string vs_manager = vs_input.surface_settings.core_settings.vs_manager;
|
||||
if (current_user != vs_manager) {
|
||||
string msg = $"User {current_user} does not have the permission to save vols. " +
|
||||
$"Permission can be obtained via the grid context menu item 'Acquire ownership from {vs_manager}'";
|
||||
BTSMessageBox.Show(msg, "Vol Save Error", BTSMessageBoxButtons.OK);
|
||||
return;
|
||||
}
|
||||
_sanitize_input(ref vs_input);
|
||||
vs_input = VolSurfaceDataFunctions.unset_driver_overrides(vs_input);
|
||||
vs_input.saver_login = _user;
|
||||
VolSurfaceUtilityFunctions.save_volsurface(vs_input, _analytics_service, _user);
|
||||
VolSurfaceUtilityFunctions.save_volsurface(vs_input, _analytics_service, current_user);
|
||||
_timer.stop();
|
||||
Log.Gen.info(String.Format(
|
||||
"SingleVolSurfaceGateway._on_bb_data Time to commit volsurface {0} {1} msecs",
|
||||
_volsurface_id, _timer.lastIntervalTime));
|
||||
var archived_theos = _get_theos();
|
||||
if (archived_theos != null)
|
||||
if (archived_theos != null) {
|
||||
_timer.start();
|
||||
_analytics_service.archive_theos(archived_theos);
|
||||
_timer.stop();
|
||||
Log.Gen.info($"SingleVolSurfaceGateway._on_bb_data archiving theos vs_id: {_volsurface_id} size: " +
|
||||
$"{archived_theos.SecTheo.Count} time: {_timer.lastIntervalTime} msecs");
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Log.Gen.warn(ex.ToString());
|
||||
_bb.post(SparkTags.MessageDisplayTag,
|
||||
|
||||
@@ -36,7 +36,11 @@ namespace mbts.spark {
|
||||
new SingleVolSurfaceDataSource(SingleVolSurfaceDataSource.SymbolListType.Committed, _vs_id, _bb,
|
||||
a_syms => new SingleVolSurfaceCommittedOutputRetriever(_vs_id, _bb, a_syms)),
|
||||
new SingleVolSurfaceDataSource(SingleVolSurfaceDataSource.SymbolListType.Combined, _vs_id, _bb,
|
||||
a_syms => new SingleVolSurfaceRequestCalculator(_vs_id, a_syms, _bb)),
|
||||
a_syms => new SingleVolSurfaceRequestCalculator(
|
||||
SingleVolSurfaceRequestCalculator.RequestType.UI, _vs_id, a_syms, _bb)),
|
||||
new SingleVolSurfaceDataSource(SingleVolSurfaceDataSource.SymbolListType.Combined, _vs_id, _bb,
|
||||
a_syms => new SingleVolSurfaceRequestCalculator(
|
||||
SingleVolSurfaceRequestCalculator.RequestType.AUTO, _vs_id, a_syms, _bb)),
|
||||
new SingleVolSurfaceMarketImpliedVolSource(_vs_id, _bb),
|
||||
// new SingleVolSurfaceTheoSource(_vs_id, _bb),
|
||||
// TODO: suspending heatmaps for now (12/24/21)
|
||||
@@ -44,6 +48,7 @@ namespace mbts.spark {
|
||||
// server work that can optionally be done on the client
|
||||
new SingleVolSurfaceDataSource (SingleVolSurfaceDataSource.SymbolListType.Committed, _vs_id, _bb,
|
||||
a_syms => new SingleVolSurfaceInterpGreeksPublisher (_vs_id, _bb, a_syms)),
|
||||
new SingleVolSurfaceAutoFitSource(_vs_id, _bb),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,16 @@ namespace mbts.spark {
|
||||
// Pubs :
|
||||
// 1. Calc Resp
|
||||
public class SingleVolSurfaceRequestCalculator : IDisposable {
|
||||
public enum RequestType {
|
||||
UI = 0,
|
||||
AUTO
|
||||
}
|
||||
private readonly RequestType _request_type = RequestType.UI;
|
||||
private readonly int _vs_id;
|
||||
private readonly HashSet<string> _symbols;
|
||||
private readonly ThreadedBlackboard _bb;
|
||||
private readonly ITag<VolSurfaceCalculationRequest> _in_bb_request_tag;
|
||||
private readonly ITag<VolSurfaceCalculationRequest> _in_bb_auto_request_tag;
|
||||
private readonly ITag<VolSurfaceNonVolData> _in_bb_nonvol_data_tag;
|
||||
private readonly ITag<VolSurfaceStandardTime> _in_bb_vs_standard_time_tag;
|
||||
private readonly ITag<VolSurfaceEnums.MarketSide> _in_bb_market_impliedvol_side_tag;
|
||||
@@ -30,18 +36,22 @@ namespace mbts.spark {
|
||||
/// data
|
||||
private VolSurfaceOutput _last_output;
|
||||
|
||||
public SingleVolSurfaceRequestCalculator(int vs_id, IEnumerable<string> symbols,
|
||||
public SingleVolSurfaceRequestCalculator(RequestType request_type,
|
||||
int vs_id, IEnumerable<string> symbols,
|
||||
ThreadedBlackboard threaded_bb) {
|
||||
bts.Log.Gen.info($"SingleVolSurfaceRequestCalculator cons vs_id: {vs_id}");
|
||||
bts.Log.Gen.info($"SingleVolSurfaceRequestCalculator cons vs_id: {vs_id} request_type: {request_type}");
|
||||
_request_type = request_type;
|
||||
_vs_id = vs_id;
|
||||
_symbols = new HashSet<string>(symbols);
|
||||
_bb = threaded_bb;
|
||||
_in_bb_request_tag = SparkTags.VolSurfaceCalculationRequestTag(vs_id);
|
||||
_in_bb_auto_request_tag = SparkTags.VolSurfaceAutoCalculationRequestTag(vs_id);
|
||||
_in_bb_nonvol_data_tag = SparkTags.VolSurfaceNonVolDataTag(vs_id);
|
||||
_in_bb_vs_standard_time_tag = SparkTags.VolSurfaceStandardTimeTag(vs_id);
|
||||
_in_bb_market_impliedvol_side_tag = SparkTags.VolSurfaceMarketImpliedVolSide(vs_id);
|
||||
_in_bb_dirty_tag = SparkTags.VolSurfaceDirtyStateTag(vs_id);
|
||||
_bb.subscribe(_in_bb_request_tag);
|
||||
_bb.subscribe(_in_bb_auto_request_tag);
|
||||
_bb.subscribe(_in_bb_nonvol_data_tag);
|
||||
_bb.subscribe(_in_bb_vs_standard_time_tag);
|
||||
_bb.subscribe(_in_bb_vs_standard_time_tag);
|
||||
@@ -51,11 +61,21 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
private void _on_bb_data(object tag, object data) {
|
||||
if (_in_bb_request_tag.Equals(tag) || _in_bb_dirty_tag.Equals(tag))
|
||||
_recompute_and_publish(true);
|
||||
else if (_in_bb_nonvol_data_tag.Equals(tag) || _in_bb_vs_standard_time_tag.Equals(tag))
|
||||
_recompute_and_publish(false);
|
||||
|
||||
switch (_request_type) {
|
||||
case RequestType.UI: {
|
||||
if (_in_bb_request_tag.Equals(tag) || _in_bb_dirty_tag.Equals(tag))
|
||||
_recompute_and_publish(true);
|
||||
else if (_in_bb_nonvol_data_tag.Equals(tag) || _in_bb_vs_standard_time_tag.Equals(tag))
|
||||
_recompute_and_publish(false);
|
||||
break;
|
||||
}
|
||||
case RequestType.AUTO: {
|
||||
if (tag.Equals(_in_bb_auto_request_tag)) {
|
||||
_serve_auto_request();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void _recompute_and_publish(bool is_new_request) {
|
||||
@@ -80,7 +100,7 @@ namespace mbts.spark {
|
||||
nonvol_data.driver_price_map,
|
||||
vs_standard_time, true);
|
||||
IDictionary<VolSurfaceExpiration, VolCurveAction> action_map =
|
||||
_post_process_action(effective_req, aux_data_map);
|
||||
_post_process_action(_bb, _vs_id, effective_req, aux_data_map);
|
||||
_last_output = GeneralVolSurfaceCalculator.compute(effective_req.volsurface_input,
|
||||
nonvol_data.contracts,
|
||||
aux_data_map,
|
||||
@@ -89,16 +109,49 @@ namespace mbts.spark {
|
||||
effective_req.cascade_up,
|
||||
effective_req.cascade_down);
|
||||
_timer.stop();
|
||||
bts.Log.Gen.info($"SingleVolSurfaceRequestCalculator._recompute_and_publish time: " +
|
||||
$"{_timer.lastIntervalTime}");
|
||||
bts.Log.Gen.info($"SingleVolSurfaceRequestCalculator._recompute_and_publish vs_id: {_vs_id} " +
|
||||
$"time: {_timer.lastIntervalTime}");
|
||||
_bb.post(SparkTags.VolSurfaceCalculationResponseTag(_vs_id),
|
||||
new VolSurfaceCalculationResponse(_last_output, effective_req.request_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void _serve_auto_request() {
|
||||
bts.Log.Gen.info($"SingleVolSurfaceRequestCalculator._serve_auto_request " +
|
||||
$"vs_id: {_vs_id} auto request received");
|
||||
bool is_dirty = _bb.read_state(_in_bb_dirty_tag, false);
|
||||
VolSurfaceNonVolData nonvol_data = _bb.read_state(_in_bb_nonvol_data_tag);
|
||||
VolSurfaceStandardTime vs_standard_time = _bb.read_state(_in_bb_vs_standard_time_tag);
|
||||
VolSurfaceCalculationRequest auto_req = _bb.read_state(_in_bb_auto_request_tag);
|
||||
if (is_dirty || nonvol_data == null || vs_standard_time == null || auto_req == null) {
|
||||
return;
|
||||
}
|
||||
_timer.start();
|
||||
IDictionary<VolSurfaceExpiration, VolSurfaceExpirationAuxiliaryData> aux_data_map =
|
||||
VolSurfaceUtilityFunctions.compute_aux_data(auto_req.volsurface_input,
|
||||
nonvol_data.input,
|
||||
nonvol_data.driver_price_map,
|
||||
vs_standard_time, true);
|
||||
IDictionary<VolSurfaceExpiration, VolCurveAction> action_map =
|
||||
_post_process_action(_bb, _vs_id, auto_req, aux_data_map);
|
||||
var vs_output = GeneralVolSurfaceCalculator.compute(auto_req.volsurface_input,
|
||||
nonvol_data.contracts,
|
||||
aux_data_map,
|
||||
action_map,
|
||||
null,
|
||||
auto_req.cascade_up,
|
||||
auto_req.cascade_down);
|
||||
_timer.stop();
|
||||
bts.Log.Gen.info($"SingleVolSurfaceRequestCalculator._serve_auto_request vs_id: {_vs_id} " +
|
||||
$"time: {_timer.lastIntervalTime}");
|
||||
_bb.post(SparkTags.VolSurfaceAutoCalculationResponseTag(_vs_id),
|
||||
new VolSurfaceCalculationResponse(vs_output, auto_req.request_id));
|
||||
}
|
||||
|
||||
/// Computes the impl vol if the action is fit to market
|
||||
private IDictionary<VolSurfaceExpiration, VolCurveAction> _post_process_action(
|
||||
private static IDictionary<VolSurfaceExpiration, VolCurveAction> _post_process_action(
|
||||
ThreadedBlackboard bb, int vs_id,
|
||||
VolSurfaceCalculationRequest req,
|
||||
IDictionary<VolSurfaceExpiration, VolSurfaceExpirationAuxiliaryData> aux_data_map) {
|
||||
IDictionary<VolSurfaceExpiration, VolCurveAction> rv = req.action_map;
|
||||
@@ -109,7 +162,7 @@ namespace mbts.spark {
|
||||
VolCurveAction rep_action = rv.ElementAt(0).Value;
|
||||
if (rep_action.action_type == VolCurveAction.ActionType.FitToMarket) {
|
||||
IDictionary<VolSurfaceExpiration, VolCurveMarketImpliedVolData> impl_vol_map =
|
||||
_fill_in_implied_vols(req.volsurface_input, req.action_map, aux_data_map,
|
||||
_fill_in_implied_vols(bb, vs_id, req.volsurface_input, req.action_map, aux_data_map,
|
||||
req.cascade_up, req.cascade_down);
|
||||
rv = impl_vol_map.ToDictionary(kv => kv.Key, kv => new VolCurveAction(
|
||||
new VolCurveMarketFitAction(rep_action.market_fit_action.fit_action, kv.Value,
|
||||
@@ -119,7 +172,8 @@ namespace mbts.spark {
|
||||
return rv;
|
||||
}
|
||||
|
||||
private IDictionary<VolSurfaceExpiration, VolCurveMarketImpliedVolData> _fill_in_implied_vols(
|
||||
private static IDictionary<VolSurfaceExpiration, VolCurveMarketImpliedVolData> _fill_in_implied_vols(
|
||||
ThreadedBlackboard bb, int vs_id,
|
||||
VolSurfaceInput vs_inp,
|
||||
IDictionary<VolSurfaceExpiration, VolCurveAction> action_map,
|
||||
IDictionary<VolSurfaceExpiration, VolSurfaceExpirationAuxiliaryData> aux_data_map,
|
||||
@@ -140,15 +194,15 @@ namespace mbts.spark {
|
||||
applicable_exps = applicable_exps.Where(vs_exp => master_exps.Contains(vs_exp));
|
||||
|
||||
VolSurfaceEnums.MarketSide impliedvol_side =
|
||||
SingleVolSurfaceMarketImpliedVolSource._get_market_impliedvol_side(_bb, _vs_id);
|
||||
SingleVolSurfaceMarketImpliedVolSource._get_market_impliedvol_side(bb, vs_id);
|
||||
foreach (VolSurfaceExpiration vs_exp in applicable_exps) {
|
||||
VolSurfaceExpiration md_exp;
|
||||
VolCurveMarketImpliedVolData exp_impl_data =
|
||||
VolSurfaceCoreFunctions.compute_implied_vols(vs_inp, vs_exp, aux_data_map,
|
||||
arg_vs_exp => SingleVolSurfaceMarketImpliedVolSource.get_option_bbo(_bb, arg_vs_exp),
|
||||
arg_vs_exp => SingleVolSurfaceMarketImpliedVolSource.get_option_bbo(bb, arg_vs_exp),
|
||||
impliedvol_side,
|
||||
SingleVolSurfaceMarketImpliedVolSource._ignore_market_implied_vol_source_settings(
|
||||
_bb, _vs_id),
|
||||
bb, vs_id),
|
||||
out md_exp);
|
||||
rv[vs_exp] = exp_impl_data;
|
||||
}
|
||||
|
||||
@@ -270,6 +270,7 @@ namespace mbts.spark {
|
||||
new mbts.analytics.VolSurfaceNonVolInput(exp_param_map.exp_map.ToDictionary()));
|
||||
} else if (_in_bb_req_vol.Equals(tag)) {
|
||||
ClientArchivedVolRequest client_req = _bb.read_state(_in_bb_req_vol);
|
||||
bts.Log.Ui.info($"VolSurfaceBusManager._on_bb_update ClientArchivedVolRequest: {client_req.ToString()}");
|
||||
ArchivedTheoRetrieval ret = client_req.ret;
|
||||
if (ret == null || ret.SecTup == null) {
|
||||
bts.Log.Ui.warn($"VolSurfaceBusManager malformed ArchivedTheoRetrieval");
|
||||
|
||||
@@ -5,6 +5,7 @@ using bts.api;
|
||||
using bts.collections;
|
||||
using System.Linq;
|
||||
using bts.api.orderman;
|
||||
using bts.api.analytics;
|
||||
using bts;
|
||||
|
||||
namespace mbts.spark {
|
||||
@@ -23,6 +24,8 @@ namespace mbts.spark {
|
||||
_order_service = order_service;
|
||||
_bb.subscribe(SparkTags.AllInterpGreeks);
|
||||
_bb.subscribe(SparkTags.SystemContractsTag);
|
||||
_bb.subscribe(SparkTags.CurrentUser);
|
||||
_bb.subscribe(SparkTags.VolSurfaceUsageTag);
|
||||
_bb.data_callback += _on_data;
|
||||
}
|
||||
|
||||
@@ -55,6 +58,27 @@ namespace mbts.spark {
|
||||
return rv;
|
||||
}
|
||||
|
||||
private IReadOnlyDictionary<string, OptionTheoInterpolationData>
|
||||
_filter_for_publish(IReadOnlyDictionary<string, OptionTheoInterpolationData> all_theos) {
|
||||
// find the symbol vs vs_id, get the committed vs, see if it is
|
||||
// owned by the current user, and only then publish it
|
||||
HashSet<string> syms = new HashSet<string>();
|
||||
VolSurfaceUsageListImmutable usage = _bb.read_state(SparkTags.VolSurfaceUsageTag);
|
||||
string current_user = _bb.read_state(SparkTags.CurrentUser).name;
|
||||
foreach (var u in usage.UsageList) {
|
||||
VolSurfaceInput vs_input =
|
||||
_bb.read_state(SparkTags.VolSurfaceCommittedInputTag(u.VsId));
|
||||
if (vs_input != null) {
|
||||
if (vs_input.surface_settings.core_settings.vs_manager ==
|
||||
current_user) {
|
||||
syms.Add(u.SecTup.Symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
all_theos.Where(kv => syms.Contains(kv.Key)).ToDictionary(kv => kv.Key, kv => kv.Value);
|
||||
}
|
||||
|
||||
private void _publish_data() {
|
||||
SystemContractData sc_data = _bb.read_state(SparkTags.SystemContractsTag, null);
|
||||
IReadOnlyDictionary<string, OptionTheoInterpolationData> all_theos =
|
||||
@@ -64,6 +88,11 @@ namespace mbts.spark {
|
||||
if (sc_data == null || all_theos == null || all_drivers == null)
|
||||
return;
|
||||
_timer.start();
|
||||
all_theos = _filter_for_publish(all_theos);
|
||||
if (all_theos.Count == 0) {
|
||||
bts.Log.Gen.info($"InterpTheosServerPublisher._publish_data all_theos.Count == 0");
|
||||
return;
|
||||
}
|
||||
IDictionary<UInt32, DriverMD> driver_map = _get_drivers(all_drivers, sc_data);
|
||||
IEnumerable<Tuple<SecurityImmutable, TheoPoint>> theo_map = _get_theos(all_theos, sc_data);
|
||||
// assemble it together and send it to the server
|
||||
@@ -86,6 +115,7 @@ namespace mbts.spark {
|
||||
ptp.Vega = is_call ? tp.cvega : tp.pvega;
|
||||
ptp.Theta = is_call ? tp.c_theta : tp.p_theta;
|
||||
ptp.Rho = Double.NaN;
|
||||
ptp.Vol = tp.vol;
|
||||
ptp_list.Add(ptp);
|
||||
}
|
||||
bts.api.orderman.PrecomputedTheos server_theos = new PrecomputedTheos();
|
||||
@@ -96,7 +126,7 @@ namespace mbts.spark {
|
||||
_order_service.set_precomputed_theos(server_theos);
|
||||
_timer.stop();
|
||||
var pub_time = _timer.lastIntervalTime;
|
||||
bts.Log.Gen.info($"InterpTheosServerPublisher._publish_data " +
|
||||
bts.Log.Gen.info($"InterpTheosServerPublisher._publish_data symbols: {all_theos.Count} " +
|
||||
$"size: {server_theos.Theos.Count} comp_time: {comp_time} msecs " +
|
||||
$"pub_time: {pub_time}");
|
||||
}
|
||||
|
||||
@@ -26,13 +26,13 @@ namespace mbts.spark {
|
||||
_refresh = new(_is_dirty.refresh);
|
||||
_committed_value.Changed += on_committed_changed;
|
||||
_candidate_value.Changed += on_candidate_changed;
|
||||
_is_dirty.Changed += (o, n) => {
|
||||
Log.assert(o != n);
|
||||
// TODO: John, this was logged too many times
|
||||
// Log.Ui.info(
|
||||
// $"VolSurface '{expiration}.{column}' is_dirty changed to {n}. " +
|
||||
// $"commited: {committed_value}; candidate: {candidate_value}");
|
||||
};
|
||||
if (column.type.is_editable()) {
|
||||
_is_dirty.Changed += (o, n) => {
|
||||
Log.Ui.info(
|
||||
$"VolSurface '{expiration}.{column}' is_dirty changed to {n}. " +
|
||||
$"commited: {committed_value}; candidate: {candidate_value}");
|
||||
};
|
||||
}
|
||||
|
||||
void on_committed_changed(object? oldval, object? newval) =>
|
||||
_is_dirty.refresh();
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace mbts.spark.VolSurface {
|
||||
|
||||
class VolSurfaceGridVM : DisposableViewModelBase {
|
||||
readonly VolSurfaceVM _parent;
|
||||
readonly UserTuple _user;
|
||||
readonly ObservableCollection<VolSurfaceDisplayGridExpiration> _expirations = new();
|
||||
readonly DeferrableAction _invalidate_rows;
|
||||
readonly ColumnsModel _columns_model;
|
||||
@@ -41,10 +42,12 @@ namespace mbts.spark.VolSurface {
|
||||
VolSurfaceVM vm,
|
||||
VolSurfaceDisplayModel model,
|
||||
ISet<string> hidden_columns,
|
||||
ISet<VolSurfaceExpiration> hidden_expirations) {
|
||||
ISet<VolSurfaceExpiration> hidden_expirations,
|
||||
UserTuple user) {
|
||||
Log.notnull(hidden_columns);
|
||||
Log.notnull(hidden_expirations);
|
||||
_parent = vm;
|
||||
_user = user;
|
||||
this.model = model;
|
||||
_columns_model = new(create_exp_column_view());
|
||||
_hidden_columns = hidden_columns;
|
||||
@@ -83,7 +86,7 @@ namespace mbts.spark.VolSurface {
|
||||
_columns_model);
|
||||
|
||||
VolSurfaceRowVM row_factory(VolSurfaceDisplayGridExpiration exp) =>
|
||||
new(this, exp);
|
||||
new(this, exp, _user);
|
||||
|
||||
IDisposable connect_row(VolSurfaceRowVM row) {
|
||||
row.request_calculate += request_recalc;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
namespace mbts.spark {
|
||||
using bts;
|
||||
using bts.collections;
|
||||
|
||||
using mbts.spark.VolSurface;
|
||||
@@ -9,6 +10,7 @@ namespace mbts.spark {
|
||||
using mbts.ui.utility;
|
||||
|
||||
using Spark.Core.Disposables;
|
||||
using Spark.Core.Reactive;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -22,6 +24,10 @@ namespace mbts.spark {
|
||||
readonly VolSurfaceGridVM _parent;
|
||||
readonly Property<VolSurfaceColumn?> _column;
|
||||
readonly Property<DIYGridSuperHeaderVM?> _super_header;
|
||||
readonly UserTuple _user;
|
||||
readonly Property<bool> _is_open;
|
||||
readonly DelegateProperty<string> _vs_manager;
|
||||
readonly List<IDisposable> _resources = new();
|
||||
|
||||
ICommand? _revert_cmd;
|
||||
ICommand? _shift_selection_cmd;
|
||||
@@ -34,21 +40,29 @@ namespace mbts.spark {
|
||||
ICommand? _copy_all_to_clipboard_cmd;
|
||||
ICommand? _smart_copy_rolls_rates_cmd;
|
||||
ICommand? _import_csv_cmd;
|
||||
ObservantCommand? _acquire_ownership_cmd;
|
||||
|
||||
public VolSurfaceRowContextMenuVM(VolSurfaceGridVM parent, VolSurfaceRowVM row) {
|
||||
public VolSurfaceRowContextMenuVM(VolSurfaceGridVM parent, VolSurfaceRowVM row, UserTuple user) {
|
||||
_parent = parent;
|
||||
_row = row;
|
||||
_user = user;
|
||||
_column = backing<VolSurfaceColumn?>(nameof(column));
|
||||
_super_header = backing<DIYGridSuperHeaderVM?>(nameof(super_header));
|
||||
_vs_manager = backing(
|
||||
nameof(vs_manager),
|
||||
() => _parent.model.get_active_settings()?.core_settings?.vs_manager ?? string.Empty);
|
||||
_is_open = backing(nameof(is_open), false);
|
||||
|
||||
_dispose = Disposable.Create(
|
||||
_resources.AddRange(
|
||||
_column.invalidate_others_on_change(nameof(column_name)),
|
||||
_super_header.invalidate_others_on_change(nameof(group_name)),
|
||||
_is_open.Subscribe(_ => _vs_manager.refresh()),
|
||||
_parent.visible_rows.ObserveSelf()
|
||||
.BufferLatest(TimeSpan.FromSeconds(1))
|
||||
.OnDispatcher()
|
||||
.Subscribe(_ => sync_symbols()));
|
||||
|
||||
_dispose = Disposable.Create(() => _resources.DisposeAll(true));
|
||||
void sync_symbols() {
|
||||
var visible_exps = _parent.visible_rows.Map(r => r.expiration.symbol);
|
||||
symbols.sync_with_models(
|
||||
@@ -164,6 +178,34 @@ namespace mbts.spark {
|
||||
_parent.model.csv_importing_enabled,
|
||||
null);
|
||||
|
||||
public ICommand acquire_ownership_cmd {
|
||||
get {
|
||||
if (_acquire_ownership_cmd is null) {
|
||||
_acquire_ownership_cmd = new(
|
||||
() => {
|
||||
var settings = _parent.model.get_active_settings();
|
||||
if (settings is null) {
|
||||
Log.Ui.error("Unable to acquire volsurface ownership: settings are null.");
|
||||
return;
|
||||
}
|
||||
settings.core_settings.vs_manager = _user.name;
|
||||
_parent.model.set_candidate_settings(settings);
|
||||
});
|
||||
_resources.Add(_vs_manager.observe_current(true)
|
||||
.Subscribe(SelectorObserver.builder(_acquire_ownership_cmd).wrap<string>(
|
||||
curr => curr != _user.name)));
|
||||
}
|
||||
return _acquire_ownership_cmd;
|
||||
}
|
||||
}
|
||||
|
||||
public bool is_open {
|
||||
get => _is_open.Value;
|
||||
set => _is_open.set(value);
|
||||
}
|
||||
|
||||
public string vs_manager => _vs_manager.Value;
|
||||
|
||||
VolSurfaceDisplayGridColumn _assert_model() =>
|
||||
column?.model ?? throw new InvalidOperationException();
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#nullable enable
|
||||
namespace mbts.spark.VolSurface {
|
||||
using bts;
|
||||
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
@@ -16,14 +18,16 @@ namespace mbts.spark.VolSurface {
|
||||
readonly VolSurfaceGridVM _parent;
|
||||
readonly VolSurfaceDisplayGridExpiration _exp;
|
||||
readonly Dictionary<string, CellData> _cells = new();
|
||||
readonly UserTuple _user;
|
||||
readonly DeferrableAction _request_calculate;
|
||||
|
||||
VolSurfaceRowContextMenuVM? _context_menu;
|
||||
bool _is_selected;
|
||||
|
||||
public VolSurfaceRowVM(VolSurfaceGridVM parent, VolSurfaceDisplayGridExpiration exp) {
|
||||
public VolSurfaceRowVM(VolSurfaceGridVM parent, VolSurfaceDisplayGridExpiration exp, UserTuple user) {
|
||||
_parent = parent;
|
||||
_exp = exp;
|
||||
_user = user;
|
||||
_request_calculate = new(() => request_calculate?.Invoke());
|
||||
_dispose = Disposable.Create(() => {
|
||||
_cells.Values.DisposeAll();
|
||||
@@ -49,7 +53,7 @@ namespace mbts.spark.VolSurface {
|
||||
}
|
||||
|
||||
public VolSurfaceRowContextMenuVM context_menu =>
|
||||
_context_menu ??= new(_parent, this);
|
||||
_context_menu ??= new(_parent, this, _user);
|
||||
|
||||
public VolSurfaceCellVM get_cell(VolSurfaceDisplayGridColumn column) =>
|
||||
_cells.GetOrAdd(column.unique_col_name, _ => _create_cell(column)).vm;
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
#nullable enable
|
||||
|
||||
namespace mbts.spark {
|
||||
using bts.api.domain;
|
||||
|
||||
using mbts.analytics;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using VolCurveFitRegion = analytics.VolSurfaceEnums.VolCurveFitRegion;
|
||||
|
||||
public class VolSurfaceAutoFitSettingsVM : DisposableViewModelBase {
|
||||
readonly Property<VolCurveFitRegion> _fit_region;
|
||||
readonly Property<uint?> _period_secs;
|
||||
readonly Property<double?> _driver_points_moved;
|
||||
readonly Property<double?> _atm_points_moved;
|
||||
readonly Property<bool> _is_auto_fit_enabled;
|
||||
|
||||
public VolSurfaceAutoFitSettingsVM() {
|
||||
_fit_region = backing(nameof(fit_region), VolCurveFitRegion.None);
|
||||
_is_auto_fit_enabled = backing(
|
||||
nameof(is_auto_fit_enabled),
|
||||
_fit_region.observe_current(true),
|
||||
r => r != VolCurveFitRegion.None);
|
||||
_period_secs = backing<uint?>(nameof(period_secs), 10);
|
||||
_driver_points_moved = backing<double?>(nameof(driver_points_moved));
|
||||
_atm_points_moved = backing<double?>(nameof(atm_points_moved));
|
||||
_fit_region.Changed += (o, n) => RaisePropertyChanged(nameof(is_auto_fit_enabled));
|
||||
}
|
||||
|
||||
public IEnumerable<VolCurveFitRegion> fit_regions =>
|
||||
EnumHelper.get_values<VolCurveFitRegion>();
|
||||
|
||||
public VolCurveFitRegion fit_region {
|
||||
get => _fit_region.Value;
|
||||
set => _fit_region.set(value);
|
||||
}
|
||||
|
||||
public bool is_auto_fit_enabled => _is_auto_fit_enabled.Value;
|
||||
|
||||
public uint? period_secs {
|
||||
get => _period_secs.Value;
|
||||
set => _period_secs.set(value);
|
||||
}
|
||||
|
||||
public double? driver_points_moved {
|
||||
get => _driver_points_moved.Value;
|
||||
set => _driver_points_moved.set(value);
|
||||
}
|
||||
|
||||
public TickDisplaySettingsImmutable? driver_points_display =>
|
||||
user.settings.TickFactors.pennies.to_api_tick_display_settingsi();
|
||||
|
||||
public double? atm_points_moved {
|
||||
get => _atm_points_moved.Value;
|
||||
set => _atm_points_moved.set(value);
|
||||
}
|
||||
|
||||
public TickDisplaySettingsImmutable? atm_points_display =>
|
||||
user.settings.TickFactors.pennies.to_api_tick_display_settingsi();
|
||||
|
||||
public void load_from(VolSurfaceAutoFitSettings value) {
|
||||
fit_region = value?.auto_fit_region ?? VolCurveFitRegion.None;
|
||||
period_secs = value?.time_based_settings?.period_seconds;
|
||||
driver_points_moved = value?.driver_move_based_settings?.points_moved;
|
||||
atm_points_moved = value?.atm_implied_vol_based_settings?.points_moved;
|
||||
}
|
||||
|
||||
public void assign_to(VolSurfaceAutoFitSettings value) {
|
||||
if (value is null) {
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
value.auto_fit_region = fit_region;
|
||||
value.time_based_settings = new() {
|
||||
period_seconds = period_secs
|
||||
};
|
||||
value.driver_move_based_settings = new() {
|
||||
points_moved = driver_points_moved
|
||||
};
|
||||
value.atm_implied_vol_based_settings = new() {
|
||||
points_moved = atm_points_moved
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ namespace mbts.spark {
|
||||
vol_model_cubic_spline_vm.load_from(settings.spline_model_settings);
|
||||
vol_model_dual_quadratic_vm.load_from(settings.dualquadratic_model_settings);
|
||||
vol_path_parabolic_vm.load_from(settings.core_settings.volpath_settings.parabolic_vol_path_settings);
|
||||
auto_fit_vm.load_from(settings.core_settings.market_fit_settings.vs_auto_fit_settings);
|
||||
|
||||
init_rotation_vms(call_rotations, _core_settings.call_rotation.strike_vol_movement_pairs);
|
||||
init_rotation_vms(put_rotations, _core_settings.put_rotation.strike_vol_movement_pairs);
|
||||
@@ -105,6 +106,7 @@ namespace mbts.spark {
|
||||
}).ToList();
|
||||
_core_settings.strangle.strike_vol_movement_pairs = strangles.SelectMany(r => new[] { Tuple.Create(r.target_put_strike, r.put_move), Tuple.Create(r.target_call_strike, r.call_move) }).ToList();
|
||||
_core_settings.atm_shift.atm_strike_vol_movement = atm_shifts.Count == 1 ? atm_shifts[0].move : 0;
|
||||
auto_fit_vm.assign_to(_core_settings.market_fit_settings.vs_auto_fit_settings);
|
||||
commit(_settings);
|
||||
}
|
||||
|
||||
@@ -233,23 +235,7 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public bool enable_auto_fit {
|
||||
get => _core_settings.market_fit_settings.enable_auto_fit;
|
||||
set {
|
||||
using var _ = detect_property_change();
|
||||
_core_settings.market_fit_settings.enable_auto_fit = value;
|
||||
}
|
||||
}
|
||||
|
||||
public VolSurfaceEnums.VolCurveFitRegion auto_fit_region {
|
||||
get => _core_settings.market_fit_settings.auto_fit_region;
|
||||
set { _core_settings.market_fit_settings.auto_fit_region = value; }
|
||||
}
|
||||
|
||||
public double auto_fit_time_ms {
|
||||
get => _core_settings.market_fit_settings.auto_fit_time_ms;
|
||||
set { _core_settings.market_fit_settings.auto_fit_time_ms = value; }
|
||||
}
|
||||
public VolSurfaceAutoFitSettingsVM auto_fit_vm { get; } = new();
|
||||
|
||||
public bool auto_decay_vol_time {
|
||||
get => _core_settings.time_settings.time_mode == VolSurfaceEnums.VolSurfaceTimeMode.AutoDecay;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
namespace mbts.spark {
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
|
||||
@@ -27,6 +28,7 @@ namespace mbts.spark {
|
||||
readonly IBus _bus;
|
||||
readonly string _request_id = Guid.NewGuid().ToString();
|
||||
readonly VolSurfaceDisplayModel _model;
|
||||
readonly UserTuple _user;
|
||||
readonly IRaiseCanExecuteChangedCommand _revert_cmd;
|
||||
readonly IRaiseCanExecuteChangedCommand _commit_cmd;
|
||||
readonly IDialogCommand _main_settings_cmd;
|
||||
@@ -38,10 +40,16 @@ namespace mbts.spark {
|
||||
bool _ignore_implied_vol_settings;
|
||||
|
||||
public VolSurfaceVM(
|
||||
VolSurfaceDisplayModel model, int id, IBus bus, VolSurfaceWindowSettings startup_data) {
|
||||
VolSurfaceDisplayModel model,
|
||||
int id,
|
||||
IBus bus,
|
||||
VolSurfaceWindowSettings startup_data,
|
||||
UserTuple user
|
||||
) {
|
||||
_model = model;
|
||||
_vs_id = id;
|
||||
_bus = bus;
|
||||
_user = user;
|
||||
macro_actions = new VolSurfaceMacroActionsVM(_model);
|
||||
globals = new VolSurfaceGlobalParameterVM(_model);
|
||||
|
||||
@@ -49,7 +57,7 @@ namespace mbts.spark {
|
||||
_grid_settings = startup_data.macro_grid_settings ?? new VolSurfaceMacroGridSettings();
|
||||
var hidden_exps_set = startup_data.hidden_exps.empty_if_null().ToHashSet();
|
||||
grid = new VolSurfaceGridVM(
|
||||
this, _model, _grid_settings.hidden_cols(), hidden_exps_set);
|
||||
this, _model, _grid_settings.hidden_cols(), hidden_exps_set, _user);
|
||||
grid.on_selection_changed += _on_grid_selection_changed;
|
||||
_model.publish_grid_settings(_grid_settings);
|
||||
|
||||
|
||||
@@ -4,13 +4,11 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.analytics
|
||||
{
|
||||
namespace mbts.analytics {
|
||||
/// These are the VolSurface settings that are common
|
||||
/// to any Vol Model
|
||||
[ProtoBuf.ProtoContract(Name = "VolSurfaceCoreSettings")]
|
||||
public class VolSurfaceCoreSettings
|
||||
{
|
||||
public class VolSurfaceCoreSettings {
|
||||
[ProtoBuf.ProtoMember(1)]
|
||||
public string vol_surface_name;
|
||||
[ProtoBuf.ProtoMember(2)]
|
||||
@@ -87,5 +85,7 @@ namespace mbts.analytics
|
||||
get => _time_settings ??= new();
|
||||
set => _time_settings = value;
|
||||
}
|
||||
[ProtoBuf.ProtoMember(240)]
|
||||
public string vs_manager { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,7 @@ namespace mbts.analytics {
|
||||
if (core_settings.market_fit_settings == null) {
|
||||
core_settings.market_fit_settings = new VolSurfaceMarketFitSettings();
|
||||
}
|
||||
core_settings.market_fit_settings.vs_auto_fit_settings ??= new();
|
||||
if (core_settings.time_settings == null) {
|
||||
core_settings.time_settings = new VolSurfaceTimeSettings();
|
||||
}
|
||||
|
||||
@@ -141,6 +141,7 @@ namespace mbts.analytics
|
||||
|
||||
public enum VolCurveFitRegion
|
||||
{
|
||||
None,
|
||||
ATM,
|
||||
All
|
||||
}
|
||||
@@ -230,4 +231,4 @@ namespace mbts.analytics
|
||||
USE_CALENDAR_FOR_BUSINESS
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,9 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.analytics
|
||||
{
|
||||
namespace mbts.analytics {
|
||||
[ProtoBuf.ProtoContract(Name = "VolSurfaceMarketFitSettings")]
|
||||
public class VolSurfaceMarketFitSettings
|
||||
{
|
||||
public class VolSurfaceMarketFitSettings {
|
||||
/// use one sided market or not
|
||||
[ProtoBuf.ProtoMember(10)]
|
||||
public bool ignore_one_sided_markets;
|
||||
@@ -22,19 +20,44 @@ namespace mbts.analytics
|
||||
public double graph_update_period_ms = 2500;
|
||||
/// If this is set to true a regression curve is drawn
|
||||
/// and the vol model adheres to that
|
||||
[ProtoBuf.ProtoMember(50)]
|
||||
[ProtoBuf.ProtoMember(50)]
|
||||
public bool use_weighted_regression_fit = false;
|
||||
[ProtoBuf.ProtoMember(55)]
|
||||
public uint regression_order = 4;
|
||||
/// The system auto fits and saves vols if this is
|
||||
/// set to true
|
||||
[ProtoBuf.ProtoMember(60)]
|
||||
public bool enable_auto_fit = false;
|
||||
/// If enable_auto_fit = true, this governs
|
||||
/// how often the system auto-fits
|
||||
[ProtoBuf.ProtoMember(70)]
|
||||
public double auto_fit_time_ms = 30000;
|
||||
[ProtoBuf.ProtoMember(80)]
|
||||
public VolSurfaceEnums.VolCurveFitRegion auto_fit_region = VolSurfaceEnums.VolCurveFitRegion.ATM;
|
||||
|
||||
[ProtoBuf.ProtoMember(90)]
|
||||
public VolSurfaceAutoFitSettings vs_auto_fit_settings { get; set; }
|
||||
}
|
||||
|
||||
[ProtoBuf.ProtoContract(Name = "VolSurfaceAutoFitSettings")]
|
||||
public class VolSurfaceAutoFitSettings {
|
||||
// each of these settings can be enabled independently. if they are enabled
|
||||
// then any of them being true will result in an autmatic fit
|
||||
[ProtoBuf.ProtoContract(Name = "TimeBasedSettings")]
|
||||
public class TimeBasedSettings {
|
||||
[ProtoBuf.ProtoMember(10)]
|
||||
public uint? period_seconds { get; set; }
|
||||
}
|
||||
|
||||
[ProtoBuf.ProtoContract(Name = "DriverMoveBasedSettings")]
|
||||
public class DriverMoveBasedSettings {
|
||||
[ProtoBuf.ProtoMember(10)]
|
||||
public double? points_moved { get; set; }
|
||||
}
|
||||
|
||||
[ProtoBuf.ProtoContract(Name = "ATMImpliedVolBasedSettings")]
|
||||
public class ATMImpliedVolBasedSettings {
|
||||
[ProtoBuf.ProtoMember(20)]
|
||||
public double? points_moved { get; set; }
|
||||
// we can do price terms moved as well
|
||||
}
|
||||
[ProtoBuf.ProtoMember(5)]
|
||||
public VolSurfaceEnums.VolCurveFitRegion auto_fit_region;
|
||||
[ProtoBuf.ProtoMember(10)]
|
||||
public TimeBasedSettings time_based_settings = null;
|
||||
[ProtoBuf.ProtoMember(20)]
|
||||
public DriverMoveBasedSettings driver_move_based_settings = null;
|
||||
[ProtoBuf.ProtoMember(30)]
|
||||
public ATMImpliedVolBasedSettings atm_implied_vol_based_settings = null;
|
||||
}
|
||||
}
|
||||
@@ -475,6 +475,7 @@ namespace mbts.analytics {
|
||||
public static ResultMessage save_volsurface(
|
||||
VolSurfaceInput vs_input, AnalyticsService service, string user = "") {
|
||||
vs_input.saver_login = user;
|
||||
vs_input.surface_settings.core_settings.vs_manager = user;
|
||||
// convert to VolSurfaceEntry
|
||||
var to_save = new VolSurfaceEntry();
|
||||
var serialized_vs = ProtobufSerializationHelper.serialize(vs_input);
|
||||
@@ -595,7 +596,7 @@ namespace mbts.analytics {
|
||||
_greek_getter(g)(t, is_call);
|
||||
|
||||
public static IDictionary<double, IEnumerable<Theos>> hash_strikes(IEnumerable<Theos> theos) =>
|
||||
theos.Map(t => t.strike);
|
||||
theos.Map(t => t.strike);
|
||||
|
||||
static Func<Theos, bool, double> _greek_getter(VolSurfaceGreeks g) =>
|
||||
g switch {
|
||||
|
||||
@@ -135,7 +135,10 @@ namespace bts {
|
||||
var meta = _security_metadata.get(smd.security);
|
||||
if (meta.HasValue) {
|
||||
exch_secs[smd.id] = new(
|
||||
smd.security, smd.md, meta.Value.PriceDisplay, meta.Value.QuantityDisplay);
|
||||
smd.security,
|
||||
smd.md,
|
||||
meta.Value.HasPriceDisplay ? meta.Value.PriceDisplay : null,
|
||||
meta.Value.HasQuantityDisplay ? meta.Value.QuantityDisplay : null);
|
||||
}
|
||||
}
|
||||
_bus.post(SparkTags.ServerSecuritiesTag, exch_secs);
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace mbts.spark {
|
||||
r => new ReportImmutable?(r),
|
||||
r => r?.Id ?? throw new ArgumentNullException(nameof(r)));
|
||||
_window_factories = new() {
|
||||
new VolSurfaceWindowFactory(this, bus),
|
||||
new VolSurfaceWindowFactory(this, bus, user.Value),
|
||||
new AnalyticsExpirationParametersWindowFactory(this, bus),
|
||||
new SlideReportWindowFactory(this, bus, reports_demuxer.subscribe),
|
||||
new ComboReportWindowFactory(this, bus, reports_demuxer.subscribe),
|
||||
|
||||
@@ -793,70 +793,106 @@
|
||||
Grid.Row="5"
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="4"
|
||||
DataContext="{Binding auto_fit_vm}"
|
||||
Header="Auto Fit"
|
||||
Margin="0,5,0,0"
|
||||
Padding="5"
|
||||
IsEnabled="False">
|
||||
Padding="5">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<CheckBox
|
||||
Grid.ColumnSpan="2" Content="Enabled" IsChecked="{Binding enable_auto_fit}" />
|
||||
|
||||
<TextBlock Grid.Row="1" Text="Region" Margin="16,5,0,0">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Style.Triggers>
|
||||
<DataTrigger
|
||||
Binding="{Binding enable_auto_fit}"
|
||||
Value="False">
|
||||
<Setter
|
||||
Property="Foreground"
|
||||
Value="Gray" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="Fit Region" />
|
||||
<ComboBox
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="8,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
ItemsSource="{Binding fit_regions}"
|
||||
SelectedItem="{Binding fit_region}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={x:Static shell:VolSurfaceSettingsEditor.AutoFitRegionDisplayTextConverter}}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="0,5,0,0"
|
||||
Text="Every" />
|
||||
<c:TickUpDownNullableUInt32
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="12,0,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
ItemsSource="{Binding auto_fit_region_choices}"
|
||||
SelectedValue="{Binding auto_fit_region}"
|
||||
IsEnabled="{Binding enable_auto_fit}" />
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Minimum="30"
|
||||
Margin="8,0,0,0"
|
||||
Width="60"
|
||||
Value="{Binding period_secs}"
|
||||
IsEnabled="{Binding is_auto_fit_enabled}" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="3,5,0,0"
|
||||
Text="secs" />
|
||||
|
||||
<TextBlock Grid.Row="2" Text="Fit Time" Margin="16,5,0,0">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding enable_auto_fit}" Value="False">
|
||||
<Setter Property="Foreground" Value="Gray" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
|
||||
<c:TextBoxDouble
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Margin="0,5,0,0"
|
||||
Text="When Driver moves" />
|
||||
<c:TickUpDownNullableDouble
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
c:TextBoxEx.Suffix="ms"
|
||||
ToolTipService.ShowOnDisabled="True"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Margin="8,0,0,0"
|
||||
Width="60"
|
||||
Margin="12,5,0,0"
|
||||
IsEnabled="{Binding enable_auto_fit}"
|
||||
Value="{Binding auto_fit_time_ms}"
|
||||
Template="{StaticResource SuffixTemplate}" />
|
||||
Value="{Binding driver_points_moved}"
|
||||
Increment="0.01"
|
||||
TickDisplaySettings="{Binding driver_points_display}"
|
||||
IsEnabled="False"
|
||||
ToolTip="Not yet supported (coming soon)" />
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="3,5,0,0"
|
||||
Text="%" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Margin="0,5,0,0"
|
||||
Text="When ATM moves" />
|
||||
<c:TickUpDownNullableDouble
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
ToolTipService.ShowOnDisabled="True"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="8,0,0,0"
|
||||
Width="60"
|
||||
Value="{Binding atm_points_moved}"
|
||||
Increment="0.01"
|
||||
TickDisplaySettings="{Binding atm_points_display}"
|
||||
IsEnabled="False"
|
||||
ToolTip="Not yet supported (coming soon)" />
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="3,5,0,0"
|
||||
Text="%" />
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
#nullable enable
|
||||
namespace mbts.ui.shell {
|
||||
using mbts.analytics;
|
||||
using mbts.ui.converters;
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
partial class VolSurfaceSettingsEditor : Window {
|
||||
public VolSurfaceSettingsEditor() =>
|
||||
InitializeComponent();
|
||||
|
||||
public static IValueConverter AutoFitRegionDisplayTextConverter =>
|
||||
ValueConverter.create<VolSurfaceEnums.VolCurveFitRegion, string>(fr =>
|
||||
fr switch {
|
||||
VolSurfaceEnums.VolCurveFitRegion.None => "Disabled",
|
||||
_ => fr.ToString()
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -262,7 +262,7 @@
|
||||
IsDragAdornerEnabled="False"
|
||||
ContextMenuOpening="_on_context_menu_opening">
|
||||
<c:DIYGrid.ContextMenu>
|
||||
<ContextMenu>
|
||||
<ContextMenu IsOpen="{Binding is_open}">
|
||||
<MenuItem
|
||||
Header="Populate Empty Expirations"
|
||||
Command="{Binding populate_empty_exps_cmd}" />
|
||||
@@ -413,6 +413,11 @@
|
||||
</MenuItem.ItemContainerStyle>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Import CSV..." Command="{Binding import_csv_cmd}" />
|
||||
<MenuItem Command="{Binding acquire_ownership_cmd}">
|
||||
<MenuItem.Header>
|
||||
<TextBlock Text="{Binding vs_manager, StringFormat='Acquire ownership from \'{0}\''}" />
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
</ContextMenu>
|
||||
</c:DIYGrid.ContextMenu>
|
||||
<c:DIYGrid.RowStyle>
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
}
|
||||
var mouse_pos = Mouse.GetPosition(this);
|
||||
if (!this.ChildAtPoint<VolSurfaceRowView>(mouse_pos, out var row)) {
|
||||
cm.IsOpen = false;
|
||||
cm.SetCurrentValue(ContextMenu.IsOpenProperty, false);
|
||||
return;
|
||||
}
|
||||
var cgs = g.GetColumnGeometry().ToList();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
namespace mbts.spark {
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.spark;
|
||||
|
||||
@@ -14,14 +15,20 @@
|
||||
public const string WINDOW_TYPE = "VolSurface";
|
||||
|
||||
readonly VolSurfaceWindowSettings _startup_data;
|
||||
readonly UserTuple _user;
|
||||
|
||||
VolSurfaceVM _vm;
|
||||
VolSurfaceView _view;
|
||||
|
||||
public VolSurfaceWindow(
|
||||
VolSurfaceWindowFactory factory, IBus bus, string vs_name, VolSurfaceWindowSettings startup_data) :
|
||||
base(vs_name, bus, factory, Icons.vol_surface) {
|
||||
VolSurfaceWindowFactory factory,
|
||||
IBus bus,
|
||||
string vs_name,
|
||||
VolSurfaceWindowSettings startup_data,
|
||||
UserTuple user
|
||||
) : base(vs_name, bus, factory, Icons.vol_surface) {
|
||||
_startup_data = startup_data ?? throw new ArgumentNullException(nameof(startup_data));
|
||||
_user = user;
|
||||
this.vs_name = vs_name;
|
||||
_dispose = Disposable.Create(() => Disposable.dispose(ref _vm));
|
||||
}
|
||||
@@ -37,7 +44,7 @@
|
||||
public override UIElement Content => _view;
|
||||
|
||||
public void thread_init() {
|
||||
_vm = new VolSurfaceVM(new VolSurfaceDisplayModel(vs_id, _bus, true), vs_id, _bus, _startup_data);
|
||||
_vm = new VolSurfaceVM(new (vs_id, _bus, true), vs_id, _bus, _startup_data, _user);
|
||||
_view = new VolSurfaceView {
|
||||
DataContext = _vm
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
namespace mbts.spark {
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
|
||||
@@ -10,10 +11,12 @@ namespace mbts.spark {
|
||||
|
||||
class VolSurfaceWindowFactory : ShellWindowFactory<VolSurfaceWindowSettings> {
|
||||
readonly IBus _bus;
|
||||
readonly UserTuple _user;
|
||||
|
||||
public VolSurfaceWindowFactory(IShellModule module, IBus bus)
|
||||
public VolSurfaceWindowFactory(IShellModule module, IBus bus, UserTuple user)
|
||||
: base(module, "VolSurface") {
|
||||
_bus = bus;
|
||||
_user = user;
|
||||
_dispose = bus.subscribe(SparkTags.ShowVolSurface).OnDispatcher().Subscribe(
|
||||
async state => _bus.post(SparkTags.ShowWindow, await _create(state)));
|
||||
}
|
||||
@@ -22,7 +25,7 @@ namespace mbts.spark {
|
||||
_create(state);
|
||||
|
||||
async Task<IShellWindow> _create(VolSurfaceWindowSettings state) =>
|
||||
new VolSurfaceWindow(this, _bus, await _lookup_vs_name(state.id), state);
|
||||
new VolSurfaceWindow(this, _bus, await _lookup_vs_name(state.id), state, _user);
|
||||
|
||||
async Task<string> _lookup_vs_name(int vs_id) {
|
||||
var (vol_surfaces, success) = await _bus.subscribe(SparkTags.VolSurfaceListTag).FirstAsyncTask(TimeSpan.FromSeconds(2));
|
||||
|
||||
@@ -176,10 +176,11 @@
|
||||
</usp:DataGridParameterCellTemplateConverter.QuantityMeasureTemplate>
|
||||
</usp:DataGridParameterCellTemplateConverter>
|
||||
<Style x:Key="ExpanderToggleButton" TargetType="ToggleButton">
|
||||
<Setter Property="Foreground" Value="{u:BTSColor ExecutionStrategy.expander_foreground}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ToggleButton">
|
||||
<Grid Background="Transparent">
|
||||
<Grid Background="{u:BTSColor ExecutionStrategy.expander_background}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="12" />
|
||||
<ColumnDefinition/>
|
||||
@@ -190,7 +191,7 @@
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,5,0,0"
|
||||
SnapsToDevicePixels="False"
|
||||
Stroke="#77333333"
|
||||
Stroke="{u:BTSColor ExecutionStrategy.arrow_background}"
|
||||
StrokeThickness="2"
|
||||
VerticalAlignment="Top"
|
||||
/>
|
||||
@@ -199,6 +200,7 @@
|
||||
ContentSource="{TemplateBinding Content}"
|
||||
Margin="3,0,0,0"
|
||||
/>
|
||||
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
@@ -211,7 +213,7 @@
|
||||
<Setter Property="Stroke" TargetName="arrow" Value="Black"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Stroke" TargetName="arrow" Value="#FF707070"/>
|
||||
<Setter Property="Stroke" TargetName="arrow" Value="{u:BTSColor ExecutionStrategy.arrow_background_selected}"/>
|
||||
<Setter Property="Foreground" Value="Gray"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
@@ -278,7 +280,8 @@
|
||||
<ColumnDefinition/>
|
||||
</SelectiveScrollingGrid.ColumnDefinitions>
|
||||
<DataGridRowHeader
|
||||
Background="Transparent"
|
||||
Foreground="{u:BTSColor ExecutionStrategy.expander_foreground}"
|
||||
Background="{u:BTSColor ExecutionStrategy.data_grid_row_header_background}"
|
||||
Padding="{Binding section_count, Converter={StaticResource RowHeaderGroupIndentConverter}}"
|
||||
FontWeight="{Binding font_weight, FallbackValue={u:FontWeight Normal}}"
|
||||
SelectiveScrollingGrid.SelectiveScrollingOrientation="Vertical"
|
||||
@@ -317,9 +320,17 @@
|
||||
<Style x:Key="DataGridRowStyle" TargetType="DataGridRow">
|
||||
<Setter Property="BorderThickness" Value="1,0"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="Background" Value="{u:BTSColor ExecutionStrategy.alternate_column_background}"/>
|
||||
<Setter Property="Template" Value="{StaticResource DataGridRowTemplate}"/>
|
||||
<Setter Property="usp:ExecutionStrategyEditor.RowHasErrors" Value="{Binding has_errors}"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="ListBox.AlternationIndex" Value="1">
|
||||
<Setter Property="Background" Value="{u:BTSColor ExecutionStrategy.data_grid_background}"/>
|
||||
</Trigger>
|
||||
<!-- <Trigger Property="ListBox.AlternationIndex" Value="2">
|
||||
<Setter Property="Background" Value="LightBlue"/>
|
||||
<Setter Property="Foreground" Value="Navy"/>
|
||||
</Trigger> -->
|
||||
<DataTrigger Binding="{Binding read_only}" Value="True">
|
||||
<Setter Property="TextElement.Foreground" Value="{u:BTSColor UI.input_disabled_text_dark}"/>
|
||||
<Setter Property="ToolTip">
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace mbts.spark {
|
||||
main_vm.rows.AddRange(parsed_synths.Select(ps => {
|
||||
var sec = ps.result;
|
||||
var r = new SyntheticImportRowVM(orders_model.admin_model.user.id, used_names.Contains) {
|
||||
tick_factors = TickUtils.get_regional_ticks(sec.ContractSpec).low_tick,
|
||||
tick_factors = TickUtils.get_regional_ticks(sec.immutable()).low_tick,
|
||||
name = sec.Synthetic.Name,
|
||||
parsed_synth = ps
|
||||
};
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace mbts.spark {
|
||||
public SyntheticEditorVM(SpreaderModel model, SecurityImmutable synthetic, ExecutionStrategyImmutable strategy, IObservable<bool> security_has_working_orders)
|
||||
: this(synthetic, strategy, false) {
|
||||
ExceptionAssert.InvalidOperation.That(synthetic.Id != 0, "This constructor may only be used for already-existing synthetic securities (id must be non-zero).");
|
||||
_tick_factors = TickUtils.get_regional_ticks(synthetic.ContractSpec).low_tick;
|
||||
_tick_factors = TickUtils.get_regional_ticks(synthetic).low_tick;
|
||||
if (TickUtils.is_integer_tick(synthetic.ContractSpec)) {
|
||||
_use_fractional_ticks = false;
|
||||
_integer_tick_size = DoubleHelper.to_optional_int32(synthetic.ContractSpec.LowTick) ?? 0;
|
||||
@@ -112,7 +112,7 @@ namespace mbts.spark {
|
||||
_set_ticks_automatically = false;
|
||||
_set_quantities_automatically = false;
|
||||
_set_price_mults_automatically = false;
|
||||
_tick_factors = TickUtils.get_regional_ticks(_synthetic.ContractSpec).low_tick;
|
||||
_tick_factors = TickUtils.get_regional_ticks(_synthetic.immutable()).low_tick;
|
||||
} else {
|
||||
_deduce_name();
|
||||
_deduce_ticks();
|
||||
@@ -411,7 +411,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
void _deduce_ticks() {
|
||||
_tick_factors = TickUtils.deduce_ticks(leg_tuples.Select(l => l.leg.Security.ContractSpec));
|
||||
_tick_factors = TickUtils.deduce_ticks(leg_tuples.Select(l => l.leg.Security.immutable()));
|
||||
RaisePropertyChanged(nameof(tick_factors));
|
||||
}
|
||||
|
||||
|
||||
@@ -371,6 +371,7 @@
|
||||
<usp:AlgoEditorView
|
||||
DataContext="{Binding selected_definition}"
|
||||
Background="Transparent"
|
||||
Foreground="{u:BTSColor UI.default_text}"
|
||||
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
|
||||
Padding="12,8" />
|
||||
</ScrollViewer>
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace Spark.Trading {
|
||||
meta.Value.Value.PriceDisplay : Security.Defaults.price_display_settingsi;
|
||||
if (security.HasValue) {
|
||||
rv = rv.with(
|
||||
tick_factors: security.spec_or_default().tick_factors().to_api_tick_factors());
|
||||
tick_factors: security.Value.tick_factors().to_api_tick_factors());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public OrderBookVM? create_orderbook_vm() {
|
||||
var ticks = _owner.spec.full_ticks_from_zero(real_price);
|
||||
var ticks = _owner.spec.count_ticks(real_price);
|
||||
if (!ticks.HasValue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ namespace mbts.spark {
|
||||
void increment_ticks_per_row() {
|
||||
var tpr = ticks_per_row;
|
||||
for (uint i = 1; i < 100; i++) {
|
||||
var new_tpr = TickUtils.fit_ticks_per_row_to_dollars(security.ContractSpec, ticks_per_row + i);
|
||||
var new_tpr = TickUtils.fit_ticks_per_row_to_dollars(security, ticks_per_row + i);
|
||||
if (new_tpr != tpr) {
|
||||
ticks_per_row = new_tpr;
|
||||
break;
|
||||
@@ -201,7 +201,7 @@ namespace mbts.spark {
|
||||
void decrement_ticks_per_row() {
|
||||
var tpr = ticks_per_row;
|
||||
for (uint i = 1; i < tpr; i++) {
|
||||
var new_tpr = TickUtils.fit_ticks_per_row_to_dollars(security.ContractSpec, tpr - i);
|
||||
var new_tpr = TickUtils.fit_ticks_per_row_to_dollars(security, tpr - i);
|
||||
if (new_tpr != tpr) {
|
||||
ticks_per_row = new_tpr;
|
||||
break;
|
||||
@@ -239,7 +239,8 @@ namespace mbts.spark {
|
||||
// since settings profiles may be shared among ladders
|
||||
// of different securities/tick sizes, the ticks per row setting
|
||||
// may need to be rounded to a number that is a factor of $1.00
|
||||
public uint? rounded_ticks_per_row => TickUtils.fit_ticks_per_row_to_dollars(_security.ContractSpec, ticks_per_row);
|
||||
public uint? rounded_ticks_per_row =>
|
||||
TickUtils.fit_ticks_per_row_to_dollars(_security, ticks_per_row);
|
||||
|
||||
public MarketSizeDisplayVM size_display { get; } = new MarketSizeDisplayVM();
|
||||
public SimpleGridSettingsVM mini_ob { get; } = new SimpleGridSettingsVM(OrderBookGridSettingsManager.hard_column_flags);
|
||||
|
||||
@@ -99,7 +99,7 @@ namespace mbts.spark {
|
||||
IReadOnlyDictionary<uint, OrderSecurity> _cache = DictionaryHelper.empty<uint, OrderSecurity>();
|
||||
ImmutableDictionary<AlgoKey, LadderFavoriteAlgoMetadata> _favorite_algo_metadata_map =
|
||||
ImmutableDictionary<AlgoKey, LadderFavoriteAlgoMetadata>.Empty;
|
||||
ContractSpecificationImmutable? _spec;
|
||||
RegionalTickFactors? _spec;
|
||||
|
||||
LadderVM(
|
||||
IBus bus,
|
||||
@@ -243,10 +243,9 @@ namespace mbts.spark {
|
||||
|
||||
var orders_source = settings.Select(s => s.ticks_per_row).DistinctUntilChanged()
|
||||
.SelectSeries(tpr => {
|
||||
var spec = TickUtils.multiply_tick_size(security.ContractSpec, tpr);
|
||||
var spec = TickUtils.multiply_tick_size(security.regional_ticks(), tpr);
|
||||
var price_comp = Comparer<double>.Create((a, b) =>
|
||||
Comparer<int?>.Default.Compare(
|
||||
spec.full_ticks_from_zero(a), spec.full_ticks_from_zero(b)));
|
||||
Comparer<long?>.Default.Compare(spec.count_ticks(a), spec.count_ticks(b)));
|
||||
return _order_price_source(model.orders, price_comp, p => TickUtils.normalize_price(spec, p));
|
||||
});
|
||||
_day_change.Changed += (o, n) => RaisePropertyChanged(
|
||||
@@ -394,9 +393,10 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public SecurityImmutable security => _model.security;
|
||||
public ContractSpecificationImmutable spec => _spec ?? (
|
||||
public RegionalTickFactors spec => _spec ?? (
|
||||
_spec = TickUtils.multiply_tick_size(
|
||||
security.ContractSpec, current_settings?.ticks_per_row ?? 1)).Value;
|
||||
security.regional_ticks(), current_settings?.ticks_per_row ?? 1))
|
||||
?? RegionalTickFactors.Default;
|
||||
|
||||
public double display_quantity_divisor { get; }
|
||||
public LadderSettings current_settings { get; private set; }
|
||||
@@ -826,7 +826,7 @@ namespace mbts.spark {
|
||||
if (!price.is_real()) {
|
||||
msg = "not a real number";
|
||||
} else if (settlement.is_real()) {
|
||||
var ticks_from_settle = security.ContractSpec.tickdiff(price, settlement);
|
||||
var ticks_from_settle = security.tickdiff(price, settlement);
|
||||
if (ticks_from_settle.HasValue) {
|
||||
var max_cross = (current_settings?.ladder_order_limits?.max_settlement_cross_ticks)
|
||||
.as_nullable();
|
||||
@@ -858,7 +858,7 @@ namespace mbts.spark {
|
||||
} else {
|
||||
ulong _ = 0;
|
||||
_model.md.get_bbo(ref _, out var bid, out var ask);
|
||||
var mid = TickUtils.mid_tick(security.ContractSpec, bid.price, ask.price);
|
||||
var mid = TickUtils.mid_tick(security.regional_ticks(), bid.price, ask.price);
|
||||
_day_change.set(mid - md.settlement_price);
|
||||
}
|
||||
_volume.set(volume);
|
||||
|
||||
@@ -32,6 +32,8 @@ using System.Windows.Media.Animation;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace mbts.spark {
|
||||
using RegionalTickFactors = user.settings.RegionalTickFactors;
|
||||
|
||||
partial class LadderGridHost : Control {
|
||||
static readonly int _initial_row_count;
|
||||
static readonly bool _show_scrollbar;
|
||||
@@ -83,7 +85,6 @@ namespace mbts.spark {
|
||||
bool _price_dirty = false, _piq_dirty = false;
|
||||
LadderPriceList _price_list = new() {
|
||||
length = _initial_row_count,
|
||||
spec = Security.Defaults.ContractSpecificationImmutable
|
||||
};
|
||||
LadderPriceList _pending_price_list = null;
|
||||
IComparer<double> _price_comparer;
|
||||
@@ -384,16 +385,16 @@ namespace mbts.spark {
|
||||
|
||||
Column[] _columns { get; set; } = Array.Empty<Column>();
|
||||
IEnumerable<Column> _visible_columns => _columns.Where(c => c.settings.show);
|
||||
ContractSpecificationImmutable? _spec_backing;
|
||||
ContractSpecificationImmutable _spec {
|
||||
RegionalTickFactors _spec_backing;
|
||||
RegionalTickFactors _spec {
|
||||
get {
|
||||
if (_spec_backing.HasValue) {
|
||||
return _spec_backing.Value;
|
||||
if (_spec_backing is not null) {
|
||||
return _spec_backing;
|
||||
} else if (_vm != null) {
|
||||
_spec_backing = TickUtils.multiply_tick_size(security.ContractSpec, _settings?.ticks_per_row ?? 1);
|
||||
return _spec_backing.Value;
|
||||
_spec_backing = TickUtils.multiply_tick_size(security.regional_ticks(), _settings?.ticks_per_row ?? 1);
|
||||
return _spec_backing;
|
||||
}
|
||||
return Security.Defaults.ContractSpecificationImmutable;
|
||||
return RegionalTickFactors.Default;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,6 +448,15 @@ namespace mbts.spark {
|
||||
return _price_list.high_tick + tick_diff;
|
||||
}
|
||||
|
||||
public bool scroll_to_tick(long tick) {
|
||||
if (tick > int.MaxValue) {
|
||||
Log.Ui.error(
|
||||
$"Unable to scroll to tick {tick} because it exceeds int.MaxValue");
|
||||
return false;
|
||||
}
|
||||
return scroll_to_tick((int)tick);
|
||||
}
|
||||
|
||||
public bool scroll_to_tick(int tick) {
|
||||
if (_vm is null || _settings is null || _scroll_viewer is null) {
|
||||
return false;
|
||||
@@ -460,21 +470,23 @@ namespace mbts.spark {
|
||||
|
||||
// The scrollbar's vertical offset won't change. Find the 'top tick' that will put the
|
||||
// target `tick` in the center of the viewport.
|
||||
var top_tick = (int)_get_target_top_tick(tick);
|
||||
if (_settings.animate_recenter) {
|
||||
var anim = new Int32Animation {
|
||||
From = _price_list.high_tick,
|
||||
To = top_tick,
|
||||
Duration = TimeSpan.FromSeconds(0.15)
|
||||
};
|
||||
var sb = new Storyboard();
|
||||
sb.Children.Add(anim);
|
||||
Storyboard.SetTarget(anim, this);
|
||||
Storyboard.SetTargetProperty(anim, new PropertyPath(TopTickProperty));
|
||||
sb.Begin();
|
||||
} else {
|
||||
_set_scrollbar_range(high_tick: top_tick);
|
||||
_refresh_prices();
|
||||
checked {
|
||||
var top_tick = (int)_get_target_top_tick(tick);
|
||||
if (_settings.animate_recenter) {
|
||||
var anim = new Int32Animation {
|
||||
From = (int)_price_list.high_tick,
|
||||
To = top_tick,
|
||||
Duration = TimeSpan.FromSeconds(0.15)
|
||||
};
|
||||
var sb = new Storyboard();
|
||||
sb.Children.Add(anim);
|
||||
Storyboard.SetTarget(anim, this);
|
||||
Storyboard.SetTargetProperty(anim, new PropertyPath(TopTickProperty));
|
||||
sb.Begin();
|
||||
} else {
|
||||
_set_scrollbar_range(high_tick: top_tick);
|
||||
_refresh_prices();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -744,12 +756,12 @@ namespace mbts.spark {
|
||||
internal double price_for_point(Point point) =>
|
||||
_price_for_row(_row_for_point(point));
|
||||
|
||||
static uint? _size_from_row(int row, IList<double> price_list, NativePriceSize[] depth, ContractSpecificationImmutable spec) {
|
||||
static uint? _size_from_row(int row, IList<double> price_list, NativePriceSize[] depth, RegionalTickFactors spec) {
|
||||
if (price_list == null || depth == null) return null;
|
||||
else return row <= price_list.Count ? _size_from_price(price_list[row], depth, spec) : null;
|
||||
}
|
||||
|
||||
static uint? _size_from_price(double price, NativePriceSize[] depth, ContractSpecificationImmutable spec) {
|
||||
static uint? _size_from_price(double price, NativePriceSize[] depth, RegionalTickFactors spec) {
|
||||
if (!double.IsNaN(price) && depth.TryGetFirst(nps => spec.tick_equals(price, nps.price), out NativePriceSize result))
|
||||
return result.size;
|
||||
return null;
|
||||
@@ -1099,10 +1111,16 @@ namespace mbts.spark {
|
||||
Log.assert(_price_dirty);
|
||||
_price_list = new LadderPriceList {
|
||||
length = _initial_row_count,
|
||||
spec = security.ContractSpec
|
||||
security = security
|
||||
};
|
||||
var mid_tick = security.ContractSpec.tickdiff(_mkt_break_lines.origin, 0) ?? 0;
|
||||
_set_scrollbar_range(high_tick: mid_tick + (_price_list.Count << 1));
|
||||
var mid_tick = security.tickdiff(_mkt_break_lines.origin, 0) ?? 0;
|
||||
if (mid_tick > int.MaxValue) {
|
||||
Log.Ui.error(
|
||||
$"Unable to set scrollbar range about mid_tick {mid_tick}: " +
|
||||
$"tick count exceeds int.MaxValue");
|
||||
} else {
|
||||
_set_scrollbar_range(high_tick: (int)mid_tick + (_price_list.Count << 1));
|
||||
}
|
||||
_price_dirty = true;
|
||||
}
|
||||
if (_price_dirty) {
|
||||
@@ -1181,12 +1199,12 @@ namespace mbts.spark {
|
||||
_price_list, _vm.md.settlement_price,
|
||||
_settings.net_change == LadderSettings.NetChangeTerms.Ticks,
|
||||
_settings.row_height, _settings.price_column,
|
||||
security.ContractSpec);
|
||||
_spec);
|
||||
} else {
|
||||
LadderRender.render_prices(
|
||||
_price_vis_tup.visual, _price_vis_tup.glyph_context,
|
||||
_price_list, _settings.row_height,
|
||||
_settings.price_column, security.ContractSpec,
|
||||
_settings.price_column, _spec,
|
||||
_settings.price_display_format);
|
||||
}
|
||||
}
|
||||
@@ -1341,7 +1359,7 @@ namespace mbts.spark {
|
||||
return;
|
||||
if (_price_list is null) {
|
||||
_price_list = new LadderPriceList {
|
||||
spec = security.ContractSpec
|
||||
security = security
|
||||
};
|
||||
}
|
||||
_price_list.reset(null, null, _initial_row_count);
|
||||
@@ -1425,7 +1443,7 @@ namespace mbts.spark {
|
||||
_pending_scroll_to_origin |= _settings == null;
|
||||
_invalidate_spec();
|
||||
if (_price_list != null) {
|
||||
_price_list.spec = _spec;
|
||||
_price_list.security = security;
|
||||
}
|
||||
_bid_vis_tup.glyph_context = glyph_context(s.bid_column);
|
||||
_ask_vis_tup.glyph_context = glyph_context(s.ask_column);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using bts.api;
|
||||
|
||||
namespace mbts.spark {
|
||||
using RegionalTickFactors = user.settings.RegionalTickFactors;
|
||||
|
||||
class LadderPriceLineViewAdapter {
|
||||
public static double bid_line(
|
||||
double bid, double ask, double settlement, ContractSpecificationImmutable? spec) {
|
||||
double bid, double ask, double settlement, RegionalTickFactors spec) {
|
||||
return TickUtils.normalize_price(spec, impl());
|
||||
double impl() {
|
||||
if (!double.IsNaN(bid)) {
|
||||
@@ -17,7 +19,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public static double ask_line(
|
||||
double bid, double ask, double settlement, ContractSpecificationImmutable? spec) {
|
||||
double bid, double ask, double settlement, RegionalTickFactors spec) {
|
||||
return TickUtils.normalize_price(spec, impl());
|
||||
double impl() {
|
||||
if (!double.IsNaN(ask)) {
|
||||
@@ -31,7 +33,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public static double mid_line(
|
||||
double bid, double ask, double settlement, ContractSpecificationImmutable? spec) {
|
||||
double bid, double ask, double settlement, RegionalTickFactors spec) {
|
||||
return TickUtils.normalize_price(spec, impl());
|
||||
double impl() {
|
||||
if (double.IsNaN(bid)) {
|
||||
@@ -43,16 +45,16 @@ namespace mbts.spark {
|
||||
if (double.IsNaN(ask)) {
|
||||
return bid;
|
||||
}
|
||||
if (spec.HasValue) {
|
||||
return TickUtils.mid_tick(spec.Value, bid, ask);
|
||||
if (spec is not null) {
|
||||
return TickUtils.mid_tick(spec, bid, ask);
|
||||
}
|
||||
return bid;
|
||||
}
|
||||
}
|
||||
|
||||
static double _try_tick_down(ContractSpecificationImmutable? spec, double value) {
|
||||
if (spec.HasValue) {
|
||||
return spec.Value.tickdown(value);
|
||||
static double _try_tick_down(RegionalTickFactors spec, double value) {
|
||||
if (spec is not null) {
|
||||
return spec.tickdown(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts.utils;
|
||||
|
||||
using mbts.utils;
|
||||
|
||||
@@ -8,52 +9,78 @@ using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace mbts.spark {
|
||||
using RegionalTickFactors = user.settings.RegionalTickFactors;
|
||||
public class LadderPriceList : IReadOnlyList<double>, IList<double>, INotifyCollectionChanged, IEquatable<LadderPriceList> {
|
||||
int _length;
|
||||
int _high_tick;
|
||||
const long _MAX_LENGTH = int.MaxValue;
|
||||
|
||||
long _length;
|
||||
long _high_tick;
|
||||
SecurityImmutable? _security;
|
||||
RegionalTickFactors _regional_tick_backing;
|
||||
RegionalTickFactors _regional_tick =>
|
||||
_regional_tick_backing ??= TickUtils.get_regional_ticks(_security);
|
||||
|
||||
public LadderPriceList() { }
|
||||
|
||||
public LadderPriceList(LadderPriceList other) {
|
||||
_length = other._length;
|
||||
_high_tick = other._high_tick;
|
||||
spec = other.spec;
|
||||
_security = other._security;
|
||||
}
|
||||
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
|
||||
public SecurityImmutable? security {
|
||||
get => _security;
|
||||
set {
|
||||
if (value == _security) {
|
||||
return;
|
||||
}
|
||||
_security = value;
|
||||
_regional_tick_backing = null;
|
||||
}
|
||||
}
|
||||
|
||||
public ContractSpecificationImmutable spec { get; set; }
|
||||
|
||||
public int high_tick {
|
||||
public long high_tick {
|
||||
get => _high_tick;
|
||||
set => _high_tick = value;
|
||||
}
|
||||
|
||||
public int low_tick {
|
||||
public long low_tick {
|
||||
get => high_tick - length;
|
||||
set => reset(null, value, null);
|
||||
}
|
||||
|
||||
public double start {
|
||||
get => spec.tickup(0, high_tick);
|
||||
set => high_tick = spec.tickdiff(value, 0) ?? high_tick;
|
||||
get => _regional_tick.tickup<double>(0, (uint)high_tick);
|
||||
set => high_tick = _regional_tick.tickdiff(value, 0) ?? high_tick;
|
||||
}
|
||||
|
||||
public int length {
|
||||
public long length {
|
||||
get => _length;
|
||||
set {
|
||||
if (value < 0) {
|
||||
throw new ArgumentException($"Value must be >= 0 (actually {value})");
|
||||
if (value < 0 || value > _MAX_LENGTH) {
|
||||
throw new ArgumentException(
|
||||
$"Value 'v' must be >= 0 and < {_MAX_LENGTH} (actually {value})");
|
||||
}
|
||||
_length = value;
|
||||
}
|
||||
}
|
||||
public int Count => (int)length;
|
||||
|
||||
public int Count {
|
||||
get {
|
||||
checked {
|
||||
return (int)length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public double this[int idx] {
|
||||
get => spec.tickup(0, high_tick - idx);
|
||||
get => _regional_tick.tick<double>(0, high_tick - idx);
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
@@ -67,11 +94,13 @@ namespace mbts.spark {
|
||||
GetEnumerator();
|
||||
|
||||
public int IndexOf(double item) {
|
||||
var idx = spec.tickdiff(start, item) ?? -1;
|
||||
var idx = _regional_tick.tickdiff(start, item) ?? -1;
|
||||
if (idx < 0 || idx >= length) {
|
||||
return -1;
|
||||
}
|
||||
return idx;
|
||||
checked {
|
||||
return (int)idx;
|
||||
}
|
||||
}
|
||||
|
||||
public void Insert(int index, double item) =>
|
||||
@@ -138,15 +167,15 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public void reset(int? high_tick = null, int? low_tick = null, int? length = null) {
|
||||
public void reset(long? high_tick = null, long? low_tick = null, long? length = null) {
|
||||
if (!high_tick.HasValue && !low_tick.HasValue && !length.HasValue) {
|
||||
return;
|
||||
}
|
||||
if (length.HasValue && length.Value < 0) {
|
||||
throw new ArgumentException("Must be >= 0", nameof(length));
|
||||
}
|
||||
int new_high_tick = 0;
|
||||
int new_length = 0;
|
||||
long new_high_tick = 0;
|
||||
long new_length = 0;
|
||||
if (high_tick.HasValue) {
|
||||
new_high_tick = high_tick.Value;
|
||||
if (low_tick.HasValue) {
|
||||
@@ -180,8 +209,7 @@ namespace mbts.spark {
|
||||
other != null &&
|
||||
_high_tick == other._high_tick &&
|
||||
_length == other._length &&
|
||||
spec.LowTick.Equals(other.spec.LowTick) &&
|
||||
spec.PriceBreak.Equals(other.spec.PriceBreak);
|
||||
_regional_tick.safe_equals(other._regional_tick);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as LadderPriceList);
|
||||
@@ -189,13 +217,10 @@ namespace mbts.spark {
|
||||
public override int GetHashCode() =>
|
||||
HashCode.Combine(_high_tick, _length);
|
||||
|
||||
double _tick_to_price(int tick) =>
|
||||
spec.tickup(0, tick);
|
||||
double _tick_to_price(long tick) =>
|
||||
_regional_tick.tick<double>(0, tick);
|
||||
|
||||
int? _price_to_tick(double price) =>
|
||||
spec.tickdiff(price, 0);
|
||||
|
||||
void _add_tick(int tick) {
|
||||
void _add_tick(long tick) {
|
||||
int idx;
|
||||
if (length == 0) {
|
||||
_high_tick = tick;
|
||||
@@ -204,7 +229,9 @@ namespace mbts.spark {
|
||||
_high_tick = tick;
|
||||
idx = 0;
|
||||
} else if (tick == low_tick - 1) {
|
||||
idx = length;
|
||||
checked {
|
||||
idx = (int)length;
|
||||
}
|
||||
} else {
|
||||
throw new ArgumentException($"Must be == {nameof(high_tick)} ({high_tick}) + 1, or == {nameof(low_tick)} ({low_tick}) - 1)");
|
||||
}
|
||||
@@ -212,7 +239,7 @@ namespace mbts.spark {
|
||||
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, _tick_to_price(tick), idx));
|
||||
}
|
||||
|
||||
void _remove_tick(int tick) {
|
||||
void _remove_tick(long tick) {
|
||||
if (length == 0) {
|
||||
throw new InvalidOperationException("Cannot remove tick because length is 0");
|
||||
}
|
||||
@@ -221,7 +248,9 @@ namespace mbts.spark {
|
||||
_high_tick--;
|
||||
idx = 0;
|
||||
} else if (tick == low_tick) {
|
||||
idx = length - 1;
|
||||
checked {
|
||||
idx = (int)(length - 1);
|
||||
}
|
||||
} else {
|
||||
throw new ArgumentException(
|
||||
$"Can only remove {nameof(low_tick)} ({low_tick}) or " +
|
||||
|
||||
@@ -9,6 +9,7 @@ using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.utility.text;
|
||||
|
||||
using Spark.Core;
|
||||
using Spark.Core.Disposables;
|
||||
|
||||
using System;
|
||||
@@ -19,6 +20,8 @@ using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.spark {
|
||||
using RegionalTickFactors = user.settings.RegionalTickFactors;
|
||||
|
||||
static class BadgeRender {
|
||||
// IMPORTANT: the return values for this method MUST BE IN SYNC with the "format" method, else "measure_height" and "LadderRender.measure_row_height" will be broken.
|
||||
static int _line_count_for_style(LadderSettings.BadgeStyle badge_style) => badge_style == LadderSettings.BadgeStyle.Alternate ? 2 : 1;
|
||||
@@ -73,6 +76,16 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public static int index_of(IList<double> prices, double price, IComparer<double> comparer, bool relative_idx) {
|
||||
for (int i = 1; i < prices.Count; i++) {
|
||||
Log.assert(prices[i - 1] > prices[i]);
|
||||
var c = comparer.Compare(prices[i - 1], prices[i]);
|
||||
if (c >= 0) {
|
||||
c = comparer.Compare(prices[i - 1], prices[i]);
|
||||
} else {
|
||||
c = comparer.Compare(prices[i - 1], prices[i]);
|
||||
}
|
||||
Log.assert(c < 0);
|
||||
}
|
||||
var idx = CollectionHelper.binary_search(prices, price, comparer);
|
||||
if (relative_idx && idx < 0) {
|
||||
idx = ~idx;
|
||||
@@ -95,30 +108,13 @@ namespace mbts.spark {
|
||||
settings.font_size, settings.width, row_height, HorizontalAlignment.Right,
|
||||
-4, VerticalAlignment.Center, row * row_height, true);
|
||||
|
||||
public static IList<double> gen_prices(double mid, int row_count, ContractSpecificationImmutable spec) {
|
||||
bool is_odd = row_count % 2 == 1;
|
||||
var result = new double[row_count];
|
||||
int range = row_count / 2;
|
||||
int middle = row_count / 2;
|
||||
result[middle] = mid;
|
||||
double up = mid, down = mid;
|
||||
// Starting from the middle iterate out to the edges
|
||||
for (int x = 1; x <= range; ++x) {
|
||||
up = spec.tickup(up);
|
||||
down = spec.tickdown(down);
|
||||
result[middle - x] = up;
|
||||
result[middle + x] = down;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static GlyphRun[] format_prices(IList<double> prices, double reference_price, bool format_in_tick_terms, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, ContractSpecificationImmutable spec) {
|
||||
public static GlyphRun[] format_prices(IList<double> prices, double reference_price, bool format_in_tick_terms, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, RegionalTickFactors spec) {
|
||||
var result = new GlyphRun[prices.Count];
|
||||
Parallel.For(0, prices.Count, i =>
|
||||
result[i] = format_text(spec.format(prices[i] - reference_price, format_in_tick_terms), i, row_height, settings, glyph_context));
|
||||
return result;
|
||||
}
|
||||
public static GlyphRun[] format_prices(IList<double> prices, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, ContractSpecificationImmutable spec, LadderSettings.PriceDisplayFormat price_display_format) {
|
||||
public static GlyphRun[] format_prices(IList<double> prices, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, RegionalTickFactors spec, LadderSettings.PriceDisplayFormat price_display_format) {
|
||||
var result = new GlyphRun[prices.Count];
|
||||
Parallel.For(0, prices.Count, i =>
|
||||
result[i] = format_text(spec.format(prices[i]), i, row_height, settings, glyph_context, price_display_format));
|
||||
@@ -143,12 +139,12 @@ namespace mbts.spark {
|
||||
bool format_in_tick_terms,
|
||||
double row_height,
|
||||
LadderColumnSettings settings,
|
||||
ContractSpecificationImmutable spec) {
|
||||
RegionalTickFactors tick) {
|
||||
if (price_list.IsNullOrEmpty()) {
|
||||
return;
|
||||
}
|
||||
render_prices(
|
||||
price, glyph_context, price_list, row_height, settings, spec,
|
||||
price, glyph_context, price_list, row_height, settings, tick,
|
||||
wrt_price, format_in_tick_terms);
|
||||
}
|
||||
|
||||
@@ -158,12 +154,12 @@ namespace mbts.spark {
|
||||
IList<double> dprices,
|
||||
double row_height,
|
||||
LadderColumnSettings settings,
|
||||
ContractSpecificationImmutable spec,
|
||||
RegionalTickFactors tick,
|
||||
double wrt_price,
|
||||
bool format_in_tick_terms) {
|
||||
var foreground = settings.foreground.rgb_brush();
|
||||
//var background = new SolidColorBrush(settings.background.rgb_color());
|
||||
var fprices = format_prices(dprices, wrt_price, format_in_tick_terms, row_height, settings, glyph_context, spec);
|
||||
var fprices = format_prices(dprices, wrt_price, format_in_tick_terms, row_height, settings, glyph_context, tick);
|
||||
using (var dc = price.RenderOpen())
|
||||
using (_draw_with_clip(dc, settings, row_height, dprices.Count)) {
|
||||
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, row_count * row_height));
|
||||
@@ -178,11 +174,11 @@ namespace mbts.spark {
|
||||
IList<double> dprices,
|
||||
double row_height,
|
||||
LadderColumnSettings settings,
|
||||
ContractSpecificationImmutable spec,
|
||||
RegionalTickFactors tick,
|
||||
LadderSettings.PriceDisplayFormat price_display_format) {
|
||||
var foreground = settings.foreground.rgb_brush();
|
||||
//var background = new SolidColorBrush(settings.background.rgb_color());
|
||||
var fprices = format_prices(dprices, row_height, settings, glyph_context, spec, price_display_format);
|
||||
var fprices = format_prices(dprices, row_height, settings, glyph_context, tick, price_display_format);
|
||||
using (var dc = price.RenderOpen())
|
||||
using (_draw_with_clip(dc, settings, row_height, dprices.Count)) {
|
||||
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, row_count * row_height));
|
||||
@@ -215,7 +211,7 @@ namespace mbts.spark {
|
||||
IList<double> prices,
|
||||
NativePriceSize[] rungs,
|
||||
double row_height,
|
||||
ContractSpecificationImmutable spec,
|
||||
RegionalTickFactors spec,
|
||||
LadderColumnSettings settings,
|
||||
Func<NativePriceSize, string> fmt,
|
||||
bool lighten_market_cells,
|
||||
@@ -248,17 +244,18 @@ namespace mbts.spark {
|
||||
direct_size: aggr.direct_size + rung.direct_size,
|
||||
implied_size: aggr.implied_size + rung.implied_size),
|
||||
rung => {
|
||||
//spec.round_to_tick(rung.price);
|
||||
var ticks = spec.full_ticks_from_zero(rung.price);
|
||||
var ticks = spec.count_ticks(rung.price);
|
||||
return ticks.HasValue ? spec.tickup(0, ticks.Value) : rung.price;
|
||||
});
|
||||
foreach (var rung in row_tick_groups) {
|
||||
//double price = spec.round_to_tick(rung.price);
|
||||
var ticks = spec.full_ticks_from_zero(rung.price);
|
||||
var ticks = spec.count_ticks(rung.price);
|
||||
var price = ticks.HasValue ? spec.tickup(0, ticks.Value) : rung.price;
|
||||
// prices are in descending order, so get diff from curr_price - price
|
||||
int tick_diff = spec.tickdiff_unchecked(curr_price, price);
|
||||
curr_index += tick_diff;
|
||||
var tick_diff = spec.tickdiff(curr_price, price).assert_value();
|
||||
checked {
|
||||
curr_index += (int)tick_diff;
|
||||
}
|
||||
curr_price = price;
|
||||
if (curr_index >= 0 && curr_index < prices.Count) {
|
||||
if (market_cell_background_brush != null)
|
||||
@@ -278,7 +275,7 @@ namespace mbts.spark {
|
||||
IList<double> prices,
|
||||
IEnumerable<VolumeAtPriceLevelImmutable> data,
|
||||
double row_height,
|
||||
ContractSpecificationImmutable spec,
|
||||
RegionalTickFactors spec,
|
||||
VolumeColumnSettings settings,
|
||||
int last_index,
|
||||
string last_size_and_direction,
|
||||
@@ -371,7 +368,7 @@ namespace mbts.spark {
|
||||
|
||||
public delegate void RenderCell<T>(DrawingContext dc, int row, T data_item);
|
||||
|
||||
static void _render_cells<T>(DrawingVisual dv, double row_height, IEnumerable<(double price, T value)> data, LadderColumnSettings settings, IList<double> prices, ContractSpecificationImmutable spec, RenderCell<T> render_cell) {
|
||||
static void _render_cells<T>(DrawingVisual dv, double row_height, IEnumerable<(double price, T value)> data, LadderColumnSettings settings, IList<double> prices, RegionalTickFactors spec, RenderCell<T> render_cell) {
|
||||
using (var dc = dv.RenderOpen())
|
||||
using (_draw_with_clip(dc, settings, row_height, prices.Count)) {
|
||||
if (data.is_null_or_empty())
|
||||
@@ -382,8 +379,11 @@ namespace mbts.spark {
|
||||
if (double.IsNaN(price))
|
||||
continue;
|
||||
// prices are in descending order, so get diff from curr_price - price
|
||||
int tick_diff = spec.tickdiff(curr_price, price) ?? 0;
|
||||
curr_index += tick_diff;
|
||||
var tick_diff = spec.tickdiff(curr_price, price) ?? 0;
|
||||
Log.assert(
|
||||
tick_diff > (int.MaxValue - curr_index),
|
||||
$"Unable to index ladder row for price {price}: tick count exceeds int.MaxValue");
|
||||
curr_index += (int)tick_diff;
|
||||
curr_price = price;
|
||||
if (curr_index >= 0 && curr_index < prices.Count) {
|
||||
render_cell(dc, curr_index, value);
|
||||
@@ -409,7 +409,7 @@ namespace mbts.spark {
|
||||
IList<double> prices,
|
||||
IEnumerable<(double price, uint piq)> pvp,
|
||||
double row_height,
|
||||
ContractSpecificationImmutable spec,
|
||||
RegionalTickFactors spec,
|
||||
LadderColumnSettings settings,
|
||||
Func<uint, string> fmt_value) {
|
||||
//
|
||||
@@ -425,8 +425,10 @@ namespace mbts.spark {
|
||||
foreach (var entry in pvp) {
|
||||
double price = entry.price;
|
||||
// prices are in descending order, so get diff from curr_price - price
|
||||
int tick_diff = spec.tickdiff_unchecked(curr_price, price);
|
||||
curr_index += tick_diff;
|
||||
var tick_diff = spec.tickdiff(curr_price, price).assert_value();
|
||||
checked {
|
||||
curr_index += (int)tick_diff;
|
||||
}
|
||||
curr_price = price;
|
||||
if (curr_index >= 0 && curr_index < prices.Count) {
|
||||
var gr = format_text(fmt_value(entry.piq), curr_index, row_height, settings, glyph_context);
|
||||
|
||||
@@ -31,10 +31,12 @@
|
||||
ItemsSource="{Binding rows_view}"
|
||||
MinRowHeight="17"
|
||||
RowHeaderWidth="22"
|
||||
Background="{u:BTSColor ManualFills.background}"
|
||||
>
|
||||
<!--Row Header Style-->
|
||||
<DataGrid.RowHeaderStyle>
|
||||
<Style TargetType="DataGridRowHeader">
|
||||
<Setter Property="Background" Value="transparent"/>
|
||||
<Setter Property="Content" Value="{Binding}"/>
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
@@ -123,8 +125,12 @@
|
||||
<!--Row Style-->
|
||||
<DataGrid.RowStyle>
|
||||
<Style TargetType="DataGridRow">
|
||||
<Setter Property="Background" Value="{u:BTSColor Grid.default_row_background}"/>
|
||||
<Setter Property="IsSelected" Value="{Binding is_selected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="DataGridRow.AlternationIndex" Value="1">
|
||||
<Setter Property="Background" Value="{u:BTSColor Grid.alternate_row_background}"/>
|
||||
</Trigger>
|
||||
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource SelectedRowBackground}"/>
|
||||
</DataTrigger>
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace Spark.Trading {
|
||||
meta.Value.Value.PriceDisplay : Security.Defaults.price_display_settingsi;
|
||||
if (security.HasValue) {
|
||||
rv = rv.with(
|
||||
tick_factors: security.spec_or_default().tick_factors().to_api_tick_factors());
|
||||
tick_factors: security.Value.regional_ticks().low_tick.to_api_tick_factors());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -769,7 +769,7 @@ namespace mbts.spark {
|
||||
return;
|
||||
}
|
||||
|
||||
var price_display = row.contract_spec?.tick_factors()?.to_api_tick_display_settingsi();
|
||||
var price_display = TickUtils.get_tick(row.security, price ?? 0).to_api_tick_display_settingsi();
|
||||
var update_desc = OrderUtils.update_description(
|
||||
row.order, price, quantity, price_display, row.quantity_display_settings);
|
||||
Log.Ui.info(
|
||||
|
||||
@@ -179,8 +179,7 @@ namespace mbts.spark {
|
||||
using var _ = detect_property_changes(
|
||||
nameof(spec),
|
||||
nameof(display_quantity_divisor),
|
||||
nameof(quantity_display),
|
||||
nameof(unit_quantity));
|
||||
nameof(quantity_display));
|
||||
_security_md = value;
|
||||
bbo.security_md = value;
|
||||
_payup_bid_cmd.RaiseCanExecuteChanged();
|
||||
@@ -210,9 +209,6 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public double unit_quantity =>
|
||||
(security?.ContractSpec)?.tick_factors()?.tick_unit_frac() ?? 1;
|
||||
|
||||
public AlgoDefinitionVM algo_vm { get; }
|
||||
public IReadOnlyObservableCollection<AlgoParamRowVM> param_rows { get; }
|
||||
public BBOVM bbo { get; }
|
||||
|
||||
@@ -11,6 +11,8 @@ using System.Reactive.Linq;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
using RegionalTickFactors = user.settings.RegionalTickFactors;
|
||||
|
||||
class InlineOrderTicketProperties {
|
||||
public readonly InlineUserProperty<double> price;
|
||||
public readonly InlineUserProperty<double> size;
|
||||
@@ -102,6 +104,7 @@ namespace mbts.spark {
|
||||
|
||||
public SecurityImmutable security => _security.Value;
|
||||
public double price { get; set; }
|
||||
public RegionalTickFactors price_tick => security.regional_ticks();
|
||||
public CalculatorLogic<double> size_calculator { get; }
|
||||
public double display_quantity_divisor {
|
||||
get => _display_quantity_divisor;
|
||||
|
||||
@@ -225,10 +225,7 @@ namespace bts.spark {
|
||||
var security = try_get_security(order.Def.SecurityKey);
|
||||
var change_descs = new List<string>(1);
|
||||
|
||||
TickDisplaySettingsImmutable? price_display =
|
||||
security.HasValue && security.Value.HasContractSpec ?
|
||||
security.Value.ContractSpec.tick_factors().to_api_tick_display_settingsi() : null;
|
||||
|
||||
var price_display = TickUtils.get_tick(security, price).to_api_tick_display_settingsi();
|
||||
var price_qty_change_desc = OrderUtils.update_description(
|
||||
order, price, size, price_display);
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
<usp:PriceEntryControl
|
||||
Grid.ColumnSpan="2"
|
||||
usp:OrderModificationControl.IsDirty="{Binding price_dirty, FallbackValue={x:Null}}"
|
||||
ContractSpec="{Binding security.ContractSpec}"
|
||||
RegionalTick="{Binding price_tick}"
|
||||
Focusable="false"
|
||||
TickDefaultValue="{Binding current_price, Converter={StaticResource TickDefaultPriceConverter}, FallbackValue={u:Double 0}}"
|
||||
Value="{Binding price}"
|
||||
|
||||
@@ -254,17 +254,14 @@ namespace mbts.spark {
|
||||
$"{(order.HasDef ? order.Def.Side.ToString() : "(unknown-side)")} {(order.HasState ? ($@"{spec.format(order.State.Price)} @ {order.State.Quantity}") : "(unknown-price) @ (unknown-quantity)")}";
|
||||
|
||||
public static string short_description(
|
||||
SecurityImmutable security, Side side, double price, double quantity) =>
|
||||
new StringBuilderEx(
|
||||
SecurityImmutable security, Side side, double price, double quantity) {
|
||||
var price_display = TickUtils.get_tick(security, price).to_api_tick_display_settingsi();
|
||||
return new StringBuilderEx(
|
||||
security.ToString(), " ",
|
||||
side.ToString(), " ",
|
||||
price_quantity_description(price, quantity, security.spec_or_default())
|
||||
price_quantity_description(price, quantity, price_display)
|
||||
).ToString();
|
||||
|
||||
public static string price_quantity_description(
|
||||
double price, double quantity, ContractSpecificationImmutable spec) =>
|
||||
price_quantity_description(
|
||||
price, quantity, spec.tick_factors().to_api_tick_display_settingsi());
|
||||
}
|
||||
|
||||
public static string price_quantity_description(
|
||||
double price,
|
||||
|
||||
@@ -169,7 +169,7 @@ namespace mbts.spark {
|
||||
public static ITag<int> CreateHeatmapTag { get; } = _mk_tag<int>();
|
||||
|
||||
// Publisher: VolSurfaceBlackboardManager
|
||||
// User: VoLSurfaceModule
|
||||
// User: VolSurfaceModule
|
||||
public static ITag<IDictionary<int, string>> VolSurfaceListTag { get; } =
|
||||
_mk_tag<IDictionary<int, string>>();
|
||||
|
||||
@@ -202,11 +202,17 @@ namespace mbts.spark {
|
||||
public static ITag<VolSurfaceCalculationRequest> VolSurfaceCalculationRequestTag(int vs_id) =>
|
||||
_mk_tag<VolSurfaceCalculationRequest, int>(vs_id);
|
||||
|
||||
public static ITag<VolSurfaceCalculationRequest> VolSurfaceAutoCalculationRequestTag(int vs_id) =>
|
||||
_mk_tag<VolSurfaceCalculationRequest, int>(vs_id);
|
||||
|
||||
// Publisher : SingleVolSurfaceRequestCalculator
|
||||
// Data : VolSurfaceCalculationResponse
|
||||
public static ITag<VolSurfaceCalculationResponse> VolSurfaceCalculationResponseTag(int vs_id) =>
|
||||
_mk_tag<VolSurfaceCalculationResponse, int>(vs_id);
|
||||
|
||||
public static ITag<VolSurfaceCalculationResponse> VolSurfaceAutoCalculationResponseTag(int vs_id) =>
|
||||
_mk_tag<VolSurfaceCalculationResponse, int>(vs_id);
|
||||
|
||||
// When the UI intends to save the vol surface (data or input)
|
||||
// Publisher: VolSurfaceDisplayModel
|
||||
public static ITag<VolSurfaceInput> VolSurfaceCommitTag(int vs_id) =>
|
||||
|
||||
@@ -591,9 +591,9 @@ namespace mbts.ui {
|
||||
|
||||
// Disabled
|
||||
public static BTSColor disabled_border => UI.input_disabled_border;
|
||||
public static BTSColor disabled_background => UI.input_disabled_background;
|
||||
public static BTSColor disabled_foreground => normal_foreground;
|
||||
public static BTSColor disabled_text => normal_text;
|
||||
public static BTSColor disabled_background => Button.disabled_background;
|
||||
public static BTSColor disabled_foreground => Button.disabled_foreground;
|
||||
public static BTSColor disabled_text => Button.disabled_foreground;
|
||||
public static BTSColor disabled_glyph => normal_glyph;
|
||||
public static BTSColor disabled_button_border => BTSCoreColors.transparent;
|
||||
public static BTSColor disabled_button_background => BTSCoreColors.transparent;
|
||||
@@ -647,8 +647,7 @@ namespace mbts.ui {
|
||||
public static BTSColor pressed_editable_button_background => pressed_button_background;
|
||||
public static BTSColor pressed_editable_button_border => pressed_button_border;
|
||||
|
||||
public static BTSColor disabled_editable_background =>
|
||||
editable_background.blend(disabled_background);
|
||||
public static BTSColor disabled_editable_background => Core.transparent;
|
||||
|
||||
public static BTSColor disabled_editable_border =>
|
||||
editable_border.blend(disabled_border);
|
||||
@@ -1042,6 +1041,21 @@ namespace mbts.ui {
|
||||
public static BTSColor foreground => UI.default_text;
|
||||
public static BTSColor footer_foreground => foreground;
|
||||
}
|
||||
public static class ExecutionStrategy {
|
||||
public static BTSColor expander_background => Core.transparent;//Grid.dark_column_background;
|
||||
public static BTSColor expander_foreground => UI.default_text;
|
||||
public static BTSColor data_grid_row_header_background => Core.transparent;
|
||||
public static BTSColor data_grid_row_header_foreground => UI.default_text;
|
||||
public static BTSColor data_grid_background => Grid.default_row_background;
|
||||
public static BTSColor default_column_background => Grid.default_row_background;
|
||||
public static BTSColor alternate_column_background => Grid.alternate_row_background;// 0xff494957;
|
||||
public static BTSColor arrow_background => UI.default_text;
|
||||
public static BTSColor arrow_background_selected => UI.alternate_text;
|
||||
|
||||
}
|
||||
public static class ManualFills {
|
||||
public static BTSColor background => UI.common_background;
|
||||
}
|
||||
public static class AlgoStudio {
|
||||
public static BTSColor background => UI.default_background;
|
||||
public static BTSColor foreground => UI.default_foreground;
|
||||
|
||||
@@ -76,6 +76,11 @@
|
||||
<Color x:Key="ComboBox.Pressed.Editable.BackgroundColor">#707070</Color>
|
||||
<Color x:Key="ComboBox.Pressed.Editable.Button.BackgroundColor">#FFC4E0FC</Color>
|
||||
|
||||
<Color x:Key="ComboBox.Disabled.Editable.BackgroundColor">#FFE5E5E5</Color>
|
||||
<Color x:Key="ComboBox.Disabled.Editable.BorderColor">#FFD9D9D9</Color>
|
||||
<Color x:Key="ComboBox.Disabled.Editable.Button.BackgroundColor">#FFE5E5E5</Color>
|
||||
<Color x:Key="ComboBox.Disabled.Editable.Button.BorderColor">#FFD9D9D9</Color>
|
||||
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Background" Color="{StaticResource ComboBox.Static.BackgroundColor}" />
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Foreground" Color="{StaticResource ComboBox.Static.ForegroundColor}" />
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Border" Color="{StaticResource ComboBox.Static.BorderColor}"/>
|
||||
@@ -98,7 +103,7 @@
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Border" Color="{StaticResource ComboBoxDisabledBorderColor}"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Background" Color="{StaticResource ComboBox.Disabled.Editable.BackgroundColor}" />
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Border" Color="{StaticResource ComboBox.Disabled.Editable.BorderColor}" />
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Button.Background" Color="{StaticResource ComboBox.Disabled.Editable.BackgroundColor}" />
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Button.Background" Color="{StaticResource ComboBox.Disabled.Editable.Button.BackgroundColor}" />
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Button.Border" Color="{StaticResource ComboBox.Disabled.Editable.Button.BorderColor}" />
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Glyph" Color="#FF606060"/>
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<Setter Property="Focusable" Value="False"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
<Setter Property="OverridesDefaultStyle" Value="True"/>
|
||||
<Setter Property="Background" Value="{u:BTSColor ScrollBar.thumb_pressed}"/>
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.ScrollBar.thumb_pressed}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Thumb">
|
||||
@@ -224,18 +224,18 @@
|
||||
<VisualState Name="Normal"/>
|
||||
<VisualState Name="MouseOver">
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="{u:BTSColor ScrollBar.thumb_mouse_over}"/>
|
||||
<ColorAnimation Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="{u:BTSColor UI.ScrollBar.thumb_mouse_over}"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="Pressed">
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="{u:BTSColor ScrollBar.thumb_pressed}"/>
|
||||
<ColorAnimation Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="{u:BTSColor UI.ScrollBar.thumb_pressed}"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Thumb.Background>
|
||||
<SolidColorBrush Color="{u:BTSColor ScrollBar.thumb_static}"/>
|
||||
<SolidColorBrush Color="{u:BTSColor UI.ScrollBar.thumb_static}"/>
|
||||
</Thumb.Background>
|
||||
</Thumb>
|
||||
</Track.Thumb>
|
||||
@@ -380,18 +380,18 @@
|
||||
<VisualState Name="Normal"/>
|
||||
<VisualState Name="MouseOver">
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="{StaticResource u:BTSColor ScrollBar.thumb_mouse_over}"/>
|
||||
<ColorAnimation Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="{StaticResource u:BTSColor UI.ScrollBar.thumb_mouse_over}"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="Pressed">
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="{u:BTSColor ScrollBar.thumb_presseds}"/>
|
||||
<ColorAnimation Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="{u:BTSColor UI.ScrollBar.thumb_presseds}"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Thumb.Background>
|
||||
<SolidColorBrush Color="{u:BTSColor ScrollBar.thumb_static}"/>
|
||||
<SolidColorBrush Color="{u:BTSColor UI.ScrollBar.thumb_static}"/>
|
||||
</Thumb.Background>
|
||||
</Thumb>
|
||||
</Track.Thumb>
|
||||
|
||||
@@ -15,12 +15,16 @@ using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
using RegionalTickFactors = user.settings.RegionalTickFactors;
|
||||
using TickDisplaySettings = user.settings.TickDisplaySettings;
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for PriceEntryControl.xaml
|
||||
/// </summary>
|
||||
class PriceEntryControl : Control {
|
||||
readonly PropertyUpdateGate _price_text_gate = new PropertyUpdateGate();
|
||||
ContractSpecificationImmutable? _spec;
|
||||
RegionalTickFactors _tick;
|
||||
TextBox _text;
|
||||
IDisposable _text_hooks;
|
||||
double? _raw_value;
|
||||
@@ -53,16 +57,17 @@ namespace mbts.ui.spark {
|
||||
(o, v) => o._update_text(v),
|
||||
(o, v) => {
|
||||
o._raw_value = v;
|
||||
if (!o._spec.HasValue || DoubleHelper.IsNaNOrInfinity(v))
|
||||
if ((!o._spec.HasValue && o._tick is null) || DoubleHelper.IsNaNOrInfinity(v))
|
||||
return v;
|
||||
// Note: the decimal cast here is important bc double-rounding errors can cause the keyboard text to conflict with poorly rounded result, and negate Tick Up/Down commands.
|
||||
// This happens bc the tick up/down commands flush the current text before inc/decrementing.
|
||||
double tick = (double)o._spec.Value.round_to_tick((decimal)v);
|
||||
if (MathUtils.epsilon_equals(tick, v)) {
|
||||
var v_dec = (decimal)v;
|
||||
var rounded = (double)(o._tick?.round_to_tick(v_dec) ?? o._spec.Value.round_to_tick(v_dec));
|
||||
if (MathUtils.epsilon_equals(rounded, v)) {
|
||||
// this value has already been rounded, don't make it re-enter the CoerceValueCallback delegate.
|
||||
return v;
|
||||
}
|
||||
return tick;
|
||||
return rounded;
|
||||
},
|
||||
double.NaN);
|
||||
|
||||
@@ -102,6 +107,26 @@ namespace mbts.ui.spark {
|
||||
public ContractSpecificationImmutable? ContractSpec { get => GetValue(ContractSpecProperty) as ContractSpecificationImmutable?; set => SetValue(ContractSpecProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region RegionalTick
|
||||
public static readonly DependencyProperty RegionalTickProperty =
|
||||
WPFHelper.create_dp<RegionalTickFactors, PriceEntryControl>(
|
||||
nameof(RegionalTick),
|
||||
(o, v) => {
|
||||
o._tick = v;
|
||||
if (o._raw_value.HasValue) {
|
||||
o.SetCurrentValue(ValueProperty, o._raw_value.Value);
|
||||
if (o.AlwaysReformatOnSpecChange)
|
||||
o._update_text();
|
||||
}
|
||||
}, RegionalTickFactors.Default);
|
||||
|
||||
public RegionalTickFactors RegionalTick {
|
||||
get => GetValue(RegionalTickProperty) as RegionalTickFactors;
|
||||
set => SetValue(RegionalTickProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
/// TODO: verify that this control behaves correctly when <see cref="AlwaysReformatOnSpecChange"/> is true (ideally this prop will be removed, and this control will always behave is if the prop is true).
|
||||
/// Because this control is ubiquitious in our application, such verification will require careful, meticulous analysis.
|
||||
/// Until such verification is complete, this boolean property will be used to trigger the behavior, so unsuspecting instances of this control will not be affected.
|
||||
@@ -126,6 +151,8 @@ namespace mbts.ui.spark {
|
||||
double v = Value;
|
||||
if (double.IsNaN(v)) {
|
||||
SetCurrentValue(ValueProperty, TickDefaultValue);
|
||||
} else if (_tick is not null) {
|
||||
SetCurrentValue(ValueProperty, _tick.tickup(v));
|
||||
} else {
|
||||
var spec = ContractSpec;
|
||||
if (spec.HasValue) SetCurrentValue(ValueProperty, spec.Value.tickup(v));
|
||||
@@ -137,6 +164,8 @@ namespace mbts.ui.spark {
|
||||
double v = Value;
|
||||
if (double.IsNaN(v)) {
|
||||
SetCurrentValue(ValueProperty, TickDefaultValue);
|
||||
} else if (_tick is not null) {
|
||||
SetCurrentValue(ValueProperty, _tick.tickdown(v));
|
||||
} else {
|
||||
var spec = ContractSpec;
|
||||
if (spec.HasValue) SetCurrentValue(ValueProperty, spec.Value.tickdown(v));
|
||||
@@ -149,7 +178,15 @@ namespace mbts.ui.spark {
|
||||
|
||||
void _update_text(double v) {
|
||||
if (_text != null)
|
||||
_price_text_gate.try_update(() => _text.Text = _spec.HasValue ? _spec.Value.format(v, double.NaN, false, string.Empty) : v.ToString());
|
||||
_price_text_gate.try_update(() => {
|
||||
if (_tick is not null) {
|
||||
var settings = TickDisplaySettings.Default;
|
||||
settings.tick_factors = _tick.get_tick_for(v);
|
||||
_text.Text = _tick.format(v, settings);
|
||||
return;
|
||||
}
|
||||
_text.Text = _spec.HasValue ? _spec.Value.format(v, double.NaN, false, string.Empty) : v.ToString();
|
||||
});
|
||||
}
|
||||
|
||||
void _update_value() =>
|
||||
|
||||
@@ -485,6 +485,10 @@
|
||||
Width="{TemplateBinding Width}"
|
||||
MinWidth="{TemplateBinding MinWidth}"
|
||||
MaxWidth="{TemplateBinding MaxWidth}"
|
||||
TickUpCommand="{TemplateBinding TickUpCommand}"
|
||||
TickUpCommandParameter="{TemplateBinding TickUpCommandParameter}"
|
||||
TickDownCommand="{TemplateBinding TickDownCommand}"
|
||||
TickDownCommandParameter="{TemplateBinding TickDownCommandParameter}"
|
||||
>
|
||||
<TextBox
|
||||
Name="PART_TextBox"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#nullable enable
|
||||
namespace mbts.ui.controls {
|
||||
using bts.api.domain;
|
||||
using bts.utils;
|
||||
|
||||
using mbts.ui.extensions;
|
||||
using mbts.ui.utilities;
|
||||
@@ -228,6 +229,12 @@ namespace mbts.ui.controls {
|
||||
get => _explicit_props.value;
|
||||
set => _explicit_props.value = value;
|
||||
}
|
||||
|
||||
bool _has_valid_bounds =>
|
||||
Minimum < Maximum;
|
||||
|
||||
T _default_value =>
|
||||
_has_valid_bounds ? T.Zero.within_range(Minimum, Maximum) : T.Zero;
|
||||
|
||||
public override void OnApplyTemplate() {
|
||||
Disposable.dispose(ref _text_hooks);
|
||||
@@ -272,7 +279,7 @@ namespace mbts.ui.controls {
|
||||
var v = _value_d;
|
||||
if (!v.HasValue) {
|
||||
if (AllowNullTick) {
|
||||
SetCurrentValue(ValueProperty, T.Zero);
|
||||
SetCurrentValue(ValueProperty, _default_value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -280,6 +287,10 @@ namespace mbts.ui.controls {
|
||||
TickUpDownHelper.nullable_tick_up(v, _increment_d, out var result_d, _maximum_d) :
|
||||
TickUpDownHelper.nullable_tick_down(v, _increment_d, out result_d, _minimum_d)) {
|
||||
var result = NumericCastOutConverter<T>.convert(result_d.assert_value());
|
||||
// Enforce min/max bounds (if bounds are valid)
|
||||
if (_has_valid_bounds) {
|
||||
result = result.within_range(Minimum, Maximum);
|
||||
}
|
||||
SetCurrentValue(ValueProperty, result);
|
||||
CoerceValue(ValueProperty);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.converters {
|
||||
using RegionalTickFactors = user.settings.RegionalTickFactors;
|
||||
|
||||
class TickDisplayConverter : IMultiValueConverter {
|
||||
public static TickDisplayConverter Instance => new();
|
||||
|
||||
@@ -24,10 +26,13 @@ namespace mbts.ui.converters {
|
||||
if (values.Length == 2 && values[1] is bts.api.domain.TickDisplaySettingsImmutable tdsi) {
|
||||
return tdsi.to_tick_display_settings().format(price);
|
||||
}
|
||||
var spec = get_val<ContractSpecificationImmutable?>(values, 1) ??
|
||||
Security.Defaults.ContractSpecificationImmutable;
|
||||
var spec = get_val<ContractSpecificationImmutable?>(values, 1);
|
||||
var settings = get_val<mbts.user.settings.TickDisplaySettings>(values, 2);
|
||||
return spec.format(price, settings);
|
||||
if (spec.HasValue) {
|
||||
return spec.Value.format(price, settings);
|
||||
}
|
||||
var rtick = get_val<RegionalTickFactors>(values, 1) ?? RegionalTickFactors.Default;
|
||||
return rtick.format(price, settings);
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>
|
||||
|
||||
@@ -34,7 +34,8 @@
|
||||
</Style>
|
||||
<Style x:Key="DisabledPlaceholderComboBox" TargetType="ComboBox">
|
||||
<Setter Property="IsEnabled" Value="False"/>
|
||||
<Setter Property="Foreground" Value="Gray"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.ComboBox.disabled_foreground}"/>
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.ComboBox.disabled_button_background}"/>
|
||||
<Setter Property="SelectedIndex" Value="0"/>
|
||||
<Setter Property="ItemsSource">
|
||||
<Setter.Value>
|
||||
|
||||
@@ -386,9 +386,9 @@
|
||||
<ControlTemplate x:Key="DataGridRowDefaultTemplate" TargetType="DataGridRow">
|
||||
<Border
|
||||
x:Name="DGR_Border"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Background="{TemplateBinding Background}"
|
||||
SnapsToDevicePixels="True">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager AttachToMembers="True">
|
||||
@@ -408,7 +408,9 @@
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames
|
||||
Storyboard.TargetName="DGR_Border" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSColor Grid.alternate_row_background}" />
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSColor ExecutionStrategy.default_column_background}" />
|
||||
<!-- <DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSColor Core.red}" /> -->
|
||||
<!-- <DiscreteObjectKeyFrame KeyTime="1" Value="{u:BTSColor Core.blue}" /> -->
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
@@ -425,7 +427,8 @@
|
||||
HeadersVisibility,
|
||||
ConverterParameter={x:Static DataGridHeadersVisibility.Row},
|
||||
Converter={x:Static DataGrid.HeadersVisibilityConverter},
|
||||
RelativeSource={RelativeSource AncestorType=DataGrid}}" />
|
||||
RelativeSource={RelativeSource AncestorType=DataGrid}}"
|
||||
Background="transparent"/>
|
||||
<DataGridCellsPresenter
|
||||
Grid.Column="1"
|
||||
ItemsPanel="{TemplateBinding ItemsPanel}"
|
||||
@@ -451,7 +454,7 @@
|
||||
<Style TargetType="{x:Type DataGrid}">
|
||||
<Setter Property="UseLayoutRounding" Value="True" />
|
||||
<Setter Property="ColumnHeaderStyle" Value="{StaticResource {x:Type DataGridColumnHeader}}" />
|
||||
<Setter Property="Background" Value="{uc:BTSColor Name=UI.default_background}" />
|
||||
<Setter Property="Background" Value="{uc:BTSColor Name=Core.transparent}" />
|
||||
<Setter Property="AlternationCount" Value="2" />
|
||||
<Setter Property="RowStyle" Value="{StaticResource {x:Type DataGridRow}}" />
|
||||
<Setter Property="AutoGenerateColumns" Value="False" />
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
#nullable enable
|
||||
namespace mbts {
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.domain;
|
||||
using bts.utils;
|
||||
|
||||
using mbts.co2;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
public readonly struct SecurityMD : IEquatable<SecurityMD> {
|
||||
public static readonly Func<SecurityMD, uint> key_fn = smd => smd.security.Id;
|
||||
@@ -14,21 +19,28 @@ namespace mbts {
|
||||
SecurityImmutable security,
|
||||
IMDConsumerHandle md,
|
||||
TickDisplaySettingsImmutable? price_display = null,
|
||||
TickDisplaySettingsImmutable? quantity_display = null) {
|
||||
TickDisplaySettingsImmutable? quantity_display = null,
|
||||
IEnumerable<TickDisplayRuleImmutable>? price_display_rules = null) {
|
||||
this.security = security;
|
||||
this.md = md;
|
||||
this.price_display = price_display ?? Security.Defaults.price_display_settingsi;
|
||||
this.quantity_display = quantity_display ??
|
||||
Security.Defaults.quantity_display_settingsi;
|
||||
unit_quantity = 1;
|
||||
this.price_display_rules = price_display_rules
|
||||
.empty_if_null()
|
||||
.OrderBy(r => r.MaxValue)
|
||||
.ToImmutableList();
|
||||
// Validate new 'price rules' config (be strict...this is a hotfix to prod)
|
||||
Log.assert(this.price_display_rules.All(r => r.MaxValue.is_real()));
|
||||
Log.assert(this.price_display_rules.Select(r => r.MaxValue).IsDistinct());
|
||||
}
|
||||
|
||||
public SecurityImmutable security { get; }
|
||||
public uint id => security.Id;
|
||||
public IMDConsumerHandle md { get; }
|
||||
public double unit_quantity { get; }
|
||||
public TickDisplaySettingsImmutable price_display { get; }
|
||||
public TickDisplaySettingsImmutable quantity_display { get; }
|
||||
public ImmutableList<TickDisplayRuleImmutable> price_display_rules { get; }
|
||||
|
||||
public override int GetHashCode() =>
|
||||
(int)security.Id;
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Spark.Core.Data {
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.domain;
|
||||
using bts.collections;
|
||||
|
||||
using mbts;
|
||||
using mbts.config;
|
||||
@@ -55,7 +56,21 @@ namespace Spark.Core.Data {
|
||||
}
|
||||
// Can't possibly be null (the delegate above already checked for this)
|
||||
raw_meta = Log.notnull(raw_meta);
|
||||
return convert_raw_metadata(raw_meta);
|
||||
var meta = convert_raw_metadata(raw_meta);
|
||||
if (sec.ContractCase == Security.ContractOneofCase.Spread
|
||||
&& meta.HasValue
|
||||
&& meta.Value.PriceRules.AtLeast(2)) {
|
||||
// This is a hack to force Deribit Combos to have uniform ticks
|
||||
var rules = meta.Value.PriceRules
|
||||
.Where(r => r.HasTickDisplay && r.TickDisplay.HasTickFactors);
|
||||
if (rules.TryGetMinElement(
|
||||
r => r.TickDisplay.TickFactors.tick_unit_frac(), out var min_tick_rule)) {
|
||||
var mutable_meta = meta.Value.CloneToMutable();
|
||||
mutable_meta.PriceRules.SetItems(new[] { min_tick_rule.CloneToMutable() });
|
||||
meta = mutable_meta.immutable();
|
||||
}
|
||||
}
|
||||
return meta;
|
||||
});
|
||||
|
||||
SecurityMetadataImmutable? convert_raw_metadata(SecurityMetadataItem raw_meta) {
|
||||
@@ -66,8 +81,8 @@ namespace Spark.Core.Data {
|
||||
/// Don't mutate the actual object stored in <see cref="_security_metadata"/>
|
||||
var meta = raw_meta.Value.Clone();
|
||||
meta.PriceDisplay ??= Security.Defaults.price_display_settings;
|
||||
meta.PriceDisplay.TickFactors = sec.spec_or_default()
|
||||
.tick_factors().to_api_tick_factorsi().CloneToMutable();
|
||||
// meta.PriceDisplay.TickFactors = sec.spec_or_default()
|
||||
// .tick_factors().to_api_tick_factorsi().CloneToMutable();
|
||||
return new(meta);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ namespace mbts.user.settings {
|
||||
_low_tick_inclusive_end = low_tick_inclusive_end;
|
||||
}
|
||||
|
||||
public static RegionalTickFactors Default =>
|
||||
new(new(10000, 1), new(10000, 1), double.MaxValue);
|
||||
|
||||
public TickFactors low_tick => _low_tick;
|
||||
public TickFactors high_tick => _high_tick;
|
||||
public double low_tick_inclusive_end => _low_tick_inclusive_end;
|
||||
|
||||
@@ -55,9 +55,16 @@ namespace mbts {
|
||||
this TickFactors tick_factors) =>
|
||||
new(tick_factors.to_api_tick_display_settings());
|
||||
|
||||
public static bool is_valid(this TickFactors tick) =>
|
||||
(tick.tick_unit > 0 && tick.tick_unit <= (tick.tick_base * 0.5)) ||
|
||||
tick.tick_unit == tick.tick_base;
|
||||
public static bool is_valid(this TickFactors tick) {
|
||||
if (!tick.tick_unit.is_real_positive() && !tick.tick_base.is_real_positive()) {
|
||||
return false;
|
||||
}
|
||||
var ticks_per_base = tick.tick_base / tick.tick_unit;
|
||||
if (ticks_per_base.is_int()) {
|
||||
return true;
|
||||
}
|
||||
return tick.tick_unit > tick.tick_base && tick.tick_base == 1;
|
||||
}
|
||||
|
||||
public static bool is_valid(this RegionalTickFactors regional_tick) =>
|
||||
regional_tick.low_tick.is_valid() && regional_tick.high_tick.is_valid();
|
||||
@@ -65,6 +72,10 @@ namespace mbts {
|
||||
public static bool is_integer(this TickFactors tick) =>
|
||||
tick.tick_unit == tick.tick_base;
|
||||
|
||||
public static TickFactors tick_factors(this RegionalTickFactors regional_tick, double value) =>
|
||||
value > regional_tick.low_tick_inclusive_end ?
|
||||
regional_tick.high_tick : regional_tick.low_tick;
|
||||
|
||||
public static double tick_unit_frac(this TickFactors tick) =>
|
||||
tick.tick_unit / tick.tick_base;
|
||||
|
||||
@@ -76,6 +87,21 @@ namespace mbts {
|
||||
|
||||
public static double reduced_base(this TickFactors tick) => MathUtils.factor_out(tick.tick_base, 10);
|
||||
|
||||
public static bool tick_equals(this RegionalTickFactors regional_tick, double x, double y) {
|
||||
if (x.is_nan()) {
|
||||
return y.is_nan();
|
||||
}
|
||||
if (y.is_nan()) {
|
||||
return false;
|
||||
}
|
||||
var xtick = regional_tick.tick_factors(x);
|
||||
var ytick = regional_tick.tick_factors(y);
|
||||
return xtick is not null
|
||||
&& ytick is not null
|
||||
&& xtick.Equals(ytick)
|
||||
&& xtick.tick_equals(x, y);
|
||||
}
|
||||
|
||||
public static bool tick_equals(this TickFactors tick, double x, double y) {
|
||||
if (x.is_nan()) {
|
||||
return y.is_nan();
|
||||
@@ -92,27 +118,35 @@ namespace mbts {
|
||||
}
|
||||
var x_units = MathUtils.round_fractional_units(x, tick.tick_unit_frac<T>());
|
||||
var y_units = MathUtils.round_fractional_units(y, tick.tick_unit_frac<T>());
|
||||
return NumericCastConverter<T, int>.convert(x_units) ==
|
||||
NumericCastConverter<T, int>.convert(y_units);
|
||||
return NumericCastConverter<T, long>.convert(x_units) ==
|
||||
NumericCastConverter<T, long>.convert(y_units);
|
||||
}
|
||||
|
||||
public static IEqualityComparer<T> tick_equality_comparer<T>(this TickFactors tick)
|
||||
where T : IFloatingPoint<T> =>
|
||||
EqualityComparer.Create<T>(
|
||||
(a, b) => tick_equals(tick, a, b),
|
||||
x => NumericCastConverter<T, int>.convert(MathUtils.round_fractional_units(x, tick.tick_unit_frac<T>())));
|
||||
x => NumericCastConverter<T, int>.convert(
|
||||
MathUtils.round_fractional_units(x, tick.tick_unit_frac<T>())));
|
||||
|
||||
public static IEqualityComparer<double> tick_equality_comparer(this TickFactors tick) =>
|
||||
EqualityComparer.Create<double>(
|
||||
(a, b) => tick_equals(tick, a, b),
|
||||
x => (int)MathUtils.round_fractional_units(x, tick.tick_unit_frac()));
|
||||
|
||||
public static TickFactors get_tick_for(this RegionalTickFactors rtick, double value) =>
|
||||
value > rtick.low_tick_inclusive_end ? rtick.high_tick : rtick.low_tick;
|
||||
|
||||
public static string format_without_decimal_part(this TickFactors tick, double value) =>
|
||||
format(tick, value, double.NaN, false, _nan_text, true);
|
||||
|
||||
public static string format<T>(this TickFactors tick, T value) where T : IFloatingPointIeee754<T> =>
|
||||
format(tick, value, T.NaN, false, _nan_text);
|
||||
|
||||
public static string format<T>(this RegionalTickFactors regional_tick, T value, bool show_commas)
|
||||
where T : IFloatingPointIeee754<T> =>
|
||||
format(regional_tick, value, T.NaN, show_commas, _nan_text);
|
||||
|
||||
public static string format<T>(this TickFactors tick, T value, bool show_commas)
|
||||
where T : IFloatingPointIeee754<T> =>
|
||||
format(tick, value, T.NaN, show_commas, _nan_text);
|
||||
@@ -143,6 +177,19 @@ namespace mbts {
|
||||
value, settings.display_multiplier.as_type<double, T>(), settings.show_commas);
|
||||
}
|
||||
|
||||
public static string format<T>(
|
||||
this RegionalTickFactors regional_tick,
|
||||
T value,
|
||||
T display_multiplier,
|
||||
bool show_commas,
|
||||
string nan_text,
|
||||
bool show_decimal_part = true
|
||||
) where T : IFloatingPointIeee754<T> {
|
||||
var value_d = NumericCastConverter<T, double>.convert(value);
|
||||
return regional_tick.tick_factors(value_d)
|
||||
.format(value, display_multiplier, show_commas, nan_text, show_decimal_part);
|
||||
}
|
||||
|
||||
public static string format<T>(
|
||||
this TickFactors tick,
|
||||
T value,
|
||||
@@ -231,33 +278,44 @@ namespace mbts {
|
||||
_parse(regional_tick, text, display_multiplier, nan_text) :
|
||||
_parse_no_tick(text, display_multiplier, nan_text);
|
||||
|
||||
public static T tick<T>(this TickFactors tick, T value, int count)
|
||||
public static T tick<T>(this TickFactors tick, T value, long count)
|
||||
where T : IFloatingPointIeee754<T> =>
|
||||
count < 0 ? tick.tickdown(value, (uint)-count) : tick.tickup(value, (uint)count);
|
||||
count < 0 ? tick.tickdown(value, (uint)-count) : tick.tickup(value, (ulong)count);
|
||||
|
||||
public static T tick<T>(this bts.api.domain.TickFactorsImmutable tick, T value, int count)
|
||||
public static T tick<T>(this bts.api.domain.TickFactorsImmutable tick, T value, long count)
|
||||
where T : IFloatingPointIeee754<T> =>
|
||||
tick.to_tick_factors().tick(value, count);
|
||||
|
||||
public static T tick<T>(
|
||||
this bts.api.domain.TickDisplaySettingsImmutable settings, T value, int count)
|
||||
this bts.api.domain.TickDisplaySettingsImmutable settings, T value, long count)
|
||||
where T : IFloatingPointIeee754<T> =>
|
||||
settings.HasTickFactors ?
|
||||
settings.TickFactors.tick(value, count) :
|
||||
throw new ArgumentException(
|
||||
"Can't tick because 'settings.HasTickFactors' is false", nameof(settings));
|
||||
|
||||
public static T tickup<T>(this TickFactors tick, T input, uint count = 1)
|
||||
public static double tick(
|
||||
this RegionalTickFactors regional_tick, double input, long count) =>
|
||||
tick<double>(regional_tick, input, count);
|
||||
|
||||
public static T tick<T>(
|
||||
this RegionalTickFactors regional_tick, T input, long count)
|
||||
where T : IFloatingPointIeee754<T> =>
|
||||
count > 0 ?
|
||||
tickup(regional_tick, input, (ulong)count) :
|
||||
tickdown(regional_tick, input, (ulong)-count);
|
||||
|
||||
public static T tickup<T>(this TickFactors tick, T input, ulong count = 1)
|
||||
where T : IFloatingPointIeee754<T> =>
|
||||
tickup(tick, input, count, TickUtils.precision_const<T>());
|
||||
|
||||
public static T tickup<T>(this TickFactors tick, T input, uint count, T precision)
|
||||
public static T tickup<T>(this TickFactors tick, T input, ulong count, T precision)
|
||||
where T : IFloatingPointIeee754<T> =>
|
||||
tickup<T, NumericCastOutConverter<T>>(tick, input, count, precision);
|
||||
|
||||
public static T tickup<T, TFromUInt32>(this TickFactors tick, T input, uint count, T precision)
|
||||
public static T tickup<T, TFromUInt64>(this TickFactors tick, T input, ulong count, T precision)
|
||||
where T : IFloatingPointIeee754<T>
|
||||
where TFromUInt32 : IConverter<uint, T> {
|
||||
where TFromUInt64 : IConverter<ulong, T> {
|
||||
if (!precision.is_real_positive()) {
|
||||
throw new ArgumentException("Must be a real positive number.", nameof(precision));
|
||||
}
|
||||
@@ -270,21 +328,33 @@ namespace mbts {
|
||||
if (ieeer < -precision) {
|
||||
count--;
|
||||
}
|
||||
var result = input + tuf * TFromUInt32.convert(count);
|
||||
var result = input + tuf * TFromUInt64.convert(count);
|
||||
return _round_tick(tuf, result);
|
||||
}
|
||||
|
||||
public static double tickup(
|
||||
this RegionalTickFactors regional_tick, double input, long count = 1) =>
|
||||
count switch {
|
||||
> 0 => tickup<double>(regional_tick, input, (ulong)count),
|
||||
< 0 => tickdown<double>(regional_tick, input, (ulong)-count),
|
||||
0 => input,
|
||||
};
|
||||
|
||||
public static double tickup(
|
||||
this RegionalTickFactors regional_tick, double input, ulong count) =>
|
||||
tickup<double>(regional_tick, input, count);
|
||||
|
||||
public static T tickup<T>(
|
||||
this RegionalTickFactors regional_tick, T input) where T : IFloatingPointIeee754<T> =>
|
||||
regional_tick.tickup(input, 1);
|
||||
|
||||
public static T tickup<T>(
|
||||
this RegionalTickFactors regional_tick, T input, uint count)
|
||||
this RegionalTickFactors regional_tick, T input, ulong count)
|
||||
where T : IFloatingPointIeee754<T> =>
|
||||
regional_tick.tickup(input, count, TickUtils.precision_const<T>());
|
||||
|
||||
public static T tickup<T>(
|
||||
this RegionalTickFactors regional_tick, T input, uint count, T precision)
|
||||
this RegionalTickFactors regional_tick, T input, ulong count, T precision)
|
||||
where T : IFloatingPointIeee754<T> {
|
||||
if (!precision.is_real_positive()) {
|
||||
throw new ArgumentException("Must be a real positive number.", nameof(precision));
|
||||
@@ -301,33 +371,33 @@ namespace mbts {
|
||||
var ieeer = T.Ieee754Remainder(input, tuf);
|
||||
input -= ieeer;
|
||||
if (ieeer < -precision) count--;
|
||||
val = input + tuf * count.as_type<uint, T>();
|
||||
val = input + tuf * count.as_type<ulong, T>();
|
||||
if (val > ltie) {
|
||||
count -= MathUtils.round_up((ltie - input) / tuf, precision).as_type<T, uint>();
|
||||
count -= MathUtils.round_up((ltie - input) / tuf, precision).as_type<T, ulong>();
|
||||
tuf = regional_tick.high_tick.tick_unit_frac<T>();
|
||||
val = ltie + tuf * count.as_type<uint, T>();
|
||||
val = ltie + tuf * count.as_type<ulong, T>();
|
||||
}
|
||||
} else {
|
||||
tuf = regional_tick.high_tick.tick_unit_frac<T>();
|
||||
var ieeer = T.Ieee754Remainder(input, tuf);
|
||||
input -= ieeer;
|
||||
if (ieeer < -precision) count--;
|
||||
val = input + tuf * count.as_type<uint, T>();
|
||||
val = input + tuf * count.as_type<ulong, T>();
|
||||
}
|
||||
return _round_tick(tuf, val);
|
||||
}
|
||||
|
||||
public static T tickdown<T>(this TickFactors tick, T input, uint count = 1)
|
||||
public static T tickdown<T>(this TickFactors tick, T input, ulong count = 1)
|
||||
where T : IFloatingPointIeee754<T> =>
|
||||
tickdown(tick, input, count, TickUtils.precision_const<T>());
|
||||
|
||||
public static T tickdown<T>(this TickFactors tick, T input, uint count, T precision)
|
||||
public static T tickdown<T>(this TickFactors tick, T input, ulong count, T precision)
|
||||
where T : IFloatingPointIeee754<T> =>
|
||||
tickdown<T, NumericCastOutConverter<T>>(tick, input, count, precision);
|
||||
|
||||
public static T tickdown<T, TFromUInt32>(this TickFactors tick, T input, uint count, T precision)
|
||||
public static T tickdown<T, TFromUInt64>(this TickFactors tick, T input, ulong count, T precision)
|
||||
where T : IFloatingPointIeee754<T>
|
||||
where TFromUInt32 : IConverter<uint, T> {
|
||||
where TFromUInt64 : IConverter<ulong, T> {
|
||||
if (!input.is_real()) {
|
||||
return input;
|
||||
}
|
||||
@@ -337,30 +407,42 @@ namespace mbts {
|
||||
if (ieeer > precision) {
|
||||
count--;
|
||||
}
|
||||
var result = input - tuf * TFromUInt32.convert(count);
|
||||
var result = input - tuf * TFromUInt64.convert(count);
|
||||
return _round_tick(tuf, result);
|
||||
}
|
||||
|
||||
public static double tickdown(
|
||||
this RegionalTickFactors regional_tick, double input, long count = 1) =>
|
||||
count switch {
|
||||
> 0 => tickdown<double>(regional_tick, input, (ulong)count),
|
||||
< 0 => tickup<double>(regional_tick, input, (ulong)count),
|
||||
0 => input,
|
||||
};
|
||||
|
||||
public static double tickdown(
|
||||
this RegionalTickFactors regional_tick, double input, ulong count) =>
|
||||
tickdown<double>(regional_tick, input, count);
|
||||
|
||||
public static T tickdown<T>(this RegionalTickFactors regional_tick, T input)
|
||||
where T : IFloatingPointIeee754<T> =>
|
||||
regional_tick.tickdown(input, 1);
|
||||
|
||||
public static T tickdown<T>(this RegionalTickFactors regional_tick, T input, uint count)
|
||||
public static T tickdown<T>(this RegionalTickFactors regional_tick, T input, ulong count)
|
||||
where T : IFloatingPointIeee754<T> =>
|
||||
tickdown(regional_tick, input, count, TickUtils.precision_const<T>());
|
||||
|
||||
public static T tickdown<T>(
|
||||
this RegionalTickFactors regional_tick, T input, uint count, T precision)
|
||||
this RegionalTickFactors regional_tick, T input, ulong count, T precision)
|
||||
where T : IFloatingPointIeee754<T> =>
|
||||
tickdown<T, NumericCastOutConverter<T>, NumericCastOutConverter<T>, NumericCastInConverter<T>>(
|
||||
regional_tick, input, count, precision);
|
||||
|
||||
public static T tickdown<T, TFromUInt32, TFromDouble, TToUInt16>(
|
||||
this RegionalTickFactors regional_tick, T input, uint count, T precision)
|
||||
this RegionalTickFactors regional_tick, T input, ulong count, T precision)
|
||||
where T : IFloatingPointIeee754<T>
|
||||
where TFromDouble : IConverter<double, T>
|
||||
where TFromUInt32 : IConverter<uint, T>
|
||||
where TToUInt16 : IConverter<T, uint> {
|
||||
where TFromUInt32 : IConverter<ulong, T>
|
||||
where TToUInt16 : IConverter<T, ulong> {
|
||||
if (!precision.is_real_positive()) {
|
||||
throw new ArgumentException("Must be a real positive number.", nameof(precision));
|
||||
}
|
||||
@@ -423,16 +505,6 @@ namespace mbts {
|
||||
CashPerPoint = 1
|
||||
};
|
||||
|
||||
public static bool try_get_tickdiff(this RegionalTickFactors regional_tick, double minuend, double subtrahend, out int n_low, out int n_high) {
|
||||
if (DoubleHelper.IsNaNOrInfinity(minuend) || DoubleHelper.IsNaNOrInfinity(subtrahend)) {
|
||||
n_low = default;
|
||||
n_high = default;
|
||||
return false;
|
||||
}
|
||||
(n_low, n_high) = tickdiff_unchecked(regional_tick, minuend, subtrahend);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the difference, in tick units, between <paramref name="minuend"/> and <paramref name="subtrahend"/>
|
||||
/// </summary>
|
||||
@@ -441,31 +513,14 @@ namespace mbts {
|
||||
/// </remarks>
|
||||
/// <param name="minuend">The minuend</param>
|
||||
/// <param name="subtrahend">The subtrahend</param>
|
||||
internal static (int n_low, int n_high) tickdiff_unchecked(this RegionalTickFactors regional_tick, double minuend, double subtrahend) {
|
||||
Log.assert(minuend.is_real() && subtrahend.is_real());
|
||||
|
||||
double ltie = regional_tick.low_tick_inclusive_end;
|
||||
double raw_diff = minuend - subtrahend;
|
||||
|
||||
if (minuend < ltie) {
|
||||
if (subtrahend < ltie) {
|
||||
return ((int)MathUtils.round_fractional_units(raw_diff, regional_tick.low_tick.tick_unit_frac()), 0);
|
||||
}
|
||||
double low_diff = ltie - minuend;
|
||||
double high_diff = raw_diff - low_diff;
|
||||
int n_low = (int)MathUtils.round_fractional_units(low_diff, regional_tick.low_tick.tick_unit_frac());
|
||||
int n_high = (int)MathUtils.round_fractional_units(high_diff, regional_tick.high_tick.tick_unit_frac());
|
||||
return (n_low, n_high);
|
||||
public static long? tickdiff(this RegionalTickFactors regional_tick, double minuend, double subtrahend) {
|
||||
if (!minuend.is_real() || !subtrahend.is_real()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (subtrahend < ltie) {
|
||||
double high_diff = minuend - ltie;
|
||||
double low_diff = raw_diff - high_diff;
|
||||
int n_low = (int)MathUtils.round_fractional_units(low_diff, regional_tick.high_tick.tick_unit_frac());
|
||||
int n_high = (int)MathUtils.round_fractional_units(high_diff, regional_tick.high_tick.tick_unit_frac());
|
||||
return (n_low, n_high);
|
||||
}
|
||||
return (0, (int)MathUtils.round_fractional_units(raw_diff, regional_tick.high_tick.tick_unit_frac()));
|
||||
var minuend_n = TickUtils.count_ticks(regional_tick, minuend);
|
||||
var subtrahend_n = TickUtils.count_ticks(regional_tick, subtrahend);
|
||||
var n = minuend_n - subtrahend_n;
|
||||
return n;
|
||||
}
|
||||
|
||||
public static double round(this TickFactors tick, double input) =>
|
||||
@@ -502,6 +557,10 @@ namespace mbts {
|
||||
new(tick_base ?? tick_factors.tick_base, tick_unit ?? tick_factors.tick_unit);
|
||||
|
||||
static char _separator(TickFactors tick) {
|
||||
if (tick.tick_base == 1 && tick.tick_unit > tick.tick_base &&
|
||||
MathUtils.is_int(tick.tick_unit / tick.tick_base)) {
|
||||
return '.';
|
||||
}
|
||||
double tb_log10 = Math.Log10(tick.tick_base);
|
||||
return tb_log10 >= 1 && tb_log10 % 1 == 0 ? '.' : '-';
|
||||
}
|
||||
|
||||
@@ -1,34 +1,62 @@
|
||||
using bts.api;
|
||||
using bts.utils;
|
||||
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.user.settings;
|
||||
|
||||
using Spark.Core.Data;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace bts.data.ticks {
|
||||
class TickFactorsFactory {
|
||||
static readonly RegionalTickFactors _default_rtf = new RegionalTickFactors(new TickFactors(1000, 1), new TickFactors(1000, 1), double.MaxValue);
|
||||
readonly Dictionary<SpecKey, RegionalTickFactors> _exchange_ticks = new Dictionary<SpecKey, RegionalTickFactors>();
|
||||
readonly Dictionary<uint, RegionalTickFactors> _sec_ticks = new();
|
||||
readonly Dictionary<SpecKey, RegionalTickFactors> _spec_ticks = new();
|
||||
|
||||
public RegionalTickFactors default_rtf => _default_rtf;
|
||||
|
||||
public RegionalTickFactors get(ContractSpecification spec) =>
|
||||
_exchange_ticks.GetOrAdd(new SpecKey(spec), _create_rtf);
|
||||
_spec_ticks.GetOrAdd(new SpecKey(spec), _create_rtf);
|
||||
|
||||
public RegionalTickFactors get(ContractSpecificationImmutable spec) =>
|
||||
_exchange_ticks.GetOrAdd(new SpecKey(spec), _create_rtf);
|
||||
|
||||
public RegionalTickFactors get(SecurityImmutable security) =>
|
||||
security.HasContractSpec ? get(security.ContractSpec) : _default_rtf;
|
||||
_spec_ticks.GetOrAdd(new SpecKey(spec), _create_rtf);
|
||||
|
||||
public RegionalTickFactors get(SecurityImmutable? security) =>
|
||||
security.HasValue ? get(security.Value) : _default_rtf;
|
||||
security.HasValue ?
|
||||
_sec_ticks.GetOrAdd(security.Value.Id, _ => _create_rtf(security.Value)) :
|
||||
_default_rtf;
|
||||
|
||||
static RegionalTickFactors _create_rtf(SecurityImmutable? security) {
|
||||
var meta = SecurityMetadataStore.instance.get(security.Value);
|
||||
return meta?.PriceRules?.Any() ?? false ?
|
||||
_create_rtf(meta.Value.PriceRules.ToList()) :
|
||||
_create_rtf(new SpecKey(security.Value.spec_or_default()));
|
||||
}
|
||||
|
||||
static RegionalTickFactors _create_rtf(
|
||||
IList<api.domain.TickDisplayRuleImmutable> rules) {
|
||||
if (rules.is_null_or_empty()) {
|
||||
throw new ArgumentException("Rules is null or empty", nameof(rules));
|
||||
}
|
||||
if (rules.Count > 2) {
|
||||
throw new ArgumentException("Rules cannot have more than 2 items (backwards compatibility)");
|
||||
}
|
||||
var low_tick = rules[0].TickDisplay.TickFactors;
|
||||
var high_tick_idx = rules.Count > 1 ? 1 : 0;
|
||||
var high_tick = rules[high_tick_idx].TickDisplay.TickFactors;
|
||||
var low_tick_end = rules.Count > 1 ? rules[0].MaxValue : double.MaxValue;
|
||||
return new(low_tick.to_tick_factors(), high_tick.to_tick_factors(), low_tick_end);
|
||||
}
|
||||
|
||||
static RegionalTickFactors _create_rtf(SpecKey key) {
|
||||
double tick_base = key.denominator;
|
||||
if (tick_base == 0) {
|
||||
// indicates tick is a decimal
|
||||
tick_base = 100;
|
||||
// Tick in pennies if units are < 1 (i.e., fractional)
|
||||
tick_base = key.low_tick > 1 && key.high_tick > 1 ? 1 : 100;
|
||||
}
|
||||
return _create_rtf(key.low_tick, key.high_tick, key.price_break, tick_base, key.display_digits);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.data.ticks;
|
||||
using bts.utils;
|
||||
using mbts.co2;
|
||||
|
||||
using mbts.user.settings;
|
||||
|
||||
using Spark.Core;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
@@ -36,6 +38,9 @@ namespace mbts.spark {
|
||||
public static IComparer<double> price_comparer(this ContractSpecificationImmutable spec) =>
|
||||
MathUtils.epsilon_comparer.wrap<double, double>(p => spec.round_to_tick(p));
|
||||
|
||||
public static IComparer<double> price_comparer(this RegionalTickFactors rtick) =>
|
||||
MathUtils.epsilon_comparer.wrap<double, double>(p => rtick.round_to_tick(p));
|
||||
|
||||
public static double ticksize(this ContractSpecificationImmutable spec, double price) {
|
||||
if (!spec.LowTick.is_real_positive()) {
|
||||
return spec.HighTick;
|
||||
@@ -48,8 +53,23 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public static double ticksize(this RegionalTickFactors rtick, double price) {
|
||||
var low_tick = rtick.low_tick.tick_unit_frac();
|
||||
var high_tick = rtick.high_tick.tick_unit_frac();
|
||||
var price_break = rtick.low_tick_inclusive_end;
|
||||
if (!low_tick.is_real_positive()) {
|
||||
return high_tick;
|
||||
} else if (!high_tick.is_real_positive()) {
|
||||
return low_tick;
|
||||
} else if (!price.is_real() || !price_break.is_real()) {
|
||||
return low_tick;
|
||||
} else {
|
||||
return price > price_break ? high_tick : low_tick;
|
||||
}
|
||||
}
|
||||
|
||||
public static string format(this ContractSpecificationImmutable spec, double price, bool in_tick_terms) =>
|
||||
in_tick_terms ?
|
||||
in_tick_terms ?
|
||||
_get(spec).format_without_decimal_part(price / spec.ticksize(price)) :
|
||||
_get(spec).format(price);
|
||||
|
||||
@@ -87,18 +107,13 @@ namespace mbts.spark {
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static int? tickdiff(this ContractSpecificationImmutable spec, double x, double y) {
|
||||
if (get_regional_ticks(spec).try_get_tickdiff(x, y, out int n_low, out int n_high))
|
||||
return n_low + n_high;
|
||||
return null;
|
||||
}
|
||||
public static long? tickdiff(this ContractSpecificationImmutable spec, double x, double y) =>
|
||||
get_regional_ticks(spec).tickdiff(x, y);
|
||||
|
||||
public static int tickdiff_unchecked(this ContractSpecificationImmutable spec, double x, double y) {
|
||||
(int n_low, int n_high) = get_regional_ticks(spec).tickdiff_unchecked(x, y);
|
||||
return n_low + n_high;
|
||||
}
|
||||
public static long? tickdiff(this SecurityImmutable sec, double x, double y) =>
|
||||
get_regional_ticks(sec).tickdiff(x, y);
|
||||
|
||||
public static double mid_tick(ContractSpecificationImmutable spec, double bid, double ask) {
|
||||
public static double mid_tick(RegionalTickFactors spec, double bid, double ask) {
|
||||
double raw_mid;
|
||||
if (double.IsNaN(bid)) {
|
||||
if (double.IsNaN(ask)) return double.NaN;
|
||||
@@ -114,8 +129,8 @@ namespace mbts.spark {
|
||||
raw_mid - (raw_mid % tick_size);
|
||||
}
|
||||
|
||||
public static TickFactors deduce_ticks(IEnumerable<ContractSpecification> contract_specs) {
|
||||
var ticks = new HashSet<TickFactors>(contract_specs.Select(_get).SelectMany(rtf => new[] { rtf.low_tick, rtf.high_tick }));
|
||||
public static TickFactors deduce_ticks(IEnumerable<SecurityImmutable> securities) {
|
||||
var ticks = new HashSet<TickFactors>(securities.Select(_get).SelectMany(rtf => new[] { rtf.low_tick, rtf.high_tick }));
|
||||
switch (ticks.Count) {
|
||||
case 0: return _exchange_ticks.default_rtf.low_tick;
|
||||
case 1: return ticks.First();
|
||||
@@ -126,30 +141,27 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public static RegionalTickFactors get_tick_factors(ContractSpecificationImmutable spec) =>
|
||||
_get(spec);
|
||||
|
||||
public static TickFactors tick_factors(this ContractSpecificationImmutable spec) =>
|
||||
_get(spec).low_tick;
|
||||
public static TickFactors tick_factors(this SecurityImmutable sec) =>
|
||||
_get(sec).low_tick;
|
||||
|
||||
public static double tickup(this ContractSpecificationImmutable spec, double value) =>
|
||||
_get(spec).tickup(value);
|
||||
|
||||
public static double tickup(this ContractSpecificationImmutable spec, double value, int count) {
|
||||
public static double tickup(this ContractSpecificationImmutable spec, double value, long count) {
|
||||
uint bl = 3;
|
||||
var res = value * bl;
|
||||
if (count == 0) return value;
|
||||
if (count < 0) return _get(spec).tickdown(value, (uint)(-count));
|
||||
else return _get(spec).tickup(value, (uint)count);
|
||||
if (count < 0) return _get(spec).tickdown(value, (ulong)-count);
|
||||
else return _get(spec).tickup(value, (ulong)count);
|
||||
}
|
||||
|
||||
public static double tickdown(this ContractSpecificationImmutable spec, double value) =>
|
||||
_get(spec).tickdown(value);
|
||||
|
||||
public static double tickdown(this ContractSpecificationImmutable spec, double value, int count) {
|
||||
public static double tickdown(this ContractSpecificationImmutable spec, double value, long count) {
|
||||
if (count == 0) return value;
|
||||
if (count < 0) return _get(spec).tickup(value, (uint)(-count));
|
||||
else return _get(spec).tickdown(value, (uint)count);
|
||||
if (count < 0) return _get(spec).tickup(value, (ulong)-count);
|
||||
else return _get(spec).tickdown(value, (ulong)count);
|
||||
}
|
||||
|
||||
public static bool tick_equals<T>(
|
||||
@@ -168,7 +180,7 @@ namespace mbts.spark {
|
||||
x => (int)MathUtils.round_fractional_units(x, contract.LowTick));
|
||||
|
||||
public static IEqualityComparer<T> tick_equality_comparer<T>(
|
||||
this ContractSpecificationImmutable contract) where T : IFloatingPoint<T> =>
|
||||
this ContractSpecificationImmutable contract) where T : IFloatingPoint<T> =>
|
||||
EqualityComparer.Create<T>(
|
||||
(a, b) => tick_equals(contract, a, b),
|
||||
x => (int)MathUtils.round_fractional_units(
|
||||
@@ -177,85 +189,107 @@ namespace mbts.spark {
|
||||
public static int compare_tick_fallback_to_double(this ContractSpecificationImmutable contract, double x, double y) =>
|
||||
tick_equals(contract, x, y) ? 0 : x.CompareTo(y);
|
||||
|
||||
public static double round_to_tick(this ContractSpecificationImmutable spec, double value) {
|
||||
public static double round_to_tick(this ContractSpecificationImmutable spec, double value) =>
|
||||
round_to_tick(spec.ticksize(value), value);
|
||||
|
||||
public static decimal round_to_tick(this ContractSpecificationImmutable spec, decimal value) =>
|
||||
round_to_tick((decimal)spec.ticksize((double)value), value);
|
||||
|
||||
public static double round_to_tick(this RegionalTickFactors rtick, double value) =>
|
||||
round_to_tick(rtick.ticksize(value), value);
|
||||
|
||||
public static decimal round_to_tick(this RegionalTickFactors rtick, decimal value) =>
|
||||
round_to_tick((decimal)rtick.ticksize((double)value), value);
|
||||
|
||||
public static double round_to_tick(double tick_size, double value) {
|
||||
double intpart = (long)value;
|
||||
double floatpart = value % 1;
|
||||
double tick = spec.ticksize(value);
|
||||
floatpart = ((int)Math.Round(floatpart / tick)) * tick;
|
||||
floatpart = ((int)Math.Round(floatpart / tick_size)) * tick_size;
|
||||
return intpart + floatpart;
|
||||
}
|
||||
|
||||
public static decimal round_to_tick(this ContractSpecificationImmutable spec, decimal value) {
|
||||
public static decimal round_to_tick(decimal tick_size, decimal value) {
|
||||
decimal intpart = (long)value;
|
||||
decimal floatpart = value % 1;
|
||||
decimal tick = (decimal)spec.ticksize((double)value);
|
||||
floatpart = ((int)(floatpart / tick)) * tick;
|
||||
floatpart = ((int)(floatpart / tick_size)) * tick_size;
|
||||
return intpart + floatpart;
|
||||
}
|
||||
|
||||
public static int? full_ticks_from_zero(this ContractSpecificationImmutable spec, double value) =>
|
||||
full_ticks_from(spec, 0, value);
|
||||
|
||||
public static int? full_ticks_from(this ContractSpecificationImmutable spec, double origin, double value) {
|
||||
if (!value.is_real() || !origin.is_real())
|
||||
public static long? count_ticks(this RegionalTickFactors rtick, double value) {
|
||||
if (!rtick.is_valid()) {
|
||||
return null;
|
||||
if (value < origin) {
|
||||
if (spec.LowTick.is_real_positive()) {
|
||||
if (spec.HighTick.is_real_positive() && spec.PriceBreak < origin && spec.PriceBreak > value) {
|
||||
var ticks_to_break = MathUtils.round_up((spec.PriceBreak - origin) / spec.HighTick, _precision_const);
|
||||
var low_ticks_remainder = MathUtils.round_up((value - spec.PriceBreak) / spec.LowTick, _precision_const);
|
||||
return (int)(ticks_to_break + low_ticks_remainder);
|
||||
} else {
|
||||
return (int)MathUtils.round_up((value - origin) / spec.LowTick, _precision_const);
|
||||
}
|
||||
} else if (spec.HighTick.is_real_positive()) {
|
||||
return (int)MathUtils.round_up((value - origin) / spec.HighTick, _precision_const);
|
||||
}
|
||||
} else {
|
||||
if (spec.HighTick.is_real_positive()) {
|
||||
if (spec.LowTick.is_real_positive() && spec.PriceBreak > origin && spec.PriceBreak < value) {
|
||||
var ticks_to_break = MathUtils.round_down((spec.PriceBreak - origin) / spec.LowTick, _precision_const);
|
||||
var high_ticks_remainder = MathUtils.round_down((value - spec.PriceBreak) / spec.HighTick, _precision_const);
|
||||
return (int)(ticks_to_break + high_ticks_remainder);
|
||||
} else {
|
||||
return (int)MathUtils.round_down(value / spec.HighTick, _precision_const);
|
||||
}
|
||||
} else if (spec.LowTick.is_real_positive()) {
|
||||
return (int)MathUtils.round_down(value / spec.LowTick, _precision_const);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
var n = _count_ticks(
|
||||
rtick.low_tick.tick_unit_frac(),
|
||||
rtick.low_tick_inclusive_end,
|
||||
rtick.high_tick.tick_unit_frac(),
|
||||
0,
|
||||
value);
|
||||
var orig_n = n;
|
||||
// Enforce proper rounding
|
||||
var rounded_val = rtick.round_to_tick(value);
|
||||
var half_tick = rtick.get_tick_for(value).tick_unit_frac() / 2;
|
||||
if (n < 0 && (value - rounded_val) > half_tick) {
|
||||
++n;
|
||||
} else if (n > 0 && (rounded_val - value) > half_tick) {
|
||||
--n;
|
||||
}
|
||||
Log.assert(n == orig_n);
|
||||
return n;
|
||||
}
|
||||
|
||||
public static RegionalTickFactors get_regional_ticks(ContractSpecificationImmutable? spec) =>
|
||||
spec.HasValue ? _get(spec.Value) : _exchange_ticks.default_rtf;
|
||||
|
||||
public static RegionalTickFactors get_regional_ticks(SecurityImmutable security) =>
|
||||
get_regional_ticks(security.spec_or_default());
|
||||
public static RegionalTickFactors regional_ticks(this SecurityImmutable security) =>
|
||||
_exchange_ticks.get(security);
|
||||
|
||||
public static ContractSpecificationImmutable multiply_tick_size(ContractSpecificationImmutable spec, uint factor) {
|
||||
factor = fit_ticks_per_row_to_dollars(spec, factor);
|
||||
return spec.with(low_tick: spec.LowTick * factor, high_tick: spec.HighTick * factor);
|
||||
public static RegionalTickFactors get_regional_ticks(SecurityImmutable? security) =>
|
||||
_exchange_ticks.get(security);
|
||||
|
||||
public static TickFactors get_tick(SecurityImmutable? security, double value) {
|
||||
var rtick = get_regional_ticks(security);
|
||||
var tick = rtick.low_tick_inclusive_end > value ? rtick.high_tick : rtick.low_tick;
|
||||
return tick;
|
||||
}
|
||||
|
||||
public static double normalize_price(ContractSpecificationImmutable spec, double price) {
|
||||
var ticks = spec.full_ticks_from_zero(price);
|
||||
return ticks.HasValue ? spec.tickup(0, ticks.Value) : price;
|
||||
public static RegionalTickFactors multiply_tick_size(RegionalTickFactors spec, uint factor) {
|
||||
factor = _fit_ticks_per_row_to_dollars(spec, factor) ?? 1;
|
||||
return new(
|
||||
new(spec.low_tick.tick_base, spec.low_tick.tick_unit * factor),
|
||||
new(spec.high_tick.tick_base, spec.high_tick.tick_unit * factor),
|
||||
spec.low_tick_inclusive_end);
|
||||
}
|
||||
|
||||
public static double normalize_price(ContractSpecificationImmutable? spec, double price) {
|
||||
return spec.HasValue ? normalize_price(spec.Value, price) : price;
|
||||
public static double normalize_price(RegionalTickFactors rtick, double price) {
|
||||
if (!price.is_real()) {
|
||||
return price;
|
||||
}
|
||||
var ticks = rtick.count_ticks(price);
|
||||
if (!ticks.HasValue) {
|
||||
return price;
|
||||
}
|
||||
var normalized = rtick.tick(0, ticks.Value);
|
||||
var tick = price > rtick.low_tick_inclusive_end ? rtick.high_tick : rtick.low_tick;
|
||||
// Diff should always be < 1/2 tick
|
||||
Log.assert((normalized - price).abs() < tick.tick_unit_frac() * 0.5);
|
||||
return normalized;
|
||||
}
|
||||
|
||||
public static uint fit_ticks_per_row_to_dollars(ContractSpecificationImmutable spec, uint ticks_per_row) =>
|
||||
_fit_ticks_per_row_to_dollars(spec, ticks_per_row) ?? 1;
|
||||
public static uint fit_ticks_per_row_to_dollars(SecurityImmutable sec, uint ticks_per_row) =>
|
||||
_fit_ticks_per_row_to_dollars(sec, ticks_per_row) ?? 1;
|
||||
|
||||
static uint? _fit_ticks_per_row_to_dollars(ContractSpecificationImmutable spec, uint ticks_per_row) {
|
||||
public static uint fit_ticks_per_row_to_dollars(RegionalTickFactors rtick, uint ticks_per_row) =>
|
||||
_fit_ticks_per_row_to_dollars(rtick, ticks_per_row) ?? 1;
|
||||
|
||||
static uint? _fit_ticks_per_row_to_dollars(SecurityImmutable sec, uint ticks_per_row) =>
|
||||
_fit_ticks_per_row_to_dollars(sec.regional_ticks(), ticks_per_row);
|
||||
|
||||
static uint? _fit_ticks_per_row_to_dollars(RegionalTickFactors tf, uint ticks_per_row) {
|
||||
if (ticks_per_row == 0)
|
||||
return null;
|
||||
// it is assumed that low tick will be the same as high tick;
|
||||
// however, best to double check - return null if this isn't the case (let the caller decide what to do).
|
||||
var tf = get_tick_factors(spec);
|
||||
if (!tf.low_tick.Equals(tf.high_tick) || !tf.is_valid() || tf.low_tick.is_integer())
|
||||
return null;
|
||||
var ticks_per_dollar = (uint)(tf.low_tick.tick_base / tf.low_tick.tick_unit);
|
||||
@@ -283,7 +317,61 @@ namespace mbts.spark {
|
||||
return null;
|
||||
}
|
||||
|
||||
static RegionalTickFactors _get(ContractSpecification spec) => _exchange_ticks.get(spec);
|
||||
static long? _count_ticks(
|
||||
double low_tick, double price_break, double high_tick, double from, double to) {
|
||||
if (!from.is_real() || !to.is_real()) {
|
||||
return null;
|
||||
}
|
||||
var has_low = low_tick.is_real_positive();
|
||||
var has_high = high_tick.is_real_positive();
|
||||
if (!has_low) {
|
||||
if (!has_high) {
|
||||
return null;
|
||||
}
|
||||
low_tick = high_tick;
|
||||
} else if (!has_high) {
|
||||
high_tick = low_tick;
|
||||
}
|
||||
if (price_break.is_nan()) {
|
||||
// Take price_break out of play
|
||||
price_break = double.PositiveInfinity;
|
||||
}
|
||||
// Allow core impl to assume: 'from <= to'
|
||||
(from, to) = (from, to).ordered(out var negate);
|
||||
var n_ticks = _count_asc_ticks(low_tick, high_tick, from, to, price_break);
|
||||
return negate ? -n_ticks : n_ticks;
|
||||
}
|
||||
|
||||
static long _count_asc_ticks(
|
||||
double low_tick, double high_tick, double from, double to, double price_break) {
|
||||
Contract.Requires(from <= to);
|
||||
Contract.Requires(low_tick.is_real_positive() && high_tick.is_real_positive());
|
||||
Log.assert(from <= to);
|
||||
checked {
|
||||
if (to <= price_break) {
|
||||
// All ticks are below price_break
|
||||
return count_single_tick_sz(low_tick, from, to);
|
||||
}
|
||||
if (from >= price_break) {
|
||||
// All ticks are above price_break
|
||||
return count_single_tick_sz(high_tick, from, to);
|
||||
}
|
||||
// Ticks cross the price break. Compute low and high ticks separately
|
||||
var low_ticks = count_single_tick_sz(low_tick, from, price_break);
|
||||
var high_ticks = count_single_tick_sz(high_tick, price_break, to);
|
||||
var total_ticks = low_ticks + high_ticks;
|
||||
return total_ticks;
|
||||
|
||||
static long count_single_tick_sz(
|
||||
double tick_sz, double from, double to) {
|
||||
var from_n = (long)MathUtils.round_fractional_units(from, tick_sz);
|
||||
var to_n = (long)MathUtils.round_fractional_units(to, tick_sz);
|
||||
return to_n - from_n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static RegionalTickFactors _get(SecurityImmutable sec) => _exchange_ticks.get(sec);
|
||||
static RegionalTickFactors _get(ContractSpecificationImmutable spec) => _exchange_ticks.get(spec);
|
||||
|
||||
public static class CompareUtils {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace bts.reactive {
|
||||
namespace Spark.Core.Reactive {
|
||||
using System;
|
||||
|
||||
public static class ObserverExtensions {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace bts.reactive {
|
||||
namespace Spark.Core.Reactive {
|
||||
using System;
|
||||
using System.Reactive;
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace bts.reactive {
|
||||
throw new ArgumentNullException(nameof(selector));
|
||||
}
|
||||
|
||||
return new AnonymousObserver<TOuter>(outer_val => observer.OnNext(selector(outer_val)), observer.OnError, observer.OnCompleted);
|
||||
return new SelectorObserver<TOuter, TInner>(observer, selector);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Spark.Core.Reactive {
|
||||
using System;
|
||||
|
||||
public static class SelectorObserver {
|
||||
public static SelectorObserverBuilder<T> builder<T>(IObserver<T> observer) =>
|
||||
new(observer);
|
||||
}
|
||||
|
||||
public class SelectorObserver<T, TResult> : IObserver<T> {
|
||||
readonly IObserver<TResult> _observer;
|
||||
readonly Func<T, TResult> _selector;
|
||||
|
||||
public SelectorObserver(IObserver<TResult> observer, Func<T, TResult> selector) {
|
||||
_observer = observer;
|
||||
_selector = selector;
|
||||
}
|
||||
|
||||
public void OnCompleted() =>
|
||||
_observer.OnCompleted();
|
||||
|
||||
public void OnError(Exception error) =>
|
||||
_observer.OnError(error);
|
||||
|
||||
public void OnNext(T value) =>
|
||||
_observer.OnNext(_selector(value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Spark.Core.Reactive {
|
||||
using System;
|
||||
|
||||
public class SelectorObserverBuilder<TResult> {
|
||||
readonly IObserver<TResult> _observer;
|
||||
|
||||
public SelectorObserverBuilder(IObserver<TResult> observer) =>
|
||||
_observer = observer;
|
||||
|
||||
public SelectorObserver<T, TResult> wrap<T>(Func<T, TResult> selector) =>
|
||||
new(_observer, selector);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
namespace bts.utils {
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
|
||||
public static class TupleExtensions {
|
||||
public static (T1, T2, T3, T4) with<T1, T2, T3, T4>(this (T1, T2) tuple, T3 value3, T4 value4) =>
|
||||
@@ -36,5 +38,20 @@
|
||||
|
||||
public static bool none(this (bool, bool, bool, bool) tuple) =>
|
||||
!tuple.any();
|
||||
|
||||
public static T sum<T>(this (T, T) vals) where T : IAdditionOperators<T, T, T> =>
|
||||
vals.Item1 + vals.Item2;
|
||||
|
||||
public static (T, T) ordered<T>(this (T, T) vals, IComparer<T> comparer = null) where T : IComparable<T> {
|
||||
comparer ??= Comparer<T>.Default;
|
||||
return comparer.Compare(vals.Item1, vals.Item2) < 0 ?
|
||||
(vals.Item2, vals.Item1) : vals;
|
||||
}
|
||||
|
||||
public static (T, T) ordered<T>(this (T, T) vals, out bool negated, IComparer<T> comparer = null) where T : IComparable<T> {
|
||||
comparer ??= Comparer<T>.Default;
|
||||
return (negated = comparer.Compare(vals.Item1, vals.Item2) > 0) ?
|
||||
(vals.Item2, vals.Item1) : vals;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,17 @@ using bts.resources;
|
||||
using bts.test;
|
||||
using bts.utils;
|
||||
using bts.utils.proto;
|
||||
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace bts.tests.security {
|
||||
using RegionalTickFactors = mbts.user.settings.RegionalTickFactors;
|
||||
|
||||
static class TickTestHelper {
|
||||
public static IEnumerable<double> generate_test_prices(double tick_size, int radius = 200, double origin_price = 0) {
|
||||
Assert.That(radius > 0);
|
||||
@@ -29,54 +34,98 @@ namespace bts.tests.security {
|
||||
});
|
||||
}
|
||||
|
||||
public static IEnumerable<double> generate_test_prices(IEnumerable<double> price_base_set, IEnumerable<double> adjustments) =>
|
||||
price_base_set.Concat(
|
||||
adjustments.SelectMany(a => price_base_set.Select(p => p + a)));
|
||||
public static IEnumerable<double> generate_test_prices(IEnumerable<double> price_base_set, IEnumerable<double> adjustments) {
|
||||
price_base_set = price_base_set.Where(p => p < long.MaxValue && p > long.MinValue);
|
||||
return price_base_set.Concat(
|
||||
adjustments.SelectMany(
|
||||
a => price_base_set
|
||||
.Where(p => a > 0 ?
|
||||
(long.MaxValue - p > a) :
|
||||
(long.MinValue + a) < a)
|
||||
.Select(p => p + a)));
|
||||
}
|
||||
|
||||
public static IEnumerable<ContractSpecificationImmutable> read_contract_specs(string file_path) =>
|
||||
StreamHelper.ReadProtos<ContractSpecification>(ResourceHelper.LoadResource(file_path)).Select(s => new ContractSpecificationImmutable(s));
|
||||
|
||||
public static int? full_ticks_from_zero(ContractSpecificationImmutable tick, double value) {
|
||||
public static long? full_ticks_from_zero(ContractSpecificationImmutable tick, double value) =>
|
||||
count_ticks(tick.LowTick, tick.HighTick, tick.PriceBreak, value);
|
||||
|
||||
public static long? count_ticks(RegionalTickFactors tick, double value) =>
|
||||
count_ticks(
|
||||
tick.low_tick.tick_unit / tick.low_tick.tick_base,
|
||||
tick.high_tick.tick_unit / tick.high_tick.tick_base,
|
||||
tick.low_tick_inclusive_end,
|
||||
value);
|
||||
|
||||
public static long? count_ticks(double low_tick_sz, double high_tick_sz, double price_break, double value) {
|
||||
if (!value.is_real())
|
||||
return null;
|
||||
if (MathUtils.marginal_equals(value, 0, TickUtils.precision_const<double>()))
|
||||
return 0;
|
||||
bool low_tick_valid = tick.LowTick.is_real_positive();
|
||||
bool high_tick_valid = tick.HighTick.is_real_positive();
|
||||
if (low_tick_valid) {
|
||||
bool low_tick_valid = low_tick_sz.is_real_positive();
|
||||
bool high_tick_valid = high_tick_sz.is_real_positive();
|
||||
if (!low_tick_valid || !high_tick_valid) {
|
||||
if (high_tick_valid) {
|
||||
if (value < 0) {
|
||||
if (tick.PriceBreak > value) {
|
||||
if (tick.PriceBreak >= 0) {
|
||||
return (int)MathUtils.round_up(value / tick.LowTick, TickUtils.precision_const<double>());
|
||||
} else {
|
||||
var high_ticks = (int)MathUtils.round_up(tick.PriceBreak / tick.HighTick, TickUtils.precision_const<double>());
|
||||
var low_ticks = (int)MathUtils.round_up((value - tick.PriceBreak) / tick.LowTick, TickUtils.precision_const<double>());
|
||||
return high_ticks + low_ticks;
|
||||
}
|
||||
} else {
|
||||
return (int)MathUtils.round_up(value / (tick.PriceBreak.is_real() ? tick.HighTick : tick.LowTick), TickUtils.precision_const<double>());
|
||||
}
|
||||
} else {
|
||||
if (tick.PriceBreak < value) {
|
||||
if (tick.PriceBreak <= 0) {
|
||||
return (int)MathUtils.round_down(value / tick.HighTick, TickUtils.precision_const<double>());
|
||||
} else {
|
||||
var low_ticks = (int)MathUtils.round_down(tick.PriceBreak / tick.LowTick, TickUtils.precision_const<double>());
|
||||
var high_ticks = (int)MathUtils.round_down((value - tick.PriceBreak) / tick.HighTick, TickUtils.precision_const<double>());
|
||||
return low_ticks + high_ticks;
|
||||
}
|
||||
} else {
|
||||
return (int)MathUtils.round_down(value / (tick.PriceBreak.is_real() ? tick.LowTick : tick.HighTick), TickUtils.precision_const<double>());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return (int)MathUtils.round_down_abs(value / tick.LowTick, TickUtils.precision_const<double>());
|
||||
return count_ticks(value, high_tick_sz);
|
||||
}
|
||||
} else if (high_tick_valid) {
|
||||
return (int)MathUtils.round_down_abs(value / tick.HighTick, TickUtils.precision_const<double>());
|
||||
} else {
|
||||
return null;
|
||||
return low_tick_valid ? count_ticks(value, low_tick_sz) : null;
|
||||
}
|
||||
|
||||
// Both low and high ticks are valid
|
||||
if (!price_break.is_real()) {
|
||||
// Default to low_tick_sz if price_break isn't a factor
|
||||
return count_ticks(value, low_tick_sz);
|
||||
}
|
||||
if (value < 0) {
|
||||
if (price_break <= value) {
|
||||
// price_break <= value < 0
|
||||
return count_ticks(value, high_tick_sz);
|
||||
}
|
||||
if (price_break >= 0) {
|
||||
// value < 0 <= price_break
|
||||
return count_ticks(value, low_tick_sz);
|
||||
}
|
||||
// value < price_break < 0
|
||||
return count_ticks(price_break, high_tick_sz)
|
||||
+ count_ticks(value - price_break, low_tick_sz);
|
||||
}
|
||||
|
||||
if (price_break < value) {
|
||||
if (price_break <= 0) {
|
||||
// price_break <= 0 < value
|
||||
return count_ticks(value, high_tick_sz);
|
||||
}
|
||||
// 0 < price_break < value
|
||||
return count_ticks(price_break, low_tick_sz)
|
||||
+ count_ticks(value - price_break, high_tick_sz);
|
||||
}
|
||||
// 0 < value <= price_break
|
||||
return count_ticks(value, low_tick_sz);
|
||||
}
|
||||
|
||||
public static long count_ticks(double value, double tick_sz) {
|
||||
Log.assert(tick_sz.is_real_positive());
|
||||
Log.assert(value.is_real());
|
||||
return value < 0 ?
|
||||
-count_positive_ticks(-value, tick_sz) :
|
||||
count_positive_ticks(value, tick_sz);
|
||||
|
||||
static long count_positive_ticks(double value, double tick_sz) {
|
||||
Log.assert(tick_sz.is_real_positive());
|
||||
Log.assert(value.is_real_positive());
|
||||
if (value == 0) {
|
||||
return 0;
|
||||
}
|
||||
var n = (long)Math.Floor(value / tick_sz);
|
||||
Log.assert(n >= 0);
|
||||
var rounded_val = n * tick_sz;
|
||||
var diff = value - rounded_val;
|
||||
Log.assert(diff >= 0);
|
||||
if (diff >= tick_sz / 2) {
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +160,21 @@ namespace bts.tests.security {
|
||||
return partial_tick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the first tick in the specified direction should be a partial tick (to correct a rounding discrepency).
|
||||
/// If this is so, it asserts that ticking up/down once in that direction corrects the rounding discrepency as expected.
|
||||
/// </summary>
|
||||
public static int check_partial_tick(
|
||||
RegionalTickFactors tick, double value, double rounded_value, bool ticking_up) {
|
||||
var partial_tick = count_partial_tick(value, rounded_value, ticking_up);
|
||||
if (partial_tick != 0)
|
||||
Assert.That(MathUtils.marginal_equals(
|
||||
tick.tickup(value, ticking_up ? partial_tick : -partial_tick),
|
||||
rounded_value,
|
||||
TickUtils.precision_const<double>()));
|
||||
return partial_tick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the first tick in the specified direction should be a partial tick (to correct a rounding discrepency).
|
||||
/// </summary>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using bts.api;
|
||||
using bts.test;
|
||||
using bts.utils;
|
||||
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.user.settings;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -30,7 +32,7 @@ namespace bts.tests.security {
|
||||
// Hide leading zeros (with positive tick)
|
||||
res = tf.format(1.25, settings.with(pad_with_leading_zeros: false));
|
||||
Assert.IsTrue(res == "1.25");
|
||||
|
||||
|
||||
// Test with a non-decimal tick type: quarters of quarters of eigths
|
||||
tf = new TickFactors(3200, 25);
|
||||
// Show leading zeros
|
||||
@@ -47,7 +49,7 @@ namespace bts.tests.security {
|
||||
res = tf.format(1D + 100.0 / 3200.0, settings.with(pad_with_leading_zeros: false));
|
||||
Assert.IsTrue(res == "1-100");
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void test_show_zero_integer() {
|
||||
var settings = TickDisplaySettings.Default;
|
||||
@@ -79,44 +81,44 @@ namespace bts.tests.security {
|
||||
// The 'show_zero_integer' setting should bear no effect.
|
||||
res = tf.format(-1.25, settings.with(show_zero_integer: false));
|
||||
Assert.IsTrue(res == "-1.25");
|
||||
|
||||
|
||||
// Test with a non-decimal tick type: quarters of quarters of eigths
|
||||
tf = new TickFactors(3200, 25);
|
||||
// Hide zero integer (with positive tick)
|
||||
res = tf.format(25.0/3200.0, settings.with(show_zero_integer: false));
|
||||
res = tf.format(25.0 / 3200.0, settings.with(show_zero_integer: false));
|
||||
Assert.IsTrue(res == "0025");
|
||||
// Hide zero integer (with positive integer, non-zero tick).
|
||||
// The 'show_zero_integer' setting should bear no effect.
|
||||
res = tf.format(1 + 25.0/3200.0, settings.with(show_zero_integer: false));
|
||||
res = tf.format(1 + 25.0 / 3200.0, settings.with(show_zero_integer: false));
|
||||
Assert.IsTrue(res == "1-0025");
|
||||
|
||||
|
||||
// Hide zero integer (with negative tick)
|
||||
res = tf.format(-25.0/3200.0, settings.with(show_zero_integer: false));
|
||||
res = tf.format(-25.0 / 3200.0, settings.with(show_zero_integer: false));
|
||||
Assert.IsTrue(res == "-0025");
|
||||
// Hide zero integer (with negative tick, non-zero integer).
|
||||
// The 'show_zero_integer' setting should bear no effect.
|
||||
res = tf.format(-1 - 25.0/3200.0, settings.with(show_zero_integer: false));
|
||||
res = tf.format(-1 - 25.0 / 3200.0, settings.with(show_zero_integer: false));
|
||||
Assert.IsTrue(res == "-1-0025");
|
||||
|
||||
// Hide zero integer (with negative tick), and hide leading zeros
|
||||
res = tf.format(-25.0/3200.0, settings.with(show_zero_integer: false, pad_with_leading_zeros: false));
|
||||
res = tf.format(-25.0 / 3200.0, settings.with(show_zero_integer: false, pad_with_leading_zeros: false));
|
||||
Assert.IsTrue(res == "-25");
|
||||
// Hide zero integer (with negative integer, non-zero tick), and hide leading zeros.
|
||||
// The 'show_zero_integer' setting should bear no effect.
|
||||
res = tf.format(-1 - 25.0/3200.0, settings.with(show_zero_integer: false, pad_with_leading_zeros: false));
|
||||
res = tf.format(-1 - 25.0 / 3200.0, settings.with(show_zero_integer: false, pad_with_leading_zeros: false));
|
||||
Assert.IsTrue(res == "-1-25");
|
||||
|
||||
// Hide zero integer (with negative tick)
|
||||
res = tf.format(-25.0/3200.0, settings.with(show_zero_integer: false));
|
||||
res = tf.format(-25.0 / 3200.0, settings.with(show_zero_integer: false));
|
||||
// This isn't pretty, but it's logical: a negative number,
|
||||
// greater than -1, in tick form.
|
||||
Assert.IsTrue(res == "-0025");
|
||||
// Hide zero integer (with negative tick), and hide leading zeros
|
||||
res = tf.format(-25.0/3200.0, settings.with(show_zero_integer: false, pad_with_leading_zeros: false));
|
||||
res = tf.format(-25.0 / 3200.0, settings.with(show_zero_integer: false, pad_with_leading_zeros: false));
|
||||
// Should be the same as above, except without the leading zeros in the tick part.
|
||||
Assert.IsTrue(res == "-25");
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void test_tick_increment() {
|
||||
var from_sec_files = SecurityFiles.all().SelectMany(SecurityTestHelper.read_securities).Select(s => s.ContractSpec);
|
||||
@@ -146,26 +148,29 @@ namespace bts.tests.security {
|
||||
}
|
||||
|
||||
void _test_tick_increment(ContractSpecificationImmutable tick, IEnumerable<double> prices) {
|
||||
Assert.That(!(tick.LowTick < 0));
|
||||
Assert.That(!(tick.HighTick < 0));
|
||||
_test_tick_increment(TickUtils.get_regional_ticks(tick), prices);
|
||||
}
|
||||
|
||||
void _test_tick_increment(RegionalTickFactors tick, IEnumerable<double> prices) {
|
||||
var low_tick_sz = tick.low_tick.tick_unit_frac();
|
||||
var high_tick_sz = tick.high_tick.tick_unit_frac();
|
||||
Assert.That(!(low_tick_sz < 0));
|
||||
Assert.That(!(high_tick_sz < 0));
|
||||
var price_break = tick.low_tick_inclusive_end;
|
||||
foreach (var p in prices) {
|
||||
var expected_ticks_from_zero = TickTestHelper.full_ticks_from_zero(tick, p);
|
||||
var actual_ticks_from_zero = TickUtils.full_ticks_from_zero(tick, p);
|
||||
if (expected_ticks_from_zero != actual_ticks_from_zero) {
|
||||
expected_ticks_from_zero = TickTestHelper.full_ticks_from_zero(tick, p);
|
||||
actual_ticks_from_zero = TickUtils.full_ticks_from_zero(tick, p);
|
||||
}
|
||||
var expected_ticks_from_zero = TickTestHelper.count_ticks(tick, p);
|
||||
var actual_ticks_from_zero = TickUtils.count_ticks(tick, p);
|
||||
Assert.That(expected_ticks_from_zero == actual_ticks_from_zero,
|
||||
$"Expected ticks from zero to be '{expected_ticks_from_zero}', actually '{actual_ticks_from_zero}' " +
|
||||
$"(low/high/pricebreak: {tick.LowTick}/{tick.HighTick}/{tick.PriceBreak}, price: {p}).");
|
||||
$"(low/high/pricebreak: {tick.low_tick}/{tick.high_tick}/{price_break}, price: {p}).");
|
||||
if (expected_ticks_from_zero.HasValue) {
|
||||
Assert.That(p.is_real());
|
||||
_test_tick_increment(tick, p - 1, p);
|
||||
if (tick.PriceBreak.is_real() && tick.PriceBreak != double.MaxValue && tick.PriceBreak != double.MinValue) {
|
||||
if (price_break.is_real() && price_break != double.MaxValue && price_break != double.MinValue) {
|
||||
// test both sides of the break with a few adjustments
|
||||
foreach (var adjustment in new[] { 1, tick.LowTick, tick.HighTick }) {
|
||||
_test_tick_increment(tick, p, tick.PriceBreak + adjustment);
|
||||
_test_tick_increment(tick, p, tick.PriceBreak - adjustment);
|
||||
foreach (var adjustment in new[] { 1, low_tick_sz, high_tick_sz }) {
|
||||
_test_tick_increment(tick, p, price_break + adjustment);
|
||||
_test_tick_increment(tick, p, price_break - adjustment);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,6 +195,50 @@ namespace bts.tests.security {
|
||||
}
|
||||
}
|
||||
|
||||
void _test_tick_increment(RegionalTickFactors tick, double from, double to) {
|
||||
if (!from.is_real() || !to.is_real())
|
||||
return;
|
||||
var (min, max) = MathUtils.get_outliers(from, to);
|
||||
var price_break = tick.low_tick_inclusive_end;
|
||||
var low_tick_sz = tick.low_tick.tick_unit / tick.low_tick.tick_base;
|
||||
var high_tick_sz = tick.high_tick.tick_unit / tick.high_tick.tick_base;
|
||||
if (price_break.is_real()) {
|
||||
if (min >= price_break) {
|
||||
_test_tick_increment(tick, high_tick_sz, min, max);
|
||||
} else if (max <= price_break) {
|
||||
_test_tick_increment(tick, low_tick_sz, min, max);
|
||||
} else {
|
||||
_test_tick_increment_across_break(tick, min, max);
|
||||
}
|
||||
} else {
|
||||
_test_tick_increment(tick, low_tick_sz, min, max);
|
||||
}
|
||||
}
|
||||
|
||||
void _test_tick_increment_across_break(RegionalTickFactors tick, double min, double max) {
|
||||
var price_break = tick.low_tick_inclusive_end;
|
||||
var low_tick_sz = tick.low_tick.tick_unit / tick.low_tick.tick_base;
|
||||
var high_tick_sz = tick.high_tick.tick_unit / tick.high_tick.tick_base;
|
||||
Assert.That(min < price_break);
|
||||
Assert.That(max > price_break);
|
||||
// 2 tick counts are used to determine the number of ticks between min and max,
|
||||
// 1) number of ticks from min to the price break
|
||||
// 2) number of ticks from the price break to max
|
||||
// if either min/max is beyond epsilon away from a real tick,
|
||||
// a partial tick is used to correct the rounding error.
|
||||
Assert.That(TickTestHelper.round_ticks(low_tick_sz, price_break - min, out var rounded_min, out var min_ticks_to_break));
|
||||
Assert.That(TickTestHelper.round_ticks(high_tick_sz, max - price_break, out var rounded_max, out var max_ticks_to_break));
|
||||
rounded_min = price_break - rounded_min;
|
||||
rounded_max = price_break + rounded_max;
|
||||
int min_partial_tick = TickTestHelper.check_partial_tick(tick, min, rounded_min, true);
|
||||
int max_partial_tick = TickTestHelper.check_partial_tick(tick, max, rounded_max, false);
|
||||
// assert that ticking up/down between min/max behaves expectedly.
|
||||
var batch_tick_up_result = tick.tickup(min, min_ticks_to_break + max_ticks_to_break + min_partial_tick);
|
||||
Assert.That(MathUtils.marginal_equals(batch_tick_up_result, rounded_max, TickUtils.precision_const<double>()));
|
||||
var batch_tick_down_result = tick.tickdown(max, min_ticks_to_break + max_ticks_to_break + max_partial_tick);
|
||||
Assert.That(MathUtils.marginal_equals(batch_tick_down_result, rounded_min, TickUtils.precision_const<double>()));
|
||||
}
|
||||
|
||||
void _test_tick_increment_across_break(ContractSpecificationImmutable spec, double min, double max) {
|
||||
Assert.That(min < spec.PriceBreak);
|
||||
Assert.That(max > spec.PriceBreak);
|
||||
@@ -222,5 +271,19 @@ namespace bts.tests.security {
|
||||
Assert.That(MathUtils.marginal_equals(rounded_max, spec.tickup(min, full_ticks + min_partial_tick), TickUtils.precision_const<double>()));
|
||||
Assert.That(MathUtils.marginal_equals(rounded_min, spec.tickup(max, -(full_ticks + max_partial_tick)), TickUtils.precision_const<double>()));
|
||||
}
|
||||
|
||||
void _test_tick_increment(RegionalTickFactors tick, double tick_size, double min, double max) {
|
||||
// min round/partial tick
|
||||
Assert.That(TickTestHelper.round_ticks(tick_size, min, out var rounded_min, out var rounded_min_ticks));
|
||||
var min_partial_tick = TickTestHelper.check_partial_tick(tick, min, rounded_min, true);
|
||||
// max round/partial tick
|
||||
Assert.That(TickTestHelper.round_ticks(tick_size, max, out var rounded_max, out var rounded_max_ticks));
|
||||
var max_partial_tick = TickTestHelper.check_partial_tick(tick, max, rounded_max, false);
|
||||
var full_ticks = rounded_max_ticks - rounded_min_ticks;
|
||||
Assert.That(MathUtils.marginal_equals(rounded_max, tick.tickup(min, full_ticks + min_partial_tick), TickUtils.precision_const<double>()));
|
||||
if (!MathUtils.marginal_equals(rounded_min, tick.tickup(max, -(full_ticks + max_partial_tick)), TickUtils.precision_const<double>())) {
|
||||
Assert.That(MathUtils.marginal_equals(rounded_min, tick.tickup(max, -(full_ticks + max_partial_tick)), TickUtils.precision_const<double>()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
set(sources api/analytics/ExpirationDriver.proto;api/analytics/TimeWeightedInterpolatedDriver.proto;api/apicore.proto;api/apigateway.proto;api/apioptions.proto;api/core/ApiClientInterop.cpp;api/core/ApiServerInterop.cpp;api/domain/Currency.proto;api/domain/SecurityCriteria.proto;api/domain/SecurityCriteriaClause.proto;api/domain/SecurityExpiration.proto;api/domain/SecurityMetadata.proto;api/domain/SecurityMetadataCollection.proto;api/domain/SecurityMetadataItem.proto;api/domain/SecurityType.proto;api/domain/TickDisplaySettings.proto;api/domain/TickFactors.proto;api/domain/Venue.proto;api/io/URL.proto;api/md/MarketDataSubscriptionParams.proto;api/md/binance/BinanceDepthStreamParams.proto;api/md/binance/BinanceMarketDataSubscriptionParams.proto;api/md/deribit/DeribitMarketDataSubscriptionParams.proto;api/md/ftx/FTXDepthStreamParams.proto;api/md/ftx/FTXMarketDataSubscriptionParams.proto;api/md/glbx/GlbxMarketDataSubscriptionParams.proto;api/md/huobi/HuobiMarketDataSubscriptionParams.proto;api/public/ApiServer.cpp;api/public/ClientChannel.cpp;api/public/QuickbusClientChannel.cpp;api/public/ServerChannel.cpp;api/public/ServiceContext.cpp;api/service_helpers/AdminServiceUtils.cpp;api/service_helpers/AlgoServiceUtils.cpp;api/service_helpers/PositionUtilities.cpp;api/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/AnalyticsService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/BasePositionService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarginService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.proto;api/services/MarketDataService.proto;api/services/MoneyFileService.proto;api/services/OrderService.proto;api/services/OutOfProcessURLListener.proto;api/services/PersistenceService.proto;api/services/PositionService.proto;api/services/ShellDiagnosticService.proto;api/services/ShellLatencyService.proto;api/services/SyntheticSecurityService.proto;api/test/ApiTests.cpp;api/test/HelloServiceServer.cpp;config/ConfigCheck.cpp;config/ConfigDownloader.cpp;config/ConfigErrorCollector.cpp;config/ConfigLog.cpp;config/ConfigPropertyEvaluator.cpp;config/ConfigValueQueue.cpp;config/ConfigurationException.cpp;config/ConfigurationState.cpp;config/PreprocessInputStream.cpp;config/config.cpp;config/defaults/MarketData.conf;config/defaults/Ports.conf;config/defaults/SecurityMetadataCollection.conf;config/defaults/Symbols.conf;config/defaults/cme_reference_data.conf;config/defaults/edge.conf;config/defaults/jetfix.conf;config/defaults/keycode.conf;config/defaults/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.conf;config/defaults/position_cfg.conf;config/defaults/span.conf;config/defaults/spreader.conf;config/defaults/testkeys.conf;config/defaults/threads.conf;config/defaults/toggles.conf;config/extensions/extensions.proto;config/invalid/invalid.proto;config/protos/algo_types.proto;config/protos/cme_reference_data.proto;config/protos/exchange_sim_cfg.proto;config/protos/jetfix.proto;config/protos/keycode.proto;config/protos/logging.proto;config/protos/market_data.proto;config/protos/mercury_cfg.proto;config/protos/orderman_cfg.proto;config/protos/orders_fills_cleanup.proto;config/protos/perflog_cfg.proto;config/protos/ports.proto;config/protos/position_cfg.proto;config/protos/quickbus_cfg.proto;config/protos/span.proto;config/protos/spartan.proto;config/protos/spreader.proto;config/protos/subprocess.proto;config/protos/symbols.proto;config/protos/system.proto;config/protos/testkeys.proto;config/protos/threads.proto;config/protos/toggles.proto;config/protos/ui.proto;config/tests/HTTPServer.cpp;config/tests/config_ref_tests.cpp;config/tests/config_tests.cpp;config/text_format/text_format.cpp;domain_model/Security.cpp;domain_model/Security.proto;domain_model/SecurityTests.cpp;domain_model/sod.cpp;domain_model/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_tests.cpp;io/QueryParams.cpp;io/URL.cpp;io/http/CurlHandle.cpp;io/http/HTTPClient.cpp;io/http/HTTPHeaders.cpp;io/http/HTTPRequest.cpp;io/http/HTTPResponse.cpp;io/websocket/WebsocketClient.cpp;io/websocket/WebsocketClientConfiguration.cpp;io/websocket/WebsocketError.cpp;io/websocket/WebsocketMessage.cpp;io/websocket/WebsocketSSLClientInternal.cpp;io/websocket/tests/WebsocketClientTests.cpp;logging/LoggerHub.cpp;logging/Sink.cpp;logging/logging.cpp;logging/tests/logging_tests.cpp;md/CCApiDepthWalker.cpp;md/CCApiListenerAdaptor.cpp;md/CCApiRequestor.cpp;md/Diag/MDPeriodicDumper.cpp;md/Diag/ShmDumper.cpp;md/MBOProcessor.cpp;md/MBOServiceServer.cpp;md/MDClient.cpp;md/MDConsumer.cpp;md/MDConsumerHandle.cpp;md/MDConsumerInterop.cpp;md/MDProducer.cpp;md/MDProducerHandle.cpp;md/MarketDataError.cpp;md/MarketDataGateway.cpLine truncated
|
||||
set(sources api/analytics/ExpirationDriver.proto;api/analytics/TimeWeightedInterpolatedDriver.proto;api/apicore.proto;api/apigateway.proto;api/apioptions.proto;api/core/ApiClientInterop.cpp;api/core/ApiServerInterop.cpp;api/domain/Currency.proto;api/domain/SecurityCriteria.proto;api/domain/SecurityCriteriaClause.proto;api/domain/SecurityExpiration.proto;api/domain/SecurityMetadata.proto;api/domain/SecurityMetadataCollection.proto;api/domain/SecurityMetadataItem.proto;api/domain/SecurityType.proto;api/domain/TickDisplayRule.proto;api/domain/TickDisplaySettings.proto;api/domain/TickFactors.proto;api/domain/Venue.proto;api/io/URL.proto;api/md/MarketDataSubscriptionParams.proto;api/md/binance/BinanceDepthStreamParams.proto;api/md/binance/BinanceMarketDataSubscriptionParams.proto;api/md/deribit/DeribitMarketDataSubscriptionParams.proto;api/md/ftx/FTXDepthStreamParams.proto;api/md/ftx/FTXMarketDataSubscriptionParams.proto;api/md/glbx/GlbxMarketDataSubscriptionParams.proto;api/md/huobi/HuobiMarketDataSubscriptionParams.proto;api/public/ApiServer.cpp;api/public/ClientChannel.cpp;api/public/QuickbusClientChannel.cpp;api/public/ServerChannel.cpp;api/public/ServiceContext.cpp;api/service_helpers/AdminServiceUtils.cpp;api/service_helpers/AlgoServiceUtils.cpp;api/service_helpers/PositionUtilities.cpp;api/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/AnalyticsService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/BasePositionService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarginService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.proto;api/services/MarketDataService.proto;api/services/MoneyFileService.proto;api/services/OrderService.proto;api/services/OutOfProcessURLListener.proto;api/services/PersistenceService.proto;api/services/PositionService.proto;api/services/ShellDiagnosticService.proto;api/services/ShellLatencyService.proto;api/services/SyntheticSecurityService.proto;api/test/ApiTests.cpp;api/test/HelloServiceServer.cpp;config/ConfigCheck.cpp;config/ConfigDownloader.cpp;config/ConfigErrorCollector.cpp;config/ConfigLog.cpp;config/ConfigPropertyEvaluator.cpp;config/ConfigValueQueue.cpp;config/ConfigurationException.cpp;config/ConfigurationState.cpp;config/PreprocessInputStream.cpp;config/config.cpp;config/defaults/MarketData.conf;config/defaults/Ports.conf;config/defaults/SecurityMetadataCollection.conf;config/defaults/Symbols.conf;config/defaults/cme_reference_data.conf;config/defaults/edge.conf;config/defaults/jetfix.conf;config/defaults/keycode.conf;config/defaults/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.conf;config/defaults/position_cfg.conf;config/defaults/span.conf;config/defaults/spreader.conf;config/defaults/testkeys.conf;config/defaults/threads.conf;config/defaults/toggles.conf;config/extensions/extensions.proto;config/invalid/invalid.proto;config/protos/algo_types.proto;config/protos/cme_reference_data.proto;config/protos/exchange_sim_cfg.proto;config/protos/jetfix.proto;config/protos/keycode.proto;config/protos/logging.proto;config/protos/market_data.proto;config/protos/mercury_cfg.proto;config/protos/orderman_cfg.proto;config/protos/orders_fills_cleanup.proto;config/protos/perflog_cfg.proto;config/protos/ports.proto;config/protos/position_cfg.proto;config/protos/quickbus_cfg.proto;config/protos/span.proto;config/protos/spartan.proto;config/protos/spreader.proto;config/protos/subprocess.proto;config/protos/symbols.proto;config/protos/system.proto;config/protos/testkeys.proto;config/protos/threads.proto;config/protos/toggles.proto;config/protos/ui.proto;config/tests/HTTPServer.cpp;config/tests/config_ref_tests.cpp;config/tests/config_tests.cpp;config/text_format/text_format.cpp;domain_model/Security.cpp;domain_model/Security.proto;domain_model/SecurityTests.cpp;domain_model/sod.cpp;domain_model/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_tests.cpp;io/QueryParams.cpp;io/URL.cpp;io/http/CurlHandle.cpp;io/http/HTTPClient.cpp;io/http/HTTPHeaders.cpp;io/http/HTTPRequest.cpp;io/http/HTTPResponse.cpp;io/websocket/WebsocketClient.cpp;io/websocket/WebsocketClientConfiguration.cpp;io/websocket/WebsocketError.cpp;io/websocket/WebsocketMessage.cpp;io/websocket/WebsocketSSLClientInternal.cpp;io/websocket/tests/WebsocketClientTests.cpp;logging/LoggerHub.cpp;logging/Sink.cpp;logging/logging.cpp;logging/tests/logging_tests.cpp;md/CCApiDepthWalker.cpp;md/CCApiListenerAdaptor.cpp;md/CCApiRequestor.cpp;md/Diag/MDPeriodicDumper.cpp;md/Diag/ShmDumper.cpp;md/MBOProcessor.cpp;md/MBOServiceServer.cpp;md/MDClient.cpp;md/MDConsumer.cpp;md/MDConsumerHandle.cpp;md/MDConsumerInterop.cpp;md/MDProducer.cpp;md/MDProducerHandle.cpp;md/MarketDataLine truncated
|
||||
@@ -1,12 +1,13 @@
|
||||
syntax = "proto3";
|
||||
package bts.api.domain;
|
||||
|
||||
import "lib/api/domain/TickDisplayRule.proto";
|
||||
import "lib/api/domain/TickDisplaySettings.proto";
|
||||
|
||||
option csharp_namespace = "bts.api.domain";
|
||||
option cc_enable_arenas=true;
|
||||
|
||||
message SecurityMetadata {
|
||||
bts.api.domain.TickDisplaySettings price_display = 1;
|
||||
bts.api.domain.TickDisplaySettings quantity_display = 2;
|
||||
repeated bts.api.domain.TickDisplayRule price_rules = 3;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
syntax = "proto3";
|
||||
package bts.api.domain;
|
||||
|
||||
import "lib/api/domain/TickDisplaySettings.proto";
|
||||
|
||||
option csharp_namespace = "bts.api.domain";
|
||||
|
||||
message TickDisplayRule {
|
||||
TickDisplaySettings tick_display = 1;
|
||||
double max_value = 2;
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
namespace bts {
|
||||
namespace api {
|
||||
@@ -55,8 +56,14 @@ class StatefulPublisher {
|
||||
}
|
||||
}
|
||||
void erase(TKey m) {
|
||||
// there is an assumption that we are not dealing with that within a
|
||||
// transaction, add_or_update and erase is called just once a key
|
||||
if (_has_key(m)) {
|
||||
_erased.insert(m);
|
||||
if (_keep_state)
|
||||
_state_data.erase(m);
|
||||
else
|
||||
_state.erase(m);
|
||||
}
|
||||
}
|
||||
void flush() {
|
||||
|
||||
@@ -44,6 +44,7 @@ message PrecomputedTheoPoint {
|
||||
double vega = 60;
|
||||
double theta = 70;
|
||||
double rho = 80;
|
||||
double vol = 90;
|
||||
}
|
||||
|
||||
message PrecomputedTheos {
|
||||
|
||||
@@ -15,8 +15,7 @@ SecurityMetadataCollection {
|
||||
tick_unit: 10
|
||||
}
|
||||
display_multiplier: NaN
|
||||
show_decimal_part: true
|
||||
pad_with_leading_zeros: true
|
||||
show_decimal_part: false
|
||||
show_zero_integer: true
|
||||
nan_text: ""
|
||||
}
|
||||
@@ -42,6 +41,53 @@ SecurityMetadataCollection {
|
||||
show_zero_integer: true
|
||||
nan_text: ""
|
||||
}
|
||||
price_rules {
|
||||
max_value: 0.0050
|
||||
tick_display {
|
||||
tick_factors {
|
||||
tick_unit: 1
|
||||
tick_base: 10000
|
||||
}
|
||||
}
|
||||
}
|
||||
price_rules {
|
||||
max_value: 99999999
|
||||
tick_display {
|
||||
tick_factors {
|
||||
tick_unit: 5
|
||||
tick_base: 10000
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
items {
|
||||
scope {
|
||||
clauses {
|
||||
symbols: "ETH"
|
||||
security_types: OPTION
|
||||
}
|
||||
}
|
||||
value {
|
||||
price_rules {
|
||||
max_value: 0.0050
|
||||
tick_display {
|
||||
tick_factors {
|
||||
tick_unit: 1
|
||||
tick_base: 10000
|
||||
}
|
||||
}
|
||||
}
|
||||
price_rules {
|
||||
max_value: 99999999
|
||||
tick_display {
|
||||
tick_factors {
|
||||
tick_unit: 5
|
||||
tick_base: 10000
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +114,10 @@ inline bool is_same(Price p1, Price p2) {
|
||||
return std::abs(p1.value() - p2.value()) < market_epsilon();
|
||||
}
|
||||
|
||||
inline bool is_same(double d1, double d2) {
|
||||
return std::abs(d1 - d2) < market_epsilon();
|
||||
}
|
||||
|
||||
inline bool is_equal_or_better(api::Side side, Price p1, Price p2) {
|
||||
return is_same(p1, p2) || is_better(side, p1, p2);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
#include "QueryParams.h"
|
||||
#include <fmt/format.h>
|
||||
#include <range/v3/view.hpp>
|
||||
|
||||
namespace bts::io {
|
||||
|
||||
std::string QueryParams::to_string() const {
|
||||
std::string rv;
|
||||
auto query_param_strs = ranges::transform_view(
|
||||
*this, [](const auto &qp) { return qp.first + "=" + qp.second; });
|
||||
rv.append(fmt::format(
|
||||
"{}", fmt::join(query_param_strs.begin(), query_param_strs.end(), "&")));
|
||||
auto it = begin();
|
||||
if (it == end()) {
|
||||
return "";
|
||||
}
|
||||
rv.append(it->first + "=" + it->second);
|
||||
while (++it != end()) {
|
||||
rv.append("&" + it->first + "=" + it->second);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
+2
-12
@@ -1,15 +1,11 @@
|
||||
#include "URL.h"
|
||||
#include "lib/api/io/URL.pb.h"
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <range/v3/view.hpp>
|
||||
#include <range/v3/view/transform.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace bts::io {
|
||||
|
||||
// URL:: URL(const api::io::URL &url) : protocol_(url.protocol()),
|
||||
// host_(url.host()), port_(url.port()), path_(url.path()) {}
|
||||
URL::URL(const bts::api::io::URL &url)
|
||||
: protocol_(url.protocol()), host_(url.host()), port_(url.port()),
|
||||
path_(url.path()) {
|
||||
@@ -76,13 +72,7 @@ std::string URL::to_string() const {
|
||||
}
|
||||
if (!query_params_.empty()) {
|
||||
rv.append("?");
|
||||
auto query_param_strs =
|
||||
ranges::transform_view(query_params_, [](const auto &qp) {
|
||||
return qp.first + "=" + qp.second;
|
||||
});
|
||||
rv.append(fmt::format(
|
||||
"{}",
|
||||
fmt::join(query_param_strs.begin(), query_param_strs.end(), "&")));
|
||||
rv.append(query_params_.to_string());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -1,39 +1,35 @@
|
||||
#include "HTTPHeaders.h"
|
||||
#include "lib/utils/StringSplitter.h"
|
||||
#include <fmt/format.h>
|
||||
#include <range/v3/view.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace bts::io::http {
|
||||
|
||||
HTTPHeaders::HTTPHeaders(const std::string &str) {
|
||||
auto item_strs = utils::StringSplitter::split(str, "\n");
|
||||
auto items = ranges::transform_view(
|
||||
item_strs,
|
||||
[](const std::string &item_str) -> std::pair<std::string, std::string> {
|
||||
auto kv = utils::StringSplitter::split(item_str, ":");
|
||||
if (kv.empty()) {
|
||||
return std::make_pair("", "");
|
||||
}
|
||||
if (kv.size() == 1) {
|
||||
return std::make_pair(kv[0], std::string(""));
|
||||
}
|
||||
return std::make_pair(kv[0], kv[1]);
|
||||
});
|
||||
for (auto p : items) {
|
||||
try_emplace(p.first, p.second);
|
||||
for (const auto &item_str : item_strs) {
|
||||
auto kv = utils::StringSplitter::split(item_str, ":");
|
||||
if (kv.empty()) {
|
||||
try_emplace("", "");
|
||||
continue;
|
||||
}
|
||||
if (kv.size() == 1) {
|
||||
try_emplace(kv[0], "");
|
||||
continue;
|
||||
}
|
||||
try_emplace(kv[0], kv[1]);
|
||||
}
|
||||
}
|
||||
|
||||
std::string HTTPHeaders::to_string() const {
|
||||
std::string rv;
|
||||
auto query_param_strs = ranges::transform_view(
|
||||
*this, [](const auto &qp) { return qp.first + ": " + qp.second; });
|
||||
rv.append(fmt::format(
|
||||
"{}",
|
||||
fmt::join(query_param_strs.begin(), query_param_strs.end(), "\n")));
|
||||
auto it = begin();
|
||||
if (it == end()) {
|
||||
return "";
|
||||
}
|
||||
std::string rv{it->first + ": " + it->second};
|
||||
while (++it != end()) {
|
||||
rv.append("\n" + it->first + ": " + it->second);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#ifdef SPARK_ENABLE_CCAPI
|
||||
#include "BinanceBookLevel.h"
|
||||
#include "lib/os/os.h"
|
||||
|
||||
@@ -42,3 +43,4 @@ void to_json(nlohmann::json &j, const BinanceBookLevel &upd) {
|
||||
}
|
||||
|
||||
} // namespace bts::md::binance
|
||||
#endif
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#ifdef SPARK_ENABLE_CCAPI
|
||||
#include <cstddef>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <ostream>
|
||||
@@ -21,3 +22,4 @@ void from_json(const nlohmann::json &j, BinanceBookLevel &upd);
|
||||
void to_json(nlohmann::json &j, const BinanceBookLevel &upd);
|
||||
|
||||
} // namespace bts::md::binance
|
||||
#endif
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#ifdef SPARK_ENABLE_CCAPI
|
||||
#include "BinanceBookLevel.h"
|
||||
#include "lib/domain/Price.h"
|
||||
#include "lib/domain/Side.h"
|
||||
@@ -44,3 +45,4 @@ constexpr bool BinanceBookLevelComp::operator()(
|
||||
}
|
||||
|
||||
} // namespace bts::md::binance
|
||||
#endif
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#ifdef SPARK_ENABLE_CCAPI
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
@@ -27,3 +28,4 @@ class BinanceBookTickerUpdate {
|
||||
};
|
||||
|
||||
} // namespace bts::md::binance
|
||||
#endif
|
||||
@@ -1,3 +1,4 @@
|
||||
#ifdef SPARK_ENABLE_CCAPI
|
||||
#include "BinanceDepthListenerAdaptor.h"
|
||||
#include "BinanceDepthSnapshot.h"
|
||||
#include "BinanceDiffDepthUpdate.h"
|
||||
@@ -115,3 +116,4 @@ void BinanceDepthListenerAdaptor::on_diff_update(
|
||||
}
|
||||
|
||||
} // namespace bts::md::binance
|
||||
#endif
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#ifdef SPARK_ENABLE_CCAPI
|
||||
#include "lib/domain/Book.h"
|
||||
#include "lib/domain/SecurityResolver.h"
|
||||
#include "lib/domain/Venue.h"
|
||||
@@ -37,3 +38,4 @@ class BinanceDepthListenerAdaptor {
|
||||
};
|
||||
|
||||
} // namespace bts::md::binance
|
||||
#endif
|
||||
@@ -1,3 +1,4 @@
|
||||
#ifdef SPARK_ENABLE_CCAPI
|
||||
#include "BinanceDepthSnapshot.h"
|
||||
#include "BinanceBookLevelComp.h"
|
||||
#include "BinanceDiffDepthUpdate.h"
|
||||
@@ -125,3 +126,4 @@ std::ostream &operator<<(
|
||||
}
|
||||
|
||||
} // namespace bts::md::binance
|
||||
#endif
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#ifdef SPARK_ENABLE_CCAPI
|
||||
#include "BinanceBookLevel.h"
|
||||
#include <cstddef>
|
||||
#include <nlohmann/json.hpp>
|
||||
@@ -43,3 +44,4 @@ std::ostream &operator<<(
|
||||
std::ostream &out, const BinanceDepthSnapshot &snapshot) noexcept;
|
||||
|
||||
} // namespace bts::md::binance
|
||||
#endif
|
||||
@@ -1,3 +1,4 @@
|
||||
#ifdef SPARK_ENABLE_CCAPI
|
||||
#include "BinanceDepthStreamParams.h"
|
||||
#include "lib/api/md/binance/BinanceMarketDataSubscriptionParams.pb.h"
|
||||
#include "lib/core/VectorAdapter.h"
|
||||
@@ -122,3 +123,4 @@ std::ostream &operator<<(
|
||||
}
|
||||
|
||||
} // namespace bts::md::binance
|
||||
#endif
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#ifdef SPARK_ENABLE_CCAPI
|
||||
#include "lib/api/md/binance/BinanceMarketDataSubscriptionParams.pb.h"
|
||||
#include "lib/io/QueryParams.h"
|
||||
#include "lib/io/URL.h"
|
||||
@@ -74,3 +75,4 @@ io::QueryParams BinanceDepthStreamParams::query_params(
|
||||
}
|
||||
|
||||
} // namespace bts::md::binance
|
||||
#endif
|
||||
@@ -1,3 +1,4 @@
|
||||
#ifdef SPARK_ENABLE_CCAPI
|
||||
#include "BinanceDepthStreamSiteKey.h"
|
||||
#include "lib/api/md/binance/BinanceDepthStreamParams.pb.h"
|
||||
#include "lib/core/HashPhaser.h"
|
||||
@@ -44,3 +45,4 @@ std::size_t hash<bts::md::binance::BinanceDepthStreamSiteKey>::operator()(
|
||||
}
|
||||
|
||||
} // namespace std
|
||||
#endif
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#ifdef SPARK_ENABLE_CCAPI
|
||||
#include "BinanceDepthStreamParams.h"
|
||||
#include "lib/domain/SecurityID.h"
|
||||
#include "lib/io/URL.h"
|
||||
@@ -46,3 +47,4 @@ struct hash<bts::md::binance::BinanceDepthStreamSiteKey> {
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
#endif
|
||||
@@ -1,3 +1,4 @@
|
||||
#ifdef SPARK_ENABLE_CCAPI
|
||||
#include "BinanceDiffDepthSubscription.h"
|
||||
#include "BinanceDepthListenerAdaptor.h"
|
||||
#include "BinanceDepthSnapshot.h"
|
||||
@@ -196,3 +197,4 @@ bool BinanceDiffDepthSubscription::remove_listener(
|
||||
}
|
||||
|
||||
} // namespace bts::md::binance
|
||||
#endif
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#ifdef SPARK_ENABLE_CCAPI
|
||||
#include "lib/io/URL.h"
|
||||
#include "lib/md/IMarketDataListener.h"
|
||||
#include "lib/md/binance/BinanceDiffDepthUpdate.h"
|
||||
@@ -56,3 +57,4 @@ class BinanceDiffDepthSubscription {
|
||||
};
|
||||
|
||||
} // namespace bts::md::binance
|
||||
#endif
|
||||
@@ -1,3 +1,4 @@
|
||||
#ifdef SPARK_ENABLE_CCAPI
|
||||
#include "BinanceDiffDepthUpdate.h"
|
||||
#include "BinanceBookLevelComp.h"
|
||||
#include "BinanceDepthSnapshot.h"
|
||||
@@ -145,3 +146,4 @@ BinanceDiffDepthUpdate &operator+=(
|
||||
}
|
||||
|
||||
} // namespace bts::md::binance
|
||||
#endif
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#ifdef SPARK_ENABLE_CCAPI
|
||||
#include "BinanceBookLevel.h"
|
||||
#include "lib/domain/Book.h"
|
||||
#include <cstddef>
|
||||
@@ -53,3 +54,4 @@ BinanceDiffDepthUpdate &operator+=(
|
||||
BinanceDiffDepthUpdate &lhs, const BinanceDiffDepthUpdate &rhs);
|
||||
|
||||
} // namespace bts::md::binance
|
||||
#endif
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#ifdef SPARK_ENABLE_CCAPI
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
@@ -33,3 +34,4 @@ class BinanceMiniTickerUpdate {
|
||||
};
|
||||
|
||||
} // namespace bts::md::binance
|
||||
#endif
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#ifdef SPARK_ENABLE_CCAPI
|
||||
#include "BinanceBookLevel.h"
|
||||
#include <vector>
|
||||
|
||||
@@ -21,3 +22,4 @@ class BinancePartialBookDepthUpdate {
|
||||
};
|
||||
|
||||
} // namespace bts::md::binance
|
||||
#endif
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#ifdef SPARK_ENABLE_CCAPI
|
||||
#include "lib/io/websocket/IWebsocketListener.h"
|
||||
#include "lib/io/websocket/WebsocketMessage.h"
|
||||
#include "server/sparkd/cmds/spartan/system/Delegate.h"
|
||||
@@ -52,3 +53,4 @@ void BinanceStreamListener<T>::on_data(const std::string &data) {
|
||||
}
|
||||
|
||||
} // namespace bts::md::binance
|
||||
#endif
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#ifdef SPARK_ENABLE_CCAPI
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
@@ -55,3 +56,4 @@ class BinanceTickerUpdate {
|
||||
};
|
||||
|
||||
} // namespace bts::md::binance
|
||||
#endif
|
||||
Loaded 100 of 117 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user