Compare commits

..
29 Commits
Author SHA1 Message Date
Mohammad Arham 30ba7ecd19 added logs for _default_abs_moves 2023-10-10 15:14:28 -07:00
Mohammad Arham db6b34c6a8 changd default_abs_moves from hard coded to config 2023-10-05 23:17:02 -07:00
Taha Afzal 4dbafcab91 TaskingEye can shoot deep in the book as well 2023-08-18 05:44:12 +00:00
Taha Afzal 9fd7f8a0a6 Adding security list for TaskingEye Algo 2023-08-17 21:33:40 +00:00
John Colvin 04e0297e86 refactor: Simplify 'render_piq' by using common code
No need to re-invent the 'render_cells' wheel
2023-07-31 14:50:44 -04:00
John Colvin 562706b968 fix: Don't assume ladder row-data is sorted by price
Bugs (fixed):
   * Ladder assert-crashes when rendering volume-at-price

   Cause: The render routine expected items to be sorted by price, and
   asserted as much (indirectly). In hindsight, this expectation placed
   an unnecessary burden on the caller.
2023-07-31 14:20:08 -04:00
John Colvin 4195207a71 fix: Replace non-existent StaticResource w/ BTSColor
Bugs (fixed):
   * The Activity Log window crashes immediately opened.

   Cause: WPF throws an Exception because it fails to resolve the
   StaticResources 'TextBox.BackgroundBrush', and
   'TextBox.ForegroundBrush', as they are defined in Colors.xaml, which
   is not imported by LogViewer.xaml.

   I replaced the StaticResources w/ BTSColor extension (rather than
   include Colors.xaml), because it is less error prone, and allows us
   to keep colors organized in 1 place: BTSColors.cs.

   I'm not sure how long the bug was around. It likely slipped through
   the cracks because the ActivityLog window isn't used often.
2023-07-28 14:16:46 -04:00
John Colvin 71eb89b6a1 fix: Check 'HasQuantityDisplay' before accessing prop
Bugs (fixed):
   * The PNL Summary window throws a null ref ex when the first row is
     populated.

     Cause: The SecurityMetadataImmutable's internal QuantityDisplay is
     null. In other words, the mutable SecurityMetadata obj around which
     it wraps has a null QuantityDisplay, which is not allowed since
     Immutable protos (such as 'QuantityDisplayImmutable') must be
     constructed w/ a non-null instance of their mutable counterpart.

* Implement same fix (pre-emptively) in ManualFillRow, and
  OrderCompletedEventPredicate.
2023-07-28 14:01:57 -04:00
John Colvin 94e4a0b725 fix: Change bus data type to nullable immutable
Bugs (fixed):
   * The InterpTheosServerPublisher throws a null ref during startup when it
     accesses 'UsageList' from the VolSurfaceUsageTag data.

     Cause: Immutable protos instantiated w/ the default ctr have an
     invalid state, because the mutable (reference type) instance around
     which they wrap is null. Since an immutable type's props are trivial
     wrappers for this mutable instance, they throw a null ref if the
     mutable instance is null.
2023-07-28 12:02:50 -04:00
John Colvin 40d1fe20e3 diag: Add detailed error logs to notification serv order handler
Maintain a circular buffer of the last N updates. Then if/when an
exception occurs, log the update sequence that led up to it.
2023-07-26 15:30:55 -04:00
John Colvin 2ed23159ca style: Clean up jenkins build script
* Get rid of double backslashes in quoted paths to improve readability
* Call 'activate.bat' instead of 'activate' (official python docs say to
  use the former)
* Add docs
2023-07-26 15:26:30 -04:00
John Colvin faa8438adf build: Make jenkins build script independent of working dir
This alleviates an unneccessary burden on callers (previously, it had to
be run from the repo root dir).
2023-07-26 11:59:03 -04:00
John Colvin 0b35c2ccde fix: Add lock, try/catch in notification serv order handler
Brandon has been experiencing a crash on startup. The stack trace shows
this handler is throwing a duplicate-key ex on 'dictionary.Add' (unclear
why).
2023-07-26 00:55:50 -04:00
John Colvin 98eea0ef09 chore: Remove unused iface member 2023-07-26 00:13:22 -04:00
John Colvin 4bf237aebe fix (pre-emptive): Guard against null/empty prices in LadderRender 2023-07-25 14:27:13 -04:00
John Colvin da927adef4 fix: Check price is within ladder's range before rendering
Bugs (fixed):
  The render routine was indirectly asserting that each item's price was
  >= the ladder's min price. That seems like an inappropriate burden to
  put on the caller. Now we filter these items out in the routine.
2023-07-25 14:24:16 -04:00
John Colvin cf587e26d5 fix: Remove bad assert abt tickdiff in LadderRender
* The test condition was wrong in the first place
* Instead of assert/crashing, log a warning
2023-07-25 14:22:15 -04:00
John Colvin 9e4354fb55 fix buid: Don't goto ERROR just bc venv already exists
This seems like a typo
2023-07-18 23:40:18 -04:00
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
Taha 3704d1ed16 InterptheosServerPublisher is now opulated the newly added vol field in the PrecomputedTheoPoint proto 2023-07-05 14:41:31 -04:00
Taha Afzal f2c3ff4be6 Adding vol to PrecomputedTheoPoint 2023-07-05 15:16:45 +00:00
30 changed files with 972 additions and 206 deletions

No files matched your search

