Compare commits

...
65 Commits
Author SHA1 Message Date
John Colvin e6bb4c73a8 fix build (jenkins): Upgrade pip via 'python -m pip'
For some reason 'pip install --upgrade pip' works on my laptop, but fails on
the Jenkins box.
2023-07-18 22:53:32 -04:00
John Colvin e43a2a4818 build: Add build script for new Jenkins (on aws) 2023-07-18 22:25:56 -04:00
John Colvin cde0f44b7d build: Default to Release cfg for 'publish' target 2023-07-18 22:25:06 -04:00
Taha b07a1e2c74 Fixing the issues, where the theos are only published out by the vol surface owner 2023-07-18 20:40:59 -04:00
Taha Afzal 8376fce5fe Merge branch 'spark_crypto.prod.vol_order' into spark_crypto.prod 2023-07-14 17:01:29 +00:00
Taha Afzal d9e5c846c2 Adding more logs to VolAlgo 2023-07-14 16:57:55 +00:00
Taha Afzal 65736c13ef VolAlgo completed 2023-07-14 03:54:14 +00:00
Taha Afzal 071c28e925 VolAlgo reflects its state correctly now 2023-07-10 03:20:05 +00:00
Taha Afzal 8355e3e1be VolAlgo code complete - need to test 2023-07-09 04:21:42 +00:00
John Colvin f9a6e0963c chore: Remove Spark.Admin.WebApp
These files were added by accident (leftover from switching branches
from development -> spark_crypto.prod)
2023-07-06 14:52:57 +00:00
John Colvin b2879837c6 chore: Remove devcontainer.json 2023-07-06 12:26:48 +00:00
John Colvin 0d03ddb009 Merge remote-tracking branch 'origin/spark_crypto.prod' into spark_crypto.tick_hotfix 2023-07-06 12:26:28 +00:00
John Colvin a6dfa4ae80 fix: Don't show decimal BTC Futs/Spd qtys
They tick in multiples of 10, so decimal precision isn't needed
2023-07-05 18:20:53 -04:00
John Colvin e66ec82ba8 hotfix: Setup default cfg for new Deribit price ticks 2023-07-05 18:19:49 -04:00
John Colvin 9ce5e971c6 fix: Fix typos in BTSColor paths in xaml 2023-07-05 18:11:40 -04:00
John Colvin 3e7cc49ebb hotfix: Format text w/ RegionalTickFactors in PriceEntryControl 2023-07-05 18:11:40 -04:00
John Colvin d07e68b6e2 hotfix: Use 1st leg's min price tick rule for Deribit combos 2023-07-05 18:11:40 -04:00
John Colvin 619ece2313 refactor: Consolidate impl for 'round_to_tick' overloads
Also add a 'decimal' overload for RegionalTickFactors. This is more work
for Deribit tick hotfix.
2023-07-05 18:11:40 -04:00
John Colvin 1dda9566f3 hotfix: Use '.' separator if tick unit is multiple of base 2023-07-05 18:11:06 -04:00
Taha 3704d1ed16 InterptheosServerPublisher is now opulated the newly added vol field in the PrecomputedTheoPoint proto 2023-07-05 14:41:31 -04:00
John Colvin a524ca02a5 refactor: Change tick arithmetic to use int64
The tick count is "price / tick_size" and tick_size is usually < 1, so
the extra bits may be needed to avoid arithmetic overflow
2023-07-05 12:02:28 -04:00
John Colvin 3c97f2552b fix build: Don't use libfmt or ranges in win cpp
I can't deal with this anymore. My build is suddenly broken on Windows
(for spark_crypto.prod branch). I assume they updated the compiler
in a way that isn't backwards compatible. Thanks Windows.
2023-07-05 11:58:14 -04:00
John Colvin 15b1d55ef1 fix build: Disable Binance md code on win 2023-07-05 11:58:14 -04:00
John Colvin 4e42c9f01b test: Refactor tick tests for RegionalTickFactors
Gives us some peace of mind about the dynamic ticks hotfix for Deribit
2023-07-05 11:58:14 -04:00
John Colvin 76775f821a hotfix: Support dynamic ticks in order ticket 2023-07-05 11:58:14 -04:00
John Colvin e7088f6b1c hotfix: Support dynamic ticks in ladder
Deribit now provides dynamic price ticks (a list of ticks per range).

