Compare commits

..
Author SHA1 Message Date
Taha 021431b567 Merging 2025-03-03 23:21:30 -05:00
Mohammad Arham e7b426cd3d Fix: Exchange sec id fixed 2025-03-03 17:50:25 +00:00
arham ee02c3ffa7 Add: small changes 2025-03-03 12:03:13 -05:00
Taha 21b725e5df LocalMDDaemon works now 2025-03-03 00:07:31 -05:00
Taha 649931ae3c Trying to make the local md daemon work 2025-03-02 02:23:17 -05:00
Taha 7b5f034ed2 Local/Direct bbo subscription 2025-03-02 02:23:17 -05:00
Mohammad Arham 74b376ce56 Add: run only securities nnot books databento 2025-02-28 15:01:53 +00:00
Mohammad Arham fc96b9e436 Add: TBBO to databento 2025-02-28 14:28:36 +00:00
Mohammad Arham fd015577b2 MD working on my laptop 2025-02-26 02:49:41 +00:00
223 changed files with 3045 additions and 10398 deletions

No files matched your search

Submodule
+1
Submodule docker/linux added at 56c61231f3.
+1
Submodule docker/windows added at de7d40c09d.
+12 -8
View File
@@ -133,18 +133,18 @@ namespace mbts.spark {
TheoPoint tp = t.third;
var call_sec_md = sc_data.get_option(exchange, sym, exp, strike, PutOrCall.Call);
{
NativePriceSize call_bid, call_ask;
ulong time = 0;
call_sec_md.Value.md.get_bbo(ref time, out call_bid, out call_ask);
double call_mid = (call_bid.price + call_ask.price) / 2;
double bid_price, ask_price;
float bid_sz, ask_sz;
call_sec_md.Value.md.get_bbo(out bid_price, out bid_sz, out ask_price, out ask_sz);
double call_mid = (bid_price + ask_price) / 2;
tp.call = call_mid;
}
{
var put_sec_md = sc_data.get_option(exchange, sym, exp, strike, PutOrCall.Put);
NativePriceSize put_bid, put_ask;
ulong time = 0;
put_sec_md.Value.md.get_bbo(ref time, out put_bid, out put_ask);
double put_mid = (put_bid.price + put_ask.price) / 2;
double bid_price, ask_price;
float bid_sz, ask_sz;
put_sec_md.Value.md.get_bbo(out bid_price, out bid_sz, out ask_price, out ask_sz);
double put_mid = (bid_price + ask_price) / 2;
tp.put = put_mid;
}
}
@@ -164,6 +164,10 @@ namespace mbts.spark {
private void _interp_and_publish() {
// int i = 0;
// if (i < Int32.MaxValue) {
// return;
// }
var timer = Stopwatch.StartNew();
var res = new Dictionary<uint, GreeksTuple>();
var driver_map = _bb.read_state(_drivers_tag);
@@ -3,6 +3,8 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Apache.Arrow;
using bts;
using bts.api;
using bts.collections;
@@ -161,11 +163,11 @@ namespace mbts.spark {
if (!sec_md_map.TryGetValue(id.Value, out sec_md)) {
return null;
}
NativePriceSize bid, ask;
ulong time = 0;
sec_md.md.get_bbo(ref time, out bid, out ask);
double bid_price, ask_price;
float bid_sz, ask_sz;
sec_md.md.get_bbo(out bid_price, out bid_sz, out ask_price, out ask_sz);
return Tuple.Create(sec_md.security,
new VolSurfaceMDUnit(bid, ask, sec_md.md.fixing_price));
new VolSurfaceMDUnit(bid_price, bid_sz, ask_price, ask_sz, sec_md.md.fixing_price));
}
public Tuple<SecurityImmutable, VolSurfaceMDUnit> get_exp_md(SecurityExpiration sec_exp) {
+243
View File
@@ -0,0 +1,243 @@
using bts.api;
using mbts.co2;
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Threading;
namespace bts {
public class LocalMDConsumerHandle : mbts.co2.IMDConsumerHandle {
private readonly SecurityImmutable _sec;
private readonly LocalMDDaemon.BBO _bbo;
// private static NativePriceSize bid_nps = new NativePriceSize(new IntPrice(123.45), Convert.ToSingle(100));
// private static NativePriceSize ask_nps = new NativePriceSize(new IntPrice(123.46), Convert.ToSingle(100));
public LocalMDConsumerHandle(SecurityImmutable sec, LocalMDDaemon.BBO bbo) {
_sec = sec;
_bbo = bbo;
}
public UInt32 key => _sec.Id;
// The exchange key can change at CBOE. This numeric_id will always be correct
public ulong numeric_id => Convert.ToUInt64(_sec.Cboe.ExchangeId);
public void set_bbo(double bid_price, UInt32 bid_qty, double ask_price, UInt32 ask_qty) {
_bbo.bid_price = bid_price;
_bbo.bid_qty = bid_qty;
_bbo.ask_price = ask_price;
_bbo.ask_qty = ask_qty;
}
public bool is_heart_beating => true;
public bool has_depth => true;
// This property exists for specific performance optimization purposes
// Most callers should just use get_bbo/get_depth
public ulong market_timestamp => 0;
public ulong misc_timestamp => 0;
public ulong total_volume => 0;
public double settlement_price => Double.NaN;
public DateTime settlement_date => DateTime.Now;
public double previous_settlement_price => Double.NaN;
public DateTime previous_settlement_date => DateTime.Now;
public double open_price => Double.NaN;
public DateTime open_date => DateTime.Now;
public double close_price => Double.NaN;
public DateTime close_date => DateTime.Now;
public double session_high => Double.NaN;
public double session_low => Double.NaN;
public double fixing_price => Double.NaN;
public bool get_bbo(out double bid_price, out float bid_sz, out double ask_price, out float ask_sz) {
bid_price = _bbo.bid_price;
bid_sz = _bbo.bid_qty;
ask_price = _bbo.ask_price;
ask_sz = _bbo.ask_qty;
return true;
}
public bool get_bbo(ref ulong time, out NativePriceSize bid, out NativePriceSize ask) {
bid = new NativePriceSize(new IntPrice(_bbo.bid_price), Convert.ToSingle(_bbo.bid_qty));
ask = new NativePriceSize(new IntPrice(_bbo.ask_price), Convert.ToSingle(_bbo.ask_qty));
// bid = bid_nps;
// ask = ask_nps;
return true;
}
public bool get_depth(
ref ulong time, ref NativePriceSize[] bids, ref NativePriceSize[] asks, uint depth) {
return true;
}
public bool get_last_trade(
ref ulong time, out NativePriceSize ps, out DateTime timestamp, out ulong total_volume) {
ps = new NativePriceSize();
timestamp = DateTime.MinValue;
total_volume = 0;
return false;
}
public bool get_last_trade(
ref ulong time,
out NativePriceSize ps,
out DateTime timestamp,
out ulong total_volume,
out bool from_spread
) {
ps = new NativePriceSize();
timestamp = DateTime.MinValue;
total_volume = 0;
from_spread = false;
return false;
}
public class PriceLevel {
public double price;
public uint qty;
public PriceLevel(double price_arg, uint qty_arg) {
price = price_arg;
qty = qty_arg;
}
public static PriceLevel empty => new(double.NaN, 0);
public static IntPrice intprice_from_double(double d) =>
double.IsNaN(d) ? IntPrice.NaN : new IntPrice((long)d, 0);
public NativePriceSize get_native_ps() =>
new(intprice_from_double(price), qty);
}
}
public class LocalMDDaemon {
public class BBO {
public double bid_price = Double.NaN;
public UInt32 bid_qty = 0;
public double ask_price = Double.NaN;
public UInt32 ask_qty = 0;
public UInt32 exchange_id = 0;
public BBO(uint exchange_id) :
this(Double.NaN, 0, Double.NaN, 0, exchange_id) { }
public BBO(double bid_price_, uint bid_qty_, double ask_price_, uint ask_qty_, uint exchange_id_) {
bid_price = bid_qty_ == 0 ? Double.NaN : bid_price_;
bid_qty = bid_qty_;
ask_price = ask_qty_ == 0 ? Double.NaN : ask_price_;
ask_qty = ask_qty_;
exchange_id = exchange_id_;
}
public override string ToString() {
return $"bid_price: {bid_price} bid_qty: {bid_qty} ask_price: {ask_price} ask_qty: {ask_qty} exchange_id: {exchange_id}";
}
}
static Process producer_process = null;
static void start_or_restart_producer_process(string exePath) {
foreach (var process in Process.GetProcessesByName(Path.GetFileNameWithoutExtension(exePath))) {
try {
bts.Log.Gen.info($"Stopping existing producer process (PID: {process.Id})...");
process.Kill();
process.WaitForExit();
} catch (Exception ex) {
bts.Log.Gen.error($"Stopping existing producer process (PID: {process.Id})...ex: {ex.Message}");
}
}
if (producer_process != null && !producer_process.HasExited) {
bts.Log.Gen.info("Stopping existing producer process...");
producer_process.Kill();
producer_process.WaitForExit();
producer_process.Dispose();
}
bts.Log.Gen.info("Starting new producer process...");
producer_process = new Process();
producer_process.StartInfo.FileName = exePath; // Path to the EXE
producer_process.StartInfo.UseShellExecute = false;
// producer_process.StartInfo.RedirectStandardOutput = true;
// producer_process.StartInfo.RedirectStandardError = true;
producer_process.Start();
bts.Log.Gen.info("Producer process started.");
}
public Action<LocalMDDaemon.BBO> on_bbo = (bbo) => { };
public LocalMDDaemon() {
Log.Gen.info("LocalMDDaemon.cons called, starting the reading thread now");
System.Threading.Thread pipe_read_thr = new Thread(_start_reading);
pipe_read_thr.IsBackground = true;
pipe_read_thr.Start();
}
private void _start_reading() {
// TODO, fix this
string exe_path = @"dist\producer_delayed\producer_delayed.exe";
//@"dist\producer\producer.exe";
// delete pipe if already exists
using NamedPipeClientStream old_pipe_client = new NamedPipeClientStream(".", "MyPipe", PipeDirection.In);
try {
old_pipe_client.Close();
old_pipe_client.Dispose();
} catch (Exception ex) {
Log.Gen.error($"LocalMDDaemon._start_reading ex: {ex}");
}
start_or_restart_producer_process(exe_path);
// Connect to the named pipe
using NamedPipeClientStream pipe_client = new NamedPipeClientStream(".", "MyPipe", PipeDirection.In);
System.Threading.Thread.Sleep(100);
pipe_client.Connect();
bts.Log.Gen.info("Connected to pipe. Waiting for messages...");
byte[] buffer = new byte[1024];
while (true) {
try {
int bytes_read = pipe_client.Read(buffer, 0, buffer.Length);
if (bytes_read == 0)// this means there was an error
{
System.Threading.Thread.Sleep(2);
continue;
}
Func<double, bool> qty_ok = (d) => {
if (d < 0 || d > Convert.ToDouble(UInt32.MaxValue))
return false;
return true;
};
double double1 = BitConverter.ToDouble(buffer, 0);
double double2 = BitConverter.ToDouble(buffer, 8);
double double3 = BitConverter.ToDouble(buffer, 16);
double double4 = BitConverter.ToDouble(buffer, 24);
uint int5 = BitConverter.ToUInt32(buffer, 32);
//Log.Gen.info($"Read from pipe {double1} , {double2} , {double3} , {double4} , {int5}");
if (!qty_ok(double2) || !qty_ok(double4))
continue;
LocalMDDaemon.BBO bbo = new BBO(double1, Convert.ToUInt32(double2),
double3, Convert.ToUInt32(double4), int5);
on_bbo(bbo);
} catch (IOException) {
bts.Log.Gen.error("Pipe disconnected. Attempting to reconnect...");
pipe_client.Dispose();
System.Threading.Thread.Sleep(1000);
pipe_client.Connect(); // Try reconnecting
} catch (Exception ex) {
bts.Log.Gen.error($"Exception: {ex.Message}");
}
}
}
}
}
+104
View File
@@ -0,0 +1,104 @@
using bts;
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Text;
class LocalMDDaemon2 {
static Process producerProcess = null;
static void StartOrRestartProducerProcess(string exePath) {
// foreach (var process in Process.GetProcessesByName(Path.GetFileNameWithoutExtension(exePath)))
// {
// try
// {
// Console.WriteLine($"Stopping existing producer process (PID: {process.Id})...");
// process.Kill();
// process.WaitForExit();
// }
// catch (Exception ex)
// {
// Console.WriteLine($"Error stopping process: {ex.Message}");
// }
// }
// if (producerProcess != null && !producerProcess.HasExited)
// {
// Console.WriteLine("Stopping existing producer process...");
// producerProcess.Kill();
// producerProcess.WaitForExit();
// producerProcess.Dispose();
// }
Console.WriteLine("Starting new producer process...");
producerProcess = new Process();
producerProcess.StartInfo.FileName = exePath; // Path to the EXE
producerProcess.StartInfo.UseShellExecute = false;
// producerProcess.StartInfo.RedirectStandardOutput = true;
// producerProcess.StartInfo.RedirectStandardError = true;
producerProcess.Start();
Console.WriteLine("Producer process started.");
}
public static void start() {
// string exePath = @".\dist\producer\producer.exe"; //uncomment for live
// string exePath = @".\dist\producer_delayed\producer_delayed.exe"; //UNCOMMENT FOR DELAYED
string exePath = //@".\dist\producer_replay\producer_replay.exe"; //UNCOMMENT FOR replay
@"C:\Users\m_tah\Downloads\HelloWorld\HelloWorld\dist\producer_replay\producer_replay.exe";
// delete pipe if already exists
// using NamedPipeClientStream oldpipeClient = new NamedPipeClientStream(".", "MyPipe", PipeDirection.In);
// try{
// oldpipeClient.Close();
// oldpipeClient.Dispose();
// }catch{
// }
StartOrRestartProducerProcess(exePath);
// Connect to the named pipe
using NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", "MyPipe", PipeDirection.In);
// Thread.Sleep(2000);
pipeClient.Connect();
Console.WriteLine("Connected to pipe. Waiting for messages...");
byte[] buffer = new byte[1024];
var start = true;
while (true) {
try {
int bytesRead = pipeClient.Read(buffer, 0, buffer.Length);
if (bytesRead == 0)// this means there was an error
{
Console.WriteLine("0 bytes recieved ending...");
if (start) {
start = false;
continue;
}
break;
}
double double1 = BitConverter.ToDouble(buffer, 0);
double double2 = BitConverter.ToDouble(buffer, 8);
double double3 = BitConverter.ToDouble(buffer, 16);
double double4 = BitConverter.ToDouble(buffer, 24);
uint int5 = BitConverter.ToUInt32(buffer, 32);
Log.Gen.info($"Received: {double1}, {double2}, {double3}, {double4}, {int5}");
} catch (IOException) {
Console.WriteLine("Pipe disconnected. Attempting to reconnect...");
pipeClient.Dispose();
System.Threading.Thread.Sleep(1000);
pipeClient.Connect(); // Try reconnecting
}
}
// Console.WriteLine("Pipe closed. Exiting...");
// producerProcess?.WaitForExit();
}
}
+1
View File
@@ -136,6 +136,7 @@ namespace bts {
var (priceDisplay, qtyDisplay) = GetDisplaySettings(smd.security);
exch_secs[smd.id] = new(smd.security, smd.md, priceDisplay, qtyDisplay);
}
Log.Gen.info($"MDDaemon.initialize posting sec, n: {exch_secs.Count}");
_bus.post(SparkTags.ServerSecuritiesTag, exch_secs);
}
+91 -58
View File
@@ -1,10 +1,7 @@
using BCG;
using bts.api;
using bts.api.synthetic;
using bts.collections;
using bts.md;
using bts.utils;
using mbts;
using mbts.co2;
@@ -14,78 +11,103 @@ using mbts.ui;
using Spark.Core.Disposables;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Threading;
using SecurityKey = System.UInt32;
namespace bts
{
namespace bts {
[Export(typeof(IShellDaemon))]
internal class MergedMDDaemon : IShellDaemon, IDisposable
{
internal class MergedMDDaemon : IShellDaemon, IDisposable {
private readonly IBus _bus;
private LocalMDDaemon _local_md_daemon;
IDisposable _dispose;
Dictionary<uint, SecurityMD> _server_sec_map = new Dictionary<uint, SecurityMD>();
ConcurrentDictionary<uint, SecurityMD> _server_sec_map = new ConcurrentDictionary<uint, SecurityMD>();
Dictionary<uint, SecurityMD> _fake_sec_map = new Dictionary<uint, SecurityMD>();
private ConcurrentDictionary<uint, SecurityKey> _exchange_id_sec_id_map = new ConcurrentDictionary<uint, SecurityKey>();
private readonly IList<IDisposable> _subs = new List<IDisposable>();
private SyncQueue<LocalMDDaemon.BBO> _waiting_bbo = new SyncQueue<LocalMDDaemon.BBO>();
private readonly Venue _local_md_exchange = Venue.Cboe;
private bool _localmd_daemon_inited = false;
private void _init_local_md_daemon() {
if (_localmd_daemon_inited)
return;
//LocalMDDaemon2.start();
_localmd_daemon_inited = true;
_local_md_daemon = new LocalMDDaemon();
_local_md_daemon.on_bbo += (bbo_pack) => {
//bts.Log.Gen.info($"MergedMDDaemon on_bbo, local bbo callback, bbo: {bbo_pack}");
_waiting_bbo.enqueue(bbo_pack);
};
System.Threading.Thread dequeue_bbo_thr = new Thread(_dequeue_bbo_thread_func);
dequeue_bbo_thr.IsBackground = true;
dequeue_bbo_thr.Priority = ThreadPriority.Normal;
dequeue_bbo_thr.Start();
}
[ImportingConstructor]
public MergedMDDaemon(IBus bus)
{
//TempTester.test_analytics();
public MergedMDDaemon(IBus bus) {
bts.Log.Gen.info("MergedMDDaemon started");
_bus = bus;
_subs.Add(_bus.subscribe(SparkTags.ServerSecuritiesTag).OnDispatcher().Subscribe(
d => _on_server_sec_map(d)));
_subs.Add(_bus.subscribe(SparkTags.FakeSecuritiesTag).OnDispatcher().Subscribe(
d => _on_fake_sec_map(d)));
// This is done periodically to remove expired contracts
// (disabling it right now because we are not sure if deleting securities
// works with the various UI windows)
// _subs.Add(_bus.subscribe(SparkTags.SparkTimer).BufferLatest(TimeSpan.FromMinutes(1))
// .Subscribe(t => _merge_and_post()));
//_test_md_stream();
}
// BCG.MDStream _md_stream = null;
// private void _test_md_stream()
// {
// _bus.subscribe(SparkTags.SparkTimer).Subscribe(t =>
// {
// if (_md_stream == null)
// {
// _md_stream = new BCG.MDStream();
// _md_stream.onChange += _on_md;
// }
// else
// {
// BCG.ViewStreamDatabase.instance.doWork();
// }
// });
// }
private void _on_md(object obj)
{
BCG.DataPacket dp = obj as DataPacket;
bts.Log.Gen.info($"MDStream callback dp: {dp}");
}
private void _log_options(IReadOnlyDictionary<uint, SecurityMD> sec_map)
{
private void _dequeue_bbo_thread_func() {
while (true) {
System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(0.1));
_process_local_bbo_updates();
}
}
private void _process_local_bbo_updates() {
if (_waiting_bbo.getCount() == 0)
return;
var bbo_list = _waiting_bbo.dequeue_all();
// if (bbo_list.Count > -1)
// return;
if (bbo_list.Count >= 10) {
Log.Gen.warn($"MergedMDDaemon._process_local_bbo_updates dequeued item count: {bbo_list.Count}");
}
List<LocalMDDaemon.BBO> unprocessed_bbo_list = new List<LocalMDDaemon.BBO>();
foreach (var bbo in bbo_list) {
bool processed = false;
//bts.Log.Gen.info($"MergedMDDaemon._process_local_bbo_updates read from queue bbo: {bbo}");
UInt32 exchange_id = bbo.exchange_id;
SecurityKey sec_key;
if (_exchange_id_sec_id_map.TryGetValue(exchange_id, out sec_key)) {
SecurityMD sec_md;
if (_server_sec_map.TryGetValue(sec_key, out sec_md)) {
sec_md.md.set_bbo(bbo.bid_price, bbo.bid_qty, bbo.ask_price, bbo.ask_qty);
processed = true;
}
}
if (!processed) {
// TODO: put it back in the queue
}
}
}
private void _log_options(IReadOnlyDictionary<uint, SecurityMD> sec_map) {
string rv = "MergedMDDaemon Options:\n";
foreach (var kv in sec_map)
{
foreach (var kv in sec_map) {
var sec = kv.Value.security;
var md = kv.Value.md;
ulong time = 0;
NativePriceSize bid, ask;
var has_md = md.get_bbo(ref time, out bid, out ask);
if (sec.HasOption)
{
if (sec.HasOption) {
rv += $"id: {kv.Key} desc: {sec.security_desc()} sym: {sec.Symbol} " +
$"exp: {sec.security_expiration()} " +
$"k: {sec.Option.Strike} cp: {sec.Option.CallPut} " +
@@ -96,31 +118,40 @@ namespace bts
Log.Gen.info(rv);
}
private void _on_server_sec_map(IReadOnlyDictionary<uint, SecurityMD> sec_map)
{
private void _on_server_sec_map(IReadOnlyDictionary<uint, SecurityMD> sec_map) {
//_log_options(sec_map);
bts.Log.Gen.info(String.Format("MergedMDDaemon._on_server_sec_map sz: {0}", sec_map.Count()));
_server_sec_map = new Dictionary<uint, SecurityMD>(sec_map);
foreach (var kv in sec_map) {
SecurityMD md = kv.Value;
uint sec_id = kv.Key;
SecurityMD sec_md = kv.Value;
if (kv.Value.security.Exchange == _local_md_exchange) {
UInt32 exchange_sec_id = Convert.ToUInt32(kv.Value.security.Cboe.ExchangeId);
_exchange_id_sec_id_map[exchange_sec_id] = sec_id;
_server_sec_map[sec_id] = new SecurityMD(sec_md.security, new LocalMDConsumerHandle(sec_md.security, new LocalMDDaemon.BBO(exchange_sec_id)));
} else {
_server_sec_map[sec_id] = sec_md;
}
}
_merge_and_post();
_init_local_md_daemon();
}
private void _on_fake_sec_map(IReadOnlyDictionary<uint, SecurityMD> sec_map)
{
private void _on_fake_sec_map(IReadOnlyDictionary<uint, SecurityMD> sec_map) {
bts.Log.Gen.info(String.Format("MergedMDDaemon._on_fake_sec_map sz: {0}", sec_map.Count()));
_fake_sec_map = new Dictionary<uint, SecurityMD>(sec_map);
_merge_and_post();
}
private void _merge_and_post()
{
Func<SecurityMD, bool> is_active = (sec_md) =>
{
private void _merge_and_post() {
Func<SecurityMD, bool> is_active = (sec_md) => {
var sec = sec_md.security;
return SecurityExpirationHelper.is_unexpired(sec);
};
// server secs
var to_pub = _server_sec_map.Where(kv => is_active(kv.Value)).ToDictionary(kv => kv.Key, kv => kv.Value);
foreach (var kv in _fake_sec_map)
{
// add fake secs to the map
foreach (var kv in _fake_sec_map) {
to_pub[kv.Key] = kv.Value;
}
bts.Log.Gen.info(String.Format("MergedMDDaemon._merge_and_post sz: {0}", to_pub.Count()));
@@ -132,7 +163,9 @@ namespace bts
_bus.post(SparkTags.TC2Tags.ExchangeSecurityMapTag, exs_map);
}
public void Dispose() =>
public void Dispose() {
Disposable.dispose(ref _dispose);
Log.Gen.info("LocalMDDaemon.DIspose called collecting resources now");
}
}
}
@@ -16,7 +16,7 @@ namespace mbts.spark {
private readonly ThreadedBlackboard _bb;
HashSet<BCG.NMSheetSymbol> _sym_map;
private Action _cb = () => { };
private readonly TimeSpan fire_period = TimeSpan.FromMilliseconds(300);
private readonly TimeSpan fire_period = TimeSpan.FromMilliseconds(333);
public NMSheetBBODataSource(ThreadedBlackboard bb, List<BCG.NMSheetSymbol> syms, Action cb) {
_bb = bb;
+161 -161
View File
@@ -1,161 +1,161 @@
<Window
x:Class="mbts.ui.spark.OrderTicket"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:msys="clr-namespace:bts.api;assembly=spark_api"
xmlns:u="clr-namespace:mbts.ui"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
u:WindowStyle.SizeToContent="WidthAndHeight"
u:WindowStyle.TitleContent="{Binding bbo, Mode=OneWay}"
Style="{StaticResource BTSWindowTitleTemplate}"
>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Resources/Converters.xaml"/>
</ResourceDictionary.MergedDictionaries>
<sys:Double x:Key="HorizontalSpacing">8.0</sys:Double>
<sys:Double x:Key="VerticalSpacing">4.0</sys:Double>
<sys:Double x:Key="ButtonHeight">22.0</sys:Double>
<Style x:Key="SellButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="{u:BTSColor MarketData.ask_dark}" />
<Setter Property="Foreground" Value="White" />
<Setter Property="MinHeight" Value="40" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center" Text="Sell" />
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
CornerRadius="0"
BorderThickness="1"
BorderBrush="{u:BTSColor UI.Button.normal_border}"
Background="{u:BTSColor MarketData.ask}"
Padding="2"
Height="Auto"
SnapsToDevicePixels="True">
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"
RecognizesAccessKey="True"
Name="button_content"
/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="#99c00000" />
<Setter TargetName="Border" Property="BorderBrush" Value="{u:BTSColor UI.Button.mouseover_border}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="BuyButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="{u:BTSColor MarketData.bid_dark}" />
<Setter Property="Foreground" Value="White" />
<Setter Property="MinHeight" Value="40" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center" Text="Buy" />
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
CornerRadius="0"
BorderThickness="1"
BorderBrush="{u:BTSColor UI.Button.normal_border}"
Background="{u:BTSColor MarketData.bid}"
Padding="2"
Height="Auto"
SnapsToDevicePixels="True">
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"
RecognizesAccessKey="True"
Name="button_content"
/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="#99167ed3" />
<Setter TargetName="Border" Property="BorderBrush" Value="{u:BTSColor UI.Button.mouseover_border}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Window.Resources>
<Window.InputBindings>
<KeyBinding Key="Esc" Command="{x:Static SystemCommands.CloseWindowCommand}"/>
</Window.InputBindings>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="120"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalSpacing}}"/>
<ColumnDefinition MinWidth="120"/>
</Grid.ColumnDefinitions>
<usp:OrderModificationControl Grid.ColumnSpan="3" />
<Button Grid.Row="2" Style="{StaticResource BuyButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Buy, Type=msys:Side}" />
<Button Grid.Row="2" Grid.Column="2" Style="{StaticResource SellButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Sell, Type=msys:Side}" />
</Grid>
<u:WindowStyle.TitleContentTemplate>
<DataTemplate>
<TextBlock Style="{StaticResource TextBlock.Dark}">
<TextBlock.Inlines>
<Run FontWeight="Bold" Text="{Binding security, Mode=OneWay}" />
<Run Foreground="{u:BTSColor MarketData.bid}" FontSize="10">
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}" StringFormat="({0})">
<Binding Path="bid_size" Mode="OneWay"/>
<Binding Path="DataContext.qty_display" RelativeSource="{RelativeSource AncestorType=usp:OrderTicket}"/>
</MultiBinding>
</Run>
<Run Foreground="{u:BTSColor MarketData.bid}" FontWeight="Bold">
<Run.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="bid"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</Run.Text>
</Run>
<Run Foreground="{u:BTSColor MarketData.ask}" FontWeight="Bold">
<Run.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="ask"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</Run.Text>
</Run>
<Run Foreground="{u:BTSColor MarketData.ask}" FontSize="10">
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}" StringFormat="({0})">
<Binding Path="ask_size" Mode="OneWay"/>
<Binding Path="DataContext.qty_display" RelativeSource="{RelativeSource AncestorType=usp:OrderTicket}"/>
</MultiBinding>
</Run>
</TextBlock.Inlines>
</TextBlock>
</DataTemplate>
</u:WindowStyle.TitleContentTemplate>
</Window>
<Window
x:Class="mbts.ui.spark.OrderTicket"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:msys="clr-namespace:bts.api;assembly=spark_api"
xmlns:u="clr-namespace:mbts.ui"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
u:WindowStyle.SizeToContent="WidthAndHeight"
u:WindowStyle.TitleContent="{Binding bbo, Mode=OneWay}"
Style="{StaticResource BTSWindowTitleTemplate}"
>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Resources/Converters.xaml"/>
</ResourceDictionary.MergedDictionaries>
<sys:Double x:Key="HorizontalSpacing">8.0</sys:Double>
<sys:Double x:Key="VerticalSpacing">4.0</sys:Double>
<sys:Double x:Key="ButtonHeight">22.0</sys:Double>
<Style x:Key="SellButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="{u:BTSColor MarketData.ask}" />
<Setter Property="Foreground" Value="White" />
<Setter Property="MinHeight" Value="40" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center" Text="Sell" />
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
CornerRadius="0"
BorderThickness="1"
BorderBrush="{u:BTSColor UI.Button.normal_border}"
Background="{u:BTSColor MarketData.ask}"
Padding="2"
Height="Auto"
SnapsToDevicePixels="True">
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"
RecognizesAccessKey="True"
Name="button_content"
/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="#99c00000" />
<Setter TargetName="Border" Property="BorderBrush" Value="{u:BTSColor UI.Button.mouseover_border}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="BuyButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="{u:BTSColor MarketData.bid}" />
<Setter Property="Foreground" Value="White" />
<Setter Property="MinHeight" Value="40" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center" Text="Buy" />
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
CornerRadius="0"
BorderThickness="1"
BorderBrush="{u:BTSColor UI.Button.normal_border}"
Background="{u:BTSColor MarketData.bid}"
Padding="2"
Height="Auto"
SnapsToDevicePixels="True">
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"
RecognizesAccessKey="True"
Name="button_content"
/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="#99167ed3" />
<Setter TargetName="Border" Property="BorderBrush" Value="{u:BTSColor UI.Button.mouseover_border}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Window.Resources>
<Window.InputBindings>
<KeyBinding Key="Esc" Command="{x:Static SystemCommands.CloseWindowCommand}"/>
</Window.InputBindings>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="120"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalSpacing}}"/>
<ColumnDefinition MinWidth="120"/>
</Grid.ColumnDefinitions>
<usp:OrderModificationControl Grid.ColumnSpan="3" />
<Button Grid.Row="2" Style="{StaticResource BuyButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Buy, Type=msys:Side}" />
<Button Grid.Row="2" Grid.Column="2" Style="{StaticResource SellButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Sell, Type=msys:Side}" />
</Grid>
<u:WindowStyle.TitleContentTemplate>
<DataTemplate>
<TextBlock Style="{StaticResource TextBlock.Dark}">
<TextBlock.Inlines>
<Run FontWeight="Bold" Text="{Binding security, Mode=OneWay}" />
<Run Foreground="{u:BTSColor MarketData.bid}" FontSize="10">
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}" StringFormat="({0})">
<Binding Path="bid_size" Mode="OneWay"/>
<Binding Path="DataContext.qty_display" RelativeSource="{RelativeSource AncestorType=usp:OrderTicket}"/>
</MultiBinding>
</Run>
<Run Foreground="{u:BTSColor MarketData.bid}" FontWeight="Bold">
<Run.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="bid"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</Run.Text>
</Run>
<Run Foreground="{u:BTSColor MarketData.ask}" FontWeight="Bold">
<Run.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="ask"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</Run.Text>
</Run>
<Run Foreground="{u:BTSColor MarketData.ask}" FontSize="10">
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}" StringFormat="({0})">
<Binding Path="ask_size" Mode="OneWay"/>
<Binding Path="DataContext.qty_display" RelativeSource="{RelativeSource AncestorType=usp:OrderTicket}"/>
</MultiBinding>
</Run>
</TextBlock.Inlines>
</TextBlock>
</DataTemplate>
</u:WindowStyle.TitleContentTemplate>
</Window>
+30 -101
View File
@@ -4,9 +4,6 @@ using bts.api;
using BCG;
using mbts.co2;
using System.Xml.Serialization;
using ZstdSharp.Unsafe;
using bts.proto;
namespace mbts.analytics {
// Pairs ITradeLogRow along with the data (theo, md) that is needed to compute
@@ -21,87 +18,20 @@ namespace mbts.analytics {
public SecurityImmutable security { get { return md.security; } }
// private double _option_pnl(double trade_price, double now_price) {
// double coin_terms = (now_price - trade_price) * tl_row.size;
// double usd_terms = coin_terms * driver;
// return usd_terms;
// }
private bool _is_notional {
get {
return false;
}
private double _option_pnl(double trade_price, double now_price) {
double coin_terms = (now_price - trade_price) * tl_row.size;
double usd_terms = coin_terms * driver;
return usd_terms;
}
private bool _is_inverse_option_contract {
get {
return false;
}
private double _future_pnl(double trade_price, double now_price) {
// the qty is in USD, so this is the initial cash flow
double init_asset_value = tl_row.size;
double asset_size = init_asset_value / tl_row.price;
double now_asset_value = asset_size * now_price;
return now_asset_value - init_asset_value;
}
private double _cpp {
get {
return 1.0;
}
}
private bool _is_reverse_option_contract {
get {
// check for Deribit options
return false;
}
}
private double _option_pnl {
get {
if (_is_notional) {
var now_price = theo;
double coin_terms = (now_price - tl_row.price) * tl_row.size;
double usd_terms = coin_terms * driver;
return usd_terms;
} else {
return _cpp * tl_row.size * (theo - tl_row.price);
}
}
}
private double _spot_pnl {
get {
// both are computed the same way
return _future_pnl;
}
}
private double _future_pnl {
get {
if (_is_notional) {
// TODO:
// let it fall through right now
// the qty is in USD, so this is the initial cash flow
var now_price = theo;
double init_asset_value = tl_row.size;
double asset_size = init_asset_value / tl_row.price;
double now_asset_value = asset_size * now_price;
return now_asset_value - init_asset_value;
} else {
// TODO: is position pnl, compute it wrt settlement price?
return tl_row.size * (theo - tl_row.price);
}
}
}
// private double _future_pnl(double trade_price, double now_price) {
// if (_is_notional) {
// // the qty is in USD, so this is the initial cash flow
// double init_asset_value = tl_row.size;
// double asset_size = init_asset_value / tl_row.price;
// double now_asset_value = asset_size * now_price;
// return now_asset_value - init_asset_value;
// } else {
// return tl_row.size * (theo - tl_row.price);
// }
// }
public double driver {
get {
@@ -116,16 +46,9 @@ namespace mbts.analytics {
public double theo {
get {
switch (tl_row.security_type) {
// TODO:
case SecurityType.Option:
if (_is_inverse_option_contract) {
return tl_row.call_put == PutOrCall.Call ?
opt_theo.fwd_normalized_call : opt_theo.fwd_normalized_put;
} else {
return tl_row.call_put == PutOrCall.Call ?
opt_theo.call : opt_theo.put;
}
// future and options, just get mid market
return tl_row.call_put == PutOrCall.Call ?
opt_theo.fwd_normalized_call : opt_theo.fwd_normalized_put;
case SecurityType.Future:
//return fut_theo.theo;
case SecurityType.Spot:
@@ -139,22 +62,28 @@ namespace mbts.analytics {
public double pnl {
get {
double rv = Double.NaN;
switch (tl_row.security_type) {
case SecurityType.Option:
rv = _option_pnl;
break;
case SecurityType.Future:
rv = _future_pnl;
break;
case SecurityType.Spot:
// = tl_row.size * (theo - tl_row.price);
rv = _spot_pnl;
break;
if (is_day_trade) {
if (tl_row.security_type == SecurityType.Future) {
rv = _future_pnl(tl_row.price, theo);
} else if (tl_row.security_type == SecurityType.Option) {
rv = _option_pnl(tl_row.price, theo);
} else if (tl_row.security_type == SecurityType.Spot) {
rv = tl_row.size * (theo - tl_row.price);
}
return rv;
}
// use settle price ?
if (tl_row.security_type == SecurityType.Future) {
rv = _future_pnl(tl_row.price, theo);
} else if (tl_row.security_type == SecurityType.Option) {
rv = _option_pnl(tl_row.price, theo);
} else if (tl_row.security_type == SecurityType.Spot) {
rv = tl_row.size * tl_row.price - settle_price;
}
return rv;
}
}
public double settle_price {
get {
return md.md == null ? Double.NaN : md.md.settlement_price;
@@ -216,10 +216,8 @@ namespace mbts.ui {
public static BTSColor ask_text => BTSCoreColors.red_dark;
public static BTSColor bid_text => BTSCoreColors.blue;
public static BTSColor bid => 0xff76b7fa;
public static BTSColor bid_dark => 0xff26239a;
public static BTSColor bid_alt => BTSCoreColors.blue_light.with(0x70);
public static BTSColor ask => 0xffff6666;
public static BTSColor ask_dark => 0xff7a2331;
public static BTSColor ask_alt => 0x70ffb4b4;
public static BTSColor last => BTSCoreColors.black;
public static BTSColor bid_ask_alt_blend => BTSCoreColors.purple_light;
@@ -19,7 +19,7 @@
<sys:Double x:Key="ButtonHeight">22.0</sys:Double>
<Style x:Key="SellButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="{u:BTSBrush MarketData.ask_dark}"/>
<Setter Property="Background" Value="{u:BTSColor MarketData.ask}"/>
<Setter Property="BorderBrush" Value="{u:BTSColor UI.Button.normal_border}"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.default_text}" />
<Setter Property="MinHeight" Value="40"/>
@@ -32,7 +32,7 @@
</Style>
<Style x:Key="BuyButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="{u:BTSBrush MarketData.bid_dark}"/>
<Setter Property="Background" Value="{u:BTSColor MarketData.bid}"/>
<Setter Property="BorderBrush" Value="{u:BTSColor UI.Button.normal_border}"/>
<Setter Property="Padding" Value="2"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.default_text}" />
@@ -12,6 +12,10 @@ namespace mbts.co2 {
readonly ulong _numeric_key;
public void set_bbo(double bid_price, UInt32 bid_qty, double ask_price, UInt32 ask_qty) {
}
public FakeMDConsumerHandle(
security_key key,
ulong numeric_key,
@@ -52,6 +56,17 @@ namespace mbts.co2 {
public double session_low => 500;
public double fixing_price => 20;
public bool get_bbo(out double bid_price, out float bid_sz, out double ask_price, out float ask_sz) {
NativePriceSize bid;
NativePriceSize ask;
ulong time = 0;
bool rv = get_bbo(ref time, out bid, out ask);
bid_price = bid.price;
bid_sz = bid.size;
ask_price = ask.price;
ask_sz = ask.size;
return rv;
}
public bool get_bbo(ref ulong time, out NativePriceSize bid, out NativePriceSize ask) {
bool rv = false;
@@ -7,6 +7,7 @@ namespace mbts.co2 {
bool is_heart_beating { get; }
bool has_depth { get; }
security_key key { get; }
public void set_bbo(double bid_price, UInt32 bid_qty, double ask_price, UInt32 ask_qty);
// The exchange key can change at CBOE. This numeric_id will always be correct
ulong numeric_id { get; }
@@ -32,6 +33,7 @@ namespace mbts.co2 {
double session_low { get; }
double fixing_price { get; }
bool get_bbo(out double bid_price, out float bid_sz, out double ask_price, out float ask_sz);
bool get_bbo(ref ulong time, out NativePriceSize bid, out NativePriceSize ask);
bool get_depth(
+28 -3
View File
@@ -4,13 +4,38 @@ using System.Runtime.InteropServices;
namespace mbts.co2 {
[StructLayout(LayoutKind.Explicit)]
public readonly struct IntPrice : IEquatable<IntPrice> {
public static readonly IntPrice NaN = new(0,127);
public static readonly IntPrice NaN = new(0, 127);
[FieldOffset(0)]
readonly long _mantissa;
[FieldOffset(7)]
readonly sbyte _exponent;
private static bool _is_invalid_double(double value) {
return (Double.IsNaN(value) || double.IsInfinity(value) ||
value >= long.MaxValue || value <= long.MinValue);
}
private static Tuple<long, sbyte> double_to_mant_exp(double v) {
if (_is_invalid_double(v)) {
return Tuple.Create<long, sbyte>(0, 127);
}
sbyte exp = -7;
double v_m = v * Math.Pow(10, Math.Abs(exp));
if (_is_invalid_double(v_m)) {
return Tuple.Create<long, sbyte>(0, 127);
}
long m = Convert.ToInt64(v_m);
return Tuple.Create<long, sbyte>(m, exp);
}
public IntPrice(double v) :
this(double_to_mant_exp(v)) {
}
public IntPrice(Tuple<long, sbyte> t)
: this(t.Item1, t.Item2) { }
public IntPrice(long mantissa, sbyte exponent) {
_mantissa = mantissa;
_exponent = exponent;
@@ -38,10 +63,10 @@ namespace mbts.co2 {
public static bool operator ==(IntPrice a, IntPrice b) => a.Equals(b);
public static bool operator !=(IntPrice a, IntPrice b) => !a.Equals(b);
public static bool operator < (IntPrice a, IntPrice b) =>
public static bool operator <(IntPrice a, IntPrice b) =>
a.exponent == b.exponent ? a.mantissa < b.mantissa : a.price() < b.price();
public static bool operator > (IntPrice a, IntPrice b) =>
public static bool operator >(IntPrice a, IntPrice b) =>
a.exponent == b.exponent ? a.mantissa > b.mantissa : a.price() > b.price();
}
}
@@ -11,6 +11,9 @@
using security_key = System.UInt32;
public unsafe class MDConsumerHandle : IMDConsumerHandle {
public void set_bbo(double bid_price, UInt32 bid_qty, double ask_price, UInt32 ask_qty) {
throw new NotImplementedException();
}
readonly HotEntry* _hot;
readonly HotDepthEntry* _hot_depth;
readonly ColdEntry* _cold;
@@ -74,6 +77,19 @@
public double session_low => _cold->session_low;
public double fixing_price => _cold->fixing_price;
public bool get_bbo(out double bid_price, out float bid_sz, out double ask_price, out float ask_sz) {
NativePriceSize bid;
NativePriceSize ask;
ulong time = 0;
bool rv = get_bbo(ref time, out bid, out ask);
bid_price = bid.price;
bid_sz = bid.size;
ask_price = ask.price;
ask_sz = ask.size;
return rv;
}
public bool get_bbo(ref ulong time, out NativePriceSize bid, out NativePriceSize ask) {
var updated = market_timestamp;
if (updated <= time || updated == TimeStamp.invalid_value) {
+11 -4
View File
@@ -13,9 +13,9 @@ namespace mbts {
using System.Linq;
using System.Threading;
public readonly struct SecurityMD : IEquatable<SecurityMD> {
public /*readonly*/ struct SecurityMD : IEquatable<SecurityMD> {
public static readonly Func<SecurityMD, uint> key_fn = smd => smd.security.Id;
private IMDConsumerHandle _md;
public SecurityMD(
SecurityImmutable security,
IMDConsumerHandle md,
@@ -23,7 +23,7 @@ namespace mbts {
TickDisplaySettingsImmutable? quantity_display = null,
IEnumerable<TickDisplayRuleImmutable>? price_display_rules = null) {
this.security = security;
this.md = md;
_md = md;
this.price_display = price_display ?? Security.Defaults.price_display_settingsi;
this.quantity_display = quantity_display ??
Security.Defaults.quantity_display_settingsi;
@@ -38,7 +38,14 @@ namespace mbts {
public SecurityImmutable security { get; }
public uint id => security.Id;
public IMDConsumerHandle md { get; }
public IMDConsumerHandle md {
get {
return _md;
}
set {
_md = value;
}
}
public TickDisplaySettingsImmutable price_display { get; }
public TickDisplaySettingsImmutable quantity_display { get; }
public ImmutableList<TickDisplayRuleImmutable> price_display_rules { get; }
@@ -15,4 +15,5 @@ message DatabentoMarketDataConfig {
string dataset = 70;
uint32 filter_securities_upto_n_months= 80;
bool load_reference_data = 90;
uint32 debug_sec = 100;
}
-174
View File
@@ -1,174 +0,0 @@
#include "OrderService.h"
#include "lib/api/services/OrderService.pb.h"
namespace bts::api::orderman {
#define PARSE_STRING(name, dest, it) \
it = j.find(#name); \
if (it != j.end()) { \
dest.mutable_##name()->clear(); \
it->get_to(*dest.mutable_##name()); \
}
#define PARSE_VALUE(name, dest, it) \
it = j.find(#name); \
if (it != j.end()) { \
dest.set_##name(it->get<decltype(dest.name())>()); \
}
#define PARSE_STRUCT(name, dest, it) \
it = j.find(#name); \
if (it != j.end()) { \
it->get_to(*dest.mutable_##name()); \
}
#define PARSE_ARRAY(name, dest, it) \
it = j.find(#name); \
if (it != j.end() && it->is_array()) { \
c.mutable_##name()->Clear(); \
it->get_to(*c.mutable_##name()); \
}
#define PARSE_ONEOF(name, dest, it) \
if (!parsed_oneof) { \
it = j.find(#name); \
if (it != j.end()) { \
it->get_to(*dest.mutable_##name()); \
parsed_oneof = true; \
} \
}
#define PARSE_ONEOFS(for_each) \
{ \
bool parsed_oneof = false; \
for_each(PARSE_ONEOF) \
}
#define WRITE_VALUE(src, name) j[#name] = src.name()
#define WRITE_STRUCT(src, name) \
if (src.has_##name()) { \
j[#name] = src.name(); \
}
#define WRITE_IF_NOT_ZERO(src, name) \
if (src.name() != 0) { \
j[#name] = src.name(); \
}
#define WRITE_IF_NOT_NAN(src, name) \
if (!std::isnan(src.name())) { \
j[#name] = src.name(); \
}
#define WRITE_IF_NOT_EMPTY(src, name) \
if (!src.name().empty()) { \
j[#name] = src.name(); \
}
#define WRITE_TIMESTAMP(src, name) \
j[#name] = bts::core::TimeStamp::from_nanos( \
src.name().seconds() * 1'000'000'000 + src.name().nanos()) \
.to_iso_string()
#define PARSE_TIMESTAMP(name, dest, it) \
it = j.find(#name); \
if (it != j.end()) { \
bts::core::TimeStamp tmp_ts = \
bts::core::TimeStamp::from_iso_string(it->get<std::string>()); \
tmp_ts.to_protobuf(*dest.mutable_##name()); \
}
void to_json(nlohmann::json &j, const Fill &f) {
WRITE_IF_NOT_ZERO(f, order_key);
WRITE_IF_NOT_ZERO(f, security_key);
WRITE_IF_NOT_EMPTY(f, account);
WRITE_IF_NOT_NAN(f, last_price);
WRITE_IF_NOT_NAN(f, avg_price);
WRITE_VALUE(f, side);
WRITE_IF_NOT_ZERO(f, last_shares);
WRITE_IF_NOT_EMPTY(f, fill_key);
WRITE_TIMESTAMP(f, timestamp);
WRITE_IF_NOT_ZERO(f, internal_account_id);
WRITE_VALUE(f, fill_kind);
WRITE_VALUE(f, fill_status);
}
void from_json(const nlohmann::json &j, Fill &f) {
nlohmann::json::const_iterator it;
PARSE_VALUE(order_key, f, it);
PARSE_VALUE(security_key, f, it);
PARSE_STRING(account, f, it);
PARSE_VALUE(last_price, f, it);
PARSE_VALUE(avg_price, f, it);
PARSE_VALUE(side, f, it);
PARSE_VALUE(last_shares, f, it);
PARSE_STRING(fill_key, f, it);
PARSE_TIMESTAMP(timestamp, f, it);
PARSE_VALUE(internal_account_id, f, it);
PARSE_VALUE(fill_kind, f, it);
PARSE_VALUE(fill_status, f, it);
}
void to_json(nlohmann::json &j, const NativeOrderState &n) {
WRITE_VALUE(n, exchange_order_id);
WRITE_VALUE(n, last_msg_seq_num);
WRITE_VALUE(n, sub_error_code);
WRITE_VALUE(n, native_status);
WRITE_VALUE(n, routing_record);
WRITE_VALUE(n, native_type);
WRITE_VALUE(n, last_transact_time);
WRITE_VALUE(n, last_transact_time_unix_ns);
WRITE_VALUE(n, stop_px);
WRITE_VALUE(n, exchange_order_id_u64);
}
void from_json(const nlohmann::json &j, NativeOrderState &n) {
nlohmann::json::const_iterator it;
PARSE_STRING(exchange_order_id, n, it);
PARSE_VALUE(last_msg_seq_num, n, it);
PARSE_VALUE(sub_error_code, n, it);
PARSE_VALUE(native_status, n, it);
PARSE_STRUCT(routing_record, n, it);
PARSE_VALUE(native_type, n, it);
PARSE_STRING(last_transact_time, n, it);
PARSE_VALUE(last_transact_time_unix_ns, n, it);
PARSE_VALUE(stop_px, n, it);
PARSE_VALUE(exchange_order_id_u64, n, it);
}
void to_json(nlohmann::json &j, const RoutingRecord &r) {
WRITE_IF_NOT_EMPTY(r, session);
WRITE_IF_NOT_EMPTY(r, account);
WRITE_IF_NOT_EMPTY(r, operator_id);
WRITE_VALUE(r, cti_code);
WRITE_VALUE(r, customer_or_firm);
WRITE_IF_NOT_EMPTY(r, smp_id);
WRITE_VALUE(r, smp_instruction);
WRITE_IF_NOT_EMPTY(r, location);
WRITE_IF_NOT_EMPTY(r, cust_order_handing_inst);
WRITE_IF_NOT_EMPTY(r, sender_compid);
WRITE_IF_NOT_EMPTY(r, clearing_firm);
WRITE_IF_NOT_EMPTY(r, customer_account_ref_id);
WRITE_IF_NOT_EMPTY(r, account_code);
WRITE_IF_NOT_EMPTY(r, company_id);
WRITE_IF_NOT_EMPTY(r, authorized_trader);
WRITE_IF_NOT_EMPTY(r, authorized_group);
WRITE_IF_NOT_EMPTY(r, cust_order_handing_inst);
WRITE_IF_NOT_EMPTY(r, order_server_id);
}
void from_json(const nlohmann::json &j, RoutingRecord &r) {
nlohmann::json::const_iterator it;
PARSE_STRING(session, r, it);
PARSE_STRING(account, r, it);
PARSE_STRING(operator_id, r, it);
PARSE_VALUE(cti_code, r, it);
PARSE_VALUE(customer_or_firm, r, it);
PARSE_STRING(smp_id, r, it);
PARSE_VALUE(smp_instruction, r, it);
PARSE_STRING(location, r, it);
PARSE_STRING(cust_order_handling_inst, r, it);
PARSE_STRING(sender_compid, r, it);
PARSE_STRING(clearing_firm, r, it);
PARSE_STRING(customer_account_ref_id, r, it);
PARSE_STRING(account_code, r, it);
PARSE_STRING(company_id, r, it);
PARSE_STRING(authorized_trader, r, it);
PARSE_STRING(authorized_group, r, it);
PARSE_STRING(cust_order_handing_inst, r, it);
PARSE_STRING(order_server_id, r, it);
}
} // namespace bts::api::orderman
+8 -60
View File
@@ -1,10 +1,6 @@
#pragma once
#include "lib/api/services/OrderService.pb.h"
#include "lib/api/services/native_order_type.h"
#include "lib/api/services/order_status.h"
#include "lib/api/services/time_in_force.h"
#include "lib/api/services/trigger_price_type.h"
#include "lib/core/EnumMacros.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/io/JsonReader.h"
@@ -14,70 +10,22 @@
namespace bts::api::orderman {
PB_ENUM_FROM_JSON(Fill_FillKind)
PB_ENUM_TO_JSON(Fill_FillKind)
PB_ENUM_FROM_JSON(OrderStatus)
PB_ENUM_TO_JSON(OrderStatus)
PB_ENUM_FROM_JSON(Fill_FillStatus)
PB_ENUM_TO_JSON(Fill_FillStatus)
void to_json(nlohmann::json &j, const Fill &f);
void from_json(const nlohmann::json &j, Fill &f);
PB_ENUM_FROM_JSON(OrderSubType)
PB_ENUM_TO_JSON(OrderSubType)
PB_ENUM_FROM_JSON(SelfMatchPreventionInstruction)
PB_ENUM_TO_JSON(SelfMatchPreventionInstruction)
PB_ENUM_FROM_JSON(CustomerOrFirm)
PB_ENUM_TO_JSON(CustomerOrFirm)
void to_json(nlohmann::json &j, const RoutingRecord &r);
void from_json(const nlohmann::json &j, RoutingRecord &r);
PB_ENUM_FROM_JSON(NativeOrderStatus)
PB_ENUM_TO_JSON(NativeOrderStatus)
PB_ENUM_FROM_JSON(NativeOrderState_SubError)
PB_ENUM_TO_JSON(NativeOrderState_SubError)
void to_json(nlohmann::json &j, const NativeOrderState &n);
void from_json(const nlohmann::json &j, NativeOrderState &n);
PB_ENUM_FROM_JSON(TriggerPriceType)
PB_ENUM_TO_JSON(TriggerPriceType)
} // namespace bts::api::orderman
FMT_IMPL(
bts::api::orderman::Fill_FillKind,
k,
"{}",
bts::api::orderman::Fill_FillKind_Name(k));
FMT_IMPL(
bts::api::orderman::Fill_FillStatus,
bts::api::orderman::OrderStatus,
s,
"{}",
bts::api::orderman::Fill_FillStatus(s));
bts::api::orderman::OrderStatus_Name(s));
FMT_IMPL(
bts::api::orderman::OrderSubType,
bts::api::orderman::TriggerPriceType,
t,
"{}",
bts::api::orderman::OrderSubType_Name(t));
FMT_IMPL(
bts::api::orderman::SelfMatchPreventionInstruction,
s,
"{}",
bts::api::orderman::SelfMatchPreventionInstruction_Name(s));
FMT_IMPL(
bts::api::orderman::NativeOrderStatus,
s,
"{}",
bts::api::orderman::NativeOrderStatus_Name(s));
FMT_IMPL(
bts::api::orderman::NativeOrderState_SubError,
s,
"{}",
bts::api::orderman::NativeOrderState_SubError_Name(s));
bts::api::orderman::TriggerPriceType_Name(t));
-57
View File
@@ -1,57 +0,0 @@
#pragma once
#include "lib/api/services/OrderService.pb.h"
#include "lib/core/EnumMacros.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/io/JsonReader.h"
#include "lib/logging/logging.h"
#include "lib/utils/string_utils.h"
#include <nlohmann/json.hpp>
namespace bts::api::orderman {
/**
* Parses NativeOrderType from a string. Unlike `bts::api::orderman::NativeOrderType_Parse`,
* this implementation is case insensitive, and makes the 'NATIVE_ORDER_TYPE_' prefix optional.
*/
bool native_order_type_parse_smart(
const std::string &str, NativeOrderType *result);
PB_ENUM_FROM_JSON(NativeOrderType)
PB_ENUM_TO_JSON(NativeOrderType)
std::ostream &operator<<(std::ostream &out, NativeOrderType t) noexcept;
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline bool native_order_type_parse_smart(
const std::string &str, NativeOrderType *result) {
if (NativeOrderType_Parse(str, result)) {
return true;
}
std::string s = bts::system::string_utils::copy_to_upper(str);
if (NativeOrderType_Parse(s, result)) {
return true;
}
const char *prefix = "NATIVE_ORDER_TYPE_";
if (!s.starts_with(prefix)) {
s.insert(0, prefix);
}
return NativeOrderType_Parse(s, result);
}
inline std::ostream &operator<<(std::ostream &out, NativeOrderType t) noexcept {
out << NativeOrderType_Name(t);
return out;
}
} // namespace bts::api::orderman
FMT_IMPL(
bts::api::orderman::NativeOrderType,
t,
"{}",
bts::api::orderman::NativeOrderType_Name(t));
-56
View File
@@ -1,56 +0,0 @@
#pragma once
#include "lib/api/services/OrderService.pb.h"
#include "lib/core/EnumMacros.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/io/JsonReader.h"
#include "lib/logging/logging.h"
#include "lib/utils/string_utils.h"
#include <nlohmann/json.hpp>
namespace bts::api::orderman {
/**
* Parses OrderStatus from a string. Unlike `bts::api::orderman::OrderStatus_Parse`,
* this implementation is case insensitive, and makes the 'ORDER_STATUS_' prefix optional.
*/
bool order_status_parse_smart(const std::string &str, OrderStatus *result);
PB_ENUM_FROM_JSON(OrderStatus)
PB_ENUM_TO_JSON(OrderStatus)
std::ostream &operator<<(std::ostream &out, OrderStatus s) noexcept;
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline bool order_status_parse_smart(
const std::string &str, OrderStatus *result) {
if (OrderStatus_Parse(str, result)) {
return true;
}
std::string s = bts::system::string_utils::copy_to_upper(str);
if (OrderStatus_Parse(s, result)) {
return true;
}
const char *prefix = "ORDER_STATUS_";
if (!s.starts_with(prefix)) {
s.insert(0, prefix);
}
return OrderStatus_Parse(s, result);
}
inline std::ostream &operator<<(std::ostream &out, OrderStatus s) noexcept {
out << OrderStatus_Name(s);
return out;
}
} // namespace bts::api::orderman
FMT_IMPL(
bts::api::orderman::OrderStatus,
s,
"{}",
bts::api::orderman::OrderStatus_Name(s));
-56
View File
@@ -1,56 +0,0 @@
#pragma once
#include "lib/api/services/OrderService.pb.h"
#include "lib/core/EnumMacros.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/io/JsonReader.h"
#include "lib/logging/logging.h"
#include "lib/utils/string_utils.h"
#include <nlohmann/json.hpp>
namespace bts::api::orderman {
/**
* Parses TimeInForce from a string. Unlike `bts::api::orderman::TimeInForce_Parse`,
* this implementation is case insensitive, and makes the 'TIME_IN_FORCE_' prefix optional.
*/
bool time_in_force_parse_smart(const std::string &str, TimeInForce *result);
PB_ENUM_FROM_JSON(TimeInForce)
PB_ENUM_TO_JSON(TimeInForce)
std::ostream &operator<<(std::ostream &out, TimeInForce tif) noexcept;
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline bool time_in_force_parse_smart(
const std::string &str, TimeInForce *result) {
if (TimeInForce_Parse(str, result)) {
return true;
}
std::string s = bts::system::string_utils::copy_to_upper(str);
if (TimeInForce_Parse(s, result)) {
return true;
}
const char *prefix = "TIME_IN_FORCE_";
if (!s.starts_with(prefix)) {
s.insert(0, prefix);
}
return TimeInForce_Parse(s, result);
}
inline std::ostream &operator<<(std::ostream &out, TimeInForce tif) noexcept {
out << TimeInForce_Name(tif);
return out;
}
} // namespace bts::api::orderman
FMT_IMPL(
bts::api::orderman::TimeInForce,
v,
"{}",
bts::api::orderman::TimeInForce_Name(v));
-58
View File
@@ -1,58 +0,0 @@
#pragma once
#include "lib/api/services/OrderService.pb.h"
#include "lib/core/EnumMacros.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/io/JsonReader.h"
#include "lib/logging/logging.h"
#include "lib/utils/string_utils.h"
#include <nlohmann/json.hpp>
namespace bts::api::orderman {
/**
* Parses TriggerPriceType from a string. Unlike `bts::api::orderman::TriggerPriceType_Parse`,
* this implementation is case insensitive, and makes the 'TRIGGER_PRICE_TYPE_' prefix optional.
*/
bool trigger_price_type_parse_smart(
const std::string &str, TriggerPriceType *result);
PB_ENUM_FROM_JSON(TriggerPriceType)
PB_ENUM_TO_JSON(TriggerPriceType)
std::ostream &operator<<(std::ostream &out, TriggerPriceType t) noexcept;
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline bool trigger_price_type_parse_smart(
const std::string &str, TriggerPriceType *result) {
if (TriggerPriceType_Parse(str, result)) {
return true;
}
std::string s = bts::system::string_utils::copy_to_upper(str);
if (TriggerPriceType_Parse(s, result)) {
return true;
}
const char *prefix = "TRIGGER_PRICE_TYPE_";
if (!s.starts_with(prefix)) {
s.insert(0, prefix);
}
return TriggerPriceType_Parse(s, result);
}
inline std::ostream &operator<<(
std::ostream &out, TriggerPriceType t) noexcept {
out << TriggerPriceType_Name(t);
return out;
}
} // namespace bts::api::orderman
FMT_IMPL(
bts::api::orderman::TriggerPriceType,
t,
"{}",
bts::api::orderman::TriggerPriceType_Name(t));
@@ -75,7 +75,6 @@ function(get_cpp_sources dest)
expression/ParserExceptions.cpp
expression/Value.cpp
expression/tests/expression_parser_tests.cpp
io/JsonReader.cpp
logging/LoggerHub.cpp
logging/Sink.cpp
logging/logging.cpp
@@ -74,7 +74,6 @@ function(get_cpp_sources dest)
expression/ParserExceptions.cpp
expression/Value.cpp
expression/tests/expression_parser_tests.cpp
io/JsonReader.cpp
logging/LoggerHub.cpp
logging/Sink.cpp
logging/logging.cpp
-98
View File
@@ -1,98 +0,0 @@
#pragma once
#include "lib/utils/LockBox.h"
#include "lib/utils/bts_assert.h"
#include "lib/utils/type_name.h"
#include <functional>
#include <memory>
namespace bts::collections {
template <class KeyT, class ValueT>
class ConcurrentFactory {
utils::LockBox<std::unordered_map<KeyT, std::shared_ptr<ValueT>>> store;
public:
std::shared_ptr<ValueT> add(
const KeyT &key,
std::function<void(std::shared_ptr<ValueT> &)> callback) {
return store.lock([&](auto &map) {
auto [it, is_new] = map.try_emplace(key, std::make_shared<ValueT>());
bts_assert_msgf(
is_new, "{}::add. Duplicate key '{}'",
utils::cached_type_name<ConcurrentFactory<KeyT, ValueT>>(), key);
callback(it->second);
return it->second;
});
}
std::size_t erase(const KeyT &key) {
return store.lock([&](auto &map) {
map.erase(key);
return map.size();
});
}
std::size_t size() const {
return store.lock([&](auto &map) { return map.size(); });
}
template <
typename CallbackT = std::function<void(std::shared_ptr<ValueT> &)>>
std::shared_ptr<const ValueT> get(const KeyT &key, CallbackT callback) {
return store.lock([&](auto &map) {
auto [it, is_new] = map.try_emplace(key, std::make_shared<ValueT>());
bts_assert_msgf(
!is_new, "{}::get. Key not found '{}'",
utils::cached_type_name<ConcurrentFactory<KeyT, ValueT>>(), key);
callback(it->second);
return it->second;
});
}
template <
typename CallbackT =
std::function<void(std::shared_ptr<ValueT> &, bool)>>
std::shared_ptr<const ValueT> get_or_add(
const KeyT &key, CallbackT callback) {
return store.lock([&](auto &map) {
auto [it, is_new] = map.try_emplace(key);
if (is_new) {
it->second = std::make_shared<ValueT>();
}
callback(it->second, is_new);
return it->second;
});
}
template <
typename CallbackT =
std::function<void(std::shared_ptr<ValueT> &, bool)>>
std::shared_ptr<const ValueT> replace(const KeyT &key, CallbackT callback) {
return store.lock([&](auto &map) {
auto [it, is_new] = map.try_emplace(key);
bts_assert_msgf(
!is_new, "{}::replace. Key not found '{}'",
utils::cached_type_name<ConcurrentFactory<KeyT, ValueT>>(), key);
it->second = std::make_shared<ValueT>();
callback(it->second, is_new);
return it->second;
});
}
template <
typename CallbackT =
std::function<void(std::shared_ptr<ValueT> &, bool)>>
std::shared_ptr<const ValueT> replace_or_add(
const KeyT &key, CallbackT callback) {
return store.lock([&](auto &map) {
auto [it, is_new] = map.try_emplace(key);
it->second = std::make_shared<ValueT>();
callback(it->second, is_new);
return it->second;
});
}
};
} // namespace bts::collections
@@ -1,34 +1,34 @@
OrdermanCfg {
rule: "DeploymentType.type == 'dev'"
value {
executor {
thread_name: "oman_orders"
poll: false
hosted_actor: "MDSource"
hosted_actor: "OrderManager"
hosted_actor: "ExecutionGateway"
}
executor {
thread_name: "oman_slow"
poll: false
hosted_actor: "*"
}
}
rule: "DeploymentType.type == 'dev'"
value {
executor {
thread_name: "oman_orders"
poll: false
hosted_actor: "MDSource"
hosted_actor: "OrderManager"
hosted_actor: "ExecutionGateway"
}
executor {
thread_name: "oman_slow"
poll: false
hosted_actor: "*"
}
}
}
OrdermanCfg {
value {
executor {
thread_name: "oman_orders"
poll: true
hosted_actor: "MDSource"
hosted_actor: "OrderManager"
hosted_actor: "ExecutionGateway"
}
executor {
thread_name: "oman_slow"
poll: false
hosted_actor: "*"
}
}
}
value {
executor {
thread_name: "oman_orders"
poll: true
hosted_actor: "MDSource"
hosted_actor: "OrderManager"
hosted_actor: "ExecutionGateway"
}
executor {
thread_name: "oman_slow"
poll: false
hosted_actor: "*"
}
}
}
-14
View File
@@ -110,19 +110,6 @@
ENUM_TO_JSON(type) \
ENUM_EQUALITY_OPS(type)
#define ENUM_CLASS_DEF_NO_JSON(type, for_all) \
class type { \
public: \
enum Value { for_all(ENUM_DECLARE_VALUE) }; \
\
ENUM_BASIC_MEMBERS(type) \
}; \
\
ENUM_OUTPUT_OPS(type) \
ENUM_NAME_DEF(type, for_all) \
ENUM_PARSE_DEF(type, for_all) \
ENUM_EQUALITY_OPS(type)
#define ENUM_BITWISE_COMPARISON_OP(enum_type, numeric_type, op) \
constexpr enum_type operator op(enum_type lhs, enum_type rhs) noexcept { \
return static_cast<enum_type>(static_cast<numeric_type>(lhs) \
@@ -167,4 +154,3 @@
inline void to_json(nlohmann::json &j, const enum_type &value) { \
j = enum_type##_Name(value); \
}
-7
View File
@@ -1,7 +1,6 @@
#include "TimeStamp.h"
#include "lib/utils/datetime.h"
#include <boost/date_time/posix_time/time_formatters.hpp>
#include <google/protobuf/timestamp.pb.h>
namespace bts::core {
@@ -44,12 +43,6 @@ bool TimeStamp::from_ymd(
return true;
}
void TimeStamp::to_protobuf(google::protobuf::Timestamp &ts) const {
ts.set_seconds(seconds());
bts_assert(nanos() >= seconds() * 1'000'000'000);
ts.set_nanos(nanos() - seconds() * 1'000'000'000);
}
TimeStamp TimeStamp::from_iso_string(const std::string &iso_string) {
auto ptime = datetime::parse_iso_date_time_string(iso_string);
return from_ptime(ptime);
-2
View File
@@ -10,7 +10,6 @@
#include <boost/date_time/time.hpp>
#include <cstdint>
#include <fmt/format.h>
#include <google/protobuf/timestamp.pb.h>
#include <limits>
#include <string>
@@ -88,7 +87,6 @@ struct TimeStamp {
// Gets a TimeStamp representing this date at midnight.
TimeStamp date() const;
constexpr boost::posix_time::ptime to_ptime() const;
void to_protobuf(google::protobuf::Timestamp &ts) const;
std::string to_iso_string() const;
// current epoch ns
static uint64_t now_ns() {
-52
View File
@@ -1,52 +0,0 @@
#pragma once
#include "lib/core/EnumMacros.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/io/JsonReader.h"
#include "lib/logging/logging.h"
#include "lib/utils/string_utils.h"
#include <nlohmann/json.hpp>
namespace bts::api {
/**
* Parses PutOrCall from a string. Unlike `bts::api::SecurityType_Parse`, this
* implementation is case insensitive, and makes the 'PUTORCALL_' prefix optional.
*/
bool put_or_call_parse_smart(const std::string &str, PutOrCall *result);
PB_ENUM_FROM_JSON(PutOrCall)
PB_ENUM_TO_JSON(PutOrCall)
std::ostream &operator<<(std::ostream &out, const PutOrCall &poc) noexcept;
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline bool put_or_call_parse_smart(const std::string &str, PutOrCall *result) {
if (PutOrCall_Parse(str, result)) {
return true;
}
std::string s = bts::system::string_utils::copy_to_upper(str);
if (PutOrCall_Parse(s, result)) {
return true;
}
const char *prefix = "PUTORCALL_";
if (!s.starts_with(prefix)) {
s.insert(0, prefix);
}
return PutOrCall_Parse(s, result);
}
inline std::ostream &operator<<(
std::ostream &out, const PutOrCall &poc) noexcept {
out << PutOrCall_Name(poc);
return out;
}
} // namespace bts::api
FMT_IMPL(bts::api::PutOrCall, poc, "{}", bts::api::PutOrCall_Name(poc));
-389
View File
@@ -2,13 +2,10 @@
#include "lib/config/config.h"
#include "lib/config/protos/orders_fills_cleanup.pb.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/io/JsonMacros.h"
#include "lib/md/common.h"
#include "lib/utils/pb_json_macros.h"
#include <boost/date_time/posix_time/posix_time.hpp>
#include <google/protobuf/text_format.h>
#include <nlohmann/json.hpp>
namespace bts {
namespace securities {
@@ -194,389 +191,3 @@ std::string unique_descriptor(const api::Security &security) {
} // namespace securities
} // namespace bts
namespace bts::api {
using nlohmann::json;
using std::uint32_t;
void to_json(json &j, const api::ContractQuantityInfo &cqi) {
PB_JSON_WRITE_IF_NOT_ZERO(j, cqi, min_value);
PB_JSON_WRITE_IF_NOT_ZERO(j, cqi, max_value);
PB_JSON_WRITE_IF_NOT_ZERO(j, cqi, tick_unit);
PB_JSON_WRITE_IF_NOT_ZERO(j, cqi, tick_base);
PB_JSON_WRITE_IF_NOT_ZERO(j, cqi, multiplier);
PB_JSON_WRITE_IF_NOT_ZERO(j, cqi, max_price);
}
void from_json(const json &j, api::ContractQuantityInfo &cqi) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, cqi, min_value, it);
PB_JSON_READ_VALUE(j, cqi, max_value, it);
PB_JSON_READ_VALUE(j, cqi, max_value, it);
PB_JSON_READ_VALUE(j, cqi, tick_unit, it);
PB_JSON_READ_VALUE(j, cqi, tick_base, it);
PB_JSON_READ_VALUE(j, cqi, max_price, it);
}
void to_json(json &j, const api::ContractSpecification &spec) {
PB_JSON_WRITE_IF_NOT_ZERO(j, spec, low_tick);
PB_JSON_WRITE_IF_NOT_ZERO(j, spec, price_break);
PB_JSON_WRITE_IF_NOT_ZERO(j, spec, high_tick);
PB_JSON_WRITE_IF_NOT_ZERO(j, spec, cash_per_point);
PB_JSON_WRITE_IF_NOT_ZERO(j, spec, price_multiplier);
PB_JSON_WRITE_IF_NOT_ZERO(j, spec, main_fraction);
PB_JSON_WRITE_IF_NOT_ZERO(j, spec, sub_fraction);
PB_JSON_WRITE_IF_NOT_ZERO(j, spec, price_display_format);
PB_JSON_WRITE_IF_NOT_EMPTY(j, spec, quantity_infos);
PB_JSON_WRITE_IF_NOT_EMPTY(j, spec, notional_quantity_infos);
}
void from_json(const json &j, api::ContractSpecification &spec) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, spec, low_tick, it);
PB_JSON_READ_VALUE(j, spec, price_break, it);
PB_JSON_READ_VALUE(j, spec, high_tick, it);
PB_JSON_READ_VALUE(j, spec, cash_per_point, it);
PB_JSON_READ_VALUE(j, spec, price_multiplier, it);
PB_JSON_READ_VALUE(j, spec, main_fraction, it);
PB_JSON_READ_VALUE(j, spec, sub_fraction, it);
PB_JSON_READ_VALUE(j, spec, price_display_format, it);
PB_JSON_READ_ARRAY(j, spec, quantity_infos, it);
PB_JSON_READ_ARRAY(j, spec, notional_quantity_infos, it);
}
void to_json(json &, const SpotContract &) {}
void from_json(const json &, SpotContract &) {}
template <class ExpContract>
static void exp_to_json(json &j, const ExpContract &ec) {
j = bts::core::TimeStamp::from_ymd(
ec.expiration_year(), ec.expiration_month(), ec.expiration_day())
.to_iso_string();
}
template <class ExpContract>
static void exp_from_json(const json &j, ExpContract &ec) {
const auto dt = bts::core::TimeStamp::from_iso_string(j.get<std::string>());
uint32_t y, m, d;
dt.to_date(y, m, d);
ec.set_expiration_year(y);
ec.set_expiration_month(m);
ec.set_expiration_day(d);
}
void to_json(json &j, const FutureContract &f) {
exp_to_json(j["expiration"], f);
}
void from_json(const json &j, FutureContract &f) {
const auto it = j.find("expiration");
if (it != j.end()) {
exp_from_json(*it, f);
}
}
void to_json(json &j, const OptionContract &opt) {
exp_to_json(j["expiration"], opt);
PB_JSON_WRITE_VALUE(j, opt, strike);
PB_JSON_WRITE_VALUE(j, opt, call_put);
}
void from_json(const json &j, OptionContract &opt) {
json::const_iterator it;
it = j.find("expiration");
if (it != j.end()) {
exp_from_json(*it, opt);
}
PB_JSON_READ_VALUE(j, opt, strike, it);
PB_JSON_READ_VALUE(j, opt, call_put, it);
}
void to_json(json &j, const ContractLeg &leg) {
PB_JSON_WRITE_STRUCT(j, leg, security);
PB_JSON_WRITE_VALUE(j, leg, quantity);
PB_JSON_WRITE_VALUE(j, leg, side);
PB_JSON_WRITE_VALUE(j, leg, price_multiplier);
PB_JSON_WRITE_IF_NOT_ZERO(j, leg, leg_delta);
PB_JSON_WRITE_VALUE(j, leg, price_calc);
}
void from_json(const json &j, ContractLeg &leg) {
json::const_iterator it;
PB_JSON_READ_STRUCT(j, leg, security, it);
PB_JSON_READ_VALUE(j, leg, quantity, it);
PB_JSON_READ_VALUE(j, leg, side, it);
PB_JSON_READ_VALUE(j, leg, price_multiplier, it);
PB_JSON_READ_VALUE(j, leg, leg_delta, it);
PB_JSON_READ_VALUE(j, leg, price_calc, it);
}
void to_json(json &j, const SpreadContract &spd) {
PB_JSON_WRITE_IF_NOT_EMPTY(j, spd, legs);
}
void from_json(const json &j, SpreadContract &spd) {
json::const_iterator it;
PB_JSON_READ_ARRAY(j, spd, legs, it);
}
void to_json(json &j, const SyntheticContract &syn) {
PB_JSON_WRITE_IF_NOT_EMPTY(j, syn, name);
PB_JSON_WRITE_IF_NOT_ZERO(j, syn, user_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, syn, legs);
PB_JSON_WRITE_VALUE(j, syn, price_calc);
}
void from_json(const json &j, SyntheticContract &syn) {
json::const_iterator it;
PB_JSON_READ_STRING(j, syn, name, it);
PB_JSON_READ_VALUE(j, syn, user_id, it);
PB_JSON_READ_ARRAY(j, syn, legs, it);
PB_JSON_READ_VALUE(j, syn, price_calc, it);
}
void to_json(json &j, const GLBXExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, cfi_code);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, cme_symbol);
PB_JSON_WRITE_IF_NOT_ZERO(j, data, market_segment_id);
}
void from_json(const json &j, GLBXExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
PB_JSON_READ_STRING(j, data, cfi_code, it);
PB_JSON_READ_STRING(j, data, cme_symbol, it);
PB_JSON_READ_VALUE(j, data, market_segment_id, it);
}
void to_json(json &j, const CBOEExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
PB_JSON_WRITE_IF_NOT_ZERO(j, data, class_key);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, source);
}
void from_json(const json &j, CBOEExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
PB_JSON_READ_VALUE(j, data, class_key, it);
PB_JSON_READ_STRING(j, data, source, it);
}
void to_json(json &j, const CBOECFEExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
PB_JSON_WRITE_IF_NOT_ZERO(j, data, class_key);
}
void from_json(const json &j, CBOECFEExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
PB_JSON_READ_VALUE(j, data, class_key, it);
}
void to_json(json &j, const ICEExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
PB_JSON_WRITE_IF_NOT_ZERO(j, data, price_denominator);
PB_JSON_WRITE_IF_NOT_ZERO(j, data, deal_denominator);
PB_JSON_WRITE_IF_NOT_ZERO(j, data, settle_denominator);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, market_description);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, cleared_alias);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, product_name);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, hub_alias);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, strip_name);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, mic_code);
PB_JSON_WRITE_IF_TRUE(j, data, is_strip);
}
void from_json(const json &j, ICEExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
PB_JSON_READ_VALUE(j, data, price_denominator, it);
PB_JSON_READ_VALUE(j, data, deal_denominator, it);
PB_JSON_READ_VALUE(j, data, settle_denominator, it);
PB_JSON_READ_STRING(j, data, market_description, it);
PB_JSON_READ_STRING(j, data, cleared_alias, it);
PB_JSON_READ_STRING(j, data, product_name, it);
PB_JSON_READ_STRING(j, data, hub_alias, it);
PB_JSON_READ_STRING(j, data, strip_name, it);
PB_JSON_READ_STRING(j, data, mic_code, it);
PB_JSON_READ_VALUE(j, data, is_strip, it);
}
void to_json(json &j, const TAIFEXExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
PB_JSON_WRITE_IF_NOT_ZERO(j, data, class_key);
}
void from_json(const json &j, TAIFEXExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
PB_JSON_READ_VALUE(j, data, class_key, it);
}
void to_json(json &j, const DeribitExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
}
void from_json(const json &j, DeribitExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
}
void to_json(json &j, const FTXExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
}
void from_json(const json &j, FTXExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
}
void to_json(json &j, const BinanceExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
}
void from_json(const json &j, BinanceExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
}
void to_json(json &j, const BinanceUsdsFuturesExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
}
void from_json(const json &j, BinanceUsdsFuturesExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
}
void to_json(json &j, const BinanceCoinFuturesExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
}
void from_json(const json &j, BinanceCoinFuturesExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
}
void to_json(json &j, const BinanceUsExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
}
void from_json(const json &j, BinanceUsExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
}
void to_json(json &j, const OkxExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
}
void from_json(const json &j, OkxExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
}
void to_json(json &j, const CoinbaseExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
}
void from_json(const json &j, CoinbaseExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
}
void to_json(json &j, const Security &s) {
// clang-format off
#define CONTRACT_ONEOF(oneof) \
oneof(j, s, Spot, spot) \
oneof(j, s, Future, future) \
oneof(j, s, Option, option) \
oneof(j, s, Spread, spread) \
oneof(j, s, Synthetic, synthetic)
#define EXCHANGE_DATA_ONEOF(oneof) \
oneof(j, s, Glbx, glbx) \
oneof(j, s, Cboe, cboe) \
oneof(j, s, Cfe, cfe) \
oneof(j, s, Ice, ice) \
oneof(j, s, Deribit, deribit) \
oneof(j, s, Ftx, ftx) \
oneof(j, s, Binance, binance) \
oneof(j, s, BinanceCoinFutures, binance_coin_futures) \
oneof(j, s, BinanceUsdsFutures, binance_usds_futures) \
oneof(j, s, BinanceUs, binance_us) \
oneof(j, s, Coinbase, coinbase) \
oneof(j, s, Okx, okx) \
oneof(j, s, Taifex, taifex)
// clang-format on
PB_JSON_WRITE_IF_NOT_ZERO(j, s, id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, s, symbol);
PB_JSON_WRITE_VALUE(j, s, exchange);
PB_JSON_WRITE_STRUCT(j, s, contract_spec);
PB_JSON_WRITE_IF_NOT_EMPTY(j, s, currency);
PB_JSON_WRITE_ONEOF(j, s, Contract, CONTRACT_ONEOF);
PB_JSON_WRITE_ONEOF(j, s, ExchangeData, EXCHANGE_DATA_ONEOF);
}
void from_json(const json &j, Security &s) {
json::const_iterator it;
#define SEC_TYPE_ONEOF(oneof) \
oneof(j, s, future, it); \
oneof(j, s, option, it); \
oneof(j, s, spread, it); \
oneof(j, s, synthetic, it)
#define CONTRACT_CASE_ONEOF(oneof) \
oneof(j, s, glbx, it); \
oneof(j, s, cboe, it); \
oneof(j, s, cfe, it); \
oneof(j, s, ice, it); \
oneof(j, s, deribit, it); \
oneof(j, s, ftx, it); \
oneof(j, s, binance, it); \
oneof(j, s, binance_usds_futures, it); \
oneof(j, s, binance_coin_futures, it); \
oneof(j, s, binance_us, it); \
oneof(j, s, coinbase, it); \
oneof(j, s, okx, it); \
oneof(j, s, taifex, it);
PB_JSON_READ_VALUE(j, s, id, it);
PB_JSON_READ_STRING(j, s, symbol, it);
PB_JSON_READ_STRING(j, s, currency, it);
PB_JSON_READ_STRUCT(j, s, contract_spec, it);
PB_JSON_READ_ONEOFS(SEC_TYPE_ONEOF);
PB_JSON_READ_ONEOFS(CONTRACT_CASE_ONEOF);
}
} // namespace bts::api
+19 -88
View File
@@ -1,6 +1,6 @@
#pragma once
#include "lib/domain_model/PutOrCall.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/domain_model/SecurityType.h"
#include "lib/domain_model/Side.h"
@@ -9,7 +9,6 @@
#include "lib/utils/bts_assert.h"
#include <cmath>
#include <nlohmann/json_fwd.hpp>
namespace boost {
namespace gregorian {
@@ -17,83 +16,14 @@ class date;
}
} // namespace boost
namespace bts::api {
namespace bts {
namespace api {
class Security;
class ICEExchangeData;
} // namespace api
void to_json(nlohmann::json &j, const ContractQuantityInfo &c);
void from_json(const nlohmann::json &j, ContractQuantityInfo &c);
void to_json(nlohmann::json &j, const ContractSpecification &c);
void from_json(const nlohmann::json &j, ContractSpecification &c);
void to_json(nlohmann::json &j, const ContractLeg &c);
void from_json(const nlohmann::json &j, ContractLeg &c);
void to_json(nlohmann::json &j, const SpotContract &c);
void from_json(const nlohmann::json &j, SpotContract &c);
void to_json(nlohmann::json &j, const FutureContract &c);
void from_json(const nlohmann::json &j, FutureContract &c);
void to_json(nlohmann::json &j, const OptionContract &c);
void from_json(const nlohmann::json &j, OptionContract &c);
void to_json(nlohmann::json &j, const SpreadContract &c);
void from_json(const nlohmann::json &j, SpreadContract &c);
void to_json(nlohmann::json &j, const SyntheticContract_PriceCalc &c);
void from_json(const nlohmann::json &j, SyntheticContract_PriceCalc &c);
void to_json(nlohmann::json &j, const SyntheticContract &c);
void from_json(const nlohmann::json &j, SyntheticContract &c);
void to_json(nlohmann::json &j, const GLBXExchangeData &data);
void from_json(const nlohmann::json &j, GLBXExchangeData &data);
void to_json(nlohmann::json &j, const CBOEExchangeData &data);
void from_json(const nlohmann::json &j, CBOEExchangeData &data);
void to_json(nlohmann::json &j, const CBOECFEExchangeData &data);
void from_json(const nlohmann::json &j, CBOECFEExchangeData &data);
void to_json(nlohmann::json &j, const ICEExchangeData &data);
void from_json(const nlohmann::json &j, ICEExchangeData &data);
void to_json(nlohmann::json &j, const DeribitExchangeData &data);
void from_json(const nlohmann::json &j, DeribitExchangeData &data);
void to_json(nlohmann::json &j, const FTXExchangeData &data);
void from_json(const nlohmann::json &j, FTXExchangeData &data);
void to_json(nlohmann::json &j, const BinanceExchangeData &data);
void from_json(const nlohmann::json &j, BinanceExchangeData &data);
void to_json(nlohmann::json &j, const BinanceUsdsFuturesExchangeData &data);
void from_json(const nlohmann::json &j, BinanceUsdsFuturesExchangeData &data);
void to_json(nlohmann::json &j, const BinanceCoinFuturesExchangeData &data);
void from_json(const nlohmann::json &j, BinanceCoinFuturesExchangeData &data);
void to_json(nlohmann::json &j, const BinanceUsExchangeData &data);
void from_json(const nlohmann::json &j, BinanceUsExchangeData &data);
void to_json(nlohmann::json &j, const OkxExchangeData &data);
void from_json(const nlohmann::json &j, OkxExchangeData &data);
void to_json(nlohmann::json &j, const CoinbaseExchangeData &data);
void from_json(const nlohmann::json &j, CoinbaseExchangeData &data);
void to_json(nlohmann::json &j, const Security &s);
void from_json(const nlohmann::json &j, Security &s);
PB_ENUM_FROM_JSON(SyntheticContract_PriceCalc);
PB_ENUM_TO_JSON(SyntheticContract_PriceCalc);
} // namespace bts::api
namespace bts::securities {
namespace securities {
/**
* Gets a descriptor string for a security (should be unique within a particular exchange).
@@ -130,8 +60,7 @@ inline api::Side other_side(api::Side side) {
return side;
}
}
template <class V>
class TwoSided {
template <class V> class TwoSided {
public:
TwoSided() : buy(), sell() {}
@@ -175,8 +104,7 @@ inline bool is_worse(api::Side s, Price p1, Price p2) {
return is_better(s, p2, p1);
}
template <class C>
void apply_to_both_sides(C c) {
template <class C> void apply_to_both_sides(C c) {
c(api::SIDE_BUY);
c(api::SIDE_SELL);
}
@@ -250,8 +178,8 @@ inline Price encode_exchange_price(api::Security const &sec, Price in) {
return in;
}
inline double raw_round_to_quote_price(
double raw_price, double tick_size, api::Side side) {
inline double
raw_round_to_quote_price(double raw_price, double tick_size, api::Side side) {
double factor = 1000000000;
auto i_price = llround(factor * raw_price);
auto i_tick_size = llround(tick_size * factor);
@@ -294,8 +222,8 @@ inline double checked_round_to_quote_price(
}
}
inline Price round_to_quote_price(
api::Security const &sec, Price in, api::Side side) {
inline Price
round_to_quote_price(api::Security const &sec, Price in, api::Side side) {
auto p = in.value();
bts_assert_d(!std::isnan(p));
if (p < -sec.contract_spec().price_break() ||
@@ -359,8 +287,8 @@ inline const std::string &descriptor(
// Looking to round md::IntPrice? Look in IntPrice.h
// computes price_2 - price_1 in ticks
inline int price_diff_to_ticks(
double price_1, double price_2, double tick_size) {
inline int
price_diff_to_ticks(double price_1, double price_2, double tick_size) {
return static_cast<int>(std::round((price_2 - price_1) / tick_size));
}
// returns the difference (in ticks) bw and order and a market.
@@ -384,8 +312,8 @@ inline int order_diff_market(
// simple double equality can not be used to compare prices.
// this function deems 2 prices to be equal if they are within
// 1 % of the tick size
inline bool test_price_equality(
double price_1, double price_2, double tick_size) {
inline bool
test_price_equality(double price_1, double price_2, double tick_size) {
return std::fabs(price_2 - price_1) < 0.01 * tick_size;
}
inline bool gr_than_eq(double p1, double p2, double tick_size) {
@@ -434,4 +362,7 @@ inline bool is_spread_of_strips(const bts::api::Security &sec) {
return false;
}
} // namespace bts::securities
} // namespace securities
} // namespace bts
FMT_IMPL(bts::api::Security, s, "{}", bts::securities::descriptor(s));
+9 -5
View File
@@ -3,14 +3,18 @@
#define _TO_JSON_FIELD(field) j[#field] = t.field;
#define _FROM_JSON_FIELD(field) bts::io::JsonReader{&j, #field}.read(t.field);
#define SPARK_TO_JSON_IMPL(type, fields) \
void to_json(nlohmann::json &j, const type &t) { fields(_TO_JSON_FIELD) }
#define SPARK_TO_JSON_IMPL(type, for_each) \
inline void to_json(nlohmann::json &j, const type &t) { \
FOR_EACH(_TO_JSON_FIELD) \
}
#define SPARK_FROM_JSON_IMPL(type, fields) \
void from_json(const nlohmann::json &j, type &t) { fields(_FROM_JSON_FIELD) }
#define SPARK_FROM_JSON_IMPL(type, for_each) \
inline void from_json(const nlohmann::json &j, type &t) { \
FOR_EACH(_FROM_JSON_FIELD) \
}
#define OSTREAM_JSON_IMPL(type) \
std::ostream &operator<<(std::ostream &out, const type &t) { \
inline std::ostream &operator<<(std::ostream &out, const type &t) { \
nlohmann::json j; \
to_json(j, t); \
out << j.dump(2); \
+1 -39
View File
@@ -94,18 +94,7 @@ class JsonReader {
* @return True if the read succeeds, otherwise false
*/
template <class T>
bool read_or(const std::string &name, T &result, T default_value = {}) const;
template <class T>
bool read_optional_or(
std::optional<T> &result,
std::optional<T> default_value = std::nullopt) const;
template <class T>
bool read_optional_or(
const std::string &name,
std::optional<T> &result,
std::optional<T> default_value = std::nullopt) const;
bool read_or(const std::string &name, T &result, T default_value) const;
private:
const nlohmann::json *json_;
@@ -280,33 +269,6 @@ inline bool JsonReader::read_or(
return false;
}
template <class T>
inline bool JsonReader::read_optional_or(
std::optional<T> &result, std::optional<T> default_value) const {
if (read_optional(result)) {
return true;
}
result = std::move(default_value);
return false;
}
template <class T>
inline bool JsonReader::read_optional_or(
const std::string &name,
std::optional<T> &result,
std::optional<T> default_value) const {
if (json_ && json_->is_object()) {
auto it = json_->find(name);
if (it != json_->end()) {
if (JsonReader{&it.value()}.read_optional(result)) {
return true;
}
}
}
result = std::move(default_value);
return false;
}
template <class T>
bool JsonReader::read_flat(
const std::vector<nlohmann::json> &items, std::vector<T> &result) {
-4
View File
@@ -1,7 +1,5 @@
#pragma once
#include "lib/logging/logging.h"
#include <atomic>
#include <curl/curl.h>
#include <curl/easy.h>
@@ -14,7 +12,6 @@ struct CurlSession {
CurlSession() {
if (++ref_count == 1) {
LOG(Http, Info, "Initializing curl (global)");
curl_global_init(CURL_GLOBAL_ALL);
}
curl = curl_easy_init();
@@ -23,7 +20,6 @@ struct CurlSession {
~CurlSession() {
curl_easy_cleanup(curl);
if (--ref_count == 0) {
LOG(Http, Info, "Cleaning up curl (global)");
curl_global_cleanup();
}
}
+1 -4
View File
@@ -117,10 +117,7 @@ void HttpClient::send(const HttpRequest &req, IHttpResponse *response) {
// Prepare req
//to debug curl requests
// curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, curl_debug_callback);
// Disable signals to avoid strange stack corruption related to set/longjmp
// https://stackoverflow.com/questions/9191668/error-longjmp-causes-uninitialized-stack-frame
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 0L);
curl_easy_setopt(curl, CURLOPT_FAILONERROR, true);
curl_easy_setopt(curl, CURLOPT_URL, req.url.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curl_headers.data());
if (response != nullptr) {
-54
View File
@@ -1,54 +0,0 @@
#pragma once
#include <fmt/format.h>
#include <nlohmann/json.hpp>
#include <string>
namespace bts::io {
/**
* This wrapper struct provides a means to format arbitrary objects as json
* in templated strings (e.g., LOG macros).
*/
template <class T>
struct JsonFormatter {
T value;
std::string to_string() const { return fmt::format("{}", *this); }
};
template <class T>
JsonFormatter<T> json_formatter(T value) {
return JsonFormatter<T>{std::move(value)};
}
} // namespace bts::io
template <class T>
struct fmt::formatter<bts::io::JsonFormatter<T>> {
int indent = -1;
constexpr auto parse(fmt::format_parse_context &ctx) -> decltype(ctx.end()) {
const auto end = ctx.end();
auto it = ctx.begin();
if (it != end) {
indent = 0;
do {
int x = *it - '0';
if (x < 0 || x > 9) {
return it;
}
indent = indent * 10 + x;
} while (++it != end);
}
return it;
}
template <typename FormatContext>
auto format(const bts::io::JsonFormatter<T> &jf, FormatContext &ctx) const
-> decltype(ctx.out()) {
nlohmann::json j;
to_json(j, jf.value);
return fmt::format_to(ctx.out(), "{}", j.dump(indent));
}
};
+73 -123
View File
@@ -11,10 +11,7 @@
#include "lib/utils/bts_assert.h"
#include "lib/utils/formatters/exception_formatter.h"
#include <algorithm>
#include <atomic>
#include <boost/asio/io_context.hpp>
#include <chrono>
#include <exception>
#include <fmt/format.h>
#include <mutex>
@@ -22,7 +19,6 @@
#include <optional>
#include <stdexcept>
#include <string>
#include <system_error>
#include <thread>
#include <websocketpp/close.hpp>
#include <websocketpp/common/connection_hdl.hpp>
@@ -38,7 +34,6 @@ namespace bts::websocket {
struct WebsocketClientSnapshot {
int client_id;
WebsocketClientState state;
int n_reconnects;
int version;
};
@@ -47,10 +42,9 @@ struct WebsocketClientSnapshot {
FMT_IMPL(
bts::websocket::WebsocketClientSnapshot,
t,
"id: {}; state: {}; n_reconnects: {}; version: {}",
"id: {}; state: {}; version: {}",
t.client_id,
t.state,
t.n_reconnects,
t.version);
namespace bts::websocket {
@@ -68,8 +62,6 @@ using std::size_t;
using std::string;
using std::thread;
using std::uint32_t;
using std::chrono::nanoseconds;
using std::this_thread::sleep_for;
using websocketpp::config::asio_tls_client;
using WebsocketppClient =
@@ -94,16 +86,9 @@ using State = WebsocketClientState;
#define ERROR(...) LOG(Http, Error, __VA_ARGS__)
#define ERROR_FN(...) LOG(Http, Error, "WebsocketClient::" __VA_ARGS__)
static bts::core::TimeSpan reconnect_interval(std::uint32_t n_attempts) {
std::int64_t secs = 5 * n_attempts;
secs = std::clamp(secs, 5L, 300L);
return bts::core::TimeSpan::from_seconds(secs);
}
struct WebsocketClient::PImpl {
WebsocketClientConfig config;
WebsocketppClient client{};
boost::asio::io_context *io_context = nullptr;
WebsocketppClient::connection_ptr con;
optional<OpenHandler> open_handler;
optional<MessageHandler> message_handler;
@@ -113,45 +98,8 @@ struct WebsocketClient::PImpl {
optional<thread> io_thread;
int client_id{client_id_counter++};
mutex instance_mx;
State state = State::IDLE;
State state;
int version = 0;
int n_reconnects = 0;
PImpl() {
client.set_access_channels(websocketpp::log::alevel::all);
// client.clear_access_channels(websocketpp::log::alevel::frame_payload);
client.set_error_channels(websocketpp::log::elevel::all);
if (io_context == nullptr) {
client.init_asio();
} else {
client.init_asio(io_context);
}
client.set_message_handler(
[this](auto hdl, auto msg) { on_message(hdl, msg); });
client.set_open_handler([this](auto hdl) { on_open(hdl); });
client.set_ping_handler([](auto /*hdl*/, auto /*msg*/) {
LOG(Http, Info, "Received ping");
return true;
});
client.set_pong_timeout(0);
client.set_pong_handler([](auto /*hdl*/, auto /*msg*/) {
LOG(Http, Info, "Received pong");
return true;
});
client.set_tls_init_handler([&](auto hdl) { return on_tls_init(hdl); });
client.set_close_handler([=, this](auto hdl) {
on_close(hdl);
if (config.reconnect_on_close) {
sleep_for(nanoseconds{reconnect_interval(n_reconnects).nanos()});
connect();
}
});
client.set_fail_handler([this](auto hdl) {
const auto ec = client.get_con_from_hdl(hdl)->get_ec();
ERROR_FN("fail_handler. {}; err: {}", snapshot(), ec.message());
on_fail(hdl);
});
}
~PImpl() {
if (io_thread) {
@@ -172,7 +120,7 @@ struct WebsocketClient::PImpl {
}
constexpr WebsocketClientSnapshot snapshot() const {
return {client_id, state, n_reconnects, version};
return {client_id, state, version};
}
void close(websocketpp::close::status::value code, std::string reason) {
@@ -216,7 +164,8 @@ struct WebsocketClient::PImpl {
/**
* Connects to the websocket server
*/
void connect() {
void connect(
const map<string, string> &headers, bool run_in_background = true) {
lock_guard<mutex> lock{instance_mx};
INFO_FN("connect. {}", snapshot());
switch (state.value) {
@@ -228,6 +177,9 @@ struct WebsocketClient::PImpl {
}
set_state(State::OPENING);
try {
init_logging();
init_handlers();
// Get connection
websocketpp::lib::error_code ec;
con = client.get_connection(config.url, ec);
if (ec) {
@@ -235,11 +187,20 @@ struct WebsocketClient::PImpl {
return;
}
// Add headers
for (const auto &[k, v] : config.headers) {
for (const auto &[k, v] : headers) {
con->append_header(k, v);
}
// Connect
client.connect(con);
if (run_in_background) {
bts_assert_msgf(
!io_thread, "Websocket io_thread already instantiated. {}",
snapshot());
io_thread = std::thread{[this]() { run(); }};
if (config.connect_timeout > core::TimeSpan::zero()) {
wait_for_connect(config.connect_timeout);
}
}
} catch (const std::exception &ex) {
std::string str = ex.what();
ERROR_FN(
@@ -251,11 +212,22 @@ struct WebsocketClient::PImpl {
}
}
void reconnect() {
WARN_FN("reconnect. {}", snapshot());
sleep_for(nanoseconds{reconnect_interval(n_reconnects).nanos()});
++n_reconnects;
connect();
void wait_for_connect(const TimeSpan timeout) {
INFO_FN("wait_for_connect. {}; timeout: {}", snapshot(), timeout);
const auto start = TimeStamp::now();
while (state == State::OPENING) {
const auto elapsed = TimeStamp::now() - start;
const auto rem = timeout - elapsed;
if (rem < TimeSpan::zero()) {
throw std::runtime_error{fmt::format(
"Timed out waiting for connection after {} secs. {}",
elapsed.total_seconds(), snapshot())};
}
WARN_FN(
"wait_for_connect. Still waiting. {}; timeout: {}; rem: {}",
snapshot(), timeout, rem);
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
void run() {
@@ -286,33 +258,10 @@ struct WebsocketClient::PImpl {
INFO("Websocket 'run' thread end. {}", snapshot());
}
void run_one() {
try {
client.run_one();
} catch (const websocketpp::exception &ex) {
ERROR_FN(
"io_thread (run_one). Websocket io threw websocketpp::exception. "
"{}; err: {}",
snapshot(), ex);
} catch (const std::exception &ex) {
ERROR_FN(
"io_thread (run_one). Websocket io threw std::exception. {}; err: "
"{}",
snapshot(), ex);
} catch (...) {
bts_assert_msgf(
false,
"io_thread (run_one). Websocket io threw unknown exception. Could "
"this have been thrown by a callback delegate? {}",
snapshot());
}
}
private:
static std::atomic<int> client_id_counter;
std::shared_ptr<boost::asio::ssl::context> on_tls_init(
ConnectionHdl /*hdl*/) {
auto on_tls_init(ConnectionHdl /*hdl*/) {
INFO_FN("on_tls_init. state: {}; version: {}", state, version);
auto result =
websocketpp::lib::make_shared<SslContext>(SslContext::sslv23_client);
@@ -320,6 +269,38 @@ struct WebsocketClient::PImpl {
return result;
}
void init_handlers() {
INFO_FN("init_handlers. state: {}; version: {}", state, version);
bts_assert(!io_thread);
client.init_asio();
client.set_message_handler(
[this](auto hdl, auto msg) { on_message(hdl, msg); });
client.set_open_handler([this](auto hdl) { on_open(hdl); });
client.set_ping_handler([](auto /*hdl*/, auto /*msg*/) {
LOG(Http, Info, "Received ping");
return true;
});
client.set_pong_timeout(0);
client.set_pong_handler([](auto /*hdl*/, auto /*msg*/) {
LOG(Http, Info, "Received pong");
return true;
});
client.set_tls_init_handler(
[this](auto hdl) { return on_tls_init(hdl); });
client.set_close_handler([this](auto hdl) { on_close(hdl); });
client.set_fail_handler([this](auto hdl) {
const auto ec = client.get_con_from_hdl(hdl)->get_ec();
ERROR_FN("fail_handler. {}; err: {}", snapshot(), ec.message());
});
}
void init_logging() {
INFO_FN("init_logging. {}", snapshot());
client.set_access_channels(websocketpp::log::alevel::all);
// client.clear_access_channels(websocketpp::log::alevel::frame_payload);
client.set_error_channels(websocketpp::log::elevel::all);
}
void on_open(ConnectionHdl hdl) {
lock_guard<mutex> lock{instance_mx};
INFO_FN("on_open. {}", snapshot());
@@ -377,37 +358,11 @@ struct WebsocketClient::PImpl {
if (close_handler) {
(*close_handler)(ec);
}
if (config.reconnect_on_close) {
reconnect();
}
}
void on_fail(ConnectionHdl hdl) {
lock_guard<mutex> lock{instance_mx};
ERROR_FN("on_fail. {}", snapshot());
set_state(State::IDLE);
const auto con = client.get_con_from_hdl(hdl);
const auto ec = con->get_ec();
if (ec) {
ERROR_FN(
"on_fail. {}; local_code: {}; "
"local_reason: {}; remote_code: {}; remote_reason: {}; err: {}",
snapshot(), con->get_local_close_code(),
con->get_local_close_reason(), con->get_remote_close_code(),
con->get_remote_close_reason(), ec.message());
}
if (config.reconnect_on_fail) {
reconnect();
}
}
};
WebsocketClient::WebsocketClient(
WebsocketClientConfig config, boost::asio::io_context *io_context)
: me_(std::make_unique<PImpl>()) {
me_->config = std::move(config);
me_->io_context = io_context;
}
WebsocketClient::WebsocketClient(WebsocketClientConfig config)
: me_(std::make_unique<PImpl>(std::move(config))) {}
WebsocketClient::~WebsocketClient() {
me_->close(websocketpp::close::status::going_away, "");
@@ -436,16 +391,13 @@ WebsocketClient::CloseHandler &WebsocketClient::close_handler() {
return *me_->close_handler;
}
void WebsocketClient::connect() { me_->connect(); }
void WebsocketClient::connect(
const std::map<std::string, std::string> &headers, bool run_in_background) {
me_->connect(headers, run_in_background);
}
void WebsocketClient::run() { me_->run(); }
void WebsocketClient::run_one() { me_->run_one(); }
boost::asio::io_context *WebsocketClient::io_context() {
return me_->io_context;
}
websocketpp::lib::error_code WebsocketClient::send_text(const string &text) {
return me_->send_text(text);
}
@@ -463,6 +415,4 @@ bool WebsocketClient::is_connected() const {
void WebsocketClient::ping(const string &payload) { me_->con->ping(payload); }
const string &WebsocketClient::url() const { return me_->config.url; }
} // namespace bts::websocket
+4 -10
View File
@@ -3,7 +3,6 @@
#include "lib/io/websocket/websocket_client_config.h"
#include "lib/utils/Delegate.h"
#include <boost/asio/io_context.hpp>
#include <memory>
#include <string>
#include <websocketpp/config/core_client.hpp>
@@ -13,33 +12,28 @@ namespace bts::websocket {
class WebsocketClient {
public:
WebsocketClient(
WebsocketClientConfig config,
boost::asio::io_context *io_context = nullptr);
explicit WebsocketClient(WebsocketClientConfig config);
~WebsocketClient();
using OpenHandler = bts::core::Delegate<void(const std::error_code &)>;
using MessageHandler = bts::core::Delegate<void(const std::string &)>;
using CloseHandler = bts::core::Delegate<void(const std::error_code &)>;
void connect();
void connect(
const std::map<std::string, std::string> &headers = {},
bool run_in_background = true);
void disconnect();
bool is_connected() const;
boost::asio::io_context *io_context();
void run();
void run_one();
CloseHandler &open_handler();
MessageHandler &message_handler();
CloseHandler &close_handler();
websocketpp::lib::error_code send_text(const std::string &text);
void ping(const std::string &payload = "");
const std::string &url() const;
private:
struct PImpl;
std::unique_ptr<PImpl> me_;
};
} // namespace bts::websocket
@@ -5,12 +5,9 @@
namespace bts::websocket {
struct WebsocketClientConfig {
std::string url{};
core::TimeSpan connect_timeout = core::TimeSpan::zero();
core::TimeSpan pong_timeout = core::TimeSpan::zero();
bool reconnect_on_close = false;
bool reconnect_on_fail = false;
std::map<std::string, std::string> headers{};
std::string url;
core::TimeSpan connect_timeout;
core::TimeSpan pong_timeout;
};
} // namespace bts::websocket
+2 -1
View File
@@ -9,6 +9,7 @@
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/path.hpp>
#include <cstdint>
#include <fmt/ostream.h>
#include <fstream>
#include <iostream>
@@ -94,7 +95,7 @@ std::ifstream::pos_type filesize(std::string const &filename) {
Sink file_sink(LoggingConfig const &cfg) {
static std::ofstream stream;
static uint64_t bytes_written = 0;
const auto max_sz = 120 * 1024 * 1024;
const uintmax_t max_sz = 1024 * 1024 * 1024 ;
auto f = formatter(cfg);
auto path = log_path();
+5 -2
View File
@@ -36,6 +36,7 @@
#include "lib/utils/bts_assert.h"
#include <bits/stdint-uintn.h>
#include <cstddef>
#include <google/protobuf/timestamp.pb.h>
#include <google/protobuf/util/time_util.h>
#include <memory>
@@ -732,11 +733,13 @@ static bool get_qty_info(
}
return true;
}
bool is_number(const std::string &s) {
return !s.empty() && std::all_of(s.begin(), s.end(), ::isdigit);
}
void GeneralMarketDataListener::_copy_security_common(
const bts::domain::Security &from, uint64_t key, FixedSecurity &to) const {
to.key = key;
to.numeric_id = key;
to.numeric_id =is_number(from.security_common.exchange_sec_id)?std::stoul(from.security_common.exchange_sec_id):key;
auto symbol = from.security_common.symbol;
to.symbol(symbol);
to.security_desc(from.security_common.desc);
+1 -2
View File
@@ -435,8 +435,7 @@ class SyntheticConsumerInst {
void add_security(const api::Security &sec) {
Mutex::scoped_lock lock(_workmutex);
if (_data.find(sec.id()) == _data.end()) {
LOG(md, Info, "MDConsumer::add_security. {}",
bts::securities::descriptor(sec));
LOG(md, Info, "MDConsumer::add_security. {}", sec);
auto ptr = to_strategy(make_shared<api::Security>(sec));
vector<IncompleteStrategy> vec;
vec.push_back(ptr);
+6 -28
View File
@@ -14,15 +14,6 @@
namespace bts {
namespace md {
#define ASSERT_FN(test, fn, ...) \
bts_assert_msg(test, "SecurityCache::" fn ". " __VA_ARGS__)
#define LOG_FN(level, fn, ...) \
LOG(md, level, "SecurityCache::" fn ". " __VA_ARGS__)
#define DIAG_FN(fn, ...) LOG_FN(Diag, fn, __VA_ARGS__)
#define INFO_FN(fn, ...) LOG_FN(Info, fn, __VA_ARGS__)
#define WARN_FN(fn, ...) LOG_FN(Warn, fn, __VA_ARGS__)
#define ERROR_FN(fn, ...) LOG_FN(Error, fn, __VA_ARGS__)
using std::string;
using std::unordered_map;
@@ -108,9 +99,6 @@ struct SecurityCacheIndex {
// Insert but don't overwrite existing
auto result =
cache.insert(std::make_pair(SecurityKey(security->id()), security));
venue_type_ids.get_or_add(security->exchange())
.get_or_add(bts::securities::security_type(*security))
.try_emplace(bts::securities::descriptor(*security), security->id());
return result.second;
}
@@ -178,8 +166,8 @@ struct SecurityCacheImpl {
};
void SecurityCacheImpl::enqueue_update_db(const SecurityPtr &security) {
DIAG_FN(
"enqueue_update_db", "security: {}", securities::descriptor(*security));
LOG(md, Diag, "SecurityCacheImpl::enqueue_update_db security: {}",
bts::securities::descriptor(*security));
update_queue.push(security);
}
@@ -192,13 +180,7 @@ void SecurityCacheImpl::flush() {
work.push_back(temp);
}
if (!work.empty()) {
INFO_FN("flush", "Saving securities to db. count: {}", work.size());
if (db::insert_or_replace(work) == Result::error) {
WARN_FN(
"flush",
"Failed to save securities to db. Will retry on next flush. "
"count: {}",
work.size());
// If we fail to insert then retry later
for (auto &s : work)
update_queue.push(std::move(s));
@@ -288,18 +270,14 @@ std::vector<api::Security const *> SecurityCache::get_securities() {
}
bool SecurityCache::add_live_security(const SecurityPtr &security) {
ASSERT_FN(security, "add_live_security", "security is null");
const bool is_new_sec = _me->cache.lock([&](SecurityCacheIndex &cache) {
bts_assert(security);
LOG(md, Diag, "SecurityCache::add_live_security security: {}",
bts::securities::descriptor(*security));
return _me->cache.lock([&](SecurityCacheIndex &cache) {
const bool is_new_sec = cache.add_live_security(security);
_me->enqueue_update_db(security);
return is_new_sec;
});
if (is_new_sec) {
INFO_FN(
"add_live_security", "new security: {}",
bts::securities::descriptor(*security));
}
return is_new_sec;
}
void SecurityCache::flush_to_disk() { _me->flush(); }
+1
View File
@@ -7,6 +7,7 @@
#include "lib/utils/checked_arithmetic.h"
#include <boost/filesystem.hpp>
#include <boost/pool/object_pool.hpp>
#include <memory>
#include <sqlite/sqlite3.h>
#include <stdexcept>
+1 -59
View File
@@ -8,10 +8,7 @@ namespace mercury {
BrokerProxy::BrokerProxy(ILocalBroker &real_broker)
: _real_broker(real_broker), _id(real_broker.id()), _cycles(0),
_outgoing(config::get<config::Mercury>().queue_size()),
_incoming(config::get<config::Mercury>().queue_size()), _action_pool() {
_action_pool.set_max_count(4096 * 10);
_action_pool.set_log_freq(100);
}
_incoming(config::get<config::Mercury>().queue_size()), _action_pool() {}
BrokerProxy::~BrokerProxy() {}
@@ -99,60 +96,5 @@ void BrokerProxy::schedule_action(std::function<void()> const &action) {
send(*m);
}
void BrokerProxy::do_work(trace::PerfLog &log) {
_cycles++;
_outgoing.consume_all([this, &log](BrokerMsg *m) {
switch (m->msg_type) {
case BrokerMsgType::ADD_NEIGHBOUR:
_real_broker.add_neighbour(*m->neighbour);
break;
case BrokerMsgType::REMOVE_NEIGHBOUR:
_real_broker.remove_neighbour(m->broker_id);
break;
case BrokerMsgType::ADVERTISE:
_real_broker.advertise(m->broker_id, m->stream_id, m->topic);
break;
case BrokerMsgType::UNADVERTISE:
_real_broker.unadvertise(m->broker_id, m->stream_id);
break;
case BrokerMsgType::SUBSCRIBE:
_real_broker.subscribe(m->broker_id, m->sub_id, m->stream_id);
break;
case BrokerMsgType::UNSUBSCRIBE:
_real_broker.unsubscribe(m->broker_id, m->sub_id, m->stream_id);
break;
case BrokerMsgType::NOTIFY: {
LOG(Latency, Diag, "Notify broker {}. Cycles: {}", _id.value(),
_cycles);
auto scoped_do_work = trace::log_scope(log, {}, "BrokerProxy::notify");
_real_broker.notify(
m->broker_id, m->stream_id, m->sub_id, m->notification);
} break;
case BrokerMsgType::LOCAL_SUBSCRIBE:
_real_broker.local_subscribe(
m->local_sub_id, m->filter, m->local_handler);
break;
case BrokerMsgType::LOCAL_UNSUBSCRIBE:
_real_broker.local_unsubscribe(m->local_sub_id);
break;
case BrokerMsgType::EXECUTE_ACTION: {
auto start = Timer::now();
LOG(Latency, Diag, "Executing lambda on broker {}", _id.value());
m->action();
auto stop = Timer::now();
LOG(Latency, Diag,
"Finished executing lambda on broker {}. Took: {}us", _id.value(),
us_diff(stop, start));
break;
}
default:
bts_assert(false);
break;
}
_incoming.drain();
_incoming.push(m);
});
}
} // namespace mercury
} // namespace bts
+57 -3
View File
@@ -5,8 +5,8 @@
#include "lib/mercury/topics/Topic.h"
#include "lib/tracing/PerfLog.h"
#include "lib/utils/GrowableSPSCQueue.h"
#include "lib/utils/object_pool.h"
#include <boost/pool/object_pool.hpp>
#include <memory>
namespace bts {
@@ -80,7 +80,61 @@ class BrokerProxy : public ILocalBroker {
send(m);
}
void do_work(trace::PerfLog &log);
void do_work(trace::PerfLog &log) {
_cycles++;
_outgoing.consume_all([this, &log](BrokerMsg *m) {
switch (m->msg_type) {
case BrokerMsgType::ADD_NEIGHBOUR:
_real_broker.add_neighbour(*m->neighbour);
break;
case BrokerMsgType::REMOVE_NEIGHBOUR:
_real_broker.remove_neighbour(m->broker_id);
break;
case BrokerMsgType::ADVERTISE:
_real_broker.advertise(m->broker_id, m->stream_id, m->topic);
break;
case BrokerMsgType::UNADVERTISE:
_real_broker.unadvertise(m->broker_id, m->stream_id);
break;
case BrokerMsgType::SUBSCRIBE:
_real_broker.subscribe(m->broker_id, m->sub_id, m->stream_id);
break;
case BrokerMsgType::UNSUBSCRIBE:
_real_broker.unsubscribe(m->broker_id, m->sub_id, m->stream_id);
break;
case BrokerMsgType::NOTIFY: {
LOG(Latency, Diag, "Notify broker {}. Cycles: {}", _id.value(),
_cycles);
auto scoped_do_work =
trace::log_scope(log, {}, "BrokerProxy::notify");
_real_broker.notify(
m->broker_id, m->stream_id, m->sub_id, m->notification);
} break;
case BrokerMsgType::LOCAL_SUBSCRIBE:
_real_broker.local_subscribe(
m->local_sub_id, m->filter, m->local_handler);
break;
case BrokerMsgType::LOCAL_UNSUBSCRIBE:
_real_broker.local_unsubscribe(m->local_sub_id);
break;
case BrokerMsgType::EXECUTE_ACTION: {
auto start = Timer::now();
LOG(Latency, Diag, "Executing lambda on broker {}", _id.value());
m->action();
auto stop = Timer::now();
LOG(Latency, Diag,
"Finished executing lambda on broker {}. Took: {}us",
_id.value(), us_diff(stop, start));
break;
}
default:
bts_assert(false);
break;
}
_incoming.drain();
_incoming.push(m);
});
}
private:
ILocalBroker &_real_broker;
@@ -88,7 +142,7 @@ class BrokerProxy : public ILocalBroker {
uint64_t _cycles;
utils::GrowableSPSCQueue<BrokerMsg *> _outgoing;
utils::GrowableSPSCQueue<BrokerMsg *> _incoming;
bts::utils::object_pool<BrokerMsg> _action_pool;
boost::object_pool<BrokerMsg> _action_pool;
void send(BrokerMsg &m) {
_outgoing.push(&m);
-43
View File
@@ -1,6 +1,5 @@
#pragma once
#include "lib/api/services/OrderService.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/domain_model/Security.h"
#include "lib/domain_model/common.h"
@@ -25,25 +24,6 @@ struct CreateOrderMsg {
api::orderman::RoutingRecord const *routing_record;
};
/**
* A CreateOrderMsg wrapper with a custom fmt::formatter that doesn't access the
* raw security pointer. This is mainly used for logging purposes (because the
* security pointer may be invalid by the time the fmt::formatter is invoked).
*/
struct CreateOrderMsg_JsonData {
CreateOrderMsg_JsonData() = default;
CreateOrderMsg_JsonData(CreateOrderMsg source) : source(std::move(source)) {
if (this->source.security != nullptr) {
sec_name = securities::descriptor(*this->source.security);
}
}
CreateOrderMsg source;
std::string sec_name;
};
void to_json(nlohmann::json &j, const CreateOrderMsg &msg);
inline std::ostream &operator<<(std::ostream &out, CreateOrderMsg const &msg) {
out << "key: " << msg.key.value() << " price: " << msg.price.value()
<< " qty: " << msg.quantity.full_value() << " security: "
@@ -63,29 +43,6 @@ inline std::ostream &operator<<(std::ostream &out, CreateOrderMsg const &msg) {
return out;
}
inline void to_json(
nlohmann::json &j, const CreateOrderMsg_JsonData &msg_data) {
const auto &msg = msg_data.source;
j["key"] = msg.key.value();
j["price"] = msg.price.value();
j["quantity"] = msg.quantity.value();
if (msg.display_quantity.value() != 0) {
j["display_quantity"] = msg.quantity.value();
}
if (!msg_data.sec_name.empty()) {
j["security"] = msg_data.sec_name;
}
j["side"] = msg.side;
if (msg.stop_px.value() != 0) {
j["stop_px"] = msg.stop_px.value();
}
j["time_in_force"] = msg.time_in_force;
j["manual_order_indicator"] = msg.manual_order_indicator;
if (msg.routing_record != nullptr) {
j["routing_record"] = *msg.routing_record;
}
}
} // namespace bts::orderman
FMT_IMPL_OSTREAM(bts::orderman::CreateOrderMsg);
+3 -45
View File
@@ -8,7 +8,6 @@
#include "lib/config/config.h"
#include "lib/config/protos/jetfix.pb.h"
#include "lib/config/protos/orderman_cfg.pb.h"
#include "lib/core/FMT.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/domain_model/common.h"
#include "lib/logging/logging.h"
@@ -30,11 +29,11 @@
#include "lib/orderman/actors/order_manager/order_gateway_definition.h"
#include "lib/orderman/events/ExchangePositionEvent.h"
#include "lib/orderman/events/OrderOpResultEvent.h"
#include "lib/orderman/gateways/execution_gateway_provider.h"
#include "lib/orderman/gateways/i_execution_gateway_provider.h"
#include "lib/orderman/order_factory/DynamicOrderRegistry.h"
#include "lib/orderman/orderman_utils/ExchangeOpenOrders.h"
#include "lib/orderman/orders/Order.h"
#include "lib/os/os.h"
#include "lib/os/spark_data_directory.h"
#include "lib/plugin/SparkServices.h"
#include "lib/plugin/plugin.h"
@@ -42,6 +41,7 @@
#include "lib/utils/bts_chrono.h"
#include "lib/utils/thread_manager.h"
#include "lib/utils/type_name.h"
#include "lib_binance_execution/binance_execution/execution_gateway_provider.h"
#include <bits/stdint-uintn.h>
#include <boost/algorithm/string/predicate.hpp>
@@ -73,58 +73,16 @@ struct ActorThread {
void start(trace::PerfLog &log) {
if (cfg.poll())
thread = threading::make_thread(cfg.thread_name(), [this, &log]() {
const core::TimeSpan log_interval =
core::TimeSpan::from_seconds(30);
core::TimeStamp last_log_time{0};
core::TimeStamp now;
core::TimeSpan elapsed;
std::uint32_t last_count = 0;
for (;;) {
now = core::TimeStamp::now();
elapsed = now - last_log_time;
if (elapsed > log_interval) {
LOG(OrderMan, Info,
"OrderServiceServer ActorThread work speed is {:.2f} "
"cycles/sec",
static_cast<float>(last_count) / elapsed.total_seconds());
last_log_time = now;
last_count = 0;
}
for (;;)
executor.do_work(log);
last_count++;
}
});
else {
thread = threading::make_thread(cfg.thread_name(), [this, &log]() {
// for logging purposes
const std::string t_name = bts::os::get_thread_name();
const std::string actors_desc = FMT_JOIN(cfg.hosted_actor(), ", ");
const core::TimeSpan log_interval =
core::TimeSpan::from_seconds(30);
const auto inner_iter = 1000;
bts::core::TimeStamp last_log_time{0};
core::TimeStamp now;
core::TimeSpan elapsed;
std::uint32_t last_count = 0;
while (!done) {
now = core::TimeStamp::now();
elapsed = now - last_log_time;
auto log_elapsed = now - last_log_time;
if (log_elapsed > log_interval) {
LOG(OrderMan, Info,
"OrderServiceServer ActorThread {} work speed is {:.2f} "
"cycles/sec ({})",
t_name,
static_cast<float>(last_count) / elapsed.total_seconds(),
actors_desc);
last_log_time = now;
last_count = 0;
}
auto start = Timer::now();
for (auto i = 0; i < inner_iter; i++)
executor.do_work(log);
last_count += inner_iter;
auto stop = Timer::now();
loop_metric->observe(ns_diff(stop, start) / inner_iter);
std::this_thread::sleep_for(10ms);
+3 -18
View File
@@ -24,12 +24,6 @@
namespace bts {
namespace orderman {
#define LOG_FN(level, fn, ...) \
LOG(OrderMan, level, "FillStore::" fn ". " __VA_ARGS__)
#define INFO_FN(fn, ...) LOG_FN(Info, fn, __VA_ARGS__)
#define WARN_FN(fn, ...) LOG_FN(Warn, fn, __VA_ARGS__)
#define ERROR_FN(fn, ...) LOG_FN(Error, fn, __VA_ARGS__)
auto const fills_ns = "fills";
struct FillStore::UnresolvedFills {
@@ -166,7 +160,7 @@ void FillStore::on_execution_report(
ev.data->to_string());
auto &er = *ev.data;
auto fk = fmt::format(
"{}:{}", er.order_key.value(), to_strn(er.exchange_fill_id));
"{}:{}", er.order_key.value(), to_str(er.exchange_fill_id));
auto is_known = me->known_fills.count(fk);
if (er.exec_type == api::orderman::EXEC_TYPE_PARTIAL_FILL ||
er.exec_type == api::orderman::EXEC_TYPE_FILL) {
@@ -182,7 +176,7 @@ void FillStore::on_execution_report(
fill.set_fill_kind(api::orderman::Fill::FILL_KIND_STANDARD);
fill.set_security_key(er.security->id());
}
fill.set_account(to_strn(er.account_name));
fill.set_account(to_str(er.account_name));
fill.set_last_price(er.last_price.value());
fill.set_avg_price(er.avg_price.value());
fill.set_side(er.side);
@@ -213,15 +207,7 @@ void FillStore::on_execution_report(
auto it = me->known_orders.find(er.order_key);
if (it != me->known_orders.end()) {
auto &order_def = it->second;
if (fill.account() != order_def.account()) {
WARN_FN(
"on_execution_report",
"ExecutionReport account '{}' does not match "
"OrderDefinition account '{}' (order_key: {}). "
"Setting fill account to the latter",
fill.account(), order_def.account(), er.order_key);
fill.set_account(order_def.account());
}
fill.set_account(order_def.account());
fill.set_internal_account_id(order_def.account_id());
} else
me->fills_with_unresolved_accounts[er.order_key]
@@ -229,7 +215,6 @@ void FillStore::on_execution_report(
auto fv = utils::pb::to_string(fill);
me->store->insert(fills_ns, fk, fv);
LOG(OrderMan, Info, "Publishing fill. {}", fv);
publish(me->fill_stream, fill, *er.security);
} else
LOG(OrderMan, Info,
+44 -66
View File
@@ -37,12 +37,6 @@
namespace bts {
namespace orderman {
#define _LOG_FN(level, fn, ...) \
LOG(OrderMan, level, "ServiceBridge::" fn ". " __VA_ARGS__)
#define INFO_FN(fn, ...) _LOG_FN(Info, fn, __VA_ARGS__)
#define WARN_FN(fn, ...) _LOG_FN(Warn, fn, __VA_ARGS__)
#define ERROR_FN(fn, ...) _LOG_FN(Error, fn, __VA_ARGS__)
struct ServiceBridge::PImpl {
PImpl(
OnReady const &on_ready,
@@ -179,72 +173,56 @@ void ServiceBridge::init() {
} // namespace orderman
void ServiceBridge::on(mercury::Update<StoredFillEvent> const &update) {
if (!update.events) {
WARN_FN("on StoredFillEvent", "no event data");
return;
if (update.events) {
std::vector<api::orderman::Fill> fills;
fills.reserve(update.events->size());
for (auto ev : *(update.events))
fills.push_back(ev.fill);
me->on_fills(fills);
}
INFO_FN("on StoredFillEvent", "n_events: {}", update.events->size());
std::vector<api::orderman::Fill> fills;
fills.reserve(update.events->size());
for (auto ev : *(update.events))
fills.push_back(ev.fill);
me->on_fills(fills);
}
void ServiceBridge::on(mercury::Update<StoredOrderEvent> const &update) {
if (!update.events) {
WARN_FN("on StoredOrderEvent", "no event data");
return;
}
INFO_FN("on StoredOrderEvent", "n_events: {}", update.events->size());
bool was_ready = is_ready();
me->have_orders = true;
if (update.events) {
bool was_ready = is_ready();
me->have_orders = true;
std::vector<api::orderman::OrderRecord> orders;
orders.reserve(update.events->size());
for (auto ev : *(update.events)) {
me->known_orders.insert(OrderKey(ev.order_record.key()));
orders.push_back(ev.order_record);
std::vector<api::orderman::OrderRecord> orders;
orders.reserve(update.events->size());
for (auto ev : *(update.events)) {
me->known_orders.insert(OrderKey(ev.order_record.key()));
orders.push_back(ev.order_record);
}
me->on_orders(orders);
maybe_signal_ready(was_ready);
}
me->on_orders(orders);
maybe_signal_ready(was_ready);
}
void ServiceBridge::on(mercury::Update<OrderManagerReadyEvent> const &update) {
if (!update.events) {
WARN_FN("on OrderManagerReadyEvent", "no event data");
return;
if (update.events) {
bool was_ready = is_ready();
me->order_manager_ready = true;
maybe_signal_ready(was_ready);
}
INFO_FN("on OrderManagerReadyEvent", "n_events: {}", update.events->size());
bool was_ready = is_ready();
me->order_manager_ready = true;
maybe_signal_ready(was_ready);
}
void ServiceBridge::on(mercury::Update<NewAdminDataEvent> const &update) {
if (!update.events) {
WARN_FN("on NewAdminDataEvent", "no event data");
return;
}
INFO_FN("on NewAdminDataEvent", "n_events: {}", update.events->size());
for (auto ev : *(update.events)) {
me->order_router.update(*ev.admin_data);
}
if (update.events)
for (auto ev : *(update.events))
me->order_router.update(*ev.admin_data);
}
void ServiceBridge::on(mercury::Update<OrderOpResultEvent> const &update) {
if (!update.events) {
WARN_FN("on OrderOpResultEvent", "no event data");
return;
}
INFO_FN("on OrderOpResultEvent", "n_events: {}", update.events->size());
for (auto ev : *(update.events)) {
me->on_order_op_result(ev);
}
if (update.events)
for (auto ev : *(update.events))
me->on_order_op_result(ev);
}
void ServiceBridge::create_order(uint32_t rid, api::orderman::Create const &c) {
INFO_FN("create_order", "rid: {}; req: {}", rid, utils::pb::to_string(c));
LOG(OrderMan, Info, "Received new order from client: {}",
utils::pb::to_string(c));
me->service_call_marshaller.schedule_action([this, c, rid]() {
if (is_ready()) {
auto &order =
@@ -261,7 +239,9 @@ void ServiceBridge::create_order(uint32_t rid, api::orderman::Create const &c) {
}
void ServiceBridge::update_order(uint32_t rid, api::orderman::Update const &c) {
INFO_FN("update_order", "rid: {}; req: {}", rid, utils::pb::to_string(c));
LOG(OrderMan, Info, "ServiceBridge::update_order update: {}",
utils::pb::to_string(c));
me->service_call_marshaller.schedule_action([this, c, rid]() {
auto ok = OrderKey(c.order_key());
if (is_ready()) {
@@ -279,7 +259,9 @@ void ServiceBridge::update_order(uint32_t rid, api::orderman::Update const &c) {
}
void ServiceBridge::cancel_order(uint32_t rid, api::orderman::Cancel const &c) {
INFO_FN("cancel_order", "rid: {}; req: {}", rid, utils::pb::to_string(c));
LOG(OrderMan, Info, "Received order cancel from client: {}",
utils::pb::to_string(c));
me->service_call_marshaller.schedule_action([this, c, rid]() {
auto ok = OrderKey(c.order_key());
if (is_ready()) {
@@ -297,7 +279,9 @@ void ServiceBridge::cancel_order(uint32_t rid, api::orderman::Cancel const &c) {
}
void ServiceBridge::detach_order(uint32_t rid, api::orderman::Detach const &c) {
INFO_FN("detach_order", "rid: {}; req: {}", rid, utils::pb::to_string(c));
LOG(OrderMan, Info, "Received order detach from client: {}",
utils::pb::to_string(c));
me->service_call_marshaller.schedule_action([this, c, rid]() {
auto ok = OrderKey(c.order_key());
if (is_ready()) {
@@ -316,13 +300,15 @@ void ServiceBridge::detach_order(uint32_t rid, api::orderman::Detach const &c) {
void ServiceBridge::manually_bust_all_fills(
const api::orderman::ManualBustParameters &c) {
INFO_FN("manually_bust_all_fills", "req: {}", utils::pb::to_string(c));
LOG(OrderMan, Info, "ServiceBridge::manually_bust_all_fills: {}",
utils::pb::to_string(c));
me->service_call_marshaller.schedule_action(
[this, c]() { publish(me->manually_bust_fills_pub, c.account()); });
}
void ServiceBridge::send_rfq(api::orderman::RFQRequest const &rfq) {
INFO_FN("send_rfq", "req: {}", utils::pb::to_string(rfq));
LOG(OrderMan, Info, "ServiceBridge::send_rfq rfq: {}",
utils::pb::to_string(rfq));
me->service_call_marshaller.schedule_action(
[this, rfq]() { publish(me->rfq_pub, rfq); });
}
@@ -412,11 +398,6 @@ std::optional<FilledPositionEvent> make_pos_event(
void ServiceBridge::update_position(
api::position::Position_dataUpdate const &pos) {
INFO_FN(
"update_position",
"n_added: {}; n_changed: {}; n_removed: {}; n_total: {}",
pos.added().size(), pos.changed().size(), pos.removed().size(),
pos.all().size());
std::vector<FilledPositionEvent> evs;
for (auto const &pu : pos.added()) {
auto ev = make_pos_event(*pu, me->sec_db);
@@ -449,9 +430,6 @@ bool ServiceBridge::is_ready() const {
}
void ServiceBridge::maybe_signal_ready(bool old_ready) {
INFO_FN(
"maybe_signal_ready", "old_ready: {}; is_ready: {}", old_ready,
is_ready());
if (!old_ready && is_ready())
me->on_ready();
}
@@ -40,13 +40,6 @@
namespace bts {
namespace orderman {
#define _LOG_FN(level, fn, ...) \
LOG(OrderMan, level, "OrderManager::" fn ". " __VA_ARGS__)
#define DIAG_FN(fn, ...) _LOG_FN(Diag, fn, __VA_ARGS__)
#define INFO_FN(fn, ...) _LOG_FN(Info, fn, __VA_ARGS__)
#define WARN_FN(fn, ...) _LOG_FN(Warn, fn, __VA_ARGS__)
#define ERROR_FN(fn, ...) _LOG_FN(Error, fn, __VA_ARGS__)
using namespace mercury;
enum class OrderManager::State {
@@ -87,23 +80,6 @@ struct OrderManager::PImpl {
mercury::Stream<OrderManagerReadyEvent> pub_ready;
std::thread background_thread;
std::atomic<bool> shutdown = false;
constexpr std::string state_name() {
switch (state) {
case State::WAIT_ADMIN:
return "WAIT_ADMIN";
case State::WAIT_SEC:
return "WAIT_SEC";
case State::WAIT_FILLED_POSITION:
return "WAIT_FILLED_POSITION";
case State::WAIT_ORDERS:
return "WAIT_ORDERS";
case State::READY:
return "READY";
default:
return "UNKNOWN";
}
}
};
OrderManager::OrderManager(
@@ -132,7 +108,6 @@ OrderManager::~OrderManager() {
}
void OrderManager::init() {
INFO_FN("init", "state: {}", me->state_name());
subscribe<NewAdminDataEvent>(
[this](auto const &admin_update) { this->on_admin_data(admin_update); });
subscribe<AccountPnLUpdateEvent>(
@@ -144,365 +119,301 @@ void OrderManager::init() {
}
void OrderManager::on_admin_data(const mercury::Update<NewAdminDataEvent> &m) {
if (!m.events) {
WARN_FN("on_admin_data", "no event data. state: {}", me->state_name());
return;
}
INFO_FN("on_admin_data", "state: {}", me->state_name());
if (me->state == State::WAIT_ADMIN) {
for (auto &osd : me->services_data.sessions)
osd->gw->on_admin_data(m);
if (m.events) {
if (me->state == State::WAIT_ADMIN) {
LOG(OrderMan, Info,
"Order manager received admin data. Subscribing to security list "
"now ");
for (auto &osd : me->services_data.sessions)
osd->gw->on_admin_data(m);
me->state = State::WAIT_SEC;
INFO_FN("on_admin_data", "state: {}", me->state_name());
subscribe<NewSecuritiesEvent>(
[this](auto const &sec_update) { this->on_securities(sec_update); });
}
me->state = State::WAIT_SEC;
subscribe<NewSecuritiesEvent>([this](auto const &sec_update) {
this->on_securities(sec_update);
});
}
for (auto ev : *m.events) {
me->services_data.order_router.update(*ev.admin_data);
me->services_data.limiter.update(*ev.admin_data);
for (auto ev : *m.events) {
me->services_data.order_router.update(*ev.admin_data);
me->services_data.limiter.update(*ev.admin_data);
}
}
}
void OrderManager::on_pnl_update(
const mercury::Update<AccountPnLUpdateEvent> &u) {
if (!u.events) {
WARN_FN("on_pnl_update", "no event data. state: {}", me->state_name());
return;
}
// This log is too noisy. It fires regularly on an interval (sometimes when
// the pnl hasn't actually changed).
// INFO_FN(
// "on_pnl_update", "state: {}; n_events: {}", me->state_name(),
// u.events->size());
for (auto ev : *u.events) {
me->services_data.limiter.set_day_pnl(ev.account_id, ev.day_pnl);
}
if (u.events)
for (auto ev : *u.events)
me->services_data.limiter.set_day_pnl(ev.account_id, ev.day_pnl);
}
void OrderManager::on_excess_liquidity_update(
const mercury::Update<ExcessLiquidityUpdateEvent> &u) {
if (!u.events) {
WARN_FN(
"on_excess_liquidity_update", "no event data. state: {}",
me->state_name());
return;
}
INFO_FN(
"on_excess_liquidity_update", "state: {}; n_events: {}",
me->state_name(), u.events->size());
for (auto ev : *u.events) {
me->services_data.limiter.set_excess_liquidity(
ev.account_id, ev.excess_liquidity);
}
if (u.events)
for (auto ev : *u.events)
me->services_data.limiter.set_excess_liquidity(
ev.account_id, ev.excess_liquidity);
}
void OrderManager::on_rfq_send(const mercury::Update<RFQEvent> &rfq) {
if (!rfq.events) {
WARN_FN("on_rfq_send", "no event data. state: {}", me->state_name());
return;
}
INFO_FN(
"on_rfq_send", "state: {}; n_events: {}", me->state_name(),
rfq.events->size());
std::vector<OrderMsg> msgs = {};
for (auto ev : *rfq.events) {
RFQMsg rfq_msg = RFQMsg(ev);
msgs.emplace_back(rfq_msg);
}
trace::CorID cid = {};
for (auto &osd : me->services_data.sessions) {
osd->gw->send_msgs(msgs, cid);
if (rfq.events) {
std::vector<OrderMsg> msgs = {};
for (auto ev : *rfq.events) {
RFQMsg rfq_msg = RFQMsg(ev);
msgs.emplace_back(rfq_msg);
}
trace::CorID cid = {};
LOG(OrderMan, Info, "OrderManager::on_rfq_send n_events: {}",
rfq.events->size());
for (auto &osd : me->services_data.sessions)
osd->gw->send_msgs(msgs, cid);
}
}
void OrderManager::on_securities(
const mercury::Update<NewSecuritiesEvent> &update) {
if (!update.events) {
WARN_FN("on_securities", "no event data. state: {}", me->state_name());
return;
}
INFO_FN(
"on_securities", "state: {}; n_events: {}", me->state_name(),
update.events->size());
// forward to execution gateways
for (auto &osd : me->services_data.sessions)
osd->gw->on_securities(update);
// update our security db and accumulate for the sake of the limiter
std::vector<api::Security const *> secs;
for (auto const &ev : *(update.events)) {
auto sec_ptr = ev.security;
secs.push_back(sec_ptr);
}
me->services_data.limiter.add_securities(secs);
if (update.events) {
if (me->state == State::WAIT_SEC) {
me->state = State::WAIT_FILLED_POSITION;
INFO_FN(
"on_securities", "received initial set of {} securities. state: {}",
update.events->size(), me->state_name());
me->filled_position_sub = subscribe<FilledPositionEvent>(
[this](auto const &pos) { this->on_filled_position(pos); });
// update our security db and accumulate for the sake of the limiter
std::vector<api::Security const *> secs;
for (auto const &ev : *(update.events)) {
auto sec_ptr = ev.security;
secs.push_back(sec_ptr);
}
me->services_data.limiter.add_securities(secs);
if (me->state == State::WAIT_SEC) {
LOG(OrderMan, Info,
"Order manager received initial set of {} "
"securities. Waiting for the filled position now",
update.events->size());
me->state = State::WAIT_FILLED_POSITION;
me->filled_position_sub = subscribe<FilledPositionEvent>(
[this](auto const &pos) { this->on_filled_position(pos); });
}
}
}
void OrderManager::on_filled_position(
const mercury::Update<FilledPositionEvent> &update) {
if (!update.events) {
WARN_FN(
"on_filled_position", "no event data. state: {}", me->state_name());
return;
}
INFO_FN(
"on_filled_position", "state: {}; n_events: {}", me->state_name(),
update.events->size());
if (me->state == State::WAIT_FILLED_POSITION) {
me->state = State::WAIT_ORDERS;
INFO_FN("on_filled_position", "new_state: {}", me->state_name());
me->stored_order_sub = subscribe<StoredOrderEvent>(
[this](auto const &orders) { this->on_order_recovery(orders); });
}
if (update.events) {
if (me->state == State::WAIT_FILLED_POSITION) {
LOG(OrderMan, Info,
"Order manager received initial set of {} "
"stored filled position records. Waiting for stored orders now",
update.events->size());
me->state = State::WAIT_ORDERS;
me->stored_order_sub = subscribe<StoredOrderEvent>(
[this](auto const &orders) { this->on_order_recovery(orders); });
}
for (auto const &ev : *(update.events)) {
me->services_data.position.update(ev);
me->services_data.limiter.set_filled_position(
*ev.security, ev.account_id, ev.long_quantity, ev.short_quantity);
for (auto const &ev : *(update.events)) {
me->services_data.position.update(ev);
me->services_data.limiter.set_filled_position(
*ev.security, ev.account_id, ev.long_quantity, ev.short_quantity);
}
}
}
void OrderManager::on_order_recovery(
const mercury::Update<StoredOrderEvent> &update) {
if (!update.events) {
WARN_FN(
"on_order_recovery", "no event data. state: {}", me->state_name());
return;
}
if (me->state != State::WAIT_ORDERS) {
WARN_FN(
"on_order_recovery",
"Expected state WAIT_ORDERS. state: {}; n_events: {}",
me->state_name(), update.events->size());
return;
}
// Got the first batch of persisted orders which has to match the orders
// stored on disk (since we are the only ones producing new orders)
INFO_FN(
"on_order_recovery", "state: {}; n_events: {}", me->state_name(),
update.events->size());
// Don't need to monitor persisted orders anymore, all the new orders we care
// about will be generated by us.
unsubscribe(me->stored_order_sub);
if (update.events)
if (me->state == State::WAIT_ORDERS) {
init_order_services();
// Got the first batch of persisted orders which has to match
// the orders stored on disk (since we are the only ones
// producing new orders)
LOG(OrderMan, Info,
"Order manager received initial set of {} persisted orders.",
update.events->size());
// recover orders from disk
for (auto const &ev : *(update.events)) {
auto &order = me->services_data.order_factory.recover(ev.order_record);
auto const &def = order.def();
publish_order_def_update(OrderKey(ev.key()), def);
order.init_order_services(me->services_data);
// Don't need to monitor persisted orders anymore, all the
// new orders we care about will be generated by us.
unsubscribe(me->stored_order_sub);
auto r = me->orders.emplace(order.key(), &order);
bts_assert(r.second);
auto const &ostate = ev.order_record.state();
order.on_activate(
Price(ostate.price()), Quantity(ostate.quantity()), *order._services);
}
init_order_services();
// signal that we are ready
INFO_FN("on_order_recovery", "OrderManager is ready");
publish(me->pub_ready);
me->state = State::READY;
// recover orders from disk
for (auto const &ev : *(update.events)) {
auto &order =
me->services_data.order_factory.recover(ev.order_record);
auto const &def = order.def();
publish_order_def_update(OrderKey(ev.key()), def);
order.init_order_services(me->services_data);
auto r = me->orders.emplace(order.key(), &order);
bts_assert(r.second);
auto const &ostate = ev.order_record.state();
order.on_activate(
Price(ostate.price()), Quantity(ostate.quantity()),
*order._services);
}
// signal that we are ready
LOG(OrderMan, Info, "Order manager is ready");
publish(me->pub_ready);
me->state = State::READY;
}
}
void OrderManager::on_execution_report(
const mercury::Update<NewExecutionReportEvent> &m) {
if (!m.events) {
WARN_FN("on_execution_report", "no event data");
return;
}
INFO_FN(
"on_execution_report", "state: {}; n_events", me->state_name(),
m.events->size());
for (auto const &report : *m.events) {
if (report.data->leg_security != nullptr) {
continue;
}
auto k = report.data->order_key;
auto it = me->orders.find(k);
if (it == me->orders.end()) {
ERROR_FN(
"on_execution_report",
"Received an execution report for an unknown order with key {}. "
"Order state USED TO be reconstructed from this report but some "
"information will be lost, including CustomerOrFirm,CTI code and "
"parent order id but now these are IGNORED",
k.value());
continue;
// auto order = &me->services_data.order_factory.recreate_from_exe_report(
// *report.data);
// auto const &def = order->def();
// publish_order_def_update(OrderKey(k), def);
// order->init_order_services(me->services_data);
// auto r = me->orders.emplace(order->key(), order);
// bts_assert(r.second);
// order->on_activate(
// report.data->price, report.data->quantity, *order->_services);
}
}
if (m.events)
for (auto const &report : *m.events)
if (report.data->leg_security == nullptr) {
auto k = report.data->order_key;
auto it = me->orders.find(k);
if (it == me->orders.end()) {
LOG(OrderMan, Error,
"Received an execution report for an unknown order with key "
"{}. Order state USED TO be reconstructed from this report but "
"some "
"information will be lost, including CustomerOrFirm,CTI "
"code "
"and "
"parent order id but now these are IGNORED",
k.value());
continue;
auto order =
&me->services_data.order_factory.recreate_from_exe_report(
*report.data);
auto const &def = order->def();
publish_order_def_update(OrderKey(k), def);
order->init_order_services(me->services_data);
auto r = me->orders.emplace(order->key(), order);
bts_assert(r.second);
order->on_activate(
report.data->price, report.data->quantity,
*order->_services);
}
}
}
void OrderManager::on_activate_order(
const mercury::Update<ActivateOrderEvent> &m) {
if (!m.events) {
WARN_FN("on_activate_order", "no event data");
return;
}
INFO_FN(
"on_activate_order", "state: {}; n_events: {}", me->state_name(),
m.events->size());
for (auto const &ev : *m.events) {
LOG(OrderMan, Info,
"OrderManager::on_activate_order req_id: {} p: {} q: {}", ev.req_id,
ev.price, ev.quantity);
auto order = ev.order;
auto const &def = order->def();
publish_order_def_update(order->key(), def);
order->init_order_services(me->services_data);
auto r = me->orders.emplace(order->key(), order);
bts_assert(r.second);
if (m.events)
for (auto const &ev : *m.events) {
LOG(OrderMan, Info,
"OrderManager::on_activate_order req_id: {} p: {} q: {}",
ev.req_id, ev.price, ev.quantity);
auto order = ev.order;
auto const &def = order->def();
publish_order_def_update(order->key(), def);
order->init_order_services(me->services_data);
auto r = me->orders.emplace(order->key(), order);
bts_assert(r.second);
auto rid = ev.req_id;
auto k = order->key();
auto on_response = [this, rid, k](auto const &order_op_result) {
publish(
me->services_data.report_order_op_result_pub, k, rid,
order_op_result);
};
order->_services->activate(ev.price, ev.quantity, on_response);
}
auto rid = ev.req_id;
auto k = order->key();
auto on_response = [this, rid, k](auto const &order_op_result) {
publish(
me->services_data.report_order_op_result_pub, k, rid,
order_op_result);
};
order->_services->activate(ev.price, ev.quantity, on_response);
}
}
void OrderManager::on_update_order(const mercury::Update<UpdateOrderEvent> &m) {
if (!m.events) {
WARN_FN("on_update_order", "no event data");
return;
}
INFO_FN(
"on_update_order", "state: {}; n_events: {}", me->state_name(),
m.events->size());
for (auto const &ev : *m.events) {
auto k = ev.key();
auto it = me->orders.find(k);
auto rid = ev.req_id();
if (m.events)
for (auto const &ev : *m.events) {
auto k = ev.key();
auto it = me->orders.find(k);
auto rid = ev.req_id();
auto on_order_op_result = [this, rid, k](auto const &order_op_result) {
publish(
me->services_data.report_order_op_result_pub, k, rid,
order_op_result);
};
auto on_order_op_result = [this, rid, k](auto const &order_op_result) {
publish(
me->services_data.report_order_op_result_pub, k, rid,
order_op_result);
};
if (it != me->orders.end()) {
if (ev.is_full_update()) {
it->second->_services->update(ev.full_update(), on_order_op_result);
if (it != me->orders.end()) {
if (ev.is_full_update())
it->second->_services->update(
ev.full_update(), on_order_op_result);
else
it->second->_services->update(
Price(ev.price()), Quantity(ev.quantity()),
on_order_op_result);
} else {
it->second->_services->update(
Price(ev.price()), Quantity(ev.quantity()), on_order_op_result);
on_order_op_result(OrderOpResult{
api::orderman::ORDER_ERROR_UNKNOWN_ORDER, "Unknown order"});
LOG(OrderMan, Error,
"Received an update request for an unknown order {}. "
"This should not happen and likely indicates a serious bug. ",
ev.key().value());
}
} else {
on_order_op_result(OrderOpResult{
api::orderman::ORDER_ERROR_UNKNOWN_ORDER, "Unknown order"});
ERROR_FN(
"on_update_order",
"order not found (id={}). This likely indicates a serious bug.",
ev.key().value());
}
}
}
void OrderManager::on_cancel_order(const mercury::Update<CancelOrderEvent> &m) {
if (!m.events) {
WARN_FN("on_cancel_order", "no event data");
return;
}
INFO_FN(
"on_cancel_order", "state: {}; n_events: {}", me->state_name(),
m.events->size());
for (auto const &ev : *m.events) {
auto k = ev.key();
auto it = me->orders.find(k);
auto rid = ev.req_id;
if (m.events)
for (auto const &ev : *m.events) {
auto k = ev.key();
auto it = me->orders.find(k);
auto rid = ev.req_id;
auto on_order_op_result = [this, rid, k](auto const &order_op_result) {
publish(
me->services_data.report_order_op_result_pub, k, rid,
order_op_result);
};
auto on_order_op_result = [this, rid, k](auto const &order_op_result) {
publish(
me->services_data.report_order_op_result_pub, k, rid,
order_op_result);
};
if (it != me->orders.end()) {
it->second->_services->cancel(on_order_op_result);
} else {
on_order_op_result(OrderOpResult{
api::orderman::ORDER_ERROR_UNKNOWN_ORDER, "Unknown order"});
ERROR_FN(
"on_cancel_order",
"order not found (id={}). This likely indicates a serious bug.",
ev.key().value());
if (it != me->orders.end())
it->second->_services->cancel(on_order_op_result);
else {
on_order_op_result(OrderOpResult{
api::orderman::ORDER_ERROR_UNKNOWN_ORDER, "Unknown order"});
LOG(OrderMan, Error,
"Received a cancel request for an unknown order {}. "
"This should not happen and likely indicates a serious bug. ",
ev.key().value());
}
}
}
}
void OrderManager::on_detach_order(const mercury::Update<DetachOrderEvent> &m) {
if (!m.events) {
WARN_FN("on_detach_order", "no event data");
return;
}
INFO_FN(
"on_detach_order", "state: {}; n_events: {}", me->state_name(),
m.events->size());
for (auto const &ev : *m.events) {
auto k = ev.key();
auto it = me->orders.find(k);
auto rid = ev.req_id;
if (m.events)
for (auto const &ev : *m.events) {
auto k = ev.key();
auto it = me->orders.find(k);
auto rid = ev.req_id;
auto on_order_op_result = [this, rid, k](auto const &order_op_result) {
publish(
me->services_data.report_order_op_result_pub, k, rid,
order_op_result);
};
auto on_order_op_result = [this, rid, k](auto const &order_op_result) {
publish(
me->services_data.report_order_op_result_pub, k, rid,
order_op_result);
};
if (it != me->orders.end()) {
it->second->_services->detach(on_order_op_result);
} else {
on_order_op_result(OrderOpResult{
api::orderman::ORDER_ERROR_UNKNOWN_ORDER, "Unknown Order"});
if (it != me->orders.end())
it->second->_services->detach(on_order_op_result);
else {
on_order_op_result(OrderOpResult{
api::orderman::ORDER_ERROR_UNKNOWN_ORDER, "Unknown Order"});
ERROR_FN(
"on_detach_order",
"order not found (id={}). This likely indicates a serious bug.",
ev.key().value());
LOG(OrderMan, Error,
"Received a detach request for an unknown order {}. "
"This should not happen and likely indicates a serious bug. ",
ev.key().value());
}
}
}
}
void OrderManager::on_gateway_state(
const mercury::Update<GatewayStatusChangeEvent> &m) {
if (!m.events) {
WARN_FN("on_gateway_state", "no event data");
return;
}
INFO_FN(
"on_gateway_state", "state: {}; n_events: {}", me->state_name(),
m.events->size());
for (auto const &ev : *m.events) {
LOG(OrderMan, Info,
" OrderManager::on_gateway_state gw: {} connected: {}", ev.gw_name,
ev.is_connected);
auto &sr = me->services_data.get_session(ev.gw_name.value());
sr.gw_connected = ev.is_connected;
}
if (m.events)
for (auto const &ev : *m.events) {
LOG(OrderMan, Info,
" OrderManager::on_gateway_state gw: {} connected: {}", ev.gw_name,
ev.is_connected);
auto &sr = me->services_data.get_session(ev.gw_name.value());
sr.gw_connected = ev.is_connected;
}
}
void OrderManager::add_child_order(OrderServices &parent, Order &child) {
@@ -560,12 +471,9 @@ void OrderManager::init_order_services() {
return s->session;
}),
", "));
INFO_FN(
"init_order_services", "state: {}; sessions: {}", me->state_name(),
session_names);
LOG(OrderMan, Info, "init_order_services. sessions: {}", session_names);
for (auto &s : me->services_data.sessions) {
INFO_FN("init_order_services", "Initializing session {}", s->session);
auto gw_name = GatewayName(s->session);
// Now we are ready to subscribe to the execution gateway
@@ -628,11 +536,9 @@ void OrderManager::init_order_services() {
// reject evets
auto s_ptr = s.get();
s->exe_report_pub = make_stream<NewExecutionReportEvent>(gw_name);
s->gw->on_exe_report() =
[s_ptr,
this](std::shared_ptr<const ExecutionReport> er, auto const &t) {
publish(s_ptr->exe_report_pub, std::move(er), t);
};
s->gw->on_exe_report() = [s_ptr, this](auto const &er, auto const &t) {
publish(s_ptr->exe_report_pub, &er, t);
};
s->gw_status_change_pub = make_stream<GatewayStatusChangeEvent>(gw_name);
s->gw->on_gw_status_change() = [s_ptr, this, gw_name](bool b) {
publish(s_ptr->gw_status_change_pub, gw_name, b);
@@ -658,10 +564,9 @@ void OrderManager::init_order_services() {
s->gw->on_cancel_reject() = [s_ptr, this](auto const &cr) {
publish(s_ptr->cancel_reject_pub, cr);
};
INFO_FN("init_order_services", "Starting session {}", s->session);
// handlers are ready so we can start processing fix events
s->gw->start();
INFO_FN("init_order_services", "Started session {}", s->session);
reload_fix_buffer(*s);
}
@@ -772,10 +677,9 @@ void OrderManager::after_event() {
if (queued_ops.size()) {
auto &corid = osd->current_trace;
auto now = utils::TimeStamp::now();
INFO_FN(
"after_event",
"channel: {} sn: {} app_arrival: {} now: {} "
"delta: {}",
LOG(Latency, Info,
"entry_type:after_event channel:{} sn:{} app_arrival:{} now:{} "
"delta:{}",
std::string{corid.channel}, corid.sn,
corid.app_arrival.to_uint64(), now.to_uint64(),
(now - corid.app_arrival).nanoseconds());
@@ -791,9 +695,9 @@ void OrderManager::reload_fix_buffer(SessionServicesData &osd) {
osd.buffered_fix_msgs = osd.free_buffers.back();
osd.free_buffers.pop_back();
} else {
WARN_FN(
"reload_fix_buffer",
"Allocating new fix buffers in {}. This will impact performance",
LOG(OrderMan, Warn,
"Ran out of fix buffers in {}. Will allocate directly. "
"This will have an impact on performance",
osd.cfg.name());
osd.buffered_fix_msgs = std::make_shared<jetfix::OutFIX>(osd.cfg);
}
@@ -1,6 +1,10 @@
#include "OrderServicesData.h"
#include "lib/config/config.h"
#include "lib/config/protos/jetfix.pb.h"
#include "lib/md/SecurityOracle.h"
#include "lib/orderman/actors/order_manager/order_gateway_definition.h"
#include "lib/orderman/gateways/ExecutionGatewayFactory.h"
#include "lib/os/spark_data_directory.h"
#include "lib/tracing/PerfLog.h"
namespace bts {
@@ -22,6 +26,8 @@ OrderServicesData::OrderServicesData(
for (auto &g : gateways) {
sessions.push_back(std::make_unique<SessionServicesData>(std::move(g)));
}
// spark::SparkDataDirectory data_dir;
// auto binance_j = data_dir.load_config_json("binance_execution");
}
} // namespace orderman
@@ -34,6 +34,7 @@
#include "lib/orderman/events/TickerUpdateEvent.h"
#include "lib/orderman/events/TradeSignalEvent.h"
#include "lib/orderman/events/UpdateOrderEvent.h"
#include "lib/orderman/gateways/ExecutionGatewayFactory.h"
#include "lib/orderman/gateways/IExecutionGateway.h"
#include "lib/orderman/limiter/Limiter.h"
#include "lib/orderman/order_factory/OrderFactory.h"
@@ -10,6 +10,7 @@
#include "lib/orderman/events/ExchangePositionEvent.h"
#include "lib/orderman/events/GatewayStatusChangeEvent.h"
#include "lib/orderman/events/NewExecutionReportEvent.h"
#include "lib/orderman/gateways/ExecutionGatewayFactory.h"
#include "lib/orderman/gateways/IExecutionGateway.h"
#include "lib/utils/PBUtils.h"
@@ -20,7 +20,6 @@
#include "lib/utils/bts_assert.h"
#include <bits/stdint-uintn.h>
#include <boost/lockfree/spsc_queue.hpp>
#include <ccapi_cpp/bts_session_options.h>
#include <ccapi_cpp/ccapi_element.h>
#include <ccapi_cpp/ccapi_event.h>
#include <ccapi_cpp/ccapi_macro.h>
@@ -256,8 +255,7 @@ struct CCAPIDeribitOrderMux::PImpl {
{CCAPI_DERIBIT_CLIENT_ID, d_conn_config.client_id},
{CCAPI_DERIBIT_CLIENT_SECRET, d_conn_config.client_secret},
});
session = new ccapi::Session(
{}, {ccapi::BtsVenues::DERIBIT}, session_configs, &event_handler);
session = new ccapi::Session({}, session_configs, &event_handler);
}
};
@@ -955,4 +953,4 @@ void CCAPIDeribitOrderMux::_send_buffered_orders(uint32_t sec_id) {
"n_buffered_orders: {}",
me->buffered_creates.size());
}
} // namespace bts::orderman
} // namespace bts::orderman
@@ -20,13 +20,12 @@ class NewExecutionReportEvent : public mercury::Event<NewExecutionReportEvent> {
public:
using Tag = GatewayName;
using SynopsisPolicy = mercury::CacheAll<NewExecutionReportEvent>;
std::shared_ptr<const ExecutionReport> data;
ExecutionReport const *data;
trace::CorID trace;
// used by ContentSubscription
OrderKey key() const { return data->order_key; }
NewExecutionReportEvent(
std::shared_ptr<const ExecutionReport> dt, trace::CorID const &trace)
: data(std::move(dt)), trace(trace){};
NewExecutionReportEvent(const ExecutionReport *dt, trace::CorID const &trace)
: data(dt), trace(trace){};
};
} // namespace orderman
@@ -40,7 +40,7 @@
#include <bits/stdint-uintn.h>
//#include <boost/container/small_vector.hpp>
#include "lib/orderman/ccapi/CCAPIConnectionConfig.h"
#include <boost/pool/object_pool.hpp>
#include <cmath>
#include <cstdint>
#include <optional>
@@ -138,6 +138,7 @@ struct CCAPIExecutionGateway::PImpl {
OnExchangeOpenOrdersState on_exchange_open_orders;
// orders are sent out and callbacks received via IEMOrderMuxer
size_t msg_seq_num = 0;
boost::object_pool<ExecutionReport> exe_report_pool;
std::unordered_map<OrderKey, CreateOrderMsg> order_map = {};
// this is updated as we get exchange positions
ExchangePosition exchange_position;
@@ -338,7 +339,8 @@ void CCAPIExecutionGateway::_on_order_cb(const CCAPIOrderState &s) {
const auto &cr_msg = it->second;
// cant create one on the stack
ExecutionReport er;
ExecutionReport *er_p = me->exe_report_pool.construct();
ExecutionReport &er = *er_p;
// length of these have to be <= 12
to_array(cr_msg.routing_record->account(), &er.account_name.value());
to_array(cr_msg.routing_record->operator_id(), &er.operator_id.value());
@@ -388,7 +390,7 @@ void CCAPIExecutionGateway::_on_order_cb(const CCAPIOrderState &s) {
LOG(OrderMan, Info,
"CCAPIExecutionGateway::_process_cb_and_fire_er firing er: {}",
er.to_string());
me->on_exe_report(std::make_shared<ExecutionReport>(std::move(er)), {});
me->on_exe_report(er, {});
// _fire_leg_er(em_cb, er);
}
@@ -0,0 +1,59 @@
#include "ExecutionGatewayFactory.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/md/SecurityOracle.h"
#include "lib/orderman/gateways/CCAPIExecutionGateway.h"
// #include "lib/orderman/gateways/EMExecutionGateway.h"
#include "lib/orderman/gateways/FakeExecutionGateway.h"
#include "lib/orderman/gateways/GeneralExecutionGateway.h"
#include "lib/orderman/gateways/GLBXExecutionGateway.h"
#include "lib/orderman/gateways/GLBXiLink3ExecutionGateway.h"
#include "lib/orderman/gateways/ICEExecutionGateway.h"
#include "lib/orderman/gateways/IExecutionGenericAdapter.h"
#include "lib/orderman/gateways/OkxExecutionGateway.h"
#include "lib/orderman/gateways/OnixFIXAdapter.h"
#include "lib/orderman/gateways/sim/SimExecutionGatewayFactory.h"
#include "lib/orderman/gateways/sim/SimExecutionGatewayUniqueWrapper.h"
#include "lib/tracing/PerfLog.h"
#include "lib/utils/PBUtils.h"
#include "lib/utils/ValueWrapper.h"
#include <memory>
#include <variant>
namespace bts::orderman {
std::unique_ptr<IExecutionGateway> ExecutionGatewayFactory::create_gateway(
const config::JetFIX::Connection &conn,
trace::PerfLog &pl,
md::SecurityOracle o) {
LOG(OrderMan, Info, "ExecutionGatewayFactory::create_gateway conn: {}",
utils::pb::to_string(conn));
if (SimExecutionGatewayFactory::is_sim_execution_on(conn.venue())) {
return std::make_unique<SimExecutionGatewayUniqueWrapper>(conn.venue());
}
LOG(OrderMan, Info, "ExecutionGatewayFactory::create_gateway NOT SIM conn: {}",
utils::pb::to_string(conn));
switch(conn.venue()){
case bts::api::VENUE_ICE:
case bts::api::VENUE_SIMICE:
return std::make_unique<ICEExecutionGateway>(conn, pl, o);
case bts::api::VENUE_GLBX:{
if (conn.use_ilink3())
return std::make_unique<ilink3::GLBXiLink3ExecutionGateway>( conn, pl, o);
return std::make_unique<GLBXExecutionGateway>(conn, pl, o);
}
case bts::api::VENUE_DERIBIT:
return std::make_unique<CCAPIExecutionGateway>(conn.venue(), o);
case bts::api::VENUE_CBOE:
{
std::unique_ptr<IExecutionGenericAdapter> adapter = std::make_unique<OnixFIXAdapter>(o);
return std::make_unique<GeneralExecutionGateway>(conn.venue(),std::move(adapter),o);
}
case bts::api::VENUE_OKX:
{ return std::make_unique<OkxExecutionGateway>(o);
}
default:
return std::make_unique<FakeExecutionGateway>(o);
}
}
} // namespace bts::orderman
@@ -0,0 +1,25 @@
#pragma once
#include "lib/config/protos/jetfix.pb.h"
#include <memory>
namespace bts::trace {
class PerfLog;
}
namespace bts::md {
class SecurityOracle;
}
namespace bts::orderman {
class IExecutionGateway;
class ExecutionGatewayFactory {
public:
static std::unique_ptr<IExecutionGateway> create_gateway(
const config::JetFIX::Connection &,
trace::PerfLog &,
md::SecurityOracle);
};
} // namespace bts::orderman
@@ -25,6 +25,7 @@
#include "server/sparkd/md/platforms/emumba/IEMOrderMuxer.h"
#include <atomic>
#include <boost/pool/object_pool.hpp>
#include <cstdint>
#include <set>
#include <string>
@@ -53,7 +54,7 @@ struct FakeExecutionGateway::PImpl {
md::SecurityOracle sec_oracle;
std::vector<OrderMsg> buffered_msgs = {};
unsigned long exchange_oid = 0;
std::vector<std::shared_ptr<ExecutionReport>> exe_reports = {};
std::vector<ExecutionReport> exe_reports = {};
IEMOrderMuxer *_order_mux = nullptr;
};
@@ -83,9 +84,7 @@ void FakeExecutionGateway::dispatch_incoming_fix_messages() {
"FakeExecutionGateway::dispatch_incoming_fix_message key: {} sec: "
"{} p: {} q: {}",
cr_msg.key, cr_msg.security->id(), cr_msg.price, cr_msg.quantity);
me->exe_reports.push_back(std::make_shared<ExecutionReport>());
std::shared_ptr<ExecutionReport> &er_p = me->exe_reports.back();
ExecutionReport &er = *er_p;
ExecutionReport er = {};
std::string account_name = "ACCA1";
std::array<char, 12> an_array = {};
size_t i = 0;
@@ -116,18 +115,19 @@ void FakeExecutionGateway::dispatch_incoming_fix_messages() {
er.msg = "Message Placed";
er.exchange = api::VENUE_SIM;
trace::CorID cid = {};
me->exe_reports.push_back(er);
const auto &er_pheap = me->exe_reports.back();
LOG(OrderMan, Info,
"FakeExecutionGateway::dispatch_incoming_fix_messages er: {}",
er.to_string());
me->on_exe_report(er_p, cid);
er_pheap.to_string());
me->on_exe_report(er_pheap, cid);
} else if (std::holds_alternative<CancelOrderMsg>(m)) {
CancelOrderMsg cxl_m = std::get<CancelOrderMsg>(m);
for (std::shared_ptr<ExecutionReport> &er_p : me->exe_reports) {
auto &er = *er_p;
for (auto &er : me->exe_reports) {
if (er.order_key == cxl_m.key) {
er.order_status = api::orderman::NATIVE_ORDER_STATUS_CANCELED;
er.msg = "Cancelled";
me->on_exe_report(er_p, {});
me->on_exe_report(er, {});
LOG(OrderMan, Info,
"FakeExecutionGateway::dispatch_incoming_fix_messages er: "
"{}",
@@ -138,14 +138,13 @@ void FakeExecutionGateway::dispatch_incoming_fix_messages() {
} else if (std::holds_alternative<CancelReplaceOrderMsg>(m)) {
CancelReplaceOrderMsg cxl_r_msg = std::get<CancelReplaceOrderMsg>(m);
CreateOrderMsg cr_msg = cxl_r_msg.create_fields;
for (std::shared_ptr<ExecutionReport> &er_p : me->exe_reports) {
auto &er = *er_p;
for (auto &er : me->exe_reports) {
if (er.order_key == cr_msg.key) {
er.order_status = api::orderman::NATIVE_ORDER_STATUS_REPLACED;
er.msg = "Replaced";
er.price = cr_msg.price;
er.quantity = cr_msg.quantity;
me->on_exe_report(er_p, {});
me->on_exe_report(er, {});
return;
}
}
@@ -197,4 +196,4 @@ OnExchangeOpenOrdersState &FakeExecutionGateway::on_exchange_open_orders() {
}
} // namespace orderman
} // namespace bts
} // namespace bts
@@ -13,6 +13,8 @@
#include "lib/utils/TimeStamp.h"
#include "lib/utils/bts_chrono.h"
#include <boost/pool/object_pool.hpp>
namespace bts {
namespace orderman {
@@ -29,11 +31,12 @@ struct GLBXExecutionGateway::PImpl {
config::JetFIX::Connection const &cfg,
trace::PerfLog &l,
md::SecurityOracle o)
: gw(cfg, l), state(State::WAITING_FOR_SECURITIES),
: gw(cfg, l), exe_report_pool(), state(State::WAITING_FOR_SECURITIES),
securities(), securities_by_descriptor(), gw_name(cfg.name()),
cfg(cfg), perf_log(l), sec_oracle(o) {}
JFGw gw;
boost::object_pool<ExecutionReport> exe_report_pool;
State state;
std::unordered_map<SecurityKey::Value, api::Security const *> securities;
std::unordered_map<std::string, api::Security const *>
@@ -206,8 +209,8 @@ void GLBXExecutionGateway::on_exe_report(
if (msg.has<fix::Text>())
text_msg = msg.get<fix::Text>();
ExecutionReport er;
ExecutionReport *data = &er;
// allocate from our pool
auto data = me->exe_report_pool.construct();
data->exec_type = api::orderman::ExecType(msg.get<fix::ExecType>());
data->msg_seq_num = msg.get<fix::MsgSeqNum>();
@@ -287,8 +290,7 @@ void GLBXExecutionGateway::on_exe_report(
data->cfg = &me->cfg;
data->msg = text_msg;
data->exchange = api::Venue::VENUE_GLBX;
me->on_exe_report(
std::make_shared<ExecutionReport>(std::move(er)), msg.trace());
me->on_exe_report(*data, msg.trace());
}
}
@@ -12,7 +12,6 @@
#include "lib/orderman/ilink3/IL3Reactor.h"
#include "lib/orderman/ilink3/IL3SecDB.h"
#include "lib/orderman/ilink3/SessionStore.h"
#include "lib/orderman/orderman_utils/ExecutionReport.h"
#include "lib/orderman/orderman_utils/fix_order_codec.h"
#include "lib/utils/PBUtils.h"
#include <cstddef>
@@ -150,11 +149,8 @@ void GLBXiLink3ExecutionGateway::connect(jetfix::SessionName const &sn) {
if (!me->has_replayed) {
me->has_replayed = true;
me->replay_messages = me->session_store.value().replay();
for (ExecutionReport &er : me->replay_messages) {
me->on_exe_report(
std::make_shared<ExecutionReport>(std::move(er)), {});
}
me->replay_messages.clear();
for (auto const &er : me->replay_messages)
me->on_exe_report(er, {});
}
c.raw_message_logger =
@@ -173,7 +169,7 @@ void GLBXiLink3ExecutionGateway::connect(jetfix::SessionName const &sn) {
me->gw->subscribe_execution_reports([this](auto er) {
bts_assert(me->state == State::READY || me->state == State::CONNECTING);
me->on_exe_report(std::move(er), {});
me->on_exe_report(*er, {});
});
me->gw->subscribe_session(
@@ -18,6 +18,7 @@
#include "lib/utils/helper_macros.h"
#include <atomic>
#include <boost/pool/object_pool.hpp>
#include <set>
namespace bts {
@@ -38,12 +39,13 @@ struct ICEExecutionGateway::PImpl {
const config::JetFIX::Connection &cfg,
trace::PerfLog &l,
md::SecurityOracle o)
: gw(cfg, l), state(State::WAITING_FOR_SECURITIES), securities(),
securities_by_exchange_id(), gw_name(cfg.name()), cfg(cfg),
perf_log(l), admin_data(), nusers(-1), connection_time(),
: gw(cfg, l), exe_report_pool(), state(State::WAITING_FOR_SECURITIES),
securities(), securities_by_exchange_id(), gw_name(cfg.name()),
cfg(cfg), perf_log(l), admin_data(), nusers(-1), connection_time(),
exec_id_to_memo(), sec_oracle(o) {}
JFGw gw;
boost::object_pool<ExecutionReport> exe_report_pool;
State state;
std::unordered_map<SecurityKey::Value, api::Security const *> securities;
std::unordered_map<int64_t, api::Security const *> securities_by_exchange_id;
@@ -371,8 +373,7 @@ void ICEExecutionGateway::on_exe_report(
std::to_string(msg.get<fix::DeleteReason>());
// allocate from our pool
ExecutionReport er;
ExecutionReport *data = &er;
auto data = me->exe_report_pool.construct();
if (msg.has<fix::Memo>())
data->memo = msg.get<fix::Memo>();
@@ -466,8 +467,7 @@ void ICEExecutionGateway::on_exe_report(
data->cfg = &me->cfg;
data->msg = text_msg;
data->exchange = api::Venue::VENUE_ICE;
me->on_exe_report(
std::make_shared<ExecutionReport>(std::move(er)), msg.trace());
me->on_exe_report(*data, msg.trace());
}
}
@@ -28,8 +28,8 @@ class ExecutionReport;
class CancelReject;
using OnGwStatusChange = std::function<void(bool)>;
using OnExeReport = std::function<void(
std::shared_ptr<const ExecutionReport>, trace::CorID const &)>;
using OnExeReport =
std::function<void(ExecutionReport const &, trace::CorID const &)>;
using OnCancelReject = std::function<void(CancelReject const &)>;
using OnExchangePositionState = std::function<void(ExchangePosition const &)>;
using OnExchangeOpenOrdersState =
@@ -20,8 +20,7 @@ class NewAdminDataEvent;
class ExecutionReport;
class CancelReject;
using OnExecutionReport = std::function<void(
std::shared_ptr<const ExecutionReport>, const trace::CorID &)>;
using OnExecutionReport = std::function<void(ExecutionReport const &, const trace::CorID &)>;
// execution gateway for a single session
class IExecutionGenericAdapter {
@@ -89,7 +89,9 @@ struct OkxExecutionGateway::PImpl {
const core::TimeSpan pong_timeout = core::TimeSpan::from_seconds(
config::get<config::OkxExeCfg>().ws_ping_interval_seconds());
websocket::WebsocketClient ws_client;
tbb::concurrent_queue<std::shared_ptr<ExecutionReport>> buffered_exec_reps;
boost::object_pool<ExecutionReport> ws_exe_report_pool;
boost::object_pool<ExecutionReport> rest_exe_report_pool;
tbb::concurrent_queue<ExecutionReport *> buffered_exec_reps;
std::optional<std::thread> ws_thread;
OnExeReport cb;
OnGwStatusChange on_gw_status_change;
@@ -113,7 +115,7 @@ struct OkxExecutionGateway::PImpl {
}
}
static void dummy(std::shared_ptr<const ExecutionReport>, const trace::CorID &) {
static void dummy(const ExecutionReport &, const trace::CorID &) {
LOG(OrderMan, Error, "OnixFixListener::Dummy fired");
}
void setCallback(const OnExeReport &callback) {
@@ -221,9 +223,10 @@ struct OkxExecutionGateway::PImpl {
// buffered_exec_reps, which is picked up from the other thread
// and passed over to the order infrastructure
void _push_execution_report(const nlohmann::basic_json<> &order) {
ExecutionReport er;
ExecutionReport *er_p = ws_exe_report_pool.construct();
ExecutionReport &er = *er_p;
get_exe_report(order, er);
buffered_exec_reps.push(std::make_shared<ExecutionReport>(std::move(er)));
buffered_exec_reps.push(er_p);
}
void on_message(const std::string &msg_str) {
LOG(OrderMan, Info, "okxlistener::on_message. {}", msg_str);
@@ -284,24 +287,12 @@ struct OkxExecutionGateway::PImpl {
void start_impl() {
size_t attempt = 0;
for (size_t version = 0; state != OkxExecutionState::OFF; version++) {
while (state != OkxExecutionState::OFF) {
try {
LOG(OrderMan, Warn,
"OkxExecutionAdapter::start Attempting login (Attempt: {})",
attempt + 1);
if (ws_run_thread) {
if (ws_run_thread->joinable()) {
ws_run_thread->join();
}
ws_run_thread.reset();
}
ws_client.connect();
ws_run_thread = threading::make_thread(
"okx_execution_ws", [&, orig_version = version]() {
while (version == orig_version) {
ws_client.run_one();
}
});
ws_client.connect({}, true);
sleep(2);
if (ws_ping_thread.has_value()) {
if (ws_ping_thread->joinable()) {
@@ -312,7 +303,7 @@ struct OkxExecutionGateway::PImpl {
.reset(); // Reset the optional before creating a new thread
}
ws_ping_thread.emplace([&, orig_version = version]() {
ws_ping_thread.emplace([&]() {
LOG(OrderMan, Warn,
"OkxExecutionAdapter::ws_ping_thread thread starting. url: "
"{}",
@@ -323,7 +314,7 @@ struct OkxExecutionGateway::PImpl {
std::this_thread::sleep_for(
std::chrono::milliseconds(ping_interval.millis()));
while (ws_client.is_connected() && version == orig_version) {
while (ws_client.is_connected()) {
const auto now = bts::core::TimeStamp::now();
if ((now - last_ping_time) > ping_interval) {
LOG(OrderMan, Warn,
@@ -463,13 +454,13 @@ void OkxExecutionGateway::dispatch_incoming_fix_messages() {
if (me_->buffered_exec_reps.empty()) {
return;
}
std::shared_ptr<ExecutionReport> er;
ExecutionReport *er;
while (me_->buffered_exec_reps.try_pop(er)) {
trace::CorID cid = {};
LOG(OrderMan, Info,
"OkxExecutionGateway::dispatch_incoming_fix_messages er: {} ",
er->to_string());
me_->cb(std::move(er), cid);
me_->cb(*er, cid);
}
}
OnExeReport &OkxExecutionGateway::on_exe_report() { return me_->cb; }
@@ -513,7 +504,8 @@ void OkxExecutionGateway::launch_new_order(const CreateOrderMsg &msg) {
config::get<config::OkxExeCfg>().rest_url() + REQUEST_PATH, headers,
body);
LOG(OrderMan, Warn, "Curl Command: \n{}", curlCommand);
ExecutionReport er;
ExecutionReport *er_p = me_->rest_exe_report_pool.construct();
ExecutionReport &er = *er_p;
http::HttpClient::post(
config::get<config::OkxExeCfg>().rest_url() + REQUEST_PATH, body,
headers, &res);
@@ -542,8 +534,7 @@ void OkxExecutionGateway::launch_new_order(const CreateOrderMsg &msg) {
er.order_status = api::orderman::NATIVE_ORDER_STATUS_REJECTED;
er.msg = ex_msg + " " + top_msg;
}
me_->buffered_exec_reps.push(
std::make_shared<ExecutionReport>(std::move(er)));
me_->buffered_exec_reps.push(er_p);
}
// Shut down all reactors if they exist.
catch (const std::exception &ex) {
@@ -728,4 +719,4 @@ void OkxExecutionGateway::cancel_order(const CancelOrderMsg &msg) {
// er.transact_time = now;
// me_->cb(er, {});
// return er;
// }
// }
@@ -16,7 +16,8 @@
namespace bts::orderman {
void OnixFixListener::fire_dummy_report(CreateOrderMsg msg) {
ExecutionReport er;
ExecutionReport *er_p = exe_report_pool.construct();
ExecutionReport &er = *er_p;
std::string account_name = "BTSIM";
std::array<char, 12> an_array = {};
size_t i = 0;
@@ -52,10 +53,11 @@ void OnixFixListener::fire_dummy_report(CreateOrderMsg msg) {
er.transact_time = now;
if(er.security==nullptr){
LOG(OrderMan, Error,"DID not have sec");
exe_report_pool.destroy(&er);
return;
}
LOG(OrderMan, Info,"msg {}",er.msg);
buffered_exec_reps.push(std::make_shared<ExecutionReport>(std::move(er)));
buffered_exec_reps.push(er_p);
return;
}
@@ -217,7 +219,7 @@ void OnixFixListener::onInboundApplicationMsg(Message &msg, Session * /*sn*/) {
return;
}
LOG(OrderMan, Info,"msg {}",er.msg);
buffered_exec_reps.push(std::make_shared<ExecutionReport>(std::move(er)));
buffered_exec_reps.push(er_p);
}
}
@@ -231,13 +233,13 @@ void OnixFixListener::dispatch_incoming_fix_messages() {
if (buffered_exec_reps.empty()) {
return;
}
std::shared_ptr<const ExecutionReport> er;
ExecutionReport *er;
while (buffered_exec_reps.try_pop(er)) {
trace::CorID cid = {};
LOG(OrderMan, Info,
"OnixFixListener::dispatch_incoming_fix_messages er: {} ",
er->to_string());
cb(std::move(er), cid);
cb(*er, cid);
}
}
@@ -275,4 +277,4 @@ void OnixFixListener::onWarning(WarningReason::Enum /*reason*/, const std::strin
}
} // namespace bts::orderman
} // namespace bts::orderman
+3 -5
View File
@@ -34,12 +34,10 @@ class OnixFixListener : public OnixS::FIX::ISessionListener {
SessionState::Enum prevState,
Session *sn) ONIXS_FIXENGINE_FINAL;
OnExeReport cb;
tbb::concurrent_queue<std::shared_ptr<const ExecutionReport>>
buffered_exec_reps;
tbb::concurrent_queue<ExecutionReport *> buffered_exec_reps;
void fire_dummy_report(CreateOrderMsg msg);
void get_exe_report(Message msg, ExecutionReport &er);
static void dummy(
std::shared_ptr<const ExecutionReport>, const trace::CorID &) {
static void dummy(const ExecutionReport &, const trace::CorID &) {
LOG(OrderMan, Error, "OnixFixListener::Dummy fired");
}
void onError(
@@ -53,4 +51,4 @@ class OnixFixListener : public OnixS::FIX::ISessionListener {
boost::object_pool<ExecutionReport> exe_report_pool;
};
} // namespace bts::orderman
} // namespace bts::orderman
@@ -0,0 +1,31 @@
#include "execution_gateway_provider.h"
#include "lib/orderman/actors/order_manager/order_gateway_definition.h"
#include "lib/orderman/gateways/ExecutionGatewayFactory.h"
namespace bts::orderman {
struct ExecutionGatewayProvider::PImpl {
PImpl(trace::PerfLog *l, md::SecurityOracle o) : l(l), o(std::move(o)) {}
trace::PerfLog *l;
md::SecurityOracle o;
};
ExecutionGatewayProvider::ExecutionGatewayProvider(
trace::PerfLog *l, md::SecurityOracle o)
: me_(std::make_unique<PImpl>(l, std::move(o))) {}
ExecutionGatewayProvider::~ExecutionGatewayProvider() = default;
std::vector<OrderGatewayDefinition> ExecutionGatewayProvider::create() {
auto conns = config::get<config::JetFIX>().connection();
std::vector<OrderGatewayDefinition> gateways;
for (auto c : conns) {
auto &gw = gateways.emplace_back();
gw.connection = std::move(c);
gw.gateway = ExecutionGatewayFactory::create_gateway(gw.connection, *me_->l, me_->o);
}
return gateways;
}
} // namespace bts::orderman
@@ -5,20 +5,11 @@
#include <memory>
#include <vector>
namespace bts::spark {
class SparkDataDirectory;
}
namespace bts::orderman {
class ExecutionGatewayProvider : public IExecutionGatewayProvider {
public:
ExecutionGatewayProvider(
trace::PerfLog *l,
md::SecurityOracle o,
config::JetFIX config,
std::function<std::optional<nlohmann::json>(const std::string &)>
load_config_json);
ExecutionGatewayProvider(trace::PerfLog *l, md::SecurityOracle o);
~ExecutionGatewayProvider() override;
std::vector<OrderGatewayDefinition> create() override;
@@ -33,6 +33,7 @@
#include "lib/utils/thread_manager.h"
#include <bits/stdint-uintn.h>
#include <boost/pool/object_pool.hpp>
#include <cstdint>
#include <optional>
#include <queue>
@@ -279,9 +280,10 @@ struct SimExecutionGateway::PImpl {
// send_msgs is sent on the order_man thread, its dequeued in the market data
// thread, and execution reports are queued to buffered_exec_reps
tbb::concurrent_queue<OrderMsg> buffered_order_ops;
tbb::concurrent_queue<std::shared_ptr<ExecutionReport>> buffered_exec_reps;
tbb::concurrent_queue<ExecutionReport *> buffered_exec_reps;
std::unordered_map<bts::SecurityKey, SEGSecurityData> books = {};
tbb::concurrent_hash_map<uint32_t, api::Security const *> sec_map = {};
boost::object_pool<ExecutionReport> exe_report_pool;
size_t msg_seq_num = 0;
std::set<SecurityKey> touched_secs;
api::Security const *find_sec(uint32_t key) const {
@@ -319,13 +321,13 @@ void SimExecutionGateway::dispatch_incoming_fix_messages() {
if (me->buffered_exec_reps.empty()) {
return;
}
std::shared_ptr<ExecutionReport> er;
ExecutionReport *er;
while (me->buffered_exec_reps.try_pop(er)) {
trace::CorID cid = {};
LOG(OrderMan, Info,
"ESimExecutionGateway::dispatch_incoming_fix_messages er: {} ",
er->to_string());
me->on_exe_report(std::move(er), cid);
me->on_exe_report(*er, cid);
}
}
@@ -649,9 +651,10 @@ SEGSecurityData *SimExecutionGateway::_find_add_security_entry(
// buffered_exec_reps, which is picked up from the other thread
// and passed over to the order infrastructure
void SimExecutionGateway::_push_execution_report(const SEGOrderState &seg_os) {
ExecutionReport er;
ExecutionReport *er_p = me->exe_report_pool.construct();
ExecutionReport &er = *er_p;
fill_execution_report(seg_os, er);
me->buffered_exec_reps.push(std::make_shared<ExecutionReport>(std::move(er)));
me->buffered_exec_reps.push(er_p);
}
void SimExecutionGateway::_write_book(SecurityKey key) {
+1 -1
View File
@@ -45,7 +45,7 @@ IL3GW::IL3GW(
const Config &conf,
config::JetFIX::Connection const *cfg)
: _config(conf), _x_state(State::INIT), _buffer(buffer),
_on_state_change([](auto) {}), _on_exe_report([](auto) {}),
_on_state_change([](auto) {}), _on_exe_report([](auto *) {}),
_on_session_change([](auto) {}), _connection(con), _session(session),
_msg_count(0), _time(), _pool(), _exe_parser(cfg, db), _retransmit() {
LOG(OrderMan, Info,
+5 -9
View File
@@ -10,9 +10,6 @@
#include "lib/orderman/ilink3/RawMsg.h"
#include "lib/orderman/gateways/IExecutionGateway.h"
#include "lib/orderman/orderman_utils/CancelReject.h"
#include "lib/utils/auto_init_shared_ptr.h"
#include "lib/utils/object_pool.h"
#include <cstdint>
#include <functional>
#include <queue>
@@ -49,8 +46,7 @@ struct Config {
using State = IL3State;
typedef std::function<void(State)> OnStateChange;
typedef std::function<void(std::shared_ptr<const ExecutionReport>)>
OnExecutionReport;
typedef std::function<void(const ExecutionReport *)> OnExecutionReport;
typedef std::function<void(const Session &s)> OnSessionChange;
typedef std::function<void(CancelReject &)> OnCancelReject;
@@ -105,7 +101,7 @@ class IL3GW : IL3ConnectionHandler {
// A pool of pre-allocated ExecutionReport objcets, used to avoid
// allocation when processing an execution report.
bts::utils::object_pool<utils::auto_init_shared_ptr<ExecutionReport>> _pool;
boost::object_pool<ExecutionReport> _pool;
// A helper used to turn MDP3 messages into normalized execution
// report objects
@@ -246,10 +242,10 @@ class IL3GW : IL3ConnectionHandler {
}
template <class T> void handle_exe_report(const T &msg) {
std::shared_ptr<ExecutionReport> report = _pool.construct()->value();
bool result = _exe_parser.parse(msg, report.get());
auto *report = _pool.construct();
bool result = _exe_parser.parse(msg, report);
if (result) {
_on_exe_report(std::move(report));
_on_exe_report(report);
} else {
LOG(iLink3, Error, "Failed to parse error report.");
}
@@ -377,7 +377,7 @@ DISABLED_TEST_CASE("OutrightVanillaOrder", "[ilink3][execution]") {
size_t exe_reports = 0;
gw.subscribe_execution_reports(
[&](std::shared_ptr<const ExecutionReport>) { exe_reports += 1; });
[&](const ExecutionReport *) { exe_reports += 1; });
gw.send_order(order, {});
gw.flush();
@@ -12,10 +12,10 @@ using namespace bts::api::orderman;
std::string ExecutionReport::to_string() const {
std::ostringstream rv;
rv << "account_name: " << to_trim_strn(account_name)
<< " operator_id: " << to_trim_strn(operator_id)
rv << "account_name: " << to_trim_str(account_name)
<< " operator_id: " << to_trim_str(operator_id)
<< " order_key: " << order_key.value() << " msg_seq_num: " << msg_seq_num
<< " security: " << (security == nullptr ? 0 : security->id())
<< " security: " << security->id()
<< " leg_security: " << (leg_security == nullptr ? 0 : leg_security->id())
<< " side: " << bts::api::Side_Name(side) << " time_in_force: "
<< bts::api::orderman::TimeInForce_Name(time_in_force)
@@ -32,4 +32,4 @@ std::string ExecutionReport::to_string() const {
}
} // namespace orderman
} // namespace bts
} // namespace bts
@@ -48,15 +48,6 @@ void to_str(std::array<char, N> const &in, std::string *out) {
result = std::string(&in[0], N);
}
template <size_t N>
void to_strn(std::array<char, N> const &in, std::string *out) {
std::size_t n = 0;
while (n < N && in[n] != '\0') {
n++;
}
*out = std::string(&in[0], n);
}
template <class T, class S>
T to(S const &str) {
T result;
@@ -76,13 +67,6 @@ inline std::string to_str(T const &t) {
return result;
}
template <class T>
inline std::string to_strn(T const &t) {
std::string result;
to_strn(t.value(), &result);
return result;
}
template <size_t N>
std::pair<const char *, size_t> find_trim_str(std::array<char, N> const &in) {
auto i = N;
@@ -95,36 +79,12 @@ std::pair<const char *, size_t> find_trim_str(std::array<char, N> const &in) {
return std::make_pair(&in[0], i);
}
template <size_t N>
std::pair<const char *, size_t> find_trim_strn(std::array<char, N> const &in) {
size_t i = 0;
while (i < N && in[i] != '\0') {
i++;
}
while (i > 0 && (in[i - 1] == ' ' || in[i - 1] == 0)) {
i--;
}
if (i == 0) {
return {nullptr, 0};
}
return {&in[0], i};
}
template <class T>
std::string to_trim_str(T const &t) {
auto ts = find_trim_str(t.value());
return std::string(ts.first, ts.second);
}
template <class T>
std::string to_trim_strn(T const &t) {
auto ts = find_trim_strn(t.value());
if (ts.second == 0) {
return {};
}
return std::string{ts.first, ts.second};
}
inline bts::api::orderman::OrderStatus get_external_status(
bts::api::orderman::NativeOrderStatus status) {
using namespace bts::api::orderman;
@@ -206,7 +166,7 @@ inline void set_exchange_order_id(
overloaded{
[out](ExchangeOrderID const &str) {
out->set_exchange_order_id_u64(0);
*(out->mutable_exchange_order_id()) = to_trim_strn(str);
*(out->mutable_exchange_order_id()) = to_trim_str(str);
},
[out](ExchangeOrderID64 oid) {
out->set_exchange_order_id_u64(oid.value());
@@ -332,29 +292,6 @@ inline void set_update_quantity(
} // namespace bts
FMT_IMPL(bts::orderman::AccountID, id, "{}", *id);
FMT_IMPL(bts::orderman::ExchangeOrderID, id, "{}", bts::orderman::to_strn(id));
FMT_IMPL(bts::orderman::ExchangeOrderID64, id, "{}", *id);
template <>
struct fmt::formatter<bts::orderman::ExchangeOrderIDVar> {
constexpr auto parse(fmt::format_parse_context &ctx) -> decltype(ctx.end()) {
return ctx.end();
}
template <typename FormatContext>
auto format(const bts::orderman::ExchangeOrderIDVar &id, FormatContext &ctx)
const -> decltype(ctx.out()) {
if (std::holds_alternative<bts::orderman::ExchangeOrderID64>(id)) {
return fmt::format_to(
ctx.out(), "{}",
std::get<bts::orderman::ExchangeOrderID64>(id).value());
}
if (std::holds_alternative<bts::orderman::ExchangeOrderID>(id)) {
return fmt::format_to(
ctx.out(), "{}", std::get<bts::orderman::ExchangeOrderID>(id));
}
return ctx.out();
}
};
FMT_IMPL(bts::orderman::GatewayName, n, "{}", *n);
FMT_IMPL(bts::orderman::OrderKey, k, "{}", *k);
@@ -8,7 +8,6 @@
#include "lib/utils/bts_chrono.h"
#include "lib/utils/helper_macros.h"
#include <boost/move/detail/placement_new.hpp>
#include <ccapi_cpp/bts_session_options.h>
#include <ccapi_cpp/ccapi_message.h>
#include <ccapi_cpp/ccapi_util_private.h>
#include <chrono>
@@ -66,9 +65,7 @@ class Bootstrapper {
ccapi::SessionOptions sessionOptions;
ccapi::SessionConfigs sessionConfigs;
MyEventHandler eventHandler;
ccapi::Session session(
sessionOptions, {ccapi::BtsVenues::BINANCE_USDS_FUTURES},
sessionConfigs, &eventHandler);
ccapi::Session session(sessionOptions, sessionConfigs, &eventHandler);
ccapi::Request request(
ccapi::Request::Operation::CREATE_ORDER,
CCAPI_EXCHANGE_NAME_BINANCE_USDS_FUTURES, "BTCUSD");
@@ -88,9 +85,7 @@ class Bootstrapper {
"_FY2PcXYNc8VdlfkZO5c-pkFJQFhn8P9z4_p8YA2fS0"},
});
MyEventHandler eventHandler;
ccapi::Session session(
sessionOptions, {ccapi::BtsVenues::DERIBIT}, sessionConfigs,
&eventHandler);
ccapi::Session session(sessionOptions, sessionConfigs, &eventHandler);
ccapi::Request request(
ccapi::Request::Operation::CREATE_ORDER, CCAPI_EXCHANGE_NAME_DERIBIT,
"BTC-29SEP23");
@@ -140,4 +135,4 @@ TEST_CASE("Simple", "[ccapi]") {
}
}
} // namespace bts::orderman
} // namespace bts::orderman
+1 -1
View File
@@ -26,7 +26,7 @@ boost::filesystem::path SparkDataDirectory::config_json_dir() const {
if (p.has_parent_path()) {
p = p.parent_path();
}
return p / "conf/";
return p / "conf_json/";
}
boost::filesystem::path SparkDataDirectory::config_json_file(
+1 -1
View File
@@ -73,7 +73,7 @@ std::optional<uint64_t> IDGenerator::get_id(
return std::optional<uint64_t>(rowid);
} else {
if (add_if_new) {
auto all = me->kv_store->retrieve_all();
// auto all = me->kv_store->retrieve_all();
auto new_id = me->kv_store->insert(ns, key, "");
LOG(Gen, Diag, "IDGenerator::get_id ns: {} key: id: {}", ns, key,
new_id);
+5 -42
View File
@@ -1,10 +1,8 @@
#pragma once
#include <fstream>
#include <google/protobuf/repeated_ptr_field.h>
#include <google/protobuf/text_format.h>
#include <google/protobuf/timestamp.pb.h>
#include <nlohmann/json.hpp>
#include <fstream>
#include <string>
#ifndef _WIN32
@@ -27,16 +25,14 @@ namespace utils {
namespace pb {
BTS_EXPORT std::string to_string(google::protobuf::Message const &msg);
BTS_EXPORT google::protobuf::Timestamp to_timestamp(
datetime::PTime const &ptime);
BTS_EXPORT google::protobuf::Timestamp
to_timestamp(datetime::PTime const &ptime);
template <class T>
bool from_string(const std::string &str, T *out) {
template <class T> bool from_string(const std::string &str, T *out) {
return google::protobuf::TextFormat::ParseFromString(str, out);
}
template <class T>
T from_string(const std::string &str) {
template <class T> T from_string(const std::string &str) {
T result;
bts_assert(google::protobuf::TextFormat::ParseFromString(str, &result));
return result;
@@ -109,36 +105,3 @@ inline double to_double_time(google::protobuf::Timestamp const &t1) {
} // namespace pb
} // namespace utils
} // namespace bts
namespace google::protobuf {
template <
class T,
typename std::enable_if_t<
nlohmann::detail::has_to_json<nlohmann::json, T>::value,
int> = 0>
void to_json(
nlohmann::json &j, const google::protobuf::RepeatedPtrField<T> &vals) {
j = nlohmann::json::array();
for (const T &v : vals) {
to_json(j.emplace_back(), v);
}
}
template <
class T,
typename std::enable_if_t<
nlohmann::detail::has_to_json<nlohmann::json, T>::value,
int> = 0>
void from_json(
const nlohmann::json &j, google::protobuf::RepeatedPtrField<T> &vals) {
vals.Clear();
if (!j.is_array()) {
return;
}
for (auto it = j.begin(); it != j.end(); it++) {
it->get_to(*vals.Add());
}
}
} // namespace google::protobuf
-25
View File
@@ -1,25 +0,0 @@
#pragma once
#include <memory>
namespace bts::utils {
/**
* Simple wrapper for shared_ptr that initializes the underlying data in the
* default constructor.
*/
template <typename T>
class auto_init_shared_ptr {
public:
auto_init_shared_ptr() : value_(std::make_shared<T>()) {}
std::shared_ptr<T> &value() { return value_; }
const std::shared_ptr<T> &value() const { return value_; }
T &operator*() { return *value_; }
const T &operator*() const { return *value_; }
private:
std::shared_ptr<T> value_;
};
} // namespace bts::utils
@@ -1,17 +0,0 @@
#pragma once
#include <fmt/format.h>
#include <system_error>
template <>
struct fmt::formatter<std::error_code> {
constexpr auto parse(fmt::format_parse_context &ctx) -> decltype(ctx.end()) {
return ctx.end();
}
template <typename FormatContext>
auto format(const std::error_code &e, FormatContext &ctx) const
-> decltype(ctx.out()) {
return fmt::format_to(ctx.out(), "{}", e.message());
}
};
-57
View File
@@ -1,57 +0,0 @@
#pragma once
#include "lib/utils/bts_assert.h"
#include "lib/utils/type_name.h"
#include <atomic>
#include <boost/pool/object_pool.hpp>
namespace bts::utils {
static inline std::atomic<std::uint16_t> instance_id_counter = 0;
template <typename T>
class object_pool {
std::atomic<std::size_t> count = 0;
std::size_t max_count = 128;
std::uint16_t instance_id = ++instance_id_counter;
std::size_t log_freq = 0;
std::string name;
boost::object_pool<T> pool;
public:
object_pool() : name(bts::utils::type_name<T>()) {}
object_pool(std::string name, std::size_t max_count, std::size_t log_freq)
: name(std::move(name)), max_count(max_count), log_freq(log_freq) {}
void set_max_count(std::size_t value) { max_count = value; }
void set_log_freq(std::uint16_t value) { log_freq = value; }
T *construct() {
std::size_t n = ++count;
bts_assert_msgf(
n < max_count, "n < max_count. {} < {}. name: {}; id: {}", n,
max_count, name, instance_id);
if (log_freq == 0 || n % log_freq == 0) {
LOG(Gen, Info, "object_pool::construct. name: {}; id: {}; count: {}",
name, instance_id, n);
}
return pool.construct();
}
void destroy(T *const p) {
std::size_t n = --count;
bts_assert_msgf(
n < max_count, "n < max_count. {} < {}. name: {}; id: {}", n,
max_count, name, instance_id);
if (log_freq == 0 || n % log_freq == 0) {
LOG(Gen, Info, "object_pool::destroy. name: {}; id: {}; count: {}",
name, instance_id, n);
}
pool.destroy(p);
}
};
} // namespace bts::utils
-67
View File
@@ -1,67 +0,0 @@
#pragma once
#define PB_JSON_READ_STRING(j, dest, name, it) \
it = j.find(#name); \
if (it != j.end()) { \
dest.clear_##name(); \
it->get_to(*dest.mutable_##name()); \
}
#define PB_JSON_READ_VALUE(j, dest, name, it) \
it = j.find(#name); \
if (it != j.end()) { \
dest.set_##name(it->get<decltype(dest.name())>()); \
}
#define PB_JSON_READ_STRUCT(j, dest, name, it) \
it = j.find(#name); \
if (it != j.end()) { \
it->get_to(*dest.mutable_##name()); \
}
#define PB_JSON_READ_ARRAY(j, dest, name, it) \
it = j.find(#name); \
if (it != j.end() && it->is_array()) { \
dest.clear_##name(); \
for (auto &pb_json_array_val : *it) { \
pb_json_array_val.get_to(*dest.add_##name()); \
} \
}
#define PB_JSON_READ_ONEOF(j, dest, name, it) \
if (!parsed_oneof) { \
it = j.find(#name); \
if (it != j.end()) { \
it->get_to(*dest.mutable_##name()); \
parsed_oneof = true; \
} \
}
#define PB_JSON_READ_ONEOFS(fields) \
{ \
bool parsed_oneof = false; \
fields(PB_JSON_READ_ONEOF) \
}
#define PB_JSON_WRITE_IF_TRUE(j, src, name) \
if (src.name()) { \
j[#name] = src.name(); \
}
#define PB_JSON_WRITE_IF_NOT_ZERO(j, src, name) \
if (src.name() != 0) { \
j[#name] = src.name(); \
}
#define PB_JSON_WRITE_IF_NOT_EMPTY(j, src, name) \
if (!src.name().empty()) { \
j[#name] = src.name(); \
}
#define PB_JSON_WRITE_VALUE(j, src, name) j[#name] = src.name()
#define PB_JSON_WRITE_STRUCT(j, src, name) \
if (src.has_##name()) { \
j[#name] = src.name(); \
}
#define _PB_JSON_WRITE_ONEOF_CASE(j, src, enum_name, field_name) \
case std::remove_const< \
std::remove_reference<decltype(src)>::type>::type::k##enum_name: \
PB_JSON_WRITE_VALUE(j, src, field_name); \
break;
#define PB_JSON_WRITE_ONEOF(j, src, oneof_name, cases) \
switch (src.oneof_name##_case()) { \
cases(_PB_JSON_WRITE_ONEOF_CASE) default : break; \
}
+35 -12
View File
@@ -11,9 +11,6 @@ bool copy_to_array(
std::array<char, N> &dest,
bool null_terminate = true);
template <class Iter, class UIntType>
bool parse_uint(Iter first, Iter last, UIntType &i);
template <class UIntType>
bool parse_uint(const std::string &s, UIntType &i);
@@ -47,18 +44,44 @@ bool copy_to_array(
template <class UIntType>
inline bool parse_uint(const std::string &s, UIntType &i) {
return parse_uint(s.begin(), s.end(), i);
}
template <class Iter, class UIntType>
inline bool parse_uint(Iter first, Iter last, UIntType &i) {
i = 0;
for (; first != last; first++) {
int digit = (*first - '0');
if (digit < 0 || digit > 9) {
const auto sz = s.size();
int pow10 = 1;
for (std::size_t n = 0; n < sz; n++) {
switch (s[sz - n - 1]) {
case '0':
break;
case '1':
i += pow10;
break;
case '2':
i += 2 * pow10;
break;
case '3':
i += 3 * pow10;
break;
case '4':
i += 4 * pow10;
break;
case '5':
i += 5 * pow10;
break;
case '6':
i += 6 * pow10;
break;
case '7':
i += 7 * pow10;
break;
case '8':
i += 8 * pow10;
break;
case '9':
i += 9 * pow10;
break;
default:
return false;
}
i = i * 10 + digit;
pow10 *= 10;
}
return true;
}
-5
View File
@@ -24,10 +24,5 @@ inline const char *type_name(std::type_info const &ti) {
#endif
}
template <class T> const char *cached_type_name() {
static const char *tn = type_name<T>();
return tn;
}
} // namespace utils
} // namespace bts
@@ -1,479 +0,0 @@
#include "cross_margin_gateway.h"
#include "binance_execution/cross_margin/messages/cancel_order_response.h"
#include "binance_execution/cross_margin/messages/create_order_request.h"
#include "binance_execution/cross_margin/rest_api_client.h"
#include "binance_execution/cross_margin/side_effect_type.h"
#include "binance_execution/cross_margin/trade_data_stream/trade_data_subscription.h"
#include "binance_execution/execution_report_store.h"
#include "binance_execution/gateway_config.h"
#include "binance_execution/listen_key_requestor.h"
#include "binance_execution/order_key_map.h"
#include "binance_execution/rest_api_result.h"
#include "binance_execution/type_validators.h"
#include "lib/api/service_helpers/AdminServiceUtils.h"
#include "lib/api/services/AdminService.pb.h"
#include "lib/api/services/OrderService.h"
#include "lib/config/protos/jetfix.pb.h"
#include "lib/domain_model/Security.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/io/json_formatter.h"
#include "lib/logging/logging.h"
#include "lib/md/SecurityOracle.h"
#include "lib/mercury/events/Update.h"
#include "lib/orderman/CancelOrderMsg.h"
#include "lib/orderman/CancelReplaceOrderMsg.h"
#include "lib/orderman/CreateOrderMsg.h"
#include "lib/orderman/RFQMsg.h"
#include "lib/orderman/events/NewAdminDataEvent.h"
#include "lib/orderman/events/NewSecuritiesEvent.h"
#include "lib/orderman/orderman_utils/ExchangePosition.h"
#include "lib/orderman/orderman_utils/ExecutionReport.h"
#include "lib/orderman/orderman_utils/orderman_common.h"
#include "lib/utils/overloaded.h"
#include <boost/lockfree/spsc_queue.hpp>
#include <fmt/format.h>
#include <nlohmann/json.hpp>
#include <string>
#include <variant>
#include <vector>
using nlohmann::json;
namespace binance_execution::cross_margin {
#define ASSERT_FN(test, fn, ...) \
bts_assert_msgf(test, "BinanceCrossMargin::" fn ". " __VA_ARGS__)
#define LOG_FN(level, fn, ...) \
LOG(OrderMan, level, "BinanceCrossMargin::" fn ". " __VA_ARGS__)
#define DIAG_FN(fn, ...) LOG_FN(bts::Diag, fn, __VA_ARGS__)
#define INFO_FN(fn, ...) LOG_FN(bts::Info, fn, __VA_ARGS__)
#define WARN_FN(fn, ...) LOG_FN(bts::Warn, fn, __VA_ARGS__)
#define ERROR_FN(fn, ...) LOG_FN(bts::Error, fn, __VA_ARGS__)
#define VENUE bts::api::VENUE_BINANCE
using boost::lockfree::spsc_queue;
using bts::core::Delegate;
using bts::io::json_formatter;
using bts::orderman::CancelOrderMsg;
using bts::orderman::CancelReplaceOrderMsg;
using bts::orderman::CreateOrderMsg;
using bts::orderman::ExecutionReport;
using bts::orderman::NewAdminDataEvent;
using bts::orderman::NewSecuritiesEvent;
using bts::orderman::OrderMsg;
using bts::orderman::RFQMsg;
using std::int64_t;
using std::optional;
using std::shared_ptr;
using std::size_t;
using std::string;
using std::uint32_t;
using std::vector;
struct ExecutionGatewayCallbacks {
bts::orderman::OnExeReport exe_report;
bts::orderman::OnGwStatusChange gw_status_change;
bts::orderman::OnCancelReject cancel_reject;
bts::orderman::OnExchangePositionState exchange_position_state;
bts::orderman::OnExchangeOpenOrdersState exchange_open_orders;
};
struct CrossMarginGateway::PImpl {
explicit PImpl(
bts::md::SecurityOracle sec_oracle,
GatewayConfig config,
bts::config::JetFIX_Connection jetfix_connection)
: sec_oracle(std::move(sec_oracle)), config(std::move(config)),
jetfix_connection(std::make_shared<bts::config::JetFIX_Connection>(
std::move(jetfix_connection))),
rest_api_client(
this->config.rest_endpoint,
this->config.api_key,
this->config.secret_key),
listen_key_requestor(
[this](const auto &req) {
return rest_api_client.get_listen_key(req);
},
[this](const auto &req) {
return rest_api_client.refresh_listen_key(req);
}),
key_map(std::make_shared<OrderKeyMap>()),
orders(
[this](const std::string &exch_key) {
return *key_map->get_bts_key(exch_key);
},
this->config.account,
this->jetfix_connection) {
bts_assert(this->jetfix_connection->venue() == VENUE);
bts_assert(!this->jetfix_connection->name().empty());
pos.exchange = VENUE;
}
static constexpr const char *CANCEL_REPLACE_CANCEL_SUFFIX = "crc";
bts::md::SecurityOracle sec_oracle;
GatewayConfig config;
shared_ptr<bts::config::JetFIX_Connection> jetfix_connection;
RestApiClient rest_api_client;
optional<trade_data_stream::TradeDataSubscription> trade_data_sub;
Delegate<void(trade_data_stream::AccountUpdate)> account_update_callback;
Delegate<void(trade_data_stream::OrderUpdate)> order_update_callback;
ListenKeyRequestor listen_key_requestor;
spsc_queue<trade_data_stream::AccountUpdate> incoming_account_updates{1024};
spsc_queue<trade_data_stream::OrderUpdate> incoming_order_updates{1024};
spsc_queue<shared_ptr<const ExecutionReport>> internal_order_rejects{256};
vector<trade_data_stream::AccountUpdate> account_updates_vec;
ExecutionGatewayCallbacks callbacks;
bts::orderman::ExchangePosition pos;
bts::api::admin::BTSAccount account;
shared_ptr<OrderKeyMap> key_map;
ExecutionReportStore orders;
void start() {
INFO_FN("start");
listen_key_requestor.callback().bind<&PImpl::on_listen_key_>(*this);
listen_key_requestor.start();
}
void stop() {
INFO_FN("stop");
trade_data_sub.reset();
}
void on_account_update(trade_data_stream::AccountUpdate u) {
INFO_FN("on_account_update", "\n{:2}", json_formatter(u));
incoming_account_updates.push(std::move(u));
}
void on_order_update(trade_data_stream::OrderUpdate u) {
INFO_FN("on_order_update", "\n{:2}", json_formatter(u));
if (u.orig_client_order_id.ends_with(CANCEL_REPLACE_CANCEL_SUFFIX)) {
// This is the "canceled" order part of a cancel/replace op. This op
// generates another update without such a suffix. That's the new
// order; that's the one we want.
DIAG_FN(
"on_order_update",
"Ignoring cancel/replace cancel order update. id: {}",
u.client_order_id);
return;
}
incoming_order_updates.push(std::move(u));
}
void on_listen_key_(const string &listen_key) {
INFO_FN("on_listen_key", "value: {}", listen_key);
trade_data_sub.reset();
if (listen_key.empty()) {
LOG(OrderMan, bts::Error,
"Unable to subscribe to user data because 'listen_key' "
"is empty.");
return;
}
auto &uds = trade_data_sub.emplace(
config.ws_stream_endpoint + "/ws/" + listen_key);
uds.account_update_callback().bind<&PImpl::on_account_update>(*this);
uds.order_update_callback().bind<&PImpl::on_order_update>(*this);
uds.start();
}
void create_order(const CreateOrderMsg &m, bool is_replace = false) {
CreateOrderRequest req;
req.autoRepayAtCancel = true;
req.sideEffectType = SideEffectType::AUTO_BORROW_REPAY;
std::string err;
if (!req.init(m, err)) {
ERROR_FN(
"create_order",
"{} {} {} @ {} (id={}, type={}, tif={}). Rejected "
"internally (request not sent). {}",
m.side, m.quantity, req.symbol, m.price, m.key, m.order_type,
m.time_in_force, err);
internal_order_rejects.push(orders.reject(m, err));
return;
}
if (is_replace) {
key_map->increment_order_version(m.key, req.newClientOrderId);
}
INFO_FN("create_order", "Sending request", json_formatter(req));
const auto res = rest_api_client.create_order(req);
if (!res.ok()) {
ERROR_FN(
"create_order", "id={}. {}", req.newClientOrderId,
json_formatter(res));
internal_order_rejects.push(
orders.reject({}, json_formatter(*res.error).to_string()));
}
}
void update_order(const CancelReplaceOrderMsg &m) {
CancelOrderMsg cxl;
cxl.exchange_order_id = m.exchange_order_id;
cxl.manual_order_indicator = m.create_fields.manual_order_indicator;
cxl.key = m.create_fields.key;
cxl.routing_record = m.create_fields.routing_record;
cxl.security = m.create_fields.security;
cxl.side = m.create_fields.side;
INFO_FN("update_order", "Cancelling order");
const auto *res = cancel_order(cxl);
if (res != nullptr && res->ok()) {
INFO_FN("update_order", "Sending replacement order");
create_order(m.create_fields, true);
}
}
const RestApiResult<CancelOrderResponse> *cancel_order(
const CancelOrderMsg &m) {
CancelOrderRequest req;
std::string err;
if (!req.init(m, *key_map, err)) {
ERROR_FN(
"cancel_order", "{} {} (id={}, exch_id={}). {}", m.side,
req.symbol, m.key, m.exchange_order_id, err);
internal_order_rejects.push(orders.set_error(
m.key, "Rejected internally (request not sent). " + err));
return nullptr;
}
INFO_FN(
"cancel_order", "{} {} (id={}, exch_id={})", m.side, req.symbol,
m.key, m.exchange_order_id);
const auto &res = rest_api_client.cancel_order(req);
if (!res.ok()) {
ERROR_FN(
"cancel_order", "{} {} (id={}, exch_id={}). {}", m.side,
req.symbol, m.key, m.exchange_order_id, json_formatter(res));
internal_order_rejects.push(orders.set_error(
m.key,
fmt::format(
"Cancel request failed. {}", json_formatter(*res.error))));
}
return &res;
}
void dispatch_internal_rejects() {
shared_ptr<const ExecutionReport> curr;
while (internal_order_rejects.pop(curr)) {
callbacks.exe_report(curr, {});
}
}
void dispatch_order_updates() {
if (incoming_order_updates.empty()) {
return;
}
trade_data_stream::OrderUpdate curr;
auto &km = *key_map;
while (incoming_order_updates.pop(curr)) {
auto bts_key = km.get_bts_key(curr.client_order_id);
if (!bts_key) {
ERROR_FN(
"dispatch_order_updates",
"Failed to generate exe report. Did not resolve bts key from "
"exch clientOrderId '{}'",
curr.client_order_id);
continue;
}
auto exch_key = km.get_exch_key(*bts_key);
if (exch_key != curr.client_order_id) {
INFO_FN(
"dispatch_order_updates",
"Ignoring update w/ old clientOrderId '{}' (expected '{}', "
"exch_id={}). This is expected for CancelReplace ops.",
curr.client_order_id, curr.order_id, exch_key);
continue;
}
// orders
shared_ptr<const ExecutionReport> er_p =
orders.update(curr.client_order_id, [&](auto &er_p, bool) {
auto &er = *er_p;
std::string error;
if (!curr.to_execution_report(er, error)) {
WARN_FN(
"update", "Failed to initialize exe report. {}. {}",
error, json_formatter(curr));
return;
}
er.security = sec_oracle.get_security(
VENUE, bts::api::SECURITYTYPE_SPOT, curr.symbol);
});
const ExecutionReport &er = *er_p;
string error;
if (!validate(er, error)) {
ERROR_FN(
"dispatch_order_updates",
"Generated invalid ExecutionReport (id={}, exch_id={}). {}",
curr.client_order_id, curr.order_id, error);
continue;
}
switch (curr.order_status.value) {
case OrderStatus::FILLED:
case OrderStatus::CANCELED:
case OrderStatus::REJECTED: {
const size_t n_active = orders.erase(er.order_key);
INFO_FN(
"dispatch_order_updates",
"Order reached terminal state {} (id={}, exch_id={}). {} "
"active orders.",
curr.order_status, curr.client_order_id, curr.order_id,
n_active);
break;
}
default:
break;
}
INFO_FN(
"dispatch_order_updates",
"Firing exe report callback. {} {} {} @ {} {} (filled {}/{}, "
"id={}). {} active orders",
er.side, er.security->binance().security_desc(), er.quantity,
er.price, er.order_status, er.filled_quantity, er.quantity,
er.order_key, orders.size());
callbacks.exe_report(std::move(er_p), {});
}
}
void dispatch_account_updates() {
trade_data_stream::AccountUpdate u;
auto &acct_upds = account_updates_vec;
acct_upds.clear();
while (incoming_account_updates.pop(acct_upds.emplace_back())) {
}
acct_upds.pop_back();
if (acct_upds.empty()) {
return;
}
const auto pos_ticker_comp =
[](const bts::orderman::ExchangePosition::UnitPosition &unit_pos,
const string &ticker) {
return unit_pos.exchange_sec_desc < ticker;
};
auto &dest = pos.total_exchange_positions;
for (const auto &acct_upd : acct_upds) {
for (const auto &pos : acct_upd.balances) {
bts_assert(pos.free >= 0);
bts_assert(pos.locked >= 0);
auto it = std::lower_bound(
dest.begin(), dest.end(), pos.asset, pos_ticker_comp);
if (it == dest.end() || it->exchange_sec_desc != pos.asset) {
const auto *sec = sec_oracle.get_security(
VENUE, bts::api::SECURITYTYPE_SPOT, pos.asset);
if (sec == nullptr) {
continue;
}
bts::orderman::ExchangePosition::UnitPosition new_pos;
new_pos.exchange_sec_desc = pos.asset;
new_pos.security = sec;
new_pos.account_id = account.id();
new_pos.account_name = account.name();
it = dest.insert(it, std::move(new_pos));
}
it->position = pos.locked + pos.free;
it->avg_filled_price.value() =
std::numeric_limits<double>::quiet_NaN();
}
}
callbacks.exchange_position_state(pos);
}
};
CrossMarginGateway::CrossMarginGateway(
bts::md::SecurityOracle sec_oracle,
GatewayConfig session_cfg,
bts::config::JetFIX::Connection jetfix_connection)
: me_(std::make_unique<PImpl>(
std::move(sec_oracle),
std::move(session_cfg),
std::move(jetfix_connection))) {
bts_assert(
me_->config.account.size() <=
bts::orderman::ExecutionReport{}.account_name.value().size());
}
CrossMarginGateway::~CrossMarginGateway() = default;
void CrossMarginGateway::start() { me_->start(); }
void CrossMarginGateway::on_securities(
const bts::mercury::Update<NewSecuritiesEvent> & /*update*/) {
INFO_FN(
"on_securities", "t_id: {} t_name: {}", bts::os::get_thread_id(),
bts::os::get_thread_name());
}
void CrossMarginGateway::on_admin_data(
const bts::mercury::Update<NewAdminDataEvent> &update) {
INFO_FN(
"on_admin_data", "t_id: {} t_name: {}", bts::os::get_thread_id(),
bts::os::get_thread_name());
if (update.events == nullptr || update.events->empty()) {
return;
}
const auto *account = bts::admin::AdminServiceUtils::find_account(
*update.events->back().admin_data, me_->config.account);
if (account == nullptr) {
WARN_FN(
"on_admin_data",
"Gateway account not found. Cannot execute orders. name: '{}'",
me_->config.account);
return;
}
if (me_->account.name() == account->name() &&
me_->account.id() == account->id()) {
return;
}
me_->account.CopyFrom(*account);
INFO_FN("on_admin_data", "Resolved gateway account '{}'", account->name());
}
void CrossMarginGateway::send_msgs(
vector<OrderMsg> const &msg_vec, bts::trace::CorID) {
INFO_FN("send_msgs", "count: {}", msg_vec.size());
for (const auto &msg : msg_vec) {
std::visit(
bts::overloaded{
[&](const CreateOrderMsg &m) { me_->create_order(m); },
[&](const CancelReplaceOrderMsg &m) { me_->update_order(m); },
[&](const CancelOrderMsg &m) { me_->cancel_order(m); },
[&](const RFQMsg &) {
bts_assert_msg(false, "Binance RFQ not implemented");
},
},
msg);
}
}
void CrossMarginGateway::send_fix(
const bts::jetfix::OutFIXPtr &, bts::trace::CorID) {
bts_assert_msg(false, "Binance CrossMarginGateway does not support fix");
}
void CrossMarginGateway::dispatch_incoming_fix_messages() {
me_->dispatch_internal_rejects();
me_->dispatch_order_updates();
me_->dispatch_account_updates();
}
// Callbacks
bts::orderman::OnExeReport &CrossMarginGateway::on_exe_report() {
return me_->callbacks.exe_report;
}
bts::orderman::OnGwStatusChange &CrossMarginGateway::on_gw_status_change() {
return me_->callbacks.gw_status_change;
}
bts::orderman::OnCancelReject &CrossMarginGateway::on_cancel_reject() {
return me_->callbacks.cancel_reject;
}
bts::orderman::OnExchangePositionState &
CrossMarginGateway::on_exchange_position_state() {
return me_->callbacks.exchange_position_state;
}
bts::orderman::OnExchangeOpenOrdersState &
CrossMarginGateway::on_exchange_open_orders() {
return me_->callbacks.exchange_open_orders;
}
} // namespace binance_execution::cross_margin
@@ -1,53 +0,0 @@
#pragma once
#include "lib/orderman/gateways/IExecutionGateway.h"
#include <memory>
namespace bts::config {
class JetFIX_Connection;
}
namespace bts::md {
class SecurityOracle;
}
namespace binance_execution {
struct GatewayConfig;
}
namespace binance_execution::cross_margin {
class CrossMarginGateway : public bts::orderman::IExecutionGateway {
public:
explicit CrossMarginGateway(
bts::md::SecurityOracle sec_oracle,
GatewayConfig session_cfg,
bts::config::JetFIX_Connection jetfix_connection);
~CrossMarginGateway() override;
void start() override;
void dispatch_incoming_fix_messages() override;
void on_securities(
const bts::mercury::Update<bts::orderman::NewSecuritiesEvent> &)
override;
void on_admin_data(
const bts::mercury::Update<bts::orderman::NewAdminDataEvent> &) override;
void send_fix(const bts::jetfix::OutFIXPtr &, bts::trace::CorID) override;
void send_msgs(
std::vector<bts::orderman::OrderMsg> const &,
bts::trace::CorID) override;
bts::orderman::OnExeReport &on_exe_report() override;
bts::orderman::OnGwStatusChange &on_gw_status_change() override;
bts::orderman::OnCancelReject &on_cancel_reject() override;
bts::orderman::OnExchangePositionState &
on_exchange_position_state() override;
bts::orderman::OnExchangeOpenOrdersState &on_exchange_open_orders() override;
private:
struct PImpl;
std::unique_ptr<PImpl> me_;
};
} // namespace binance_execution::cross_margin
@@ -1,96 +0,0 @@
#include "cancel_order_request.h"
#include "binance_execution/error_list.h"
#include "binance_execution/order_key_map.h"
#include "binance_execution/type_converters.h"
#include "binance_execution/type_validators.h"
#include "lib/io/JsonReader.h"
#include "lib/orderman/CancelOrderMsg.h"
#include "lib/utils/checked_arithmetic.h"
namespace binance_execution::cross_margin {
bool CancelOrderRequest::init(
const bts::orderman::CancelOrderMsg &m,
OrderKeyMap &key_map,
std::string &error) {
const std::size_t orig_error_sz = error.size();
if (validate(m.security, bts::api::VENUE_BINANCE, error)) {
symbol = m.security->binance().security_desc();
}
orderId = bts::system::math::checked_ultol(m.exchange_order_id.value());
origClientOrderId = key_map.get_exch_key(m.key);
// The exchange accepts either orderId, or origClientOrderId (only one
// need be populated).
if (orderId == 0 && origClientOrderId.empty()) {
ErrorList::append(
error, "Either orderId or origClientOrderId must be defined");
} else {
// Can't reuse the same order id (only increment order version
// when init is successful, as this means the order will be sent).
key_map.increment_order_version(m.key);
newClientOrderId = key_map.get_exch_key(m.key);
}
timestamp = convert_time_stamp(bts::core::TimeStamp::now());
return error.size() == orig_error_sz;
}
void to_json(nlohmann::json &j, const CancelOrderRequest &req) {
j["symbol"] = req.symbol;
if (req.isIsolated) {
j["isIsolated"] = true;
}
if (req.orderId != 0) {
j["orderId"] = req.orderId;
}
if (!req.origClientOrderId.empty()) {
j["origClientOrderId"] = req.origClientOrderId;
}
if (!req.newClientOrderId.empty()) {
j["newClientOrderId"] = req.newClientOrderId;
}
if (req.recvWindow > 0) {
j["recvWindow"] = req.newClientOrderId;
}
if (req.timestamp > 0) {
j["timestamp"] = req.timestamp;
}
}
void from_json(const nlohmann::json &j, CancelOrderRequest &req) {
const bts::io::JsonReader jr{&j};
j.at("symbol").get_to(req.symbol);
jr.read_or("isIsolated", req.isIsolated);
jr.read_or("orderId", req.orderId);
jr.read_or("origClientOrderId", req.origClientOrderId);
jr.read_or("newClientOrderId", req.newClientOrderId);
jr.read_or("recvWindow", req.recvWindow);
j.at("timestamp").get_to(req.timestamp);
}
void to_query_params(std::string &qps, const CancelOrderRequest &req) {
qps.append("symbol=");
qps.append(req.symbol);
if (req.isIsolated) {
qps.append("&isIsolated=true");
}
if (req.orderId != 0) {
qps.append("&orderId=");
qps.append(std::to_string(req.orderId));
}
if (!req.origClientOrderId.empty()) {
qps.append("&origClientOrderId=");
qps.append(req.origClientOrderId);
}
if (!req.newClientOrderId.empty()) {
qps.append("&newClientOrderId=");
qps.append(req.newClientOrderId);
}
if (req.recvWindow > 0) {
qps.append("&recvWindow=");
qps.append(std::to_string(req.recvWindow));
}
qps.append("&timestamp=");
qps.append(std::to_string(req.timestamp));
}
} // namespace binance_execution::cross_margin
@@ -1,39 +0,0 @@
#pragma once
#include <nlohmann/json.hpp>
#include <string>
namespace bts::orderman {
struct CancelOrderMsg;
}
namespace binance_execution {
struct OrderKeyMap;
}
namespace binance_execution::cross_margin {
/**
* @a https://developers.binance.com/docs/margin_trading/trade/Margin-Account-Cancel-Order
*/
struct CancelOrderRequest {
std::string symbol;
bool isIsolated = false;
std::int64_t orderId;
std::string origClientOrderId;
std::string newClientOrderId;
std::int64_t timestamp = 0;
std::int64_t recvWindow = 0;
bool init(
const bts::orderman::CancelOrderMsg &m,
OrderKeyMap &key_map,
std::string &error);
};
void to_json(nlohmann::json &j, const CancelOrderRequest &req);
void from_json(const nlohmann::json &j, CancelOrderRequest &req);
void to_query_params(std::string &query_params, const CancelOrderRequest &req);
} // namespace binance_execution::cross_margin
@@ -1,67 +0,0 @@
#include "cancel_order_response.h"
#include "binance_execution/enums/order_status.h"
#include "binance_execution/enums/order_type.h"
#include "binance_execution/enums/time_in_force.h"
#include "lib/io/JsonReader.h"
namespace binance_execution::cross_margin {
void to_json(nlohmann::json &j, const CancelOrderResponse &res) {
j["symbol"] = res.symbol;
if (res.isIsolated) {
j["isIsolated"] = true;
}
j["orderId"] = res.orderId;
if (!res.origClientOrderId.empty()) {
j["origClientOrderId"] = res.origClientOrderId;
}
if (!res.clientOrderId.empty()) {
j["clientOrderId"] = res.clientOrderId;
}
if (!std::isnan(res.price)) {
j["price"] = res.price;
}
if (!std::isnan(res.origQty)) {
j["origQty"] = res.origQty;
}
if (!std::isnan(res.executedQty)) {
j["executedQty"] = res.executedQty;
}
if (!std::isnan(res.cummulativeQuoteQty)) {
j["cummulativeQuoteQty"] = res.cummulativeQuoteQty;
}
if (res.status != OrderStatus::NONE) {
j["status"] = res.status;
}
if (res.timeInForce != TimeInForce::NONE) {
j["timeInForce"] = res.timeInForce;
}
if (res.type != OrderType::NONE) {
j["type"] = res.type;
}
if (res.side != Side::NONE) {
j["side"] = res.side;
}
}
void from_json(const nlohmann::json &j, CancelOrderResponse &res) {
j.at("symbol").get_to(res.symbol);
auto it = j.find("isIsolated");
if (it != j.end()) {
it->get_to(res.isIsolated);
}
const bts::io::JsonReader jr{&j};
jr.read("orderId", res.orderId);
jr.read("origClientOrderId", res.origClientOrderId);
jr.read("clientOrderId", res.clientOrderId);
jr.read("price", res.price);
jr.read("origQty", res.origQty);
jr.read("executedQty", res.executedQty);
jr.read("cummulativeQuoteQty", res.cummulativeQuoteQty);
jr.read("status", res.status);
jr.read("timeInForce", res.timeInForce);
jr.read("type", res.type);
jr.read("side", res.side);
}
} // namespace binance_execution::cross_margin
@@ -1,38 +0,0 @@
#pragma once
#include "binance_execution/enums/order_status.h"
#include "binance_execution/enums/order_type.h"
#include "binance_execution/enums/side.h"
#include "binance_execution/enums/time_in_force.h"
#include <limits>
#include <nlohmann/json.hpp>
#include <string>
namespace binance_execution::cross_margin {
/**
* @a https://developers.binance.com/docs/margin_trading/trade/Margin-Account-Cancel-Order
* @a https://developers.binance.com/docs/margin_trading/trade/Margin-Account-Cancel-Order#response-example
*/
struct CancelOrderResponse {
std::string symbol;
bool isIsolated = false;
std::int64_t orderId = 0;
std::string origClientOrderId;
std::string clientOrderId;
double price = std::numeric_limits<double>::quiet_NaN();
double origQty = std::numeric_limits<double>::quiet_NaN();
double executedQty = std::numeric_limits<double>::quiet_NaN();
// Note: 'cummulative' is deliberately mispelled to match the json payload.
double cummulativeQuoteQty = std::numeric_limits<double>::quiet_NaN();
OrderStatus status = OrderStatus::NONE;
TimeInForce timeInForce = TimeInForce::NONE;
OrderType type = OrderType::NONE;
Side side = Side::NONE;
};
void to_json(nlohmann::json &j, const CancelOrderResponse &res);
void from_json(const nlohmann::json &j, CancelOrderResponse &res);
} // namespace binance_execution::cross_margin
@@ -1,49 +0,0 @@
// #pragma once
// #include "binance_execution/enums/new_order_response_type.h"
// #include "binance_execution/enums/order_type.h"
// #include "binance_execution/enums/side.h"
// #include "binance_execution/enums/time_in_force.h"
// #include <nlohmann/json.hpp>
// #include <string>
// namespace bts::orderman {
// struct CreateOrderMsg;
// }
// namespace binance_execution::cross_margin {
// /**
// * @a https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#new-order-trade
// */
// struct CreateOcoRequest {
// std::string symbol;
// std::string listClientOrderId;
// Side side = Side::NONE;
// double quantity = 0;
// std::string limitClientOrderId;
// double price = std::numeric_limits<double>::quiet_NaN();
// double limitIcebergQty = 0;
// std::string stopClientOrderId;
// double stopPrice;
// double stopLimitPrice;
// double stopIcebergQty;
// TimeInForce stopLimitTimeInForce;
// NewOrderResponseType newOrderRespType;
// OrderType type = OrderType::NONE;
// std::int64_t timestamp = 0;
// std::string newClientOrderId;
// TimeInForce timeInForce = TimeInForce::NONE;
// std::int64_t recvWindow = 0;
// NewOrderResponseType newOrderRespType = NewOrderResponseType::NONE;
// bool init(const bts::orderman::CreateOrderMsg &m, std::string &error);
// };
// void to_json(nlohmann::json &j, const CreateOcoRequest &req);
// void from_json(const nlohmann::json &j, CreateOcoRequest &req);
// void to_query_params(std::string &query_params, const CreateOcoRequest &req);
// } // namespace binance_execution::spots
@@ -1,201 +0,0 @@
#include "create_order_request.h"
#include "binance_execution/enums/new_order_response_type.h"
#include "binance_execution/price.h"
#include "binance_execution/quantity.h"
#include "binance_execution/type_converters.h"
#include "binance_execution/type_validators.h"
#include "lib/io/JsonReader.h"
#include "lib/orderman/CreateOrderMsg.h"
namespace binance_execution::cross_margin {
bool CreateOrderRequest::init(
const bts::orderman::CreateOrderMsg &m, std::string &error) {
const std::size_t orig_error_sz = error.size();
newClientOrderId = std::to_string(m.key.value());
if (validate(m.security, bts::api::VENUE_BINANCE, error)) {
symbol = m.security->binance().security_desc();
}
convert_side(m.side, side, error);
price = m.price.value();
quantity = m.quantity.full_value();
convert_order_type(m.order_type, type, error);
convert_time_in_force(m.time_in_force, timeInForce, error);
if (error.size() != orig_error_sz) {
return false;
}
timestamp = convert_time_stamp(bts::core::TimeStamp::now());
return true;
}
void to_json(nlohmann::json &j, const CreateOrderRequest &req) {
j["symbol"] = req.symbol;
j["side"] = req.side;
j["type"] = req.type;
j["timestamp"] = req.timestamp;
if (req.isIsolated) {
j["isIsolated"] = req.isIsolated;
}
std::string str;
if (req.quantity > 0) {
quantity_to_string(req.quantity, str);
j["quantity"] = str;
str.clear();
}
if (req.quoteOrderQty > 0) {
quantity_to_string(req.quoteOrderQty, str);
j["quoteOrderQty"] = str;
str.clear();
}
if (!std::isnan(req.price)) {
price_to_string(req.price, str);
j["price"] = str;
str.clear();
}
if (!std::isnan(req.stopPrice)) {
price_to_string(req.stopPrice, str);
j["stopPrice"] = str;
str.clear();
}
if (!req.newClientOrderId.empty()) {
j["newClientOrderId"] = req.newClientOrderId;
}
if (req.icebergQty > 0) {
quantity_to_string(req.icebergQty, str);
j["icebergQty"] = str;
str.clear();
}
if (req.newOrderRespType != NewOrderResponseType::NONE) {
j["newOrderRespType"] = req.newOrderRespType;
}
if (req.sideEffectType != SideEffectType::NO_SIDE_EFFECT) {
j["sideEffectType"] = req.sideEffectType;
}
if (req.timeInForce != TimeInForce::NONE) {
j["timeInForce"] = req.timeInForce;
}
if (req.selfTradePreventionMode != SelfTradePreventionMode::NONE) {
j["selfTradePreventionMode"] = req.selfTradePreventionMode;
}
if (req.autoRepayAtCancel) {
j["autoRepayAtCancel"] = req.autoRepayAtCancel;
}
if (req.recvWindow > 0) {
j["recvWindow"] = std::to_string(req.recvWindow);
}
}
void from_json(const nlohmann::json &j, CreateOrderRequest &req) {
j.at("symbol").get_to(req.symbol);
j.at("side").get_to(req.side);
j.at("type").get_to(req.type);
j.at("timestamp").get_to(req.timestamp);
auto it = j.find("isIsolated");
bts::io::JsonReader jr{&j};
if (it != j.end()) {
it->get_to(req.isIsolated);
}
jr.read("quantity", req.price);
jr.read("quoteOrderQty", req.price);
jr.read("price", req.price);
jr.read("stopPrice", req.price);
it = j.find("newClientOrderId");
if (it != j.end()) {
it->get_to(req.newClientOrderId);
}
jr.read("icebergQty", req.price);
if (it != j.end()) {
it->get_to(req.icebergQty);
}
it = j.find("newOrderRespType");
if (it != j.end()) {
it->get_to(req.newOrderRespType);
}
it = j.find("sideEffectType");
if (it != j.end()) {
it->get_to(req.sideEffectType);
}
it = j.find("timeInForce");
if (it != j.end()) {
it->get_to(req.timeInForce);
}
it = j.find("selfTradePreventionMode");
if (it != j.end()) {
it->get_to(req.selfTradePreventionMode);
}
it = j.find("isIsolated");
if (it != j.end()) {
it->get_to(req.isIsolated);
}
it = j.find("autoRepayAtCancel");
if (it != j.end()) {
it->get_to(req.autoRepayAtCancel);
}
it = j.find("recvWindow");
if (it != j.end()) {
it->get_to(req.recvWindow);
}
}
void to_query_params(std::string &qps, const CreateOrderRequest &req) {
qps.append("symbol=");
qps.append(req.symbol);
qps.append("&side=");
qps.append(req.side.name());
qps.append("&type=");
qps.append(req.type.name());
qps.append("&timestamp=");
qps.append(std::to_string(req.timestamp));
if (req.isIsolated) {
qps.append("&isIsolated=true");
}
if (req.quantity > 0) {
qps.append("&quantity=");
quantity_to_string(req.quantity, qps);
}
if (req.quoteOrderQty > 0) {
qps.append("&quoteOrderQty=");
quantity_to_string(req.quoteOrderQty, qps);
}
if (!std::isnan(req.price)) {
qps.append("&price=");
price_to_string(req.price, qps);
}
if (!std::isnan(req.stopPrice)) {
qps.append("&stopPrice=");
price_to_string(req.stopPrice, qps);
}
if (!req.newClientOrderId.empty()) {
qps.append("&newClientOrderId=");
qps.append(req.newClientOrderId);
}
if (req.icebergQty > 0) {
qps.append("&icebergQty=");
quantity_to_string(req.icebergQty, qps);
}
if (req.newOrderRespType != NewOrderResponseType::NONE) {
qps.append("&newOrderRespType=");
qps.append(req.newOrderRespType.name());
}
if (req.sideEffectType != SideEffectType::NO_SIDE_EFFECT) {
qps.append("&sideEffectType=");
qps.append(req.sideEffectType.name());
}
if (req.timeInForce != TimeInForce::NONE) {
qps.append("&timeInForce=");
qps.append(req.timeInForce.name());
}
if (req.selfTradePreventionMode != SelfTradePreventionMode::NONE) {
qps.append("&selfTradePreventionMode=");
qps.append(req.selfTradePreventionMode.name());
}
if (req.autoRepayAtCancel) {
qps.append("&autoRepayAtCancel=true");
}
if (req.recvWindow > 0) {
qps.append("&recvWindow=");
qps.append(std::to_string(req.recvWindow));
}
}
} // namespace binance_execution::cross_margin
@@ -1,51 +0,0 @@
#pragma once
#include "binance_execution/cross_margin/side_effect_type.h"
#include "binance_execution/enums/new_order_response_type.h"
#include "binance_execution/enums/order_type.h"
#include "binance_execution/enums/self_trade_prevention_mode.h"
#include "binance_execution/enums/side.h"
#include "binance_execution/enums/time_in_force.h"
#include <limits>
#include <nlohmann/json.hpp>
#include <string>
namespace bts::orderman {
struct CreateOrderMsg;
}
namespace binance_execution::cross_margin {
/**
* @a https://developers.binance.com/docs/margin_trading/trade/Margin-Account-New-Order
*/
struct CreateOrderRequest {
std::string symbol;
bool isIsolated = false;
Side side;
OrderType type;
double quantity = std::numeric_limits<double>::quiet_NaN();
double quoteOrderQty = std::numeric_limits<double>::quiet_NaN();
double price = std::numeric_limits<double>::quiet_NaN();
double stopPrice = std::numeric_limits<double>::quiet_NaN();
std::string newClientOrderId;
double icebergQty = std::numeric_limits<double>::quiet_NaN();
NewOrderResponseType newOrderRespType = NewOrderResponseType::NONE;
SideEffectType sideEffectType = SideEffectType::NO_SIDE_EFFECT;
TimeInForce timeInForce = TimeInForce::NONE;
SelfTradePreventionMode selfTradePreventionMode =
SelfTradePreventionMode::NONE;
bool autoRepayAtCancel = false;
std::int64_t timestamp = 0;
std::int64_t recvWindow = 0;
bool init(const bts::orderman::CreateOrderMsg &m, std::string &error);
};
void to_json(nlohmann::json &j, const CreateOrderRequest &req);
void from_json(const nlohmann::json &j, CreateOrderRequest &req);
void to_query_params(std::string &query_params, const CreateOrderRequest &req);
} // namespace binance_execution::cross_margin
Loaded 100 of 223 files, more files were not shown because too many files have changed in this diff. Show more