@@ -116,7 +116,7 @@
_timer.start();
_analytics_service.archive_theos(archived_theos);
_timer.stop();
Log.Gen.info($"SingleVolSurfaceGateway._on_bb_data archiving theos size: " +
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) {
@@ -160,8 +160,8 @@ namespace mbts.spark {
public static IEnumerable<bts.api.SecurityTuple> get_vs_sectups(
int vs_id, ThreadedBlackboard bb) {
VolSurfaceUsageListImmutable vs_usage_list = bb.read_state(SparkTags.VolSurfaceUsageTag);
return vs_usage_list.get_sectups(vs_id);
var vs_usage_list = bb.read_state(SparkTags.VolSurfaceUsageTag);
return (vs_usage_list?.get_sectups(vs_id)).empty_if_null();
}
@@ -242,4 +242,4 @@ namespace mbts.spark {
}
#endregion
}
}
}
@@ -60,16 +60,18 @@ namespace mbts.spark {
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);
var usage = _bb.read_state(SparkTags.VolSurfaceUsageTag);
string current_user = _bb.read_state(SparkTags.CurrentUser).name;
foreach (var u in usage.UsageList) {
foreach (var u in (usage?.UsageList).empty_if_null()) {
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.SymbolName);
syms.Add(u.SecTup.Symbol);
}
}
}
@@ -81,14 +83,16 @@ namespace mbts.spark {
SystemContractData sc_data = _bb.read_state(SparkTags.SystemContractsTag, null);
IReadOnlyDictionary<string, OptionTheoInterpolationData> all_theos =
_bb.read_state(SparkTags.AllInterpGreeks, null);
all_theos = _filter_for_publish(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
IReadOnlyDictionary<string, OptionDriverPriceMap> all_drivers =
_bb.read_state(SparkTags.AllOptionDriverBBOTag, null);
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
@@ -111,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();
@@ -121,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}");
}
@@ -130,4 +135,4 @@ namespace mbts.spark {
_bb.data_callback -= _on_data;
}
}
}
}
@@ -22,7 +22,7 @@ namespace mbts.spark {
// ii. IntepGreeks for each vs
public class VolSurfaceInterpGreeksMultiplexer : IDisposable {
private readonly ThreadedBlackboard _bb;
private readonly ITag<VolSurfaceUsageListImmutable> _in_bb_vs_usage_tag;
private readonly ITag<VolSurfaceUsageListImmutable?> _in_bb_vs_usage_tag;
private IDictionary<int, ITag<VSTheos>> _vs_theo_tags =
new Dictionary<int, ITag<VSTheos>>();
private VolSurfaceUsageListImmutable _vs_usage_map =
@@ -48,8 +48,8 @@ namespace mbts.spark {
}
private void _analyze_usages() {
_vs_usage_map = _bb.read_state(_in_bb_vs_usage_tag,
new VolSurfaceUsageListImmutable(new VolSurfaceUsageList()));
_vs_usage_map = _bb.read_state(_in_bb_vs_usage_tag) ??
new VolSurfaceUsageList().immutable();
bts.Log.Gen.info($"VolSurfaceInterpGreeksMultiplexer._analyze_usages " +
$"count: {_vs_usage_map.UsageList.Count()}");
foreach (int vs_id in _vs_usage_map.vss()) {
@@ -40,10 +40,8 @@ namespace mbts.analytics {
/// last computed min/max driver for each exp
private IDictionary<VolSurfaceExpiration, Tuple<double, double>>
_computed_driver_range_map = new Dictionary<VolSurfaceExpiration, Tuple<double, double>>();
private readonly IEnumerable<double> _default_abs_moves =
new double[] { 300, -200, -125, -100, -75, -50, -25, 0, 25, 50, 75, 100, 125, 200, 300 };
private readonly IEnumerable<double> _default_abs_moves = config.Configuration.get<bts.config.VolSurface>().default_abs_moves;
private readonly double _cushion_percentage = 25.0;
public event
Action<IDictionary<VolSurfaceExpiration, IEnumerable<Theos>>, double, double> on_theos =
(o, vt, tt) => { };
@@ -52,7 +50,7 @@ namespace mbts.analytics {
(o, vt, tt) => { };
public VolSurfaceInterpGreeksProducer(int vs_id) {
Log.Gen.info($"VolSurfaceInterpGreeksProducer.cons vs_id: {vs_id}");
Log.Gen.info($"VolSurfaceInterpGreeksProducer.cons vs_id: {vs_id}, _default_abs_moves: {string.Join(", ", _default_abs_moves)}");
_vs_id = vs_id;
}
@@ -1281,7 +1281,7 @@ namespace mbts.spark {
if (!_settings.piq_column.show)
return;
int origin_index = LadderRender.index_of(_price_list, _mkt_break_lines.origin, _price_comparer);
LadderRender.render_piqs(_piq_vis_tup.visual, _last_vis_tup.glyph_context, origin_index, _price_list, _vm.price_vs_piq, _settings.row_height, _spec, _settings.piq_column, _fmt_mkt_quantity);
LadderRender.render_piqs(_piq_vis_tup.visual, _last_vis_tup.glyph_context, _price_list, _vm.price_vs_piq, _settings.row_height, _spec, _settings.piq_column, _fmt_mkt_quantity);
}
void _refresh_sizes() {
@@ -368,30 +368,32 @@ 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, RegionalTickFactors spec, RenderCell<T> render_cell) {
static void _render_cells<T>(DrawingVisual dv, double row_height, IEnumerable<(double price, T value)> cells, 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())
if (prices.is_null_or_empty() || cells.is_null_or_empty()) {
return;
int curr_index = 0;
double curr_price = prices[curr_index];
foreach (var (price, value) in data) {
if (double.IsNaN(price))
continue;
// prices are in descending order, so get diff from curr_price - price
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);
} else break;
}
var min_price = prices[prices.Count - 1];
var max_price = prices[0];
Log.assert(min_price.is_real() && max_price.is_real() && min_price <= max_price);
var indexed_cells = cells
.Where(item => item.price.is_real() && item.price.is_within_range(min_price, max_price))
.Select(row => {
checked {
return (row.price, row.value, (int)spec.tickdiff(max_price, row.price));
}
});
foreach (var (price, value, idx) in indexed_cells) {
var check_price = spec.tickdown(max_price, idx);
Log.assert(spec.tick_equals(price, check_price));
Log.assert(idx >= 0 && idx < prices.Count);
render_cell(dc, idx, value);
}
}
}
internal static void render_last(DrawingVisual dv, GlyphContext glyph_context, NativePriceSize last, int index, double row_height, LadderColumnSettings settings, Func<uint, string> fmt_value) {
if (fmt_value is null)
fmt_value = FormattingDelegates.default_string<uint>();
@@ -405,38 +407,18 @@ namespace mbts.spark {
internal static void render_piqs(
DrawingVisual dv,
GlyphContext glyph_context,
int origin_index,
IList<double> prices,
IEnumerable<(double price, uint piq)> pvp,
double row_height,
RegionalTickFactors spec,
LadderColumnSettings settings,
Func<uint, string> fmt_value) {
//
using (var dc = dv.RenderOpen()) {
using (_draw_with_clip(dc, settings, row_height, prices.Count)) {
if (!prices.within_range(origin_index) || pvp.is_null_or_empty())
return;
if (fmt_value is null)
fmt_value = FormattingDelegates.default_string<uint>();
var foreground = settings.foreground.rgb_brush();
int curr_index = origin_index;
double curr_price = prices[curr_index];
foreach (var entry in pvp) {
double price = entry.price;
// prices are in descending order, so get diff from curr_price - price
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);
dc.DrawGlyphRun(foreground, gr);
} else break;
}
}
}
Func<uint, string> fmt_value
) {
fmt_value ??= FormattingDelegates.default_string<uint>();
var fg = settings.foreground.rgb_brush();
_render_cells(dv, row_height, pvp, settings, prices, spec, (dc, row, piq) =>
dc.DrawGlyphRun(
fg, format_text(fmt_value(piq), row, row_height, settings, glyph_context)));
}
public static void render_mkt_break(DrawingVisual dv, int index, double row_height, uint color) =>
@@ -59,7 +59,9 @@ namespace bts.shell.manual_fills {
nameof(quantity_display),
() => {
var sec_meta = security.HasValue ? _sec_meta_store.get(security.Value) : null;
return sec_meta?.QuantityDisplay ?? Security.Defaults.quantity_display_settingsi;
return sec_meta?.HasQuantityDisplay ?? false ?
sec_meta.Value.QuantityDisplay :
Security.Defaults.quantity_display_settingsi;
});
// TODO (prob): remove conversion_data. Replace get_qty_display_divisor delegates w/
// TickDisplaySettingsImmutable for qty, obtained via ISecurityMetadataStore.
@@ -293,7 +293,10 @@ namespace bts.shell.pnl {
return _quantity_display_backing.Value;
}
if (security.HasValue) {
_quantity_display_backing = _sec_meta_store.get(security.Value)?.QuantityDisplay;
var sec_meta = _sec_meta_store.get(security.Value);
if (sec_meta?.HasQuantityDisplay ?? false) {
_quantity_display_backing = sec_meta.Value.QuantityDisplay;
}
}
return _quantity_display_backing ??= Security.Defaults.quantity_display_settingsi;
}
@@ -121,7 +121,7 @@ namespace bts.shell {
void _on_orders(TableUpdate<(SecurityImmutable? sec, OrderRecordImmutable order), uint> update) {
// must update notification_service_data_cache first, some of the predicate/triggers (esp. ToastNotifications) will use the notification_service_data_cache to lookup order-history
_data_cache.update(update.as_iincremental());
_data_cache.update(update);
foreach (var o in update.added.Concat(update.changed))
_order_notifications.process_item(o);
}
@@ -1,89 +1,157 @@
using bts.api;
using bts.api.orderman;
using mbts.spark;
using mbts.utils;
using System;
using System.Collections.Generic;
namespace bts.shell {
// Any predicate-sound/notifications that need to reference previous data (i.e. notifications based on change/time, or state changes) should use the INotificationServiceDataCache.
// The INotificationService updates these caches in its IObservable.OnNext callbacks, just before calling all of the predicate-trigger methods.
// Therfore, data referenced through this interface is guarenteed to be up-to-date, and thread safe. It also reduces performance overhead by avoiding redundant bookeeping.
// Note: last_order_reset_time can be used by sound/toast-notification sources to avoid sending false-positive notifications for old orders (i.e. all the orders that arrive in the first update on startup/server restart)
// --------------------------------------------------------------------------------------------------------------------------------------------
// IMPOARTANT!!! The current implementation of this interface is NOT INTENDED FOR GENERAL USE - order_history is NOT THREAD-SAFE
interface INotificationServiceDataCache {
DateTime last_order_reset_time { get; }
bool is_first_order_update { get; }
IReadOnlyDictionary<uint, List<TimeStampedData<OrderSecurity>>> order_history { get; }
}
static class NotificationServiceDataCacheExtensions {
public static OrderSecurity? get_previous_order_state(this INotificationServiceDataCache notification_service_data_cache, uint key) {
if (notification_service_data_cache.order_history.TryGetValue(key, out List<TimeStampedData<OrderSecurity>> l)) {
if (l.Count > 1) {
return l[l.Count - 2].data;
}
}
return null;
}
}
struct TimeStampedData<T> {
public readonly T data;
public readonly DateTime time_stamp;
public TimeStampedData(T data, DateTime time_stamp) {
this.data = data;
this.time_stamp = time_stamp;
}
}
partial class NotificationService : INotificationService, IDisposable {
class DataCache : INotificationServiceDataCache {
const int _buffer_size = 10;
readonly Dictionary<uint, OrderSecurity> _order_cache = new Dictionary<uint, OrderSecurity>();
readonly Dictionary<uint, List<TimeStampedData<OrderSecurity>>> _order_history = new Dictionary<uint, List<TimeStampedData<OrderSecurity>>>();
public IReadOnlyDictionary<uint, List<TimeStampedData<OrderSecurity>>> order_history => _order_history;
public DateTime last_order_reset_time { get; private set; }
public bool is_first_order_update { get; private set; }
internal void update(IIncrementalUpdate<(SecurityImmutable? sec, OrderRecordImmutable order), uint> data) {
DateTime now = DateTime.Now;
if (data.removed.Count == _order_cache.Count) {
last_order_reset_time = now;
_order_cache.Clear();
}
is_first_order_update = _order_cache.Count == 0 && data.added.Count > 0;
foreach (var kv in data.removed) {
if (data.added.ContainsKey(kv.Key)) {
_order_cache[kv.Key] = kv.Value;
_order_history[kv.Key].Add(new TimeStampedData<OrderSecurity>(kv.Value, now));
} else {
_order_cache.Remove(kv.Key);
// TODO: maybe these shouldn't be removed?
// if we add delta/time predicates that need to refer to order history, we might need to keep them around
// although, if the server is telling us to remove them, perhaps we shouldn't consider such data.
_order_history.Remove(kv.Key);
}
}
foreach (var kv in data.added) {
if (data.removed.ContainsKey(kv.Key))
continue;
_order_cache.Add(kv.Key, kv.Value);
_order_history.AddOrUpdate(kv.Key,
k => new List<TimeStampedData<OrderSecurity>> { new TimeStampedData<OrderSecurity>(kv.Value, now) },
(k, l) => {
l.Add(new TimeStampedData<OrderSecurity>(kv.Value, now));
if (l.Count >= 10)
l.RemoveAt(0);
});
}
}
}
}
}
using bts.api;
using bts.api.core;
using bts.api.orderman;
using mbts.spark;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Linq;
namespace bts.shell {
// Any predicate-sound/notifications that need to reference previous data (i.e. notifications based on change/time, or state changes) should use the INotificationServiceDataCache.
// The INotificationService updates these caches in its IObservable.OnNext callbacks, just before calling all of the predicate-trigger methods.
// Therfore, data referenced through this interface is guarenteed to be up-to-date, and thread safe. It also reduces performance overhead by avoiding redundant bookeeping.
// Note: last_order_reset_time can be used by sound/toast-notification sources to avoid sending false-positive notifications for old orders (i.e. all the orders that arrive in the first update on startup/server restart)
// --------------------------------------------------------------------------------------------------------------------------------------------
// IMPOARTANT!!! The current implementation of this interface is NOT INTENDED FOR GENERAL USE - order_history is NOT THREAD-SAFE
interface INotificationServiceDataCache {
bool is_first_order_update { get; }
IReadOnlyDictionary<uint, List<TimeStampedData<OrderSecurity>>> order_history { get; }
}
static class NotificationServiceDataCacheExtensions {
public static OrderSecurity? get_previous_order_state(this INotificationServiceDataCache notification_service_data_cache, uint key) {
if (notification_service_data_cache.order_history.TryGetValue(key, out List<TimeStampedData<OrderSecurity>> l)) {
if (l.Count > 1) {
return l[l.Count - 2].data;
}
}
return null;
}
}
struct TimeStampedData<T> {
public readonly T data;
public readonly DateTime time_stamp;
public TimeStampedData(T data, DateTime time_stamp) {
this.data = data;
this.time_stamp = time_stamp;
}
}
partial class NotificationService : INotificationService, IDisposable {
class DataCache : INotificationServiceDataCache {
const int _ORDER_HISTORY_BUFFER_SIZE = 10;
const int _TABLE_HISTORY_BUFFER_SIZE = 5;
readonly Dictionary<uint, OrderSecurity> _order_cache = new Dictionary<uint, OrderSecurity>();
readonly Dictionary<uint, List<TimeStampedData<OrderSecurity>>> _order_history = new Dictionary<uint, List<TimeStampedData<OrderSecurity>>>();
readonly List<(TableUpdate<(SecurityImmutable?, OrderRecordImmutable), uint>, DateTime)> _table_history = new(_TABLE_HISTORY_BUFFER_SIZE);
readonly object _lock = new();
public IReadOnlyDictionary<uint, List<TimeStampedData<OrderSecurity>>> order_history => _order_history;
public bool is_first_order_update { get; private set; }
internal void update(TableUpdate<(SecurityImmutable? sec, OrderRecordImmutable order), uint> table) {
// First, try processing the update per usual (incrementally). If it throws,
// then log the exception and reset the state.
DateTime now = DateTime.Now;
lock (_lock) {
_circular_buffer_add(_table_history, (table, now), _TABLE_HISTORY_BUFFER_SIZE);
try {
impl();
return;
} catch (Exception ex) {
var table_history_desc = _fmt_order_table_history_safe();
Log.Ui.error(
$"Error processing order update in NotificationService: {ex}.\n" +
$"***Last table updates below***\n" +
table_history_desc);
}
// This is the 'escape' path. It really shouldn't throw.
reset();
}
void impl() {
if (table.state.Count == 0) {
_order_cache.Clear();
}
is_first_order_update = _order_cache.Count == 0 && table.state.Count > 0;
foreach (var order in table.removed) {
var k = table.key(order);
_order_cache.Remove(k);
_order_history.Remove(k);
}
foreach (var ch in table.changes) {
var k = table.key(ch.New);
_order_cache[k] = ch.New;
_order_history[k].Add(new(ch.New, now));
}
foreach (var order in table.added) {
var k = table.key(order);
_order_cache[k] = order;
var hist = _order_history.GetOrAdd(k, _ => new());
_circular_buffer_add(hist, new(order, now), _ORDER_HISTORY_BUFFER_SIZE);
}
}
void reset() {
_order_cache.Clear();
_order_history.Clear();
_table_history.Clear();
foreach (var (k, order) in table.state) {
_order_cache[k] = order;
_order_history.GetOrAdd(k, _ => new()).Add(new(order, now));
}
is_first_order_update = true;
}
}
static void _circular_buffer_add<T>(List<T> list, T item, int buffer_size) {
Log.assert(buffer_size > 0);
if (list.Count > buffer_size - 1) {
// Remove oldest items (trim front)
list.RemoveRange(
0, list.Count - (buffer_size - 1));
}
list.Add(item);
}
string _fmt_order_table_history_safe() {
return _fmt_table_history_safe(_table_history, fmt_order_tup);
static string fmt_order_tup((SecurityImmutable?, OrderRecordImmutable) order_tup) {
var (sec, order) = order_tup;
var order_desc = OrderUtils.short_description(order, sec.spec_or_default());
var sec_desc = sec?.ToString() ?? ("sid=" + order.security_key().ToString());
return $"{sec_desc} {order_desc}";
}
}
static string _fmt_table_history_safe<T, TKey>(
List<(TableUpdate<T, TKey>, DateTime)> table_history, Func<T, string> fmt_value) where TKey : IEquatable<TKey> {
var table_history_desc = new System.Text.StringBuilder();
for (int i = 0; i < table_history.Count; i++) {
var (table, time_stamp) = table_history[i];
table_history_desc.AppendLine(
$"Update - {table_history.Count - i}, at {time_stamp:yyyy-MM-dd hh:mm:ss.ffffff}");
table_history_desc.AppendLine(_fmt_table_safe(table, fmt_value));
}
return table_history_desc.ToString();
}
static string _fmt_table_safe<T, TKey>(
TableUpdate<T, TKey> table, Func<T, string> fmt_value) where TKey : IEquatable<TKey> {
try {
return table.description(fmt_value);
} catch (Exception ex) {
return $"Exception while formatting TableUpdate: ${ex}";
}
}
}
}
}
@@ -90,7 +90,8 @@ namespace mbts {
}
var sec = order.security;
var sec_meta = sec.HasValue ? _sec_meta_store.get(sec.Value) : null;
var qty_display = sec_meta?.QuantityDisplay ??
var qty_display = sec_meta?.HasQuantityDisplay ?? false ?
sec_meta.Value.QuantityDisplay :
Security.Defaults.quantity_display_settingsi;
return ToastNotifications.order_completed(order, qty_display);
}
+2 -2
View File
@@ -266,8 +266,8 @@ namespace mbts.spark {
// List of symbol to volsurface mapping.
// Publisher: VolSurfaceBlackboardManager
public static ITag<VolSurfaceUsageListImmutable> VolSurfaceUsageTag { get; } =
_mk_tag<VolSurfaceUsageListImmutable>();
public static ITag<VolSurfaceUsageListImmutable?> VolSurfaceUsageTag { get; } =
_mk_tag<VolSurfaceUsageListImmutable?>();
// The AW2 machinary can compute and publish the theos
// right back locally(vs the server doing that) if configured that way.
@@ -10,8 +10,8 @@
</ResourceDictionary.MergedDictionaries>
<Style x:Key="LogViewer.TextBox" TargetType="TextBox">
<Setter Property="Background" Value="{StaticResource TextBox.BackgroundBrush}" />
<Setter Property="Foreground" Value="{StaticResource TextBox.ForegroundBrush}" />
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}" />
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}" />
<Setter Property="BorderThickness" Value="0" />
</Style>
+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
+1
View File
@@ -44,6 +44,7 @@ message PrecomputedTheoPoint {
double vega = 60;
double theta = 70;
double rho = 80;
double vol = 90;
}
message PrecomputedTheos {
@@ -0,0 +1,22 @@
VolSurface {
value {
default_abs_moves: -600
default_abs_moves: -300
default_abs_moves: -200
default_abs_moves: -125
default_abs_moves: -100
default_abs_moves: -75
default_abs_moves: -50
default_abs_moves: -25
default_abs_moves: 0
default_abs_moves: 25
default_abs_moves: 50
default_abs_moves: 75
default_abs_moves: 100
default_abs_moves: 125
default_abs_moves: 200
default_abs_moves: 300
default_abs_moves: 600
}
}
@@ -0,0 +1,6 @@
syntax = 'proto2';
package bts.config;
message VolSurface {
repeated double default_abs_moves = 1;
}
+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);
}
@@ -25,5 +25,9 @@ class ITheoCalculator {
bts::api::orderman::PrecomputedTheoPoint &result) const = 0;
// gets the driver security for a security with id sec_id
virtual std::optional<uint32_t> get_driver_sec(uint32_t sec_id) const = 0;
// given a vol for an option and the current driver, returns the theo that
// corresponds to that vol.
virtual double get_theo_correspoding_to_vol(
uint32_t sec_id, double driver, double vol) const = 0;
};
} // namespace bts::orderman::analytics
@@ -50,12 +50,15 @@ void interpolate(
lower.driver(), lower.theta(), higher.driver(), higher.theta(), driver);
double rho = lin_interp_exterp(
lower.driver(), lower.rho(), higher.driver(), higher.rho(), driver);
double vol = lin_interp_exterp(
lower.driver(), lower.vol(), higher.driver(), higher.vol(), driver);
result.set_theo(theo);
result.set_delta(delta);
result.set_gamma(gamma);
result.set_vega(vega);
result.set_theta(theta);
result.set_rho(rho);
result.set_vol(vol);
}
struct DriverNeighbors {
@@ -213,6 +216,24 @@ std::optional<uint32_t> TheoCalculator::get_driver_sec(uint32_t sec_id) const {
return me->total_theo_data.get_driver_sec(sec_id);
}
double TheoCalculator::get_theo_correspoding_to_vol(
uint32_t sec_id, double driver, double vol) const {
PrecomputedTheoPoint rv;
if (!get_theo(sec_id, driver, rv)) {
return bts::os::nan();
}
// use the vega to compute the theo
double d_vol = vol - rv.vol();
double d_theo = rv.vega() * d_vol;
double new_theo = rv.theo() + d_theo;
LOG(Gen, Info,
"TheoCalculator::get_theo_correspoding_to_vol sec_id: {} driver: {} "
"vol: {} rv.theo: {} rv.vol: {} rv.vega: {} "
"new_theo: {}",
sec_id, driver, vol, rv.theo(), rv.vol(), rv.vega(), new_theo);
return new_theo;
}
void TheoCalculator::set_data(const PrecomputedTheos &theos) {
LOG(Gen, Info, "TheoCalculator::set_data size: {}", theos.theos_size());
WriteLock write_lock(me->lock);
@@ -23,6 +23,8 @@ class TheoCalculator : public ITheoCalculator {
double driver,
bts::api::orderman::PrecomputedTheoPoint &result) const override;
std::optional<uint32_t> get_driver_sec(uint32_t sec_id) const override;
double get_theo_correspoding_to_vol(
uint32_t sec_id, double driver, double vol) const override;
void set_data(const bts::api::orderman::PrecomputedTheos &theos);
private:
@@ -279,5 +279,13 @@ inline Quantity get_update_quantity(const bts::api::orderman::Update &u) {
return rv;
}
inline void set_update_quantity(
bts::api::orderman::Update &state, Quantity cqty) {
uint32_t int_qty = cqty.int_part();
double fractional_qty = cqty.fractional_part();
state.set_quantity(int_qty);
state.mutable_fractional_quantity()->set_fraction(fractional_qty);
}
} // namespace orderman
} // namespace bts
@@ -12,6 +12,7 @@
#include "lib/orderman/orderman_utils/orderman_utils.h"
#include "lib/orderman/orders/algo/algo_orders/LimitCoverAlgo.h"
#include "lib/orderman/orders/algo/algo_orders/TaskingEyeAlgo.h"
#include "lib/orderman/orders/algo/algo_orders/VolAlgo.h"
#include "lib/orderman/orders/algo/algo_orders/limit_cover/LimitCoverAdapter.h"
#include "lib/orderman/orders/algo/algo_orders/tests/ExecutionTesterAlgo.h"
#include "lib/plugin/SparkServices.h"
@@ -299,6 +300,9 @@ extern "C" void init_execution_tester_plugin(bts::SparkServices &srv) {
extern "C" void init_task_eye_plugin(bts::SparkServices &srv) {
DynamicOrderDescriptor tasking_eye_algo;
tasking_eye_algo.set_dynamic_order_type("Tasking Eye");
// if this is set to true, it waits for the existance of Signal File
*tasking_eye_algo.add_param_desc() = param_bool("Wait For Signal");
*tasking_eye_algo.add_param_desc() = param_string("Signal File");
*tasking_eye_algo.add_param_desc() = param_string("Account");
*tasking_eye_algo.add_param_desc() = param_double("Edge");
*tasking_eye_algo.add_param_desc() = param_double("Shot Size");
@@ -325,6 +329,19 @@ extern "C" void init_task_eye_plugin(bts::SparkServices &srv) {
});
}
extern "C" void init_vol_algo_plugin(bts::SparkServices &srv) {
DynamicOrderDescriptor vol_algo;
vol_algo.set_dynamic_order_type("Vol");
//*vol_algo.add_param_desc() = param_string("Account Override");
*vol_algo.add_param_desc() = param_double("Vol");
*vol_algo.add_param_desc() = param_bool("Cross At Entry?");
*vol_algo.add_param_desc() = param_bool("Requote?");
*vol_algo.add_param_desc() = param_bool("Improve Price?");
*vol_algo.add_param_desc() = param_int32("Distance From Top");
srv.register_dynamic_algo_order(
vol_algo, []() { return std::make_unique<orderman::VolAlgo>(); });
}
extern "C" void init_spark_plugin(bts::SparkServices &srv) {
auto &dyn_algo_config = config::get<config::toggles::DynamicAlgos>();
if (dyn_algo_config.enable_dorman())
@@ -345,6 +362,7 @@ extern "C" void init_spark_plugin(bts::SparkServices &srv) {
init_execution_tester_plugin(srv);
// init_sniper_plugin(srv);
init_task_eye_plugin(srv);
init_vol_algo_plugin(srv);
}
} // namespace dorman
@@ -23,10 +23,17 @@
#include "lib/utils/bts_assert.h"
#include <bits/stdint-uintn.h>
#include <boost/algorithm/string.hpp>
#include <chrono>
#include <elf.h>
#include <filesystem>
#include <google/protobuf/timestamp.pb.h>
#include <google/protobuf/util/time_util.h>
#include <iostream>
#include <optional>
#include <sstream>
#include <string>
#include <thread>
#include <tuple>
#include <unordered_map>
using namespace bts::api::orderman;
@@ -69,6 +76,11 @@ double eps = 0.0000001;
bool eq(double d1, double d2) { return std::abs(d1 - d2) < eps; }
bool greater_then_eq(double d1, double d2) { return eq(d1, d2) || d2 > d1; }
void sleep_log() {
return;
std::this_thread::sleep_for(std::chrono::seconds(5));
}
// Returns the number of shots that are within the Shots Window
size_t n_recent_shots(
const std::vector<TEAShot> &shots, uint64_t window_nanos) {
@@ -100,7 +112,91 @@ bool in_flight_orders(const std::vector<TEAShot> &shots) {
return false;
}
std::set<SecurityKey> parse_securities(const std::string &secs) {
// returns the securities that match the criteria from the 2nd - 8th arg
std::set<SecurityKey> match_securities(
const std::vector<api::Security const *> &secs,
api::Venue venue,
const std::string &symbol,
uint32_t exp_month,
uint32_t exp_day,
uint32_t exp_yr,
std::tuple<double, double> strike_range,
api::PutOrCall cp) {
std::set<SecurityKey> rv = {};
double strike_min = std::get<0>(strike_range);
double strike_max = std::get<1>(strike_range);
for (const auto &s : secs) {
if (s->exchange() == venue && s->has_option() && s->symbol() == symbol) {
const auto &oc = s->option();
if (oc.call_put() == cp && oc.strike() >= strike_min &&
oc.strike() <= strike_max && oc.expiration_year() == exp_yr &&
oc.expiration_month() == exp_month &&
oc.expiration_day() == exp_day)
rv.insert(SecurityKey(s->id()));
}
}
return rv;
}
// Parses the sec expressed as text in the secs string.
// the format is symbol:exp:call/put:strike range, ex
// BTC:08/14/2023:Call:25000-30000
std::set<SecurityKey> parse_securities_from_range(
const std::string &secs,
const std::vector<api::Security const *> &sec_vec) {
LOG(OrderMan, Info,
"TaskingEyeAlgo::parse_securities_from_range secs: {} sec_vec: {}", secs,
sec_vec.size());
sleep_log();
if (secs.empty())
return {};
auto parse_exp = [](const std::string &exp_str) {
LOG(OrderMan, Info,
"TaskingEyeAlgo::parse_securities_from_range parse_exp exp: {}",
exp_str);
std::vector<std::string> exp_parts;
boost::split(exp_parts, exp_str, boost::is_any_of("/"));
LOG(OrderMan, Info,
"TaskingEyeAlgo::parse_securities_from_range exp_parts: {}",
exp_parts.size());
uint32_t mm = static_cast<std::uint32_t>(std::stoul(exp_parts[0]));
uint32_t dd = static_cast<std::uint32_t>(std::stoul(exp_parts[1]));
uint32_t yy = static_cast<std::uint32_t>(std::stoul(exp_parts[2]));
LOG(OrderMan, Info,
"TaskingEyeAlgo::parse_securities_from_range {} mm: {} dd: {} yy: {}",
mm, dd, yy);
return std::tuple<uint32_t, uint32_t, uint32_t>(mm, dd, yy);
};
auto parse_strike_range = [](const std::string &strike_range_str) {
std::vector<std::string> strike_parts;
boost::split(strike_parts, strike_range_str, boost::is_any_of("-"));
double k_min = std::stod(strike_parts[0]);
double k_max = std::stod(strike_parts[1]);
return std::tuple<double, double>(k_min, k_max);
};
std::vector<std::string> parts;
boost::split(parts, secs, boost::is_any_of(":"));
const std::string &symbol = parts[0];
const std::string &exp_str = parts[1];
const std::string &cp_str = parts[2];
const auto &strike_range_str = parts[3];
LOG(OrderMan, Info,
"TaskingEyeAlgo::parse_securities_from_range symbol: {} exp: {} cp: {} "
"stike_range: {}",
symbol, exp_str, cp_str, strike_range_str);
// debugging
sleep_log();
auto [mm, dd, yy] = parse_exp(exp_str);
auto strike_range = parse_strike_range(strike_range_str);
api::PutOrCall cp = (cp_str[0] == 'C' || cp_str[0] == 'c')
? api::PutOrCall::PUTORCALL_CALL
: api::PutOrCall::PUTORCALL_PUT;
return match_securities(
sec_vec, api::Venue::VENUE_DERIBIT, symbol, mm, dd, yy, strike_range,
cp);
}
std::set<SecurityKey> parse_securities_from_keys(const std::string &secs) {
std::set<SecurityKey> rv = {};
std::vector<std::string> parts;
boost::split(parts, secs, boost::is_any_of(","));
@@ -113,10 +209,34 @@ std::set<SecurityKey> parse_securities(const std::string &secs) {
return rv;
}
std::set<SecurityKey> parse_securities(
const std::string &secs,
const std::vector<api::Security const *> &sec_vec) {
bts_assert(!secs.empty());
LOG(OrderMan, Info, "TaskingEyeAlgo::parse_securities sec: {}", secs);
sleep_log();
std::set<SecurityKey> rv = {};
if (secs.find("/") != std::string::npos &&
secs.find(":") != std::string::npos) {
rv = parse_securities_from_range(secs, sec_vec);
} else {
rv = parse_securities_from_keys(secs);
}
std::stringstream ss;
for (auto k : rv) {
ss << k << " ";
}
LOG(OrderMan, Info, "TaskingEyeAlgo::parse_securities sec: {} rv: {}", secs,
ss.str());
return rv;
}
struct TaskingEyeAlgo ::PImpl {
IAlgoServices *service_ptr = nullptr;
analytics::ITheoCalculator const *theo_calc = nullptr;
TEAState state = TEAState::ACTIVE;
bool wait_for_signal = false;
std::string signal_file = "";
double edge = 100000;
double shot_size = 0;
double target_qty = 0;
@@ -150,31 +270,62 @@ TaskingEyeAlgo::~TaskingEyeAlgo() {}
OrderOpResult TaskingEyeAlgo::do_before_launch(
IBeforeLaunchAlgoServices &serv) {
auto is_data_valid = [&]() {
return greater_then_eq(0.1, me->shot_size) &&
greater_then_eq(1, me->target_qty) &&
greater_then_eq(1, me->max_shots_per_window) &&
greater_then_eq(1000000000, me->window_size_nanos) &&
me->side != api::SIDE_UNDEFINED;
bool rv = greater_then_eq(0.1, me->shot_size) &&
greater_then_eq(1, me->target_qty) &&
greater_then_eq(1, me->max_shots_per_window) &&
greater_then_eq(1000000000, me->window_size_nanos) &&
me->side != api::SIDE_UNDEFINED;
bool signal_correct = !me->wait_for_signal || !me->signal_file.empty();
rv = rv && signal_correct;
return rv;
};
LOG(OrderMan, Info,
"TaskingEyeAlgo::do_before_launch order_key: {} security: {} "
"definition: {}",
serv.order_key().value(), utils::pb::to_string(serv.security()),
utils::pb::to_string(serv.definition()));
sleep_log();
auto &def = serv.definition();
if (def.has_custom_def() && def.custom_def().has_dynamic()) {
const auto &d_params = def.custom_def().dynamic().order_params();
auto wait_for_signal = get_if<bool>(d_params, "Wait For Signal");
me->wait_for_signal =
wait_for_signal.has_value() ? wait_for_signal.value() : false;
auto signal_file = get_if<std::string>(d_params, "Signal File");
me->signal_file = signal_file.has_value() ? signal_file.value() : "";
me->edge = get_if<double>(d_params, "Edge").value();
LOG(OrderMan, Info, "TaskingEyeAlgo::do_before_launch edge: {}",
me->edge);
sleep_log();
me->shot_size = get_if<double>(d_params, "Shot Size").value();
LOG(OrderMan, Info, "TaskingEyeAlgo::do_before_launch shot_size: {}",
me->shot_size);
sleep_log();
me->target_qty = get_if<double>(d_params, "Target Quantity").value();
LOG(OrderMan, Info, "TaskingEyeAlgo::do_before_launch target_qty: {}",
me->target_qty);
sleep_log();
// TODO, make sure the side if right
me->side = def.side();
me->max_shots_per_window =
get_if<uint32_t>(d_params, "Max Shots Per Window").value();
LOG(OrderMan, Info,
"TaskingEyeAlgo::do_before_launch max_shots_per_window: {}",
me->max_shots_per_window);
sleep_log();
uint32_t window_size_secs =
get_if<uint32_t>(d_params, "Window Size (secs)").value();
LOG(OrderMan, Info,
"TaskingEyeAlgo::do_before_launch window_size_secs: {}",
window_size_secs);
sleep_log();
me->window_size_nanos = seconds_to_nanos(window_size_secs);
auto sec_str = get_if<std::string>(d_params, "Securities").value();
auto parsed_secs = parse_securities(sec_str);
LOG(OrderMan, Info, "TaskingEyeAlgo::do_before_launch Securities: {}",
sec_str);
sleep_log();
auto parsed_secs = parse_securities(sec_str, serv.known_securities());
std::stringstream ss;
std::vector<SecurityKey> final_secs;
if (parsed_secs.empty()) {
final_secs.push_back(SecurityKey{serv.security().id()});
@@ -186,18 +337,21 @@ OrderOpResult TaskingEyeAlgo::do_before_launch(
auto ct = get_tea_child_template(serv, sec_key, me->side);
me->sec_map.insert({sec_key, {ct}});
LOG(OrderMan, Info,
"TaskingEyeAlgo::do_before_launch order_key: {} successfully "
"TaskingEyeAlgo::do_before_launch order_key: {} "
"successfully "
"created ChildTemplate for sec_key: {}",
serv.order_key(), sec_key);
}
LOG(OrderMan, Info,
"TaskingEyeAlgo::do_before_launch algo constructed order_key: {} "
"edge: {} shot_size: {} is_buy: {} target_qty: {} "
"max_shots_per_window: {} window_size_nanos: {} n_secs: {}",
"TaskingEyeAlgo::do_before_launch algo constructed order_key: "
"{} "
"edge: {} shot_size: {} side: {} target_qty: {} "
"max_shots_per_window: {} window_size_nanos: {} n_secs: {} "
"wait_for_signal: {} signal_file: {}",
serv.order_key(), me->edge, me->shot_size,
me->side == api::SIDE_BUY, me->target_qty,
bts::api::Side_Name(me->side), me->target_qty,
me->max_shots_per_window, me->window_size_nanos,
me->sec_map.size());
me->sec_map.size(), me->wait_for_signal, me->signal_file);
return {};
}
}
@@ -213,7 +367,8 @@ void TaskingEyeAlgo::do_launch(IAlgoServices &serv) {
for (const auto &kv : me->sec_map) {
auto sec_key = kv.first;
LOG(OrderMan, Info,
"TaskingEyeAlgo::do_before_launch subscribing book for sec_key: {}",
"TaskingEyeAlgo::do_before_launch subscribing book for sec_key: "
"{}",
sec_key);
me->service_ptr->subscribe_book(sec_key);
}
@@ -315,9 +470,9 @@ void TaskingEyeAlgo::on_theos(
if (!sec_data.driver_sec_key.has_value() ||
sec_data.driver_sec_key->value() != driver.value()) {
LOG(OrderMan, Info,
"TaskingEyeAlgo::on_theos order_key: {} subscribing to driver "
"sec_key: {}",
me->service_ptr->order_key(), driver.value());
"TaskingEyeAlgo::on_theos order_key: {} subscribing to driver for "
"sec_key: {} driver_key: {}",
me->service_ptr->order_key(), sec_key, driver.value());
sec_data.driver_sec_key = SecurityKey(driver.value());
sub_to_driver(SecurityKey(driver.value()));
}
@@ -331,8 +486,14 @@ void TaskingEyeAlgo::on_position(PositionUpdate const &pu) {
}
void TaskingEyeAlgo::_evaluate_and_shoot() {
if (me->state != TEAState::ACTIVE)
if (me->state != TEAState::ACTIVE) {
LOG(OrderMan, Warn,
"TaskingEyeAlgo::_evaluate_and_shoot order_key: {} state is not "
"active state: {} ",
me->service_ptr->order_key(), me->state);
return;
}
auto max_shots_per_win_reached = [&]() {
return n_recent_shots(me->shots, me->window_size_nanos) >=
me->max_shots_per_window;
@@ -343,12 +504,36 @@ void TaskingEyeAlgo::_evaluate_and_shoot() {
return true;
return false;
};
auto signal_condition_met = [&]() {
if (me->wait_for_signal) {
return std::filesystem::exists(me->signal_file);
} else
return true;
};
if (!signal_condition_met()) {
// Log ?
LOG(OrderMan, Warn,
"TaskingEyeAlgo::_evaluate_and_shoot order_key: {} Waiting for "
"Signal",
me->service_ptr->order_key());
return;
}
for (auto &kv : me->sec_map) {
auto sec_key = kv.first;
if (max_shots_per_win_reached())
if (max_shots_per_win_reached()) {
LOG(OrderMan, Warn,
"TaskingEyeAlgo::_evaluate_and_shoot order_key: {} "
"max_shots_per_win_reached",
me->service_ptr->order_key());
return;
if (sec_has_active_order(sec_key))
}
if (sec_has_active_order(sec_key)) {
LOG(OrderMan, Warn,
"TaskingEyeAlgo::_evaluate_and_shoot sec_key: {} has working "
"order, skipping",
sec_key.value());
continue;
}
Price price;
Quantity qty;
if (_in_shooting_zone(sec_key, price, qty)) {
@@ -357,7 +542,8 @@ void TaskingEyeAlgo::_evaluate_and_shoot() {
auto ch_ord_key =
me->service_ptr->launch_child(*sec_data.order, price, qty);
LOG(OrderMan, Info,
"TakingEyeAlgo::_evaluate_and_shoot ord_key: {} child_ord_key: {} "
"TakingEyeAlgo::_evaluate_and_shoot ord_key: {} child_ord_key: "
"{} "
"price: {} qty: {}",
me->service_ptr->order_key(), ch_ord_key, price, qty.full_value());
me->shots.push_back(TEAShot{sec_key, ch_ord_key});
@@ -375,8 +561,13 @@ bool TaskingEyeAlgo::_in_shooting_zone(
TEASecData const &sec_data = it->second;
const md::PriceSize &driver_bid = sec_data.driver_book.bid().at(0);
const md::PriceSize &driver_ask = sec_data.driver_book.ask().at(0);
if (eq(driver_bid.size(), 0) || eq(driver_ask.size(), 0))
if (eq(driver_bid.size(), 0) || eq(driver_ask.size(), 0)) {
LOG(OrderMan, Warn,
"TaskingEyeAlgo::_in_shooting_zone no driver for sec_key: {} "
"driver_bid_qty: {} driver_ask_qty: {}",
sec_key, driver_bid.size(), driver_ask.size());
return false;
}
// finding weighted mean (TODO change it to mark price later)
double driver = (driver_bid.price() * driver_bid.size() +
driver_ask.price() * driver_ask.size()) /
@@ -386,31 +577,55 @@ bool TaskingEyeAlgo::_in_shooting_zone(
// TODO normalize theo
double theo = ptp.theo();
theo = theo / driver;
if (me->side == api::SIDE_BUY) {
// auto analyze_buy =
auto analyze_buy = [&](const md::PriceSize &md_ask, double theo,
size_t level, Price &price, Quantity &qty) -> bool {
double ee_buy_pr = theo - me->edge;
const md::PriceSize &md_ask = sec_data.sec_book.ask().at(0);
LOG(OrderMan, Diag,
"TaskingEyeAlgo::_in_shooting_zone sec_key: {} driver: {} theo: {} "
"ee_price: {} md: {}/{}",
sec_key, driver, theo, ee_buy_pr, md_ask.price(), md_ask.size());
bool rv = false;
if (greater_then_eq(1, md_ask.size()) &&
greater_then_eq(md_ask.price(), ee_buy_pr)) {
price = Price(md_ask.price());
qty = std::min(static_cast<double>(md_ask.size()), me->shot_size);
return true;
qty = me->shot_size;
rv = true;
}
} else {
double ee_sell_pr = theo + me->edge;
const md::PriceSize &md_bid = sec_data.sec_book.bid().at(0);
LOG(OrderMan, Diag,
"TaskingEyeAlgo::_in_shooting_zone sec_key: {} driver: {} theo: {} "
"ee_price: {} md: {}/{}",
sec_key, driver, theo, ee_sell_pr, md_bid.price(), md_bid.size());
"TaskingEyeAlgo::_in_shooting_zone::analyze_buy sec_key: {} "
"theo: {} level: {}"
"ee_buy_pr: {} rv: {}",
sec_key, level, theo, ee_buy_pr, rv);
return rv;
};
auto analyze_sell = [&](const md::PriceSize &md_bid, double theo,
size_t level, Price &price, Quantity &qty) -> bool {
double ee_sell_pr = theo + me->edge;
bool rv = false;
if (greater_then_eq(1, md_bid.size()) &&
greater_then_eq(ee_sell_pr, md_bid.price())) {
price = Price(md_bid.price());
qty = std::min(static_cast<double>(md_bid.size()), me->shot_size);
return true;
qty = me->shot_size;
rv = true;
}
LOG(OrderMan, Diag,
"TaskingEyeAlgo::_in_shooting_zone::analyze_sell sec_key: {} theo:"
"{} level: {} ee_sell_pr: {} rv: {}",
sec_key, theo, level, ee_sell_pr, rv);
return rv;
};
if (me->side == api::SIDE_BUY) {
const auto &ask_book = sec_data.sec_book.ask();
for (int i = ask_book.size() - 1; i >= 0; i--) {
const auto &md_ask = ask_book.at(i);
if (analyze_buy(md_ask, theo, i, price, qty)) {
return true;
}
}
} else {
const auto &bid_book = sec_data.sec_book.bid();
for (int i = bid_book.size() - 1; i >= 0; i--) {
const md::PriceSize &md_bid = bid_book.at(i);
if (analyze_sell(md_bid, theo, i, price, qty)) {
return true;
}
}
}
return false;
@@ -0,0 +1,354 @@
#include "VolAlgo.h"
#include "lib/api/services/OrderService.pb.h"
#include "lib/domain_model/Security.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/domain_model/SecurityKey.h"
#include "lib/domain_model/common.h"
#include "lib/logging/logging.h"
#include "lib/md/PriceSize.h"
#include "lib/orderman/ChildOrder.h"
#include "lib/orderman/OrderBuilder.h"
#include "lib/orderman/limiter/SecurityLimitNode.h"
#include "lib/orderman/order_factory/IOrderFactory.h"
#include "lib/orderman/orderman_utils/Book.h"
#include "lib/orderman/orderman_utils/orderman_common.h"
#include "lib/orderman/orderman_utils/orderman_utils.h"
#include "lib/orderman/orders/OrderOpResult.h"
#include "lib/orderman/orders/algo/ChildTemplate.h"
#include "lib/orderman/orders/algo/IAlgoOrder.h"
#include "lib/orderman/orders/algo/IAlgoServices.h"
#include "lib/orderman/orders/algo/IBeforeLaunchAlgoServices.h"
#include "lib/os/os.h"
#include "lib/utils/PBUtils.h"
#include "lib/utils/bts_assert.h"
#include <bits/stdint-uintn.h>
#include <google/protobuf/util/time_util.h>
#include <optional>
#include <string>
using namespace bts::api::orderman;
namespace bts::orderman {
ChildTemplate *get_vol_algo_child_template(
IBeforeLaunchAlgoServices &serv,
SecurityKey key,
api::Side side,
std::optional<std::string> account_override) {
OrderDefinition order_def = serv.definition();
order_def.set_security_key(key.value());
order_def.set_side(side);
order_def.set_time_in_force(order_def.time_in_force());
if (account_override.has_value()) {
// no need for it now
}
order_def.mutable_custom_def()->mutable_limit()->MergeFrom(
api::orderman::LimitOrderDefinition());
return &(serv.make_child_template(order_def));
}
struct VolAlgoParams {
std::optional<std::string> account_override;
double vol_perc;
bool cross_at_entry = false;
bool requote = false;
bool improve_price = false;
int distance_from_top = 2;
};
std::optional<VolAlgoParams> parse_parameters(
const DynamicOrderDefinition &def) {
const auto &d_params = def.order_params();
VolAlgoParams rv = {};
// vol
auto vol_perc = get_if<double>(d_params, "Vol");
if (!vol_perc.has_value())
return std::nullopt;
rv.vol_perc = vol_perc.value();
// cross at entry
auto cross_at_entry = get_if<bool>(d_params, "Cross At Entry?");
rv.cross_at_entry =
cross_at_entry.has_value() ? cross_at_entry.value() : false;
// requote
auto requote = get_if<bool>(d_params, "Requote?");
rv.requote = requote.has_value() ? requote.value() : false;
// improve price
auto improve_price = get_if<bool>(d_params, "Improve Price?");
rv.improve_price = improve_price.has_value() ? improve_price.value() : false;
// Distance from Top
auto distance_from_top = get_if<int>(d_params, "Distance From Top");
if (!distance_from_top.has_value())
return std::nullopt;
rv.distance_from_top = distance_from_top.value();
return rv;
}
struct VolAlgo ::PImpl {
IAlgoServices *service_ptr = nullptr;
analytics::ITheoCalculator const *theo_calc = nullptr;
OrderKey order_key;
uint32_t sec_key;
api::Side side;
std::optional<std::string> account_override;
double qty = 0;
VolAlgoParams params = {};
uint32_t driver_sec_key = 0;
bool subbed_to_driver = false;
std::optional<Book> driver_book = std::nullopt;
std::optional<Book> sec_book = std::nullopt;
// out
ChildTemplate *child_order = nullptr;
std::optional<OrderKey> child_order_key = std::nullopt;
Price current_price = Price{os::nan()};
bool inited() const {
// once we have receved both books we are ready to go
return driver_book.has_value() && sec_book.has_value();
}
bool order_fired() const { return child_order_key.has_value(); }
};
VolAlgo::VolAlgo() : me(new PImpl()) { LOG(OrderMan, Info, "VolAlgo created"); }
VolAlgo::~VolAlgo() {}
OrderOpResult VolAlgo::do_before_launch(IBeforeLaunchAlgoServices &serv) {
LOG(OrderMan, Info,
"VolAlgo::do_before_launch order_key: {} security: {} "
"definition: {}",
serv.order_key().value(), utils::pb::to_string(serv.security()),
utils::pb::to_string(serv.definition()));
auto &def = serv.definition();
me->order_key = serv.order_key();
me->sec_key = def.security_key();
me->side = def.side();
if (def.has_custom_def() && def.custom_def().has_dynamic()) {
auto va_params = parse_parameters(def.custom_def().dynamic());
if (!va_params.has_value()) {
return {
bts::api::orderman::OrderError::ORDER_ERROR_INVALID_PARAMETER,
std::string("Parameters are not well formed")};
}
me->params = va_params.value();
me->child_order = get_vol_algo_child_template(
serv, SecurityKey(me->sec_key), me->side,
me->params.account_override);
LOG(OrderMan, Info,
"VolAlgo::do_before_launch order_key: {} sec_key: {} side: {} vol: "
"{} cross_at_entry: {} re_quote: {} improve_price: {} "
"distance_from_top: {}",
me->order_key, me->sec_key, bts::api::Side_Name(me->side),
me->params.vol_perc, me->params.cross_at_entry, me->params.requote,
me->params.improve_price, me->params.distance_from_top);
return {};
}
return {
bts::api::orderman::OrderError::ORDER_ERROR_INVALID_PARAMETER,
std::string("Parameters are not well formed")};
}
void VolAlgo::do_launch(IAlgoServices &serv) {
LOG(OrderMan, Info, "VolAlgo::do_launch order_key: {} qty: {}",
serv.order_key().value(), serv.quantity());
me->service_ptr = &serv;
me->qty = serv.quantity().full_value();
me->service_ptr->subscribe_theos();
me->service_ptr->subscribe_timer();
me->service_ptr->subscribe_book(SecurityKey(me->sec_key));
}
void VolAlgo::do_launch_attached(IAlgoServices &serv, OrderKey, ChildState) {
me->service_ptr = &serv;
_terminate_algo(true, "Vol Algo can not be attached to working orders");
}
void VolAlgo::on_book(SecurityKey sec_key, Book const &book) {
LOG(OrderMan, Diag, "VolAlgo::on_book order_key: {} sec_key: {}",
me->order_key, sec_key);
if (sec_key.value() == me->sec_key) {
me->sec_book = book;
_analyze_and_act();
} else if (sec_key.value() == me->driver_sec_key) {
me->driver_book = book;
_analyze_and_act();
}
}
void VolAlgo::do_update(api::orderman::Update const &u) {
LOG(OrderMan, Info,
"VolAlgo::do_update order_key: {} sec_key: {} "
"update: {}",
me->order_key, me->sec_key, utils::pb::to_string(u));
const auto &p = u.custom().dynamic();
auto params = parse_parameters(p);
if (!params.has_value()) {
LOG(Gen, Error,
"VolAlgo::do_update order_key: {} parameters are not well formed",
me->order_key);
return;
}
me->qty = u.quantity();
me->params = params.value();
_analyze_and_act(true);
// price changes are ignored the price is the vol
}
void VolAlgo::on_child_change(OrderKey, ChildState const &state) {
// price / qty is reflected from the child
me->service_ptr->update_fill_state(state.total_fills, state.last_price);
switch (state.status) {
case ChildStatus::WORKING:
break;
case ChildStatus::COMPLETED:
me->service_ptr->complete();
break;
case ChildStatus::CANCELED:
me->service_ptr->cancel("Child Order Cancelled");
break;
}
}
void VolAlgo::on_status(SecurityKey, co2::TradingStatus) {}
void VolAlgo::on_timer() {}
void VolAlgo::on_theos(analytics::ITheoCalculator const *theo_calculator) {
LOG(OrderMan, Info, "VolAlgo::on_theos order_key: {}", me->order_key);
me->theo_calc = theo_calculator;
if (me->subbed_to_driver) {
_analyze_and_act();
return;
}
auto driver_sec = me->theo_calc->get_driver_sec(me->sec_key);
if (driver_sec.has_value()) {
LOG(OrderMan, Info,
"VolAlgo::on_theos order_key: {} subscribing to driver sec_key: {}",
me->order_key, driver_sec.value());
me->subbed_to_driver = true;
me->driver_sec_key = driver_sec.value();
me->service_ptr->subscribe_book(SecurityKey(me->driver_sec_key));
} else {
LOG(OrderMan, Warn,
"VolAlgo::on_theos order_key: {} could not find driver",
me->order_key);
}
}
void VolAlgo::on_position(PositionUpdate const &) {}
// this is where all the action happens, both new order (just once) as well as
// order updates
void VolAlgo::_analyze_and_act(bool force_reprice) {
// check for period (to stop frequent updates)
auto get_driver = [&]() {
const md::PriceSize &driver_bid = me->driver_book->bid().at(0);
const md::PriceSize &driver_ask = me->driver_book->ask().at(0);
if (bts::securities::is_same(driver_bid.size(), 0) ||
bts::securities::is_same(driver_ask.size(), 0))
return os::nan();
// finding weighted mean (TODO change it to mark price later)
double driver = (driver_bid.price() * driver_bid.size() +
driver_ask.price() * driver_ask.size()) /
(driver_bid.size() + driver_ask.size());
return driver;
};
auto crosses_opp_market = [&](Price order_price) {
if (me->side == api::SIDE_BUY) {
double top = me->sec_book->ask().at(0).price();
return securities::gr_than_eq(order_price.value(), top);
} else {
double top = me->sec_book->bid().at(0).price();
return securities::gr_than_eq(top, order_price.value());
}
};
if (me->order_fired()) {
// repricing if parameters (and arg) allows
if (!me->params.requote && !force_reprice)
return;
// working order
double driver = get_driver();
if (os::is_nan(driver)) {
// keep working it
return;
}
double theo_unnorm = me->theo_calc->get_theo_correspoding_to_vol(
me->sec_key, driver, me->params.vol_perc / 100.0);
// TODO: normalize
double theo = theo_unnorm / driver;
Price new_price = _get_order_price(theo);
LOG(OrderMan, Info,
"VolAlgo::_analyze_and_act (calc updating) order_key: {} "
"theo_unnorm: {} driver: {} theo: {} order_price: {}",
me->order_key, theo_unnorm, driver, theo, new_price);
if (securities::is_same(me->current_price, new_price)) {
return;
}
bool improving =
securities::is_better(me->side, new_price, me->current_price);
if (!improving || me->params.improve_price || force_reprice) {
bts::api::orderman::Update update;
update.set_price(new_price.value());
set_update_quantity(update, Quantity::create_new(me->qty));
me->current_price = new_price;
LOG(Gen, Info,
"VolAlgo::_analyze_and_act updating order order_key: {} price: {} "
"qty: {}",
me->order_key, me->current_price, me->qty);
me->service_ptr->update_child(me->child_order_key.value(), update);
return;
}
} else {
// new order path
if (me->inited()) {
// time to fire
double driver = get_driver();
if (os::is_nan(driver)) {
return;
}
// entering order
double theo_unnorm = me->theo_calc->get_theo_correspoding_to_vol(
me->sec_key, driver, me->params.vol_perc / 100.0);
double theo = theo_unnorm / driver;
Price order_price = _get_order_price(theo);
LOG(OrderMan, Info,
"VolAlgo::_analyze_and_act (calc new) order_key: {} theo_unnorm: "
"{} driver: {} theo: {} order_price: {}",
me->order_key, theo_unnorm, driver, theo, order_price);
if (!me->params.cross_at_entry && crosses_opp_market(order_price))
return;
me->current_price = order_price;
LOG(Gen, Info,
"VolAlgo::_analyze_and_act placing order order_key: {} price: {} "
"qty: {}",
me->order_key, me->current_price, me->qty);
me->child_order_key = me->service_ptr->launch_child(
*me->child_order, me->current_price,
Quantity::create_new(me->qty));
}
}
}
void VolAlgo::_terminate_algo(bool is_error, const std::string &err_msg) {
// TODO unsub ?
if (is_error) {
me->service_ptr->terminate_error(err_msg);
} else {
me->service_ptr->complete();
}
}
Price VolAlgo::_get_order_price(double theo) const {
// BTC Opts (p <= 0.005) : price = 0.0001 qty = 0.1
// BTC Opts (p > 0.005) : price = 0.0005 qty = 0.1
// -------------------------------------------------
// ETH Opts (p <= 0.005) : price = 0.0001 qty = 1
// ETH Opts (p > 0.005) : price = 0.0005 qty = 1
// TODO: get the above from the security itself
double rv = 0;
if (theo > 0.005) {
rv = securities::raw_round_to_quote_price(theo, 0.0005, me->side);
} else {
rv = securities::raw_round_to_quote_price(theo, 0.0001, me->side);
}
LOG(Gen, Info,
"VolAlgo::_get_order_price order_key: {} theo: {} order_price: {}",
me->order_key, theo, rv);
return Price{rv};
}
} // namespace bts::orderman
@@ -0,0 +1,38 @@
#pragma once
#include "lib/domain_model/SecurityKey.h"
#include "lib/domain_model/common.h"
#include "lib/orderman/calculations/ITheoCalculator.h"
#include "lib/orderman/orders/algo/IAlgoOrder.h"
#include <openssl/ossl_typ.h>
namespace bts::orderman {
// This is a test algo, not to be used in prod, that sends an order and modifies
// it by moving the price up and down at a given period
class VolAlgo : public IAlgoOrder {
public:
VolAlgo();
~VolAlgo();
OrderOpResult do_before_launch(IBeforeLaunchAlgoServices &) override;
void on_book(SecurityKey, Book const &) override;
void do_launch(IAlgoServices &) override;
void do_launch_attached(IAlgoServices &, OrderKey, ChildState) override;
void do_update(api::orderman::Update const &) override;
void on_child_change(OrderKey, ChildState const &) override;
void on_status(SecurityKey, co2::TradingStatus) override;
void on_timer() override;
void on_theos(analytics::ITheoCalculator const *theo_calculator) override;
void on_position(PositionUpdate const &) override;
private:
struct PImpl;
std::unique_ptr<PImpl> me;
private:
void _analyze_and_act(bool force_reprice = false);
void _terminate_algo(bool is_error, const std::string &err_msg);
Price _get_order_price(double theo) const;
};
} // namespace bts::orderman
+3 -2
View File
@@ -8,17 +8,18 @@ if exist %venv_dir% (
echo Removing python virtual environment
rmdir /S /Q %venv_dir%
) else (
GOTO ERROR
GOTO END
)
)
echo Creating python virtual environment
@call python -m venv %venv_dir%
@call %venv_dir%\Scripts\activate.bat
@call pip install --upgrade pip
@call %venv_dir%\Scripts\python -m pip install --upgrade pip
@call %venv_dir%\Scripts\pip install -r %~dp0..\requirements.txt
@call deactivate
:END
EXIT /B %ERRORLEVEL%
:ERROR
+13
View File
@@ -0,0 +1,13 @@
@echo off
@REM Build script designed for our jenkins build job(s). It assumes the py
@REM venv already exists.
setlocal
set tools_dir=%~dp0
set venv_dir=%~dp0..\.venv\
call "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\VsDevCmd.bat"
call "%venv_dir%Scripts\activate.bat"
call python "%tools_dir%py\build_spark.py" %*
EXIT /B %ERRORLEVEL%
+4 -4
View File
@@ -173,8 +173,7 @@ def _get_arg_parser():
metavar='debug|release',
type=str,
nargs='?',
help='The build configuration',
default='Debug')
help='The build configuration')
parser.add_argument(
'-o',
@@ -336,9 +335,10 @@ def _main():
link_build_dir = arg_to_path(get_arg(args_dict, 'link_output_dir'))
targets = flatten_arglist(get_arg(args_dict, 'target'))
verbosity = get_arg(args_dict, 'verbosity') or None
configuration = args.configuration or 'Release' if targets is not None and any(
filter(lambda t: t.lower() == 'publish', targets)) else 'Debug'
build(
configuration=args.configuration,
configuration=configuration,
clean=args.clean,
restore=args.restore,
build_dir=build_dir,