https://www.deribit.com/kb/4-july-2023
https://www.deribit.com/kb/deribit-on-screen-options-tick-size-reduction
2023-07-05 11:58:14 -04:00
Taha Afzal f2c3ff4be6 Adding vol to PrecomputedTheoPoint 2023-07-05 15:16:45 +00:00
Taha 1fb42ade59 Adding perf logs on the front end 2023-07-05 10:50:13 -04:00
Taha 129650e9d4 Only the skew owner publishes theos to the server 2023-07-05 10:50:13 -04:00
John Colvin 170a44ac04 fix: Check sign to tick in correct direction
Don't assume it's positive and blindly tickup
2023-07-03 22:34:46 -05:00
Taha Afzal 7e04f02327 Merge branch 'spark_crypto.prod.autofit' into spark_crypto.prod 2023-07-04 03:21:28 +00:00
Taha Afzal abda2dbf97 VolSurfaces and achived theo history size is not mantained at runtime 2023-07-04 03:13:46 +00:00
Taha Afzal ad1a45cf49 ArchivedTheos are truncated on a server start 2023-07-04 03:13:46 +00:00
John Colvin e9abdd0ff2 chore: Remove unused fn 'gen_prices' 2023-07-03 12:30:03 -05:00
John Colvin 9254002318 fix: Check HasPrice/QuantityDisplay before accessing prop 2023-07-03 12:24:20 -05:00
John Colvin 596780b7f4 chore: Remove unused 'unit_quantity' prop 2023-07-03 12:22:37 -05:00
John Colvin 7ba061ac20 hotfix: Add cfg to account for new deribit tick format 2023-07-02 16:58:53 -05:00
John Colvin c01593d119 fix (ui colors): Cherry-pick synth editor dark theme from dev 2023-06-26 12:26:38 -04:00
Taha 09e2a1764f Only the user with control over the volsurface can save it 2023-06-15 11:44:34 -04:00
John Colvin a780668bce fix: Ensure tick up/down values are within min/max bounds 2023-06-15 11:43:58 -04:00
John Colvin 5b79aeaa14 ui: Enforce min auto-fit period (30 secs) 2023-06-15 11:37:18 -04:00
John Colvin 049c006b5f ui: Disable driver/atm pts moved auto-fit ui 2023-06-15 11:37:18 -04:00
John Colvin 6fdc344472 feat: Add 'acquire ownership' button to vs context menu 2023-06-15 11:37:18 -04:00
John Colvin cb09b93553 chore: Rename ns bts.reactive -> Spark.Core.Reactive 2023-06-15 11:37:18 -04:00
John Colvin 21447d32e5 fix (vs ui): Only log 'is_dirty' changes for editable cols 2023-06-15 11:37:18 -04:00
Taha 28b03e1cd2 Only the user with control over the volsurface can save it 2023-06-14 22:23:45 -04:00
Taha a7acb89c15 Fixing merge conflict 2023-06-14 15:57:42 -04:00
John Colvin 35289710ba fix: Ensure tick up/down values are within min/max bounds 2023-06-14 14:20:47 -04:00
Taha 4677acfb39 Merge branch 'spark_crypto.prod.autofit' of bitbucket.org:bluetradesys/spark into spark_crypto.prod.autofit 2023-06-14 13:55:03 -04:00
Taha ac05d0d69e AutoFitter reads the settings from the settings now 2023-06-14 13:54:53 -04:00
John Colvin 25a9b6b433 fix: Ensure tick up/down values are within min/max bounds 2023-06-14 13:49:49 -04:00
John Colvin b78713ad0d ui: Enforce min auto-fit period (30 secs) 2023-06-14 12:59:40 -04:00
John Colvin 1f3d0e879c ui: Disable driver/atm pts moved auto-fit ui 2023-06-14 12:59:39 -04:00
John Colvin baad4dc68e feat: Add 'acquire ownership' button to vs context menu 2023-06-14 12:22:57 -04:00
John Colvin ef7c0c7a30 chore: Rename ns bts.reactive -> Spark.Core.Reactive 2023-06-14 11:39:06 -04:00
Taha 3b3c4a2a35 Merge branch 'spark_crypto.prod.autofit' of bitbucket.org:bluetradesys/spark into spark_crypto.prod.autofit 2023-06-14 10:21:55 -04:00
John Colvin ef9fc66a31 fix (vs ui): Only log 'is_dirty' changes for editable cols 2023-06-14 09:44:42 -04:00
John Colvin 212d9c853b fix: Init auto fit vals in vs settings editor 2023-06-14 09:09:34 -04:00
Taha 7815b18b6b Merge branch 'spark_crypto.prod.autofit' of bitbucket.org:bluetradesys/spark into spark_crypto.prod.autofit 2023-06-13 22:34:12 -04:00
Taha 05126074d4 Enabling AutoFit 2023-06-13 22:34:07 -04:00
John Colvin 3611d2c1f3 fix: Enable tick up/down buttons for nullable uint32 ctrl 2023-06-13 15:43:33 -04:00
John Colvin b5ad4b8104 feat: Add auto-fit ui for vs settings 2023-06-13 15:43:33 -04:00
Taha 43d7c0a2b5 Comments 2023-06-12 13:43:42 -04:00
Taha 12e361cec7 AutoFit based on a timer works now 2023-06-11 21:59:46 -04:00
John Colvin 82c126362d fix build: Fix a few path errors in publish target 2023-05-31 22:13:52 -04:00
117 changed files with 2273 additions and 629 deletions

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;
+10 -2
View File
@@ -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 {
+4 -1
View File
@@ -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}"
+5 -8
View File
@@ -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,
+7 -1
View File
@@ -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;
+6 -1
View File
@@ -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" />
+15 -3
View File
@@ -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;
+124 -65
View File
@@ -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);
}
+165 -77
View File
@@ -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;
}
}
}
+102 -38
View File
@@ -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>
+89 -26
View File
@@ -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
View File
@@ -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
+2 -1
View File
@@ -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;
}
+11
View File
@@ -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;
}
+7
View File
@@ -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() {
+1
View File
@@ -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
}
}
}
}
}
}
+4
View File
@@ -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);
}
+8 -6
View File
@@ -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
View File
@@ -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;
}
+20 -24
View File
@@ -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;
}
+2
View File
@@ -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
+2
View File
@@ -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
+2
View File
@@ -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