Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
709334a1aa |
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 vs_id: {_volsurface_id} size: " +
|
||||
Log.Gen.info($"SingleVolSurfaceGateway._on_bb_data archiving theos 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) {
|
||||
var vs_usage_list = bb.read_state(SparkTags.VolSurfaceUsageTag);
|
||||
return (vs_usage_list?.get_sectups(vs_id)).empty_if_null();
|
||||
VolSurfaceUsageListImmutable vs_usage_list = bb.read_state(SparkTags.VolSurfaceUsageTag);
|
||||
return vs_usage_list.get_sectups(vs_id);
|
||||
}
|
||||
|
||||
|
||||
@@ -242,4 +242,4 @@ namespace mbts.spark {
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,18 +60,16 @@ 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>();
|
||||
var usage = _bb.read_state(SparkTags.VolSurfaceUsageTag);
|
||||
VolSurfaceUsageListImmutable usage = _bb.read_state(SparkTags.VolSurfaceUsageTag);
|
||||
string current_user = _bb.read_state(SparkTags.CurrentUser).name;
|
||||
foreach (var u in (usage?.UsageList).empty_if_null()) {
|
||||
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);
|
||||
syms.Add(u.SymbolName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,16 +81,14 @@ 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
|
||||
@@ -115,7 +111,6 @@ 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();
|
||||
@@ -126,7 +121,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 symbols: {all_theos.Count} " +
|
||||
bts.Log.Gen.info($"InterpTheosServerPublisher._publish_data " +
|
||||
$"size: {server_theos.Theos.Count} comp_time: {comp_time} msecs " +
|
||||
$"pub_time: {pub_time}");
|
||||
}
|
||||
@@ -135,4 +130,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 VolSurfaceUsageList().immutable();
|
||||
_vs_usage_map = _bb.read_state(_in_bb_vs_usage_tag,
|
||||
new VolSurfaceUsageListImmutable(new VolSurfaceUsageList()));
|
||||
bts.Log.Gen.info($"VolSurfaceInterpGreeksMultiplexer._analyze_usages " +
|
||||
$"count: {_vs_usage_map.UsageList.Count()}");
|
||||
foreach (int vs_id in _vs_usage_map.vss()) {
|
||||
|
||||
@@ -40,8 +40,10 @@ 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 = config.Configuration.get<bts.config.VolSurface>().default_abs_moves;
|
||||
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 double _cushion_percentage = 25.0;
|
||||
|
||||
public event
|
||||
Action<IDictionary<VolSurfaceExpiration, IEnumerable<Theos>>, double, double> on_theos =
|
||||
(o, vt, tt) => { };
|
||||
@@ -50,7 +52,7 @@ namespace mbts.analytics {
|
||||
(o, vt, tt) => { };
|
||||
|
||||
public VolSurfaceInterpGreeksProducer(int vs_id) {
|
||||
Log.Gen.info($"VolSurfaceInterpGreeksProducer.cons vs_id: {vs_id}, _default_abs_moves: {string.Join(", ", _default_abs_moves)}");
|
||||
Log.Gen.info($"VolSurfaceInterpGreeksProducer.cons vs_id: {vs_id}");
|
||||
_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, _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, origin_index, _price_list, _vm.price_vs_piq, _settings.row_height, _spec, _settings.piq_column, _fmt_mkt_quantity);
|
||||
}
|
||||
|
||||
void _refresh_sizes() {
|
||||
|
||||
@@ -368,32 +368,30 @@ 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)> cells, 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)> 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 (prices.is_null_or_empty() || cells.is_null_or_empty()) {
|
||||
if (data.is_null_or_empty())
|
||||
return;
|
||||
}
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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>();
|
||||
@@ -407,18 +405,38 @@ 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
|
||||
) {
|
||||
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)));
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void render_mkt_break(DrawingVisual dv, int index, double row_height, uint color) =>
|
||||
|
||||
@@ -59,9 +59,7 @@ namespace bts.shell.manual_fills {
|
||||
nameof(quantity_display),
|
||||
() => {
|
||||
var sec_meta = security.HasValue ? _sec_meta_store.get(security.Value) : null;
|
||||
return sec_meta?.HasQuantityDisplay ?? false ?
|
||||
sec_meta.Value.QuantityDisplay :
|
||||
Security.Defaults.quantity_display_settingsi;
|
||||
return sec_meta?.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,10 +293,7 @@ namespace bts.shell.pnl {
|
||||
return _quantity_display_backing.Value;
|
||||
}
|
||||
if (security.HasValue) {
|
||||
var sec_meta = _sec_meta_store.get(security.Value);
|
||||
if (sec_meta?.HasQuantityDisplay ?? false) {
|
||||
_quantity_display_backing = sec_meta.Value.QuantityDisplay;
|
||||
}
|
||||
_quantity_display_backing = _sec_meta_store.get(security.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);
|
||||
_data_cache.update(update.as_iincremental());
|
||||
foreach (var o in update.added.Concat(update.changed))
|
||||
_order_notifications.process_item(o);
|
||||
}
|
||||
|
||||
@@ -1,157 +1,89 @@
|
||||
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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,8 +90,7 @@ namespace mbts {
|
||||
}
|
||||
var sec = order.security;
|
||||
var sec_meta = sec.HasValue ? _sec_meta_store.get(sec.Value) : null;
|
||||
var qty_display = sec_meta?.HasQuantityDisplay ?? false ?
|
||||
sec_meta.Value.QuantityDisplay :
|
||||
var qty_display = sec_meta?.QuantityDisplay ??
|
||||
Security.Defaults.quantity_display_settingsi;
|
||||
return ToastNotifications.order_completed(order, qty_display);
|
||||
}
|
||||
|
||||
@@ -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="{u:BTSColor UI.TextBox.default_background}" />
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}" />
|
||||
<Setter Property="Background" Value="{StaticResource TextBox.BackgroundBrush}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBox.ForegroundBrush}" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
</Style>
|
||||
|
||||
|
||||
@@ -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/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
|
||||
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
|
||||
@@ -44,7 +44,6 @@ message PrecomputedTheoPoint {
|
||||
double vega = 60;
|
||||
double theta = 70;
|
||||
double rho = 80;
|
||||
double vol = 90;
|
||||
}
|
||||
|
||||
message PrecomputedTheos {
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
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
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
syntax = 'proto2';
|
||||
package bts.config;
|
||||
|
||||
message VolSurface {
|
||||
repeated double default_abs_moves = 1;
|
||||
}
|
||||
@@ -114,10 +114,6 @@ 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,9 +25,5 @@ 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,15 +50,12 @@ 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 {
|
||||
@@ -216,24 +213,6 @@ 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,8 +23,6 @@ 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,13 +279,5 @@ 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,7 +12,6 @@
|
||||
#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"
|
||||
@@ -300,9 +299,6 @@ 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");
|
||||
@@ -329,19 +325,6 @@ 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())
|
||||
@@ -362,7 +345,6 @@ 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,17 +23,10 @@
|
||||
#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;
|
||||
@@ -76,11 +69,6 @@ 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) {
|
||||
@@ -112,91 +100,7 @@ bool in_flight_orders(const std::vector<TEAShot> &shots) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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> parse_securities(const std::string &secs) {
|
||||
std::set<SecurityKey> rv = {};
|
||||
std::vector<std::string> parts;
|
||||
boost::split(parts, secs, boost::is_any_of(","));
|
||||
@@ -209,34 +113,10 @@ std::set<SecurityKey> parse_securities_from_keys(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;
|
||||
@@ -270,62 +150,31 @@ TaskingEyeAlgo::~TaskingEyeAlgo() {}
|
||||
OrderOpResult TaskingEyeAlgo::do_before_launch(
|
||||
IBeforeLaunchAlgoServices &serv) {
|
||||
auto is_data_valid = [&]() {
|
||||
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;
|
||||
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;
|
||||
};
|
||||
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();
|
||||
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;
|
||||
auto parsed_secs = parse_securities(sec_str);
|
||||
std::vector<SecurityKey> final_secs;
|
||||
if (parsed_secs.empty()) {
|
||||
final_secs.push_back(SecurityKey{serv.security().id()});
|
||||
@@ -337,21 +186,18 @@ 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: {} side: {} target_qty: {} "
|
||||
"max_shots_per_window: {} window_size_nanos: {} n_secs: {} "
|
||||
"wait_for_signal: {} signal_file: {}",
|
||||
"TaskingEyeAlgo::do_before_launch algo constructed order_key: {} "
|
||||
"edge: {} shot_size: {} is_buy: {} target_qty: {} "
|
||||
"max_shots_per_window: {} window_size_nanos: {} n_secs: {}",
|
||||
serv.order_key(), me->edge, me->shot_size,
|
||||
bts::api::Side_Name(me->side), me->target_qty,
|
||||
me->side == api::SIDE_BUY, me->target_qty,
|
||||
me->max_shots_per_window, me->window_size_nanos,
|
||||
me->sec_map.size(), me->wait_for_signal, me->signal_file);
|
||||
me->sec_map.size());
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@@ -367,8 +213,7 @@ 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);
|
||||
}
|
||||
@@ -470,9 +315,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 for "
|
||||
"sec_key: {} driver_key: {}",
|
||||
me->service_ptr->order_key(), sec_key, driver.value());
|
||||
"TaskingEyeAlgo::on_theos order_key: {} subscribing to driver "
|
||||
"sec_key: {}",
|
||||
me->service_ptr->order_key(), driver.value());
|
||||
sec_data.driver_sec_key = SecurityKey(driver.value());
|
||||
sub_to_driver(SecurityKey(driver.value()));
|
||||
}
|
||||
@@ -486,14 +331,8 @@ void TaskingEyeAlgo::on_position(PositionUpdate const &pu) {
|
||||
}
|
||||
|
||||
void TaskingEyeAlgo::_evaluate_and_shoot() {
|
||||
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);
|
||||
if (me->state != TEAState::ACTIVE)
|
||||
return;
|
||||
}
|
||||
|
||||
auto max_shots_per_win_reached = [&]() {
|
||||
return n_recent_shots(me->shots, me->window_size_nanos) >=
|
||||
me->max_shots_per_window;
|
||||
@@ -504,36 +343,12 @@ 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()) {
|
||||
LOG(OrderMan, Warn,
|
||||
"TaskingEyeAlgo::_evaluate_and_shoot order_key: {} "
|
||||
"max_shots_per_win_reached",
|
||||
me->service_ptr->order_key());
|
||||
if (max_shots_per_win_reached())
|
||||
return;
|
||||
}
|
||||
if (sec_has_active_order(sec_key)) {
|
||||
LOG(OrderMan, Warn,
|
||||
"TaskingEyeAlgo::_evaluate_and_shoot sec_key: {} has working "
|
||||
"order, skipping",
|
||||
sec_key.value());
|
||||
if (sec_has_active_order(sec_key))
|
||||
continue;
|
||||
}
|
||||
Price price;
|
||||
Quantity qty;
|
||||
if (_in_shooting_zone(sec_key, price, qty)) {
|
||||
@@ -542,8 +357,7 @@ 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});
|
||||
@@ -561,13 +375,8 @@ 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)) {
|
||||
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());
|
||||
if (eq(driver_bid.size(), 0) || eq(driver_ask.size(), 0))
|
||||
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()) /
|
||||
@@ -577,55 +386,31 @@ bool TaskingEyeAlgo::_in_shooting_zone(
|
||||
// TODO normalize theo
|
||||
double theo = ptp.theo();
|
||||
theo = theo / driver;
|
||||
// auto analyze_buy =
|
||||
auto analyze_buy = [&](const md::PriceSize &md_ask, double theo,
|
||||
size_t level, Price &price, Quantity &qty) -> bool {
|
||||
if (me->side == api::SIDE_BUY) {
|
||||
double ee_buy_pr = theo - me->edge;
|
||||
bool rv = false;
|
||||
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());
|
||||
if (greater_then_eq(1, md_ask.size()) &&
|
||||
greater_then_eq(md_ask.price(), ee_buy_pr)) {
|
||||
price = Price(md_ask.price());
|
||||
qty = me->shot_size;
|
||||
rv = true;
|
||||
qty = std::min(static_cast<double>(md_ask.size()), me->shot_size);
|
||||
return true;
|
||||
}
|
||||
LOG(OrderMan, Diag,
|
||||
"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 {
|
||||
} else {
|
||||
double ee_sell_pr = theo + me->edge;
|
||||
bool rv = false;
|
||||
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());
|
||||
if (greater_then_eq(1, md_bid.size()) &&
|
||||
greater_then_eq(ee_sell_pr, md_bid.price())) {
|
||||
price = Price(md_bid.price());
|
||||
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;
|
||||
}
|
||||
qty = std::min(static_cast<double>(md_bid.size()), me->shot_size);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -1,354 +0,0 @@
|
||||
#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
|
||||
@@ -1,38 +0,0 @@
|
||||
#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
|
||||
@@ -8,18 +8,17 @@ if exist %venv_dir% (
|
||||
echo Removing python virtual environment
|
||||
rmdir /S /Q %venv_dir%
|
||||
) else (
|
||||
GOTO END
|
||||
GOTO ERROR
|
||||
)
|
||||
)
|
||||
|
||||
echo Creating python virtual environment
|
||||
@call python -m venv %venv_dir%
|
||||
@call %venv_dir%\Scripts\activate.bat
|
||||
@call %venv_dir%\Scripts\python -m pip install --upgrade pip
|
||||
@call pip install --upgrade pip
|
||||
@call %venv_dir%\Scripts\pip install -r %~dp0..\requirements.txt
|
||||
@call deactivate
|
||||
|
||||
:END
|
||||
EXIT /B %ERRORLEVEL%
|
||||
|
||||
:ERROR
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
@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%
|
||||
@@ -173,7 +173,8 @@ def _get_arg_parser():
|
||||
metavar='debug|release',
|
||||
type=str,
|
||||
nargs='?',
|
||||
help='The build configuration')
|
||||
help='The build configuration',
|
||||
default='Debug')
|
||||
|
||||
parser.add_argument(
|
||||
'-o',
|
||||
@@ -335,10 +336,9 @@ 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=configuration,
|
||||
configuration=args.configuration,
|
||||
clean=args.clean,
|
||||
restore=args.restore,
|
||||
build_dir=build_dir,
|
||||
|
||||
Reference in new issue
Block a user