Compare commits
52
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
021431b567 | ||
|
|
e7b426cd3d | ||
|
|
ee02c3ffa7 | ||
|
|
21b725e5df | ||
|
|
649931ae3c | ||
|
|
7b5f034ed2 | ||
|
|
74b376ce56 | ||
|
|
fc96b9e436 | ||
|
|
fd015577b2 | ||
|
|
8d142b8a74 | ||
|
|
8210b9f35f | ||
|
|
12b20b05ce | ||
|
|
72efbccdbd | ||
|
|
33643e9eab | ||
|
|
0f6dc6f40b | ||
|
|
8c778f5e41 | ||
|
|
f308654200 | ||
|
|
bdf5b980fc | ||
|
|
914ac597cd | ||
|
|
4aa6a77680 | ||
|
|
6b59dd84ee | ||
|
|
4e2c5f2d17 | ||
|
|
9a1b94a1b0 | ||
|
|
13b3c55771 | ||
|
|
d0a6fcdb9c | ||
|
|
1eb13028c1 | ||
|
|
424e5ff16b | ||
|
|
bed389885b | ||
|
|
85cf25a57a | ||
|
|
61471d4675 | ||
|
|
d83435d49d | ||
|
|
582fc10dce | ||
|
|
5969985d24 | ||
|
|
fabe7d665c | ||
|
|
7ce75bb8e7 | ||
|
|
b8e1c3cd60 | ||
|
|
4909944694 | ||
|
|
7258af09b4 | ||
|
|
7f2ce6c1a9 | ||
|
|
4835725e1c | ||
|
|
6a9e8de475 | ||
|
|
4639951acc | ||
|
|
d8de211aac | ||
|
|
87394bc60b | ||
|
|
beba30f65c | ||
|
|
2d58424d8f | ||
|
|
d1bbfa3791 | ||
|
|
aab17f460f | ||
|
|
461ef53322 | ||
|
|
2486bc90b2 | ||
|
|
1c5cbaf60b | ||
|
|
b8de447cfb |
No files matched your search
@@ -1,15 +1,10 @@
|
||||
{
|
||||
"dockerFile": "Dockerfile",
|
||||
// Creates local dir to mount to HOME in docker
|
||||
"initializeCommand": "mkdir -p ${localEnv:HOME}/.bts/devcontainer-mounts/${devcontainerId}",
|
||||
"hostRequirements": {
|
||||
"memory": "4gb",
|
||||
"storage": "6gb"
|
||||
"memory": "8gb",
|
||||
"storage": "10gb"
|
||||
},
|
||||
"postCreateCommand": "/home/bts/spark/docker/sparkd/dev/devcontainer_post_create.sh",
|
||||
"containerUser": "bts",
|
||||
"workspaceMount": "source=${localWorkspaceFolder},target=/home/bts/spark,type=bind",
|
||||
"workspaceFolder": "/home/bts/spark",
|
||||
"updateRemoteUserUID": true,
|
||||
"shutdownAction": "none",
|
||||
// Note: If running multiple containers on the same host, each must use a
|
||||
@@ -17,7 +12,7 @@
|
||||
// example, if 8000-8003 is taken, go with 9000-9003, then 10000-10003, etc.
|
||||
"runArgs": [
|
||||
"--shm-size",
|
||||
"2gb",
|
||||
"8gb",
|
||||
"-p",
|
||||
"8000:8000",
|
||||
"-p",
|
||||
@@ -29,22 +24,15 @@
|
||||
"-p",
|
||||
"12345:12345"
|
||||
],
|
||||
"mounts": [
|
||||
"source=${localEnv:HOME}/.bts/devcontainer-mounts/${devcontainerId},target=/home/bts,type=bind"
|
||||
],
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"clangd.arguments": [
|
||||
"--compile-commands-dir=${workspaceFolder}/build/clang/debug"
|
||||
]
|
||||
},
|
||||
"extensions": [
|
||||
"eamodio.gitlens",
|
||||
"llvm-vs-code-extensions.vscode-clangd",
|
||||
"ms-vscode.cmake-tools",
|
||||
"ms-vscode.cpptools"
|
||||
"ms-vscode.cpptools",
|
||||
"xaver.clang-format"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -133,16 +133,31 @@ namespace bts {
|
||||
|
||||
// set 'unit quantity' for each sec
|
||||
foreach (var smd in exch_secs.Values) {
|
||||
var meta = _security_metadata.get(smd.security);
|
||||
if (meta.HasValue) {
|
||||
exch_secs[smd.id] = new(
|
||||
smd.security,
|
||||
smd.md,
|
||||
meta.Value.HasPriceDisplay ? meta.Value.PriceDisplay : null,
|
||||
meta.Value.HasQuantityDisplay ? meta.Value.QuantityDisplay : null);
|
||||
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);
|
||||
}
|
||||
|
||||
(TickDisplaySettingsImmutable? Price, TickDisplaySettingsImmutable? Quantity) GetDisplaySettings(SecurityImmutable sec) {
|
||||
var meta = _security_metadata.get(sec);
|
||||
var priceDisplay = meta?.PriceDisplay ?? Security.Defaults.price_display_settingsi;
|
||||
var qtyDisplay = meta?.QuantityDisplay ?? Security.Defaults.quantity_display_settingsi;
|
||||
if (sec.HasContractSpec) {
|
||||
var pd = priceDisplay.CloneToMutable();
|
||||
pd.TickFactors = TickUtils.get_tick(sec, 0).to_api_tick_factors();
|
||||
priceDisplay = new(pd);
|
||||
if (sec.ContractSpec.QuantityInfos.TryGetFirst(out var qtyInfo)) {
|
||||
var qd = qtyDisplay.CloneToMutable();
|
||||
qd.ShowDecimalPart = qtyInfo.TickBase.is_real_non_zero() && !(qtyInfo.TickUnit / qtyInfo.TickBase).is_int();
|
||||
qd.TickFactors ??= new();
|
||||
qd.TickFactors.TickBase = qtyInfo.TickBase;
|
||||
qd.TickFactors.TickUnit = qtyInfo.TickUnit;
|
||||
qtyDisplay = new(qd);
|
||||
}
|
||||
}
|
||||
_bus.post(SparkTags.ServerSecuritiesTag, exch_secs);
|
||||
return (priceDisplay, qtyDisplay);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,13 @@ namespace bts.shell.market_view {
|
||||
class MarketViewFilterStates {
|
||||
[ProtoMember(37)]
|
||||
public Venue[] venue { get; set; }
|
||||
|
||||
[ProtoMember(38)]
|
||||
public string[] currency { get; set; }
|
||||
|
||||
[ProtoMember(1)]
|
||||
public string security { get; set; }
|
||||
|
||||
[ProtoMember(2)]
|
||||
public DoubleMinMaxState bid_price { get; set; }
|
||||
|
||||
@@ -128,6 +133,7 @@ namespace bts.shell.market_view {
|
||||
var pb = new ColumnPredicateBuilder<MarketViewVM.Row>();
|
||||
// security
|
||||
pb.add_discrete_values(venue, r => r.venue);
|
||||
pb.add_discrete_values(currency, r => r.currency);
|
||||
pb.add_regex(security, r => r.security.ToString());
|
||||
pb.add_regex(security_key, r => r.security_key.ToString());
|
||||
|
||||
|
||||
@@ -98,7 +98,14 @@ namespace mbts.spark {
|
||||
_sec_meta_store = sec_meta_store;
|
||||
_settings_hub = settings_hub;
|
||||
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
|
||||
currency_filter =new RegexFilterVM<Row>(r => r.currency, nameof(Row.currency));
|
||||
currency_filter = DiscreteValuesFilterVM.Create<Row, string>(
|
||||
bus.subscribe(SparkTags.SecuritiesTag)
|
||||
.Sample(TimeSpan.FromSeconds(1))
|
||||
.Select(sec_mds => sec_mds.Values.Select(smd => smd.security.Currency).Distinct().ToList()),
|
||||
r => r.currency,
|
||||
nameof(Row.currency),
|
||||
EqualityComparer<string>.Default,
|
||||
Comparer<string>.Default);
|
||||
// rows
|
||||
_wrapped_rows = _rows.WrapSelector(r => r.security_md, s => {
|
||||
var r = new Row(_bus, s, _orders_model, _position_model);
|
||||
@@ -124,7 +131,7 @@ namespace mbts.spark {
|
||||
|
||||
add_col(ColKey.Venue, r => r.venue.ToString(), nameof(Row.venue), venue_filter, FilterType.DiscreteValues, ColWidth.Exchange);
|
||||
add_col(ColKey.Security, r => r.security.ToString(), nameof(Row.security), security_filter, FilterType.Regex, ColWidth.Security);
|
||||
add_col(ColKey.Currency, r => r.currency, nameof(Row.currency), currency_filter, FilterType.Regex, ColWidth.Currency);
|
||||
add_col(ColKey.Currency, r => r.currency, nameof(Row.currency), currency_filter, FilterType.DiscreteValues, ColWidth.Currency);
|
||||
add_col(ColKey.Volume, r => r.volume_text, nameof(Row.volume), volume_filter, FilterType.ULongMinMax, ColWidth.Volume);
|
||||
|
||||
// last
|
||||
@@ -391,7 +398,7 @@ namespace mbts.spark {
|
||||
|
||||
public IDataGridColumnPersistentFilter<Row, Venue[]> venue_filter { get; } = DiscreteValuesFilterVM.Common.exchange<Row>(r => r.venue, nameof(Row.venue));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, string> currency_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, string[]> currency_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<Row>(r => r.bid_price, nameof(Row.bid_price));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_qty_filter { get; } = new DoubleMinMax<Row>(r => r.bid_qty_display, nameof(Row.bid_qty_display));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> dbid_qty_filter { get; } = new DoubleMinMax<Row>(r => r.dbid_qty_display, nameof(Row.dbid_qty_display));
|
||||
@@ -538,6 +545,7 @@ namespace mbts.spark {
|
||||
public MarketViewFilterStates get_filter_states() =>
|
||||
new() {
|
||||
venue = venue_filter.get_state(),
|
||||
currency = currency_filter.get_state(),
|
||||
security = security_filter.get_state(),
|
||||
bid_price = bid_price_filter.get_state(),
|
||||
bid_merged_size = bid_qty_filter.get_state(),
|
||||
@@ -584,6 +592,7 @@ namespace mbts.spark {
|
||||
|
||||
using var _ = rows_view.DeferRefresh();
|
||||
venue_filter.load_state(filters.venue);
|
||||
currency_filter.load_state(filters.currency);
|
||||
security_filter.load_state(filters.security);
|
||||
bid_price_filter.load_state(filters.bid_price);
|
||||
bid_qty_filter.load_state(filters.bid_merged_size);
|
||||
|
||||
@@ -1,378 +1,380 @@
|
||||
using bts.api;
|
||||
using bts.utils;
|
||||
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
|
||||
using Spark.Core.Collections;
|
||||
using Spark.Core.Data;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
|
||||
using PNLKey = bts.AccountSecurityKey;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLRowVM : DisposableViewModelBase, IEquatable<PNLRowVM> {
|
||||
public static readonly IComparer<PNLRowVM> key_comparer = Comparer<PNLKey>.Default.wrap<PNLKey, PNLRowVM>(r => r.key);
|
||||
static readonly Func<bool, string> _fmt_has_day_trades = hdt => hdt ? "Yes" : "No";
|
||||
//
|
||||
readonly PNLCellIndexBuilder<PNLRowData> _cell_map = new PNLCellIndexBuilder<PNLRowData>();
|
||||
readonly PNLCellEmbeddedAnimationModel<PNLRowData> _animator;
|
||||
readonly ISecurityMetadataStore _sec_meta_store;
|
||||
bool _refresh_normalized_data = true, _is_selected;
|
||||
PNLNormalizedData _normalized_data;
|
||||
api.domain.TickDisplaySettingsImmutable? _quantity_display_backing;
|
||||
|
||||
public PNLRowVM(
|
||||
PNLRowData data,
|
||||
ISecurityMetadataStore sec_meta_store,
|
||||
ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances,
|
||||
IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers,
|
||||
Func<double, string> fmt_fill_price,
|
||||
Func<double, string> fmt_market_price) {
|
||||
//
|
||||
this.data = data;
|
||||
_sec_meta_store = sec_meta_store;
|
||||
_animator = new PNLCellEmbeddedAnimationModel<PNLRowData>(default_appearances, triggers);
|
||||
_animator.on_row(data);
|
||||
pnl_price = _cell_map.add_price_double(ColumnKeys.PNLPrice, d => d.pnl_price);
|
||||
account_cell = _cell_map.add(ColumnKeys.Account, d => d.account);
|
||||
security_type_cell = _cell_map.add(ColumnKeys.SecurityType, d => d.security?.ContractCase ?? Security.ContractOneofCase.None);
|
||||
exchange_cell = _cell_map.add(ColumnKeys.Exchange, d => d.security?.Exchange ?? Venue.Unset);
|
||||
expiration_cell = _cell_map.add(
|
||||
ColumnKeys.Expiration,
|
||||
d => d.expiration,
|
||||
e => SecurityFormatter.format_expiration(e));
|
||||
symbol_cell = _cell_map.add(ColumnKeys.Symbol, d => d.symbol);
|
||||
security_cell = _cell_map.add(
|
||||
ColumnKeys.Security,
|
||||
d => d.security,
|
||||
null,
|
||||
EqualityComparer<SecurityImmutable?>.Default,
|
||||
Comparer<SecurityImmutable?>.Default);
|
||||
security_key = _cell_map.add(ColumnKeys.SecurityKey, d => d.key.security_id);
|
||||
cpp = _cell_map.add(ColumnKeys.CPP, d => d.effective_cpp);
|
||||
// sod
|
||||
sod_pnl = _cell_map.add_pnl_double(ColumnKeys.SODPNL, d => d.sod_pnl);
|
||||
sod_net_pos = _cell_map.add_net_pos(
|
||||
ColumnKeys.SODNetPos, d => d.display_sod_net_pos, fmt_qty);
|
||||
sod_long_pos = _cell_map.add_long_pos(
|
||||
ColumnKeys.SODLongPos, d => d.display_sod_long_pos, fmt_qty);
|
||||
sod_long_avg_price = _cell_map.add_long_price(ColumnKeys.SODLongAvgPrice, d => d.sod_long_avg_price);
|
||||
sod_short_pos = _cell_map.add_short_pos(
|
||||
ColumnKeys.SODShortPos, d => d.display_sod_short_pos, fmt_qty);
|
||||
sod_short_avg_price = _cell_map.add_short_price(ColumnKeys.SODShortAvgPrice, d => d.sod_short_avg_price);
|
||||
sod_delta = _cell_map.add_pnl_double(ColumnKeys.SODDelta, d => d.sod_delta, fmt_greek);
|
||||
sod_gamma = _cell_map.add_pnl_double(ColumnKeys.SODGamma, d => d.sod_gamma, fmt_greek);
|
||||
sod_vega = _cell_map.add_pnl_double(ColumnKeys.SODVega, d => d.sod_vega, fmt_greek);
|
||||
sod_theta = _cell_map.add_pnl_double(ColumnKeys.SODTheta, d => d.sod_theta, fmt_greek);
|
||||
|
||||
// day
|
||||
day_pnl = _cell_map.add_pnl_double(ColumnKeys.DayPNL, d => d.day_pnl);
|
||||
day_net_pos = _cell_map.add_net_pos(
|
||||
ColumnKeys.DayNetPos, d => d.display_day_net_pos, fmt_qty);
|
||||
day_long_pos = _cell_map.add_long_pos(
|
||||
ColumnKeys.DayLongPos, d => d.display_day_long_pos, fmt_qty);
|
||||
day_long_avg_price = _cell_map.add_long_price(ColumnKeys.DayLongAvgPrice, d => d.day_long_avg_price);
|
||||
day_short_pos = _cell_map.add_short_pos(
|
||||
ColumnKeys.DayShortPos, d => d.display_day_short_pos, fmt_qty);
|
||||
day_short_avg_price = _cell_map.add_short_price(ColumnKeys.DayShortAvgPrice, d => d.day_short_avg_price);
|
||||
day_delta = _cell_map.add_pnl_double(ColumnKeys.DayDelta, d => d.day_delta, fmt_greek);
|
||||
day_gamma = _cell_map.add_pnl_double(ColumnKeys.DayGamma, d => d.day_gamma, fmt_greek);
|
||||
day_vega = _cell_map.add_pnl_double(ColumnKeys.DayVega, d => d.day_vega, fmt_greek);
|
||||
day_theta = _cell_map.add_pnl_double(ColumnKeys.DayTheta, d => d.day_theta, fmt_greek);
|
||||
has_day_trades = _cell_map.add(ColumnKeys.HasDayTrades, d => d.has_day_trades, _fmt_has_day_trades);
|
||||
// working
|
||||
working_net_pos = _cell_map.add_net_pos(
|
||||
ColumnKeys.WorkingNetPos, d => d.display_working_net_pos, fmt_qty);
|
||||
var qty_display = _sec_meta_store.get_qty_display(security);
|
||||
working_long_pos = _cell_map.add_working_long(
|
||||
ColumnKeys.WorkingLongPos,
|
||||
d => d.working.long_data,
|
||||
d => d.bid_price,
|
||||
qty_display);
|
||||
working_long_avg_price = _cell_map.add_long_price(ColumnKeys.WorkingLongAvgPrice, d => d.working_long_avg_price);
|
||||
working_short_pos = _cell_map.add_working_short(
|
||||
ColumnKeys.WorkingShortPos, r => r.working.short_data, r => r.ask_price, qty_display);
|
||||
working_short_avg_price = _cell_map.add_short_price(ColumnKeys.WorkingShortAvgPrice, d => d.working_short_avg_price);
|
||||
// total
|
||||
total_pnl = _cell_map.add_pnl_double(ColumnKeys.TotalPNL, d => d.total_pnl);
|
||||
total_net_pos = _cell_map.add_net_pos(
|
||||
ColumnKeys.TotalNetPos, d => d.display_total_net_pos, fmt_qty);
|
||||
total_long_pos = _cell_map.add_long_pos(
|
||||
ColumnKeys.TotalLongPos, d => d.display_total_long_pos, fmt_qty);
|
||||
total_long_avg_price = _cell_map.add_long_price(ColumnKeys.TotalLongAvgPrice, d => d.total_long_avg_price);
|
||||
total_short_pos = _cell_map.add_short_pos(
|
||||
ColumnKeys.TotalShortPos, d => d.display_total_short_pos, fmt_qty);
|
||||
total_short_avg_price = _cell_map.add_short_price(ColumnKeys.TotalShortAvgPrice, d => d.total_short_avg_price);
|
||||
total_delta = _cell_map.add_pnl_double(
|
||||
ColumnKeys.TotalDelta, d => d.total_delta, fmt_greek);
|
||||
total_gamma = _cell_map.add_pnl_double(
|
||||
ColumnKeys.TotalGamma, d => d.total_gamma, fmt_greek);
|
||||
total_vega = _cell_map.add_pnl_double(ColumnKeys.TotalVega, d => d.total_vega, fmt_greek);
|
||||
total_theta = _cell_map.add_pnl_double(
|
||||
ColumnKeys.TotalTheta, d => d.total_theta, fmt_greek);
|
||||
|
||||
// market
|
||||
bid_price = _cell_map.add_price_double(ColumnKeys.BidPrice, d => d.bid_price);
|
||||
ask_price = _cell_map.add_price_double(ColumnKeys.AskPrice, d => d.ask_price);
|
||||
mid_price = _cell_map.add_price_double(ColumnKeys.MidPrice, d => d.mid_price);
|
||||
last_price = _cell_map.add_price_double(ColumnKeys.LastPrice, d => d.last_price);
|
||||
settlement_price = _cell_map.add_price_double(ColumnKeys.SettlementPrice, d => d.settlement_price);
|
||||
|
||||
_set_price_formatting_delegates(fmt_fill_price, fmt_market_price);
|
||||
_cell_map.on_row(data);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
_dispose = _cell_map.animation_model.OnDispatcher().Subscribe(new_animations => {
|
||||
animations = new_animations.Select(kv => new mbts.ui.spark.CellAnimation(kv.Key.column, kv.Key.type, kv.Value.color.to_brush())).ToList();
|
||||
RaisePropertyChanged(nameof(animations));
|
||||
});
|
||||
|
||||
static string fmt_greek(double v) =>
|
||||
v.is_nan() ? "-" : v.ToString("#,0.00");
|
||||
|
||||
string fmt_qty(double v) =>
|
||||
quantity_display.format(v);
|
||||
}
|
||||
|
||||
// events
|
||||
public event Action<PNLNormalizedData> updated;
|
||||
public event Action<bool> is_selected_changed;
|
||||
|
||||
// data
|
||||
public PNLKey key => data.key;
|
||||
public PNLRowData data { get; private set; }
|
||||
public PNLNormalizedData normalized_data {
|
||||
get {
|
||||
if (_refresh_normalized_data) {
|
||||
_refresh_normalized_data = false;
|
||||
// Only count outrights (spread positions should be normalized into outrights)
|
||||
var sec_type = security?.ContractCase ?? Security.ContractOneofCase.None;
|
||||
var (
|
||||
sod_long,
|
||||
sod_short,
|
||||
sod_delta,
|
||||
sod_gamma,
|
||||
sod_vega,
|
||||
sod_theta,
|
||||
day_long,
|
||||
day_short,
|
||||
day_delta,
|
||||
day_gamma,
|
||||
day_vega,
|
||||
day_theta) = sec_type switch {
|
||||
Security.ContractOneofCase.Future or Security.ContractOneofCase.Option => (
|
||||
data.sod.long_pos.round_to_decimal(),
|
||||
data.sod.short_pos.round_to_decimal(),
|
||||
data.sod.delta.round_to_decimal(),
|
||||
data.sod.gamma.round_to_decimal(),
|
||||
data.sod.vega.round_to_decimal(),
|
||||
data.sod.theta.round_to_decimal(),
|
||||
data.day.long_pos.round_to_decimal(),
|
||||
data.day.short_pos.round_to_decimal(),
|
||||
data.day.delta.round_to_decimal(),
|
||||
data.day.gamma.round_to_decimal(),
|
||||
data.day.vega.round_to_decimal(),
|
||||
data.day.theta.round_to_decimal()),
|
||||
_ => (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
|
||||
};
|
||||
|
||||
var working_long = data.working_long_pos.round_to_decimal();
|
||||
var working_short = data.working_short_pos.round_to_decimal();
|
||||
|
||||
var nd = new PNLNormalizedData() {
|
||||
// sod
|
||||
sod_pnl = sod_pnl.value.round_to_decimal(),
|
||||
sod_long_pos = sod_long,
|
||||
sod_short_pos = sod_short,
|
||||
sod_delta = sod_delta,
|
||||
sod_gamma = sod_gamma,
|
||||
sod_vega = sod_vega,
|
||||
sod_theta = sod_theta,
|
||||
display_sod_long_pos = sod_long,
|
||||
display_sod_short_pos = sod_short,
|
||||
|
||||
// day
|
||||
day_pnl = day_pnl.value.round_to_decimal(),
|
||||
day_long_pos = day_long,
|
||||
day_short_pos = day_short,
|
||||
day_delta = day_delta,
|
||||
day_gamma = day_gamma,
|
||||
day_vega = day_vega,
|
||||
day_theta = day_theta,
|
||||
display_day_long_pos = day_long,
|
||||
display_day_short_pos = day_short,
|
||||
|
||||
// working
|
||||
working_long_pos = working_long,
|
||||
working_short_pos = working_short,
|
||||
display_working_long_pos = working_long,
|
||||
display_working_short_pos = working_short
|
||||
};
|
||||
if (_normalized_data != nd) {
|
||||
_normalized_data = nd;
|
||||
updated?.Invoke(nd);
|
||||
}
|
||||
}
|
||||
return _normalized_data;
|
||||
}
|
||||
}
|
||||
// security
|
||||
public string account_name => data.account_name;
|
||||
public Venue exchange => security?.Exchange ?? Venue.Unset;
|
||||
public string symbol => security.symbol_or_default();
|
||||
public DateTime expiration => data.expiration;
|
||||
public Security.ContractOneofCase security_type => security?.ContractCase ?? Security.ContractOneofCase.None;
|
||||
public SecurityImmutable? security => data.security;
|
||||
// cells
|
||||
public PNLCellVM<PNLRowData, AccountTuple> account_cell { get; }
|
||||
public PNLCellVM<PNLRowData, Venue> exchange_cell { get; }
|
||||
public PNLCellVM<PNLRowData, DateTime> expiration_cell { get; }
|
||||
public PNLCellVM<PNLRowData, string> symbol_cell { get; }
|
||||
public PNLCellVM<PNLRowData, Security.ContractOneofCase> security_type_cell { get; }
|
||||
public PNLCellVM<PNLRowData, SecurityImmutable?> security_cell { get; }
|
||||
public PNLCellVM<PNLRowData, uint> security_key { get; }
|
||||
public PNLCellVM<PNLRowData, double> cpp { get; }
|
||||
// sod
|
||||
public PNLCellVM<PNLRowData, double> sod_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_short_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_delta { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_gamma { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_vega { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_theta { get; }
|
||||
|
||||
// day
|
||||
public PNLCellVM<PNLRowData, double> day_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_short_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_delta { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_gamma { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_vega { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_theta { get; }
|
||||
public PNLCellVM<PNLRowData, bool> has_day_trades { get; }
|
||||
// working
|
||||
public PNLCellVM<PNLRowData, double> working_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, WorkingOrderSideData> working_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> working_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, WorkingOrderSideData> working_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> working_short_avg_price { get; }
|
||||
// total
|
||||
public PNLCellVM<PNLRowData, double> total_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_short_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_delta { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_gamma { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_vega { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_theta { get; }
|
||||
|
||||
// (market)
|
||||
public PNLCellVM<PNLRowData, double> bid_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> mid_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> ask_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> last_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> settlement_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> pnl_price { get; }
|
||||
|
||||
// display
|
||||
public api.domain.TickDisplaySettingsImmutable quantity_display {
|
||||
get {
|
||||
if (_quantity_display_backing.HasValue) {
|
||||
return _quantity_display_backing.Value;
|
||||
}
|
||||
if (security.HasValue) {
|
||||
_quantity_display_backing = _sec_meta_store.get_qty_display(security.Value);
|
||||
}
|
||||
return _quantity_display_backing ??= Security.Defaults.quantity_display_settingsi;
|
||||
}
|
||||
}
|
||||
|
||||
public IList<mbts.ui.spark.CellAnimation> animations { get; private set; }
|
||||
|
||||
public bool is_selected {
|
||||
get => _is_selected;
|
||||
set {
|
||||
if (Set(ref _is_selected, value))
|
||||
is_selected_changed?.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<PNLCellVM<PNLRowData, double>> _market_price_cells {
|
||||
get {
|
||||
yield return pnl_price;
|
||||
yield return last_price;
|
||||
yield return bid_price;
|
||||
yield return mid_price;
|
||||
yield return ask_price;
|
||||
yield return settlement_price;
|
||||
}
|
||||
}
|
||||
|
||||
// includes sod, day, total, and working avg price columns
|
||||
IEnumerable<PNLCellVM<PNLRowData, double>> _fill_price_cells {
|
||||
get {
|
||||
// sod
|
||||
yield return sod_long_avg_price;
|
||||
yield return sod_short_avg_price;
|
||||
// day
|
||||
yield return day_long_avg_price;
|
||||
yield return day_short_avg_price;
|
||||
// total
|
||||
yield return total_long_avg_price;
|
||||
yield return total_short_avg_price;
|
||||
// working
|
||||
yield return working_long_avg_price;
|
||||
yield return working_short_avg_price;
|
||||
}
|
||||
}
|
||||
|
||||
public void update(PNLRowData data) {
|
||||
_quantity_display_backing = null;
|
||||
_refresh_normalized_data = true;
|
||||
this.data = data;
|
||||
_cell_map.on_row(data);
|
||||
_animator.on_row(data);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
}
|
||||
|
||||
public void on_settings(ImmutableDictionary<ColumnKeys, PNLCellAppearance> defaults, IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers, Func<double, string> fmt_fill_price, Func<double, string> fmt_market_price) {
|
||||
_animator.on_settings(defaults, triggers);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
_set_price_formatting_delegates(fmt_fill_price, fmt_market_price);
|
||||
}
|
||||
|
||||
public bool Equals(PNLRowVM other) =>
|
||||
other != null && key == other.key;
|
||||
|
||||
public override int GetHashCode() =>
|
||||
key.GetHashCode();
|
||||
|
||||
public override string ToString() =>
|
||||
$"account: {account_name} exchange: {exchange} symbol: {symbol} security: {security} buy_quantity: {day_long_pos} sell_quantity: {day_short_pos} pnl: {total_pnl}";
|
||||
|
||||
void _set_price_formatting_delegates(Func<double, string> fmt_fill_price, Func<double, string> fmt_market_price) {
|
||||
foreach (var cell in _fill_price_cells) {
|
||||
cell.format = fmt_fill_price;
|
||||
}
|
||||
foreach (var cell in _market_price_cells) {
|
||||
cell.format = fmt_market_price;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using bts.api;
|
||||
using bts.utils;
|
||||
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
|
||||
using Spark.Core.Collections;
|
||||
using Spark.Core.Data;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
|
||||
using PNLKey = bts.AccountSecurityKey;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLRowVM : DisposableViewModelBase, IEquatable<PNLRowVM> {
|
||||
public static readonly IComparer<PNLRowVM> key_comparer = Comparer<PNLKey>.Default.wrap<PNLKey, PNLRowVM>(r => r.key);
|
||||
static readonly Func<bool, string> _fmt_has_day_trades = hdt => hdt ? "Yes" : "No";
|
||||
//
|
||||
readonly PNLCellIndexBuilder<PNLRowData> _cell_map = new PNLCellIndexBuilder<PNLRowData>();
|
||||
readonly PNLCellEmbeddedAnimationModel<PNLRowData> _animator;
|
||||
readonly ISecurityMetadataStore _sec_meta_store;
|
||||
bool _refresh_normalized_data = true, _is_selected;
|
||||
PNLNormalizedData _normalized_data;
|
||||
api.domain.TickDisplaySettingsImmutable? _quantity_display_backing;
|
||||
|
||||
public PNLRowVM(
|
||||
PNLRowData data,
|
||||
ISecurityMetadataStore sec_meta_store,
|
||||
ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances,
|
||||
IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers,
|
||||
Func<double, string> fmt_fill_price,
|
||||
Func<double, string> fmt_market_price) {
|
||||
//
|
||||
this.data = data;
|
||||
_sec_meta_store = sec_meta_store;
|
||||
_animator = new PNLCellEmbeddedAnimationModel<PNLRowData>(default_appearances, triggers);
|
||||
_animator.on_row(data);
|
||||
pnl_price = _cell_map.add_price_double(ColumnKeys.PNLPrice, d => d.pnl_price);
|
||||
account_cell = _cell_map.add(ColumnKeys.Account, d => d.account);
|
||||
security_type_cell = _cell_map.add(ColumnKeys.SecurityType, d => d.security?.ContractCase ?? Security.ContractOneofCase.None);
|
||||
exchange_cell = _cell_map.add(ColumnKeys.Exchange, d => d.security?.Exchange ?? Venue.Unset);
|
||||
expiration_cell = _cell_map.add(
|
||||
ColumnKeys.Expiration,
|
||||
d => d.expiration,
|
||||
e => e == DateTime.MinValue
|
||||
? "N/A"
|
||||
: SecurityFormatter.format_expiration(e));
|
||||
symbol_cell = _cell_map.add(ColumnKeys.Symbol, d => d.symbol);
|
||||
security_cell = _cell_map.add(
|
||||
ColumnKeys.Security,
|
||||
d => d.security,
|
||||
null,
|
||||
EqualityComparer<SecurityImmutable?>.Default,
|
||||
Comparer<SecurityImmutable?>.Default);
|
||||
security_key = _cell_map.add(ColumnKeys.SecurityKey, d => d.key.security_id);
|
||||
cpp = _cell_map.add(ColumnKeys.CPP, d => d.effective_cpp);
|
||||
// sod
|
||||
sod_pnl = _cell_map.add_pnl_double(ColumnKeys.SODPNL, d => d.sod_pnl);
|
||||
sod_net_pos = _cell_map.add_net_pos(
|
||||
ColumnKeys.SODNetPos, d => d.display_sod_net_pos, fmt_qty);
|
||||
sod_long_pos = _cell_map.add_long_pos(
|
||||
ColumnKeys.SODLongPos, d => d.display_sod_long_pos, fmt_qty);
|
||||
sod_long_avg_price = _cell_map.add_long_price(ColumnKeys.SODLongAvgPrice, d => d.sod_long_avg_price);
|
||||
sod_short_pos = _cell_map.add_short_pos(
|
||||
ColumnKeys.SODShortPos, d => d.display_sod_short_pos, fmt_qty);
|
||||
sod_short_avg_price = _cell_map.add_short_price(ColumnKeys.SODShortAvgPrice, d => d.sod_short_avg_price);
|
||||
sod_delta = _cell_map.add_pnl_double(ColumnKeys.SODDelta, d => d.sod_delta, fmt_greek);
|
||||
sod_gamma = _cell_map.add_pnl_double(ColumnKeys.SODGamma, d => d.sod_gamma, fmt_greek);
|
||||
sod_vega = _cell_map.add_pnl_double(ColumnKeys.SODVega, d => d.sod_vega, fmt_greek);
|
||||
sod_theta = _cell_map.add_pnl_double(ColumnKeys.SODTheta, d => d.sod_theta, fmt_greek);
|
||||
|
||||
// day
|
||||
day_pnl = _cell_map.add_pnl_double(ColumnKeys.DayPNL, d => d.day_pnl);
|
||||
day_net_pos = _cell_map.add_net_pos(
|
||||
ColumnKeys.DayNetPos, d => d.display_day_net_pos, fmt_qty);
|
||||
day_long_pos = _cell_map.add_long_pos(
|
||||
ColumnKeys.DayLongPos, d => d.display_day_long_pos, fmt_qty);
|
||||
day_long_avg_price = _cell_map.add_long_price(ColumnKeys.DayLongAvgPrice, d => d.day_long_avg_price);
|
||||
day_short_pos = _cell_map.add_short_pos(
|
||||
ColumnKeys.DayShortPos, d => d.display_day_short_pos, fmt_qty);
|
||||
day_short_avg_price = _cell_map.add_short_price(ColumnKeys.DayShortAvgPrice, d => d.day_short_avg_price);
|
||||
day_delta = _cell_map.add_pnl_double(ColumnKeys.DayDelta, d => d.day_delta, fmt_greek);
|
||||
day_gamma = _cell_map.add_pnl_double(ColumnKeys.DayGamma, d => d.day_gamma, fmt_greek);
|
||||
day_vega = _cell_map.add_pnl_double(ColumnKeys.DayVega, d => d.day_vega, fmt_greek);
|
||||
day_theta = _cell_map.add_pnl_double(ColumnKeys.DayTheta, d => d.day_theta, fmt_greek);
|
||||
has_day_trades = _cell_map.add(ColumnKeys.HasDayTrades, d => d.has_day_trades, _fmt_has_day_trades);
|
||||
// working
|
||||
working_net_pos = _cell_map.add_net_pos(
|
||||
ColumnKeys.WorkingNetPos, d => d.display_working_net_pos, fmt_qty);
|
||||
var qty_display = _sec_meta_store.get_qty_display(security);
|
||||
working_long_pos = _cell_map.add_working_long(
|
||||
ColumnKeys.WorkingLongPos,
|
||||
d => d.working.long_data,
|
||||
d => d.bid_price,
|
||||
qty_display);
|
||||
working_long_avg_price = _cell_map.add_long_price(ColumnKeys.WorkingLongAvgPrice, d => d.working_long_avg_price);
|
||||
working_short_pos = _cell_map.add_working_short(
|
||||
ColumnKeys.WorkingShortPos, r => r.working.short_data, r => r.ask_price, qty_display);
|
||||
working_short_avg_price = _cell_map.add_short_price(ColumnKeys.WorkingShortAvgPrice, d => d.working_short_avg_price);
|
||||
// total
|
||||
total_pnl = _cell_map.add_pnl_double(ColumnKeys.TotalPNL, d => d.total_pnl);
|
||||
total_net_pos = _cell_map.add_net_pos(
|
||||
ColumnKeys.TotalNetPos, d => d.display_total_net_pos, fmt_qty);
|
||||
total_long_pos = _cell_map.add_long_pos(
|
||||
ColumnKeys.TotalLongPos, d => d.display_total_long_pos, fmt_qty);
|
||||
total_long_avg_price = _cell_map.add_long_price(ColumnKeys.TotalLongAvgPrice, d => d.total_long_avg_price);
|
||||
total_short_pos = _cell_map.add_short_pos(
|
||||
ColumnKeys.TotalShortPos, d => d.display_total_short_pos, fmt_qty);
|
||||
total_short_avg_price = _cell_map.add_short_price(ColumnKeys.TotalShortAvgPrice, d => d.total_short_avg_price);
|
||||
total_delta = _cell_map.add_pnl_double(
|
||||
ColumnKeys.TotalDelta, d => d.total_delta, fmt_greek);
|
||||
total_gamma = _cell_map.add_pnl_double(
|
||||
ColumnKeys.TotalGamma, d => d.total_gamma, fmt_greek);
|
||||
total_vega = _cell_map.add_pnl_double(ColumnKeys.TotalVega, d => d.total_vega, fmt_greek);
|
||||
total_theta = _cell_map.add_pnl_double(
|
||||
ColumnKeys.TotalTheta, d => d.total_theta, fmt_greek);
|
||||
|
||||
// market
|
||||
bid_price = _cell_map.add_price_double(ColumnKeys.BidPrice, d => d.bid_price);
|
||||
ask_price = _cell_map.add_price_double(ColumnKeys.AskPrice, d => d.ask_price);
|
||||
mid_price = _cell_map.add_price_double(ColumnKeys.MidPrice, d => d.mid_price);
|
||||
last_price = _cell_map.add_price_double(ColumnKeys.LastPrice, d => d.last_price);
|
||||
settlement_price = _cell_map.add_price_double(ColumnKeys.SettlementPrice, d => d.settlement_price);
|
||||
|
||||
_set_price_formatting_delegates(fmt_fill_price, fmt_market_price);
|
||||
_cell_map.on_row(data);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
_dispose = _cell_map.animation_model.OnDispatcher().Subscribe(new_animations => {
|
||||
animations = new_animations.Select(kv => new mbts.ui.spark.CellAnimation(kv.Key.column, kv.Key.type, kv.Value.color.to_brush())).ToList();
|
||||
RaisePropertyChanged(nameof(animations));
|
||||
});
|
||||
|
||||
static string fmt_greek(double v) =>
|
||||
v.is_nan() ? "-" : v.ToString("#,0.00");
|
||||
|
||||
string fmt_qty(double v) =>
|
||||
quantity_display.format(v);
|
||||
}
|
||||
|
||||
// events
|
||||
public event Action<PNLNormalizedData> updated;
|
||||
public event Action<bool> is_selected_changed;
|
||||
|
||||
// data
|
||||
public PNLKey key => data.key;
|
||||
public PNLRowData data { get; private set; }
|
||||
public PNLNormalizedData normalized_data {
|
||||
get {
|
||||
if (_refresh_normalized_data) {
|
||||
_refresh_normalized_data = false;
|
||||
// Only count outrights (spread positions should be normalized into outrights)
|
||||
var sec_type = security?.ContractCase ?? Security.ContractOneofCase.None;
|
||||
var (
|
||||
sod_long,
|
||||
sod_short,
|
||||
sod_delta,
|
||||
sod_gamma,
|
||||
sod_vega,
|
||||
sod_theta,
|
||||
day_long,
|
||||
day_short,
|
||||
day_delta,
|
||||
day_gamma,
|
||||
day_vega,
|
||||
day_theta) = sec_type switch {
|
||||
Security.ContractOneofCase.Future or Security.ContractOneofCase.Option => (
|
||||
data.sod.long_pos.round_to_decimal(),
|
||||
data.sod.short_pos.round_to_decimal(),
|
||||
data.sod.delta.round_to_decimal(),
|
||||
data.sod.gamma.round_to_decimal(),
|
||||
data.sod.vega.round_to_decimal(),
|
||||
data.sod.theta.round_to_decimal(),
|
||||
data.day.long_pos.round_to_decimal(),
|
||||
data.day.short_pos.round_to_decimal(),
|
||||
data.day.delta.round_to_decimal(),
|
||||
data.day.gamma.round_to_decimal(),
|
||||
data.day.vega.round_to_decimal(),
|
||||
data.day.theta.round_to_decimal()),
|
||||
_ => (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
|
||||
};
|
||||
|
||||
var working_long = data.working_long_pos.round_to_decimal();
|
||||
var working_short = data.working_short_pos.round_to_decimal();
|
||||
|
||||
var nd = new PNLNormalizedData() {
|
||||
// sod
|
||||
sod_pnl = sod_pnl.value.round_to_decimal(),
|
||||
sod_long_pos = sod_long,
|
||||
sod_short_pos = sod_short,
|
||||
sod_delta = sod_delta,
|
||||
sod_gamma = sod_gamma,
|
||||
sod_vega = sod_vega,
|
||||
sod_theta = sod_theta,
|
||||
display_sod_long_pos = sod_long,
|
||||
display_sod_short_pos = sod_short,
|
||||
|
||||
// day
|
||||
day_pnl = day_pnl.value.round_to_decimal(),
|
||||
day_long_pos = day_long,
|
||||
day_short_pos = day_short,
|
||||
day_delta = day_delta,
|
||||
day_gamma = day_gamma,
|
||||
day_vega = day_vega,
|
||||
day_theta = day_theta,
|
||||
display_day_long_pos = day_long,
|
||||
display_day_short_pos = day_short,
|
||||
|
||||
// working
|
||||
working_long_pos = working_long,
|
||||
working_short_pos = working_short,
|
||||
display_working_long_pos = working_long,
|
||||
display_working_short_pos = working_short
|
||||
};
|
||||
if (_normalized_data != nd) {
|
||||
_normalized_data = nd;
|
||||
updated?.Invoke(nd);
|
||||
}
|
||||
}
|
||||
return _normalized_data;
|
||||
}
|
||||
}
|
||||
// security
|
||||
public string account_name => data.account_name;
|
||||
public Venue exchange => security?.Exchange ?? Venue.Unset;
|
||||
public string symbol => security.symbol_or_default();
|
||||
public DateTime expiration => data.expiration;
|
||||
public Security.ContractOneofCase security_type => security?.ContractCase ?? Security.ContractOneofCase.None;
|
||||
public SecurityImmutable? security => data.security;
|
||||
// cells
|
||||
public PNLCellVM<PNLRowData, AccountTuple> account_cell { get; }
|
||||
public PNLCellVM<PNLRowData, Venue> exchange_cell { get; }
|
||||
public PNLCellVM<PNLRowData, DateTime> expiration_cell { get; }
|
||||
public PNLCellVM<PNLRowData, string> symbol_cell { get; }
|
||||
public PNLCellVM<PNLRowData, Security.ContractOneofCase> security_type_cell { get; }
|
||||
public PNLCellVM<PNLRowData, SecurityImmutable?> security_cell { get; }
|
||||
public PNLCellVM<PNLRowData, uint> security_key { get; }
|
||||
public PNLCellVM<PNLRowData, double> cpp { get; }
|
||||
// sod
|
||||
public PNLCellVM<PNLRowData, double> sod_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_short_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_delta { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_gamma { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_vega { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_theta { get; }
|
||||
|
||||
// day
|
||||
public PNLCellVM<PNLRowData, double> day_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_short_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_delta { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_gamma { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_vega { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_theta { get; }
|
||||
public PNLCellVM<PNLRowData, bool> has_day_trades { get; }
|
||||
// working
|
||||
public PNLCellVM<PNLRowData, double> working_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, WorkingOrderSideData> working_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> working_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, WorkingOrderSideData> working_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> working_short_avg_price { get; }
|
||||
// total
|
||||
public PNLCellVM<PNLRowData, double> total_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_short_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_delta { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_gamma { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_vega { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_theta { get; }
|
||||
|
||||
// (market)
|
||||
public PNLCellVM<PNLRowData, double> bid_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> mid_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> ask_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> last_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> settlement_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> pnl_price { get; }
|
||||
|
||||
// display
|
||||
public api.domain.TickDisplaySettingsImmutable quantity_display {
|
||||
get {
|
||||
if (_quantity_display_backing.HasValue) {
|
||||
return _quantity_display_backing.Value;
|
||||
}
|
||||
if (security.HasValue) {
|
||||
_quantity_display_backing = _sec_meta_store.get_qty_display(security.Value);
|
||||
}
|
||||
return _quantity_display_backing ??= Security.Defaults.quantity_display_settingsi;
|
||||
}
|
||||
}
|
||||
|
||||
public IList<mbts.ui.spark.CellAnimation> animations { get; private set; }
|
||||
|
||||
public bool is_selected {
|
||||
get => _is_selected;
|
||||
set {
|
||||
if (Set(ref _is_selected, value))
|
||||
is_selected_changed?.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<PNLCellVM<PNLRowData, double>> _market_price_cells {
|
||||
get {
|
||||
yield return pnl_price;
|
||||
yield return last_price;
|
||||
yield return bid_price;
|
||||
yield return mid_price;
|
||||
yield return ask_price;
|
||||
yield return settlement_price;
|
||||
}
|
||||
}
|
||||
|
||||
// includes sod, day, total, and working avg price columns
|
||||
IEnumerable<PNLCellVM<PNLRowData, double>> _fill_price_cells {
|
||||
get {
|
||||
// sod
|
||||
yield return sod_long_avg_price;
|
||||
yield return sod_short_avg_price;
|
||||
// day
|
||||
yield return day_long_avg_price;
|
||||
yield return day_short_avg_price;
|
||||
// total
|
||||
yield return total_long_avg_price;
|
||||
yield return total_short_avg_price;
|
||||
// working
|
||||
yield return working_long_avg_price;
|
||||
yield return working_short_avg_price;
|
||||
}
|
||||
}
|
||||
|
||||
public void update(PNLRowData data) {
|
||||
_quantity_display_backing = null;
|
||||
_refresh_normalized_data = true;
|
||||
this.data = data;
|
||||
_cell_map.on_row(data);
|
||||
_animator.on_row(data);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
}
|
||||
|
||||
public void on_settings(ImmutableDictionary<ColumnKeys, PNLCellAppearance> defaults, IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers, Func<double, string> fmt_fill_price, Func<double, string> fmt_market_price) {
|
||||
_animator.on_settings(defaults, triggers);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
_set_price_formatting_delegates(fmt_fill_price, fmt_market_price);
|
||||
}
|
||||
|
||||
public bool Equals(PNLRowVM other) =>
|
||||
other != null && key == other.key;
|
||||
|
||||
public override int GetHashCode() =>
|
||||
key.GetHashCode();
|
||||
|
||||
public override string ToString() =>
|
||||
$"account: {account_name} exchange: {exchange} symbol: {symbol} security: {security} buy_quantity: {day_long_pos} sell_quantity: {day_short_pos} pnl: {total_pnl}";
|
||||
|
||||
void _set_price_formatting_delegates(Func<double, string> fmt_fill_price, Func<double, string> fmt_market_price) {
|
||||
foreach (var cell in _fill_price_cells) {
|
||||
cell.format = fmt_fill_price;
|
||||
}
|
||||
foreach (var cell in _market_price_cells) {
|
||||
cell.format = fmt_market_price;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace mbts.analytics {
|
||||
return tl_row.call_put == PutOrCall.Call ?
|
||||
opt_theo.fwd_normalized_call : opt_theo.fwd_normalized_put;
|
||||
case SecurityType.Future:
|
||||
return fut_theo.theo;
|
||||
//return fut_theo.theo;
|
||||
case SecurityType.Spot:
|
||||
var bbo = top_of_book;
|
||||
return (bbo.Item1 + bbo.Item2) / 2;
|
||||
|
||||
@@ -36,6 +36,10 @@ namespace mbts {
|
||||
}
|
||||
|
||||
public static bool is_unexpired(bts.api.SecurityTuple st, DateTime exp_date) {
|
||||
if (st.Venue == Venue.Cboe || st.Venue == Venue.Glbx) {
|
||||
// server is already getting teh righ contracts
|
||||
return true;
|
||||
}
|
||||
TimeSpan exp_time = get_exp_time(st);
|
||||
DateTime full_exp_utc = exp_date.ToUniversalTime().Date.Add(exp_time);
|
||||
DateTime now_utc = DateTime.UtcNow;
|
||||
@@ -43,6 +47,8 @@ namespace mbts {
|
||||
}
|
||||
|
||||
public static bool is_unexpired(bts.api.SecurityImmutable sec) {
|
||||
if (sec.security_tuple().Venue == Venue.Cboe || sec.security_tuple().Venue == Venue.Glbx)
|
||||
return true;
|
||||
var sec_type = sec.security_type();
|
||||
switch (sec_type) {
|
||||
case bts.api.SecurityType.Future:
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace mbts.ui.controls {
|
||||
public const double
|
||||
Account = 62,
|
||||
CheckMark = 42,
|
||||
Currency = 54,
|
||||
Currency = 68,
|
||||
Exchange = 66,
|
||||
FillKey = 68,
|
||||
OrderKey = 78,
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -72,12 +72,14 @@ namespace Spark.Api.Orders {
|
||||
// TODO: remove assumption that synthetic quantities are already notional values
|
||||
public double notional_value =>
|
||||
(security?.HasSynthetic ?? false)
|
||||
&& security.Value.Synthetic.PriceCalc == SyntheticContract.Types.PriceCalc.NotionalLeg
|
||||
? order.order.quantity()
|
||||
: order.order.notional_value();
|
||||
|
||||
// TODO: remove assumption that synthetic quantities are already notional values
|
||||
public double filled_notional_value =>
|
||||
(security?.HasSynthetic ?? false)
|
||||
&& security.Value.Synthetic.PriceCalc == SyntheticContract.Types.PriceCalc.NotionalLeg
|
||||
? order.order.filled_quantity()
|
||||
: order.order.filled_notional_value();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
@@ -10,9 +10,9 @@ namespace bts
|
||||
static int _needs_init_spark_lib = 0;
|
||||
|
||||
public static void init(string config_source = null) {
|
||||
|
||||
var version = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
Console.WriteLine(
|
||||
$"Configuring BTS v2.2.4 with {(config_source is null ? "defaults" : $"'{config_source}'")}");
|
||||
$"Configuring Spark {version} with {(config_source is null ? "defaults" : $"'{config_source}'")}");
|
||||
var args = Environment.GetCommandLineArgs();
|
||||
utils.BTSNativeMethods.provide_command_line(args, args.Length);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
OkxExeCfg {
|
||||
value{
|
||||
account : "ACC A"
|
||||
ops_id : "op_id_bts"
|
||||
secret_key : "DAF6CBB09475D2520B88D2070788A976"
|
||||
api_key : "4492a9ab-5784-454c-acc7-38dc2a503ed0"
|
||||
passphrase : "Pakistan@123"
|
||||
ws_url : "wss://wspap.okx.com:8443"
|
||||
rest_url : "https://www.okx.com"
|
||||
ws_ping_timeout_seconds : 10
|
||||
ws_ping_interval_seconds : 25
|
||||
ws_reconnect_interval_seconds : 5
|
||||
ws_reconnect_max_count : 5
|
||||
simulated_trading : true
|
||||
tdmode : "cross"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
syntax = "proto3";
|
||||
package bts.config;
|
||||
message OkxExeCfg {
|
||||
string account = 1;
|
||||
string ops_id = 2;
|
||||
string secret_key = 3;
|
||||
string api_key = 4;
|
||||
string passphrase = 5;
|
||||
string ws_url = 6;
|
||||
string rest_url = 7;
|
||||
uint32 ws_ping_timeout_seconds = 8;
|
||||
uint32 ws_ping_interval_seconds = 9;
|
||||
uint32 ws_reconnect_interval_seconds = 10;
|
||||
uint32 ws_reconnect_max_count = 11;
|
||||
bool simulated_trading = 12;
|
||||
string tdmode = 13;
|
||||
|
||||
}
|
||||
@@ -34,7 +34,31 @@ static size_t write_headers(
|
||||
void *ptr, size_t size, size_t nmemb, IHttpResponse *data) {
|
||||
return data->write_headers((char *)ptr, size, nmemb);
|
||||
}
|
||||
|
||||
// static int curl_debug_callback(CURL *handle, curl_infotype type, char *data, size_t size, void *userptr) {
|
||||
// (void)handle;
|
||||
// (void)type;
|
||||
// (void)userptr;
|
||||
// switch (type) {
|
||||
// case CURLINFO_TEXT:
|
||||
// LOG(Http, Info, "CURLINFO_TEXT: {}", std::string(data, size));
|
||||
// break;
|
||||
// case CURLINFO_HEADER_IN:
|
||||
// LOG(Http, Info, "Header In: {}", std::string(data, size));
|
||||
// break;
|
||||
// case CURLINFO_HEADER_OUT:
|
||||
// LOG(Http, Info, "Header Out: {}", std::string(data, size));
|
||||
// break;
|
||||
// case CURLINFO_DATA_IN:
|
||||
// LOG(Http, Info, "Data In: {} bytes, in: {}", size,std::string(data, size));
|
||||
// break;
|
||||
// case CURLINFO_DATA_OUT:
|
||||
// LOG(Http, Info, "Data Out: {} bytes, out: {}", size, std::string(data, size));
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
// return 0;
|
||||
// }
|
||||
void HttpClient::get(
|
||||
string url,
|
||||
unordered_map<string, string> headers,
|
||||
@@ -91,6 +115,8 @@ void HttpClient::send(const HttpRequest &req, IHttpResponse *response) {
|
||||
CURL *curl = session.curl;
|
||||
CurlHeaders curl_headers{req.headers};
|
||||
// Prepare req
|
||||
//to debug curl requests
|
||||
// curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, curl_debug_callback);
|
||||
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());
|
||||
@@ -108,6 +134,7 @@ void HttpClient::send(const HttpRequest &req, IHttpResponse *response) {
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPPOST, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, req.body.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, req.body.size());
|
||||
// curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
||||
break;
|
||||
case io::http::HttpMethod::PUT:
|
||||
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "lib/io/websocket/websocket_error_code.h"
|
||||
#include "lib/io/websocket/websocketpp_exception_formatter.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/utils/bts_assert.h"
|
||||
#include "lib/utils/formatters/exception_formatter.h"
|
||||
|
||||
#include <atomic>
|
||||
@@ -201,9 +202,10 @@ struct WebsocketClient::PImpl {
|
||||
}
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
std::string str = ex.what();
|
||||
ERROR_FN(
|
||||
"connect_no_timeout. Failed to connect. {}; err: {}", snapshot(),
|
||||
ex);
|
||||
str);
|
||||
set_state(State::IDLE);
|
||||
// Don't wait for connect
|
||||
return;
|
||||
@@ -236,11 +238,16 @@ struct WebsocketClient::PImpl {
|
||||
ERROR_FN(
|
||||
"io_thread. Websocket io threw websocketpp::exception. {}; "
|
||||
"err: {}",
|
||||
snapshot(), ex);
|
||||
snapshot(), std::string(ex.what()));
|
||||
bts_assert_msgf(false, "io_thread. Websocket io threw websocketpp::exception. {}; "
|
||||
"err: {}",
|
||||
snapshot(), std::string(ex.what()));
|
||||
} catch (const std::exception &ex) {
|
||||
ERROR_FN(
|
||||
"io_thread. Websocket io threw std::exception. {}; err: {}",
|
||||
snapshot(), ex);
|
||||
snapshot(), std::string(ex.what()));
|
||||
bts_assert_msgf(false, "io_thread. Websocket io threw std::exception. {}; err: {}",
|
||||
snapshot(), std::string(ex.what()));
|
||||
} catch (...) {
|
||||
bts_assert_msgf(
|
||||
false,
|
||||
@@ -300,6 +307,7 @@ struct WebsocketClient::PImpl {
|
||||
const auto ec = client.get_con_from_hdl(hdl)->get_ec();
|
||||
if (ec) {
|
||||
ERROR_FN("on_open. {}; err: {}", snapshot(), ec.message());
|
||||
bts_assert(false);
|
||||
}
|
||||
switch (state.value) {
|
||||
case State::OPENING:
|
||||
|
||||
@@ -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();
|
||||
@@ -107,7 +108,7 @@ Sink file_sink(LoggingConfig const &cfg) {
|
||||
|
||||
if (boost::filesystem::exists(path)) {
|
||||
auto sz = boost::filesystem::file_size(path);
|
||||
if (sz > max_sz && false) {
|
||||
if (sz > max_sz) {
|
||||
auto backup = boost::filesystem::path(path.string() + ".old");
|
||||
boost::filesystem::rename(path, backup);
|
||||
}
|
||||
@@ -118,8 +119,7 @@ Sink file_sink(LoggingConfig const &cfg) {
|
||||
return [=](LogMsg const &m) {
|
||||
auto msg = f(m);
|
||||
bytes_written += msg.size();
|
||||
if (bytes_written >= max_sz && false) {
|
||||
return;
|
||||
if (bytes_written >= max_sz) {
|
||||
bytes_written = 0;
|
||||
stream.close();
|
||||
auto backup = boost::filesystem::path(path.string() + ".old");
|
||||
|
||||
@@ -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>
|
||||
@@ -682,6 +683,18 @@ static bool get_qty_info(
|
||||
qty_tick_unit = 1;
|
||||
qty_tick_base = 1000000;
|
||||
break;
|
||||
case 10'000'000:
|
||||
qty_tick_unit = 1;
|
||||
qty_tick_base = 10'000'000;
|
||||
break;
|
||||
case 100'000'000:
|
||||
qty_tick_unit = 1;
|
||||
qty_tick_base = 100'000'000;
|
||||
break;
|
||||
case 1'000'000'000:
|
||||
qty_tick_unit = 1;
|
||||
qty_tick_base = 1'000'000'000;
|
||||
break;
|
||||
case 4:
|
||||
qty_tick_unit = 25;
|
||||
qty_tick_base = 100;
|
||||
@@ -720,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);
|
||||
|
||||
@@ -509,6 +509,91 @@ void convert_security(
|
||||
}
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const FixedSecurity &f) {
|
||||
j = nlohmann::json{
|
||||
{"key", f.key},
|
||||
{"symbol", f.symbol()},
|
||||
{"type", api::SecurityType_Name(f.type)},
|
||||
{"venue", api::Venue_Name(f.exchange)},
|
||||
{"alt_symbol", f.alt_symbol()},
|
||||
{"cfi_code", f.cfi_code()},
|
||||
{"security_desc", f.security_desc()},
|
||||
{"platform_id", f.platform_id()},
|
||||
{"display_factor", f.display_factor},
|
||||
{"numeric_id", f.numeric_id},
|
||||
{"cboe_class_key", f.cboe_class_key},
|
||||
{"main_frac", (int)f.cme_main_fraction},
|
||||
{"sub_frac", (int)f.cme_sub_fraction},
|
||||
{"price_format", (int)f.cme_price_display_format},
|
||||
{"market_segment", (int)f.cme_market_segment_id},
|
||||
|
||||
{"price_break", f.price_break},
|
||||
{"low_tick", f.low_tick},
|
||||
{"high_tick", f.high_tick},
|
||||
{"cpp", f.cash_per_point},
|
||||
|
||||
{"ice_market_description", f.ice_market_description()},
|
||||
{"ice_cleared_alias", f.ice_cleared_alias()},
|
||||
{"ice_product_name", f.ice_product_name()},
|
||||
{"ice_hub_alias", f.ice_hub_alias()},
|
||||
{"ice_strip_name", f.ice_strip_name()},
|
||||
{"mic_code", f.mic_code()},
|
||||
};
|
||||
// Qty Info
|
||||
{
|
||||
auto qty_infos_j = nlohmann::json::array();
|
||||
float min_value, max_value, tick_unit, tick_base, max_price;
|
||||
bool is_notional;
|
||||
for (int8_t i = 0; f.get_quantity_info(
|
||||
i, min_value, max_value, tick_unit, tick_base, max_price,
|
||||
is_notional);
|
||||
i++) {
|
||||
auto qty_info_j = nlohmann::json::object();
|
||||
qty_info_j["min_value"] = min_value;
|
||||
qty_info_j["max_value"] = max_value;
|
||||
qty_info_j["tick_unit"] = tick_unit;
|
||||
qty_info_j["tick_base"] = tick_base;
|
||||
qty_info_j["max_price"] = max_price;
|
||||
qty_info_j["is_notional"] = is_notional;
|
||||
qty_infos_j.push_back(std::move(qty_info_j));
|
||||
}
|
||||
j["qty_infos"] = std::move(qty_infos_j);
|
||||
}
|
||||
|
||||
switch (f.type) {
|
||||
case api::SECURITYTYPE_SPOT:
|
||||
break;
|
||||
case api::SECURITYTYPE_FUTURE: {
|
||||
auto *fut = static_cast<const FixedFutureSecurity *>(&f);
|
||||
core::TimeStamp exp;
|
||||
core::TimeStamp::from_ymd(
|
||||
fut->expiration_year, fut->expiration_month, fut->expiration_day,
|
||||
&exp);
|
||||
j["expiration"] = exp.to_iso_string();
|
||||
break;
|
||||
}
|
||||
case api::SECURITYTYPE_OPTION: {
|
||||
auto *opt = static_cast<const FixedOptionSecurity *>(&f);
|
||||
core::TimeStamp exp;
|
||||
core::TimeStamp::from_ymd(
|
||||
opt->expiration_year, opt->expiration_month, opt->expiration_day,
|
||||
&exp);
|
||||
j["expiration"] = exp.to_iso_string();
|
||||
j["strike"] = opt->strike;
|
||||
j["callput"] = api::PutOrCall_Name(opt->call_put);
|
||||
break;
|
||||
}
|
||||
case api::SECURITYTYPE_SPREAD: {
|
||||
auto *spd = static_cast<const FixedSpreadSecurityXL *>(&f);
|
||||
std::vector<FixedSpreadLeg> legs{spd->legs, spd->legs + spd->leg_count()};
|
||||
j["legs"] = legs;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace md
|
||||
} // namespace bts
|
||||
|
||||
|
||||
@@ -273,71 +273,6 @@ inline void to_json(nlohmann::json &j, const FixedSpreadLeg &f) {
|
||||
};
|
||||
}
|
||||
|
||||
inline void to_json(nlohmann::json &j, const FixedSecurity &f) {
|
||||
j = nlohmann::json{
|
||||
{"key", f.key},
|
||||
{"symbol", f.symbol()},
|
||||
{"type", api::SecurityType_Name(f.type)},
|
||||
{"venue", api::Venue_Name(f.exchange)},
|
||||
{"alt_symbol", f.alt_symbol()},
|
||||
{"cfi_code", f.cfi_code()},
|
||||
{"security_desc", f.security_desc()},
|
||||
{"platform_id", f.platform_id()},
|
||||
{"display_factor", f.display_factor},
|
||||
{"numeric_id", f.numeric_id},
|
||||
{"cboe_class_key", f.cboe_class_key},
|
||||
{"main_frac", (int)f.cme_main_fraction},
|
||||
{"sub_frac", (int)f.cme_sub_fraction},
|
||||
{"price_format", (int)f.cme_price_display_format},
|
||||
{"market_segment", (int)f.cme_market_segment_id},
|
||||
|
||||
{"price_break", f.price_break},
|
||||
{"low_tick", f.low_tick},
|
||||
{"high_tick", f.high_tick},
|
||||
{"cpp", f.cash_per_point},
|
||||
|
||||
{"ice_market_description", f.ice_market_description()},
|
||||
{"ice_cleared_alias", f.ice_cleared_alias()},
|
||||
{"ice_product_name", f.ice_product_name()},
|
||||
{"ice_hub_alias", f.ice_hub_alias()},
|
||||
{"ice_strip_name", f.ice_strip_name()},
|
||||
{"mic_code", f.mic_code()},
|
||||
};
|
||||
|
||||
switch (f.type) {
|
||||
case api::SECURITYTYPE_SPOT:
|
||||
break;
|
||||
case api::SECURITYTYPE_FUTURE: {
|
||||
auto *fut = static_cast<const FixedFutureSecurity *>(&f);
|
||||
core::TimeStamp exp;
|
||||
core::TimeStamp::from_ymd(
|
||||
fut->expiration_year, fut->expiration_month, fut->expiration_day,
|
||||
&exp);
|
||||
j["expiration"] = exp.to_iso_string();
|
||||
break;
|
||||
}
|
||||
case api::SECURITYTYPE_OPTION: {
|
||||
auto *opt = static_cast<const FixedOptionSecurity *>(&f);
|
||||
core::TimeStamp exp;
|
||||
core::TimeStamp::from_ymd(
|
||||
opt->expiration_year, opt->expiration_month, opt->expiration_day,
|
||||
&exp);
|
||||
j["expiration"] = exp.to_iso_string();
|
||||
j["strike"] = opt->strike;
|
||||
j["callput"] = api::PutOrCall_Name(opt->call_put);
|
||||
break;
|
||||
}
|
||||
case api::SECURITYTYPE_SPREAD: {
|
||||
auto *spd = static_cast<const FixedSpreadSecurityXL *>(&f);
|
||||
std::vector<FixedSpreadLeg> legs{spd->legs, spd->legs + spd->leg_count()};
|
||||
j["legs"] = legs;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
inline std::ostream &operator<<(
|
||||
std::ostream &out, const FixedSecurity &f) noexcept {
|
||||
out << io::JsonDumper::dump(f);
|
||||
|
||||
@@ -155,7 +155,7 @@ void OrderStore::store_and_publish_if_complete(
|
||||
auto &rec_info = me->known_orders[k];
|
||||
auto &rec = rec_info.rec;
|
||||
if (rec.has_def() && rec.has_state()) {
|
||||
LOG(OrderMan, Info,
|
||||
LOG(OrderMan, Diag,
|
||||
"OrderStore::store_and_publish_if_complete rec: {}",
|
||||
utils::pb::to_string(rec));
|
||||
publish(me->order_stream, rec);
|
||||
|
||||
@@ -271,13 +271,14 @@ void OrderManager::on_execution_report(
|
||||
if (it == me->orders.end()) {
|
||||
LOG(OrderMan, Error,
|
||||
"Received an execution report for an unknown order with key "
|
||||
"{}. Order state will be reconstructed from this report but "
|
||||
"{}. Order state USED TO be reconstructed from this report but "
|
||||
"some "
|
||||
"information will be lost, including CustomerOrFirm,CTI "
|
||||
"code "
|
||||
"and "
|
||||
"parent order id",
|
||||
"parent order id but now these are IGNORED",
|
||||
k.value());
|
||||
continue;
|
||||
auto order =
|
||||
&me->services_data.order_factory.recreate_from_exe_report(
|
||||
*report.data);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "lib/orderman/gateways/GLBXiLink3ExecutionGateway.h"
|
||||
#include "lib/orderman/gateways/ICEExecutionGateway.h"
|
||||
#include "lib/orderman/gateways/IExecutionGenericAdapter.h"
|
||||
#include "lib/orderman/gateways/OkxExecutionAdapter.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"
|
||||
@@ -49,9 +49,7 @@ std::unique_ptr<IExecutionGateway> ExecutionGatewayFactory::create_gateway(
|
||||
return std::make_unique<GeneralExecutionGateway>(conn.venue(),std::move(adapter),o);
|
||||
}
|
||||
case bts::api::VENUE_OKX:
|
||||
{
|
||||
std::unique_ptr<IExecutionGenericAdapter> adapter = std::make_unique<OkxExecutionAdapter>(o);
|
||||
return std::make_unique<GeneralExecutionGateway>(conn.venue(),std::move(adapter),o);
|
||||
{ return std::make_unique<OkxExecutionGateway>(o);
|
||||
}
|
||||
default:
|
||||
return std::make_unique<FakeExecutionGateway>(o);
|
||||
|
||||
@@ -64,7 +64,9 @@ void GeneralExecutionGateway::send_msgs(
|
||||
msg);
|
||||
}
|
||||
}
|
||||
void GeneralExecutionGateway::dispatch_incoming_fix_messages() {}
|
||||
void GeneralExecutionGateway::dispatch_incoming_fix_messages() {
|
||||
me->_adapter->dispatch_incoming_fix_messages();
|
||||
}
|
||||
void GeneralExecutionGateway::on_securities(
|
||||
const mercury::Update<NewSecuritiesEvent> &) {}
|
||||
void GeneralExecutionGateway::on_admin_data(
|
||||
|
||||
@@ -22,7 +22,7 @@ class CancelReject;
|
||||
|
||||
using OnExecutionReport = std::function<void(ExecutionReport const &, const trace::CorID &)>;
|
||||
|
||||
// fix execution gateway for a single FIX session
|
||||
// execution gateway for a single session
|
||||
class IExecutionGenericAdapter {
|
||||
public:
|
||||
IExecutionGenericAdapter() = default;
|
||||
@@ -34,6 +34,7 @@ class IExecutionGenericAdapter {
|
||||
virtual void launch_new_order(const CreateOrderMsg &) =0;
|
||||
virtual void modify_order(const CancelReplaceOrderMsg &) = 0;
|
||||
virtual void cancel_order(const CancelOrderMsg &) =0;
|
||||
virtual void dispatch_incoming_fix_messages() = 0;
|
||||
// as new securities are added
|
||||
// virtual void update_securities(
|
||||
// const mercury::Update<NewSecuritiesEvent> &) = 0;
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
#include "OkxExecutionAdapter.h"
|
||||
#include "OnixS/FIXEngine/FIX/Engine.h"
|
||||
#include "lib/config/protos/marex_fix_cfg.pb.h"
|
||||
#include "lib/domain_model/Security.pb.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/orderman/CancelOrderMsg.h"
|
||||
#include "lib/orderman/CancelReplaceOrderMsg.h"
|
||||
#include "lib/orderman/CreateOrderMsg.h"
|
||||
#include "lib/orderman/gateways/OnixFIXListener.h"
|
||||
#include "lib/orderman/gateways/TrafixOrderStateHandler.h"
|
||||
#include "lib/orderman/orderman_utils/orderman_common.h"
|
||||
#include "lib/utils/bts_assert.h"
|
||||
#include "lib/utils/helper_macros.h"
|
||||
#include <OnixS/FIXEngine/FIX/Exception.h>
|
||||
#include <OnixS/FIXEngine/FIX/FIX42.h>
|
||||
#include <OnixS/FIXEngine/FIX/Message.h>
|
||||
#include <OnixS/FIXEngine/FIX/Numeric.h>
|
||||
#include <OnixS/FIXEngine/FIX/ProtocolVersion.h>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <quickfix/FixFields.h>
|
||||
#include <ratio>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace bts::orderman {
|
||||
|
||||
OkxExecutionAdapter::OkxExecutionAdapter(md::SecurityOracle){}//listener() {}
|
||||
OkxExecutionAdapter::~OkxExecutionAdapter() {}
|
||||
inline void handleOkException(
|
||||
const std::string &context, const std::string &error) {
|
||||
bts_assert_msgf(false, "{} Exception: {}", context, error);
|
||||
}
|
||||
void OkxExecutionAdapter::start() {
|
||||
int attempt = 0;
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
LOG(OrderMan, Warn, "OkxExecutionAdapter::start Attempting login (Attempt: {})", attempt + 1);
|
||||
LOG(OrderMan, Warn, "OkxExecutionAdapter::start Login successful");
|
||||
break; // Exit loop on success
|
||||
} catch (const std::exception &ex) {
|
||||
const std::string err = ex.what();
|
||||
LOG(OrderMan, Error, "OkxExecutionAdapter::start Login failed: {}. Retrying in {} seconds.", err, 10);
|
||||
|
||||
// if (maxRetries > 0 && ++attempt >= maxRetries) {
|
||||
// LOG(OrderMan, Error, "OkxExecutionAdapter::start Exceeded max retries ({}). Aborting.", maxRetries);
|
||||
// handleOkException("OkxExecutionAdapter::start", err);
|
||||
// break; // Exit if retries are exhausted
|
||||
// }
|
||||
// std::this_thread::sleep_for(std::chrono::seconds(std::max(60,retryInterval)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OkxExecutionAdapter::modify_order(
|
||||
const CancelReplaceOrderMsg &msg) { //confirm will have everything
|
||||
try {
|
||||
LOG(OrderMan, Warn, "OkxExecutionAdapter::modify_order modifying order{}",
|
||||
msg);
|
||||
} catch (const std::exception &ex) {
|
||||
const std::string err = ex.what();
|
||||
handleOkException("Modify order", err);
|
||||
}
|
||||
}
|
||||
|
||||
void OkxExecutionAdapter::launch_new_order(const CreateOrderMsg &msg) {
|
||||
LOG(OrderMan, Warn, "launch order {}", msg);
|
||||
try {
|
||||
LOG(OrderMan, Warn, "OkxExecutionAdapter::launch_new_order creating order");
|
||||
}
|
||||
// Shut down all reactors if they exist.
|
||||
catch (const std::exception &ex) {
|
||||
const std::string err = ex.what();
|
||||
handleOkException("Launch order", err);
|
||||
}
|
||||
}
|
||||
void OkxExecutionAdapter::register_callback([[maybe_unused]]const OnExecutionReport &cb) {
|
||||
LOG(OrderMan, Warn, "OkxExecutionAdapter::register_callback callback registered");
|
||||
// listener.setCallback(cb);
|
||||
}
|
||||
void OkxExecutionAdapter::cancel_order(const CancelOrderMsg &msg) {
|
||||
LOG(OrderMan, Warn, "cancel order {}", msg);
|
||||
try {
|
||||
LOG(OrderMan, Warn, "order cancel sent");
|
||||
}
|
||||
// Shut down all reactors if they exist.
|
||||
catch (const std::exception &ex) {
|
||||
const std::string err = ex.what();
|
||||
handleOkException("Cacnel order", err);
|
||||
}
|
||||
}
|
||||
} // namespace bts::orderman
|
||||
@@ -1,19 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "IExecutionGenericAdapter.h"
|
||||
#include "lib/md/SecurityOracle.h"
|
||||
namespace bts::orderman {
|
||||
|
||||
class OkxExecutionAdapter : public IExecutionGenericAdapter {
|
||||
public:
|
||||
OkxExecutionAdapter(md::SecurityOracle sec_oracle);
|
||||
~OkxExecutionAdapter() override;
|
||||
void register_callback(const OnExecutionReport &report) override;
|
||||
void launch_new_order(const CreateOrderMsg &msg) override;
|
||||
void cancel_order(const CancelOrderMsg &msg) override;
|
||||
void modify_order(const CancelReplaceOrderMsg &) override;
|
||||
void start() override;
|
||||
private:
|
||||
};
|
||||
|
||||
} // namespace bts::orderman
|
||||
@@ -0,0 +1,722 @@
|
||||
#include "OkxExecutionGateway.h"
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/config/protos/okx_exe_cfg.pb.h"
|
||||
#include "lib/domain_model/Security.pb.h"
|
||||
#include "lib/domain_model/common.h"
|
||||
#include "lib/io/http/http_client.h"
|
||||
#include "lib/io/http/http_response.h"
|
||||
#include "lib/io/websocket/websocket_client.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/md/SecurityOracle.h"
|
||||
#include "lib/orderman/CancelOrderMsg.h"
|
||||
#include "lib/orderman/CancelReplaceOrderMsg.h"
|
||||
#include "lib/orderman/CreateOrderMsg.h"
|
||||
#include "lib/orderman/gateways/IExecutionGateway.h"
|
||||
#include "lib/orderman/orderman_utils/ExecutionReport.h"
|
||||
#include "lib/orderman/orderman_utils/OkxUtils.h"
|
||||
#include "lib/orderman/orderman_utils/orderman_common.h"
|
||||
#include "lib/utils/Uuid.h"
|
||||
#include "lib/utils/bts_assert.h"
|
||||
#include "lib/utils/thread_manager.h"
|
||||
#include <OnixS/FIXEngine/FIX/Exception.h>
|
||||
#include <OnixS/FIXEngine/FIX/FIX42.h>
|
||||
#include <OnixS/FIXEngine/FIX/Message.h>
|
||||
#include <OnixS/FIXEngine/FIX/Numeric.h>
|
||||
#include <OnixS/FIXEngine/FIX/ProtocolVersion.h>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <ctime>
|
||||
#include <exception>
|
||||
#include <fmt/core.h>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <openssl/buffer.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/hmac.h>
|
||||
#include <quickfix/FixFields.h>
|
||||
#include <quickfix/Utility.h>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
#include <unordered_map>
|
||||
// Example namespace alias for nlohmann::json
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace bts::orderman {
|
||||
|
||||
OkxExecutionGateway::OkxExecutionGateway(md::SecurityOracle o)
|
||||
: me_(std::make_unique<PImpl>(o)) {
|
||||
LOG(OrderMan, Info, "OkxExecutionAdapter::constructor ");
|
||||
} //listener() {}
|
||||
OkxExecutionGateway::~OkxExecutionGateway() {}
|
||||
|
||||
enum class OkxExecutionState { OFF, STARTED, LOGGED_IN, SUBSCIRBED };
|
||||
void OkxExecutionGateway::send_msgs(
|
||||
std::vector<OrderMsg> const &msgs, trace::CorID) {
|
||||
LOG(OrderMan,Info,"ExecutionGateway::send_msgs" );
|
||||
for (const auto &msg : msgs) {
|
||||
std::visit(
|
||||
[this](auto &&order) {
|
||||
using T = std::decay_t<decltype(order)>;
|
||||
if constexpr (std::is_same_v<T, CreateOrderMsg>) {
|
||||
LOG(OrderMan,Warn,"ExecutionGateway::CreateOrderMsg" );
|
||||
launch_new_order(order);
|
||||
|
||||
} else if constexpr (std::is_same_v<T, CancelOrderMsg>) {
|
||||
LOG(OrderMan,Warn,"ExecutionGateway::CancelOrderMsg" );
|
||||
cancel_order(order);
|
||||
} else if constexpr (std::is_same_v<T, CancelReplaceOrderMsg>) {
|
||||
LOG(OrderMan,Warn,"ExecutionGateway::CancelReplaceOrderMsg" );
|
||||
modify_order(order);
|
||||
} else if constexpr (std::is_same_v<T, RFQMsg>) {
|
||||
// Handle RFQMsg if necessary
|
||||
// adapter->handle_rfq(order); // Example placeholder
|
||||
}
|
||||
},
|
||||
msg);
|
||||
}
|
||||
}
|
||||
struct OkxExecutionGateway::PImpl {
|
||||
const std::string api_key = config::get<config::OkxExeCfg>().api_key();
|
||||
const std::string secret_key = config::get<config::OkxExeCfg>().secret_key();
|
||||
const std::string passphrase = config::get<config::OkxExeCfg>().passphrase();
|
||||
const std::string url =
|
||||
config::get<config::OkxExeCfg>().ws_url() + "/ws/v5/private";
|
||||
const bool simulated_trading = config::get<config::OkxExeCfg>().simulated_trading();
|
||||
const std::string tdmode = config::get<config::OkxExeCfg>().tdmode();
|
||||
const core::TimeSpan connect_timeout = core::TimeSpan::from_seconds(0);
|
||||
const core::TimeSpan pong_timeout = core::TimeSpan::from_seconds(
|
||||
config::get<config::OkxExeCfg>().ws_ping_interval_seconds());
|
||||
websocket::WebsocketClient ws_client;
|
||||
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;
|
||||
OnCancelReject on_cancel_reject;
|
||||
OnExchangePositionState on_exchange_position_state;
|
||||
OnExchangeOpenOrdersState on_exchange_open_orders;
|
||||
md::SecurityOracle o;
|
||||
std::atomic<OkxExecutionState> state = OkxExecutionState::OFF;
|
||||
PImpl(md::SecurityOracle o)
|
||||
: ws_client(
|
||||
websocket::WebsocketClient({url, connect_timeout, pong_timeout})),
|
||||
cb(dummy), o(o) {
|
||||
ws_client.message_handler().bind<&PImpl::on_message>(*this);
|
||||
ws_client.close_handler().bind<&PImpl::on_close>(*this);
|
||||
};
|
||||
|
||||
~PImpl() {
|
||||
state = OkxExecutionState::OFF;
|
||||
if (ws_run_thread) {
|
||||
ws_run_thread->join();
|
||||
}
|
||||
}
|
||||
|
||||
static void dummy(const ExecutionReport &, const trace::CorID &) {
|
||||
LOG(OrderMan, Error, "OnixFixListener::Dummy fired");
|
||||
}
|
||||
void setCallback(const OnExeReport &callback) {
|
||||
LOG(OrderMan, Info,
|
||||
"OkxExecutionAdapter::setCallback callback registered");
|
||||
cb = callback;
|
||||
}
|
||||
|
||||
std::optional<std::thread> ws_run_thread;
|
||||
std::optional<std::thread> ws_ping_thread;
|
||||
|
||||
void get_exe_report(
|
||||
const nlohmann::basic_json<> &order, ExecutionReport &report) {
|
||||
report.exchange = api::VENUE_OKX;
|
||||
std::string account_name = config::get<config::OkxExeCfg>().account();
|
||||
std::string ops_id = config::get<config::OkxExeCfg>().ops_id();
|
||||
|
||||
std::array<char, 12> account_array = {};
|
||||
std::copy_n(
|
||||
account_name.begin(),
|
||||
std::min(account_name.size(), account_array.size()),
|
||||
account_array.begin());
|
||||
report.account_name = AccountName(account_array);
|
||||
|
||||
to_array(ops_id, &report.operator_id.value());
|
||||
|
||||
report.order_key =
|
||||
OrderKey(std::stoi(order.at("clOrdId").get<std::string>()));
|
||||
|
||||
auto insttype =
|
||||
OkxUtils::getInstType(order.at("instType").get<std::string>());
|
||||
auto venue = api::VENUE_OKX;
|
||||
auto inst_id = order.at("instId").get<std::string>();
|
||||
|
||||
// report.msg_seq_num = /* Generate or map message sequence */;
|
||||
bts_assert(o.get_securities(venue, insttype, inst_id).size() == 1);
|
||||
|
||||
report.security = o.get_securities(venue, insttype, inst_id)[0];
|
||||
|
||||
report.side = (order.at("side").get<std::string>() == "buy")
|
||||
? api::Side::SIDE_BUY
|
||||
: api::Side::SIDE_SELL;
|
||||
report.order_type =
|
||||
OkxUtils::getOrdType(order.at("ordType").get<std::string>());
|
||||
OkxUtils::populateExecTypeAndOrderStatus(
|
||||
report, order.at("state").get<std::string>());
|
||||
|
||||
report.exchange_order_id = ExchangeOrderID(
|
||||
OkxUtils::stringToFixedArray(order.at("ordId").get<std::string>()));
|
||||
|
||||
report.price = Price(std::stod(order.at("px").get<std::string>()));
|
||||
|
||||
report.quantity =
|
||||
Quantity::create_new(std::stod(order.at("sz").get<std::string>()));
|
||||
report.filled_quantity = Quantity::create_new(
|
||||
std::stod(order.at("accFillSz").get<std::string>()));
|
||||
report.last_price =
|
||||
Price(std::stod(order.at("lastPx").get<std::string>()));
|
||||
report.avg_price = Price(std::stod(order.at("avgPx").get<std::string>()));
|
||||
report.last_shares = Quantity::create_new(
|
||||
std::stod(order.at("fillSz").get<std::string>()));
|
||||
|
||||
//there are algos, like spreader
|
||||
// that are super sensitive to messages and cancel themselves. To avoid
|
||||
// this we only pass on the error messages when the order has terminated.
|
||||
// The assumption being that if the order has not terminated it is OK to
|
||||
// ignore the message.
|
||||
// Alternatively we can change the spreader to not do that. Right not this
|
||||
// seems to be the better choice
|
||||
if (!(order.at("code").get<std::string>() == "0")) {
|
||||
report.msg = order.at("msg").get<std::string>();
|
||||
}
|
||||
|
||||
|
||||
//report can be new order, cancel order, order filled, replace order, and partial fill or errror
|
||||
//new order, cancelled and order filled dont need special handling, error gets handled above
|
||||
//for other three we will look for fields that are only specific to the respectives types
|
||||
// if (!(order.at("cancelSource").get<std::string>() == "")) {
|
||||
|
||||
// }
|
||||
if (!(order.at("amendSource").get<std::string>() == "")) {
|
||||
if((order.at("amendResult").get<std::string>() == "0"))
|
||||
report.exec_type = api::orderman::ExecType::EXEC_TYPE_REPLACED;
|
||||
else{
|
||||
LOG(OrderMan, Warn, "order:{} replace rejected", report.order_key);
|
||||
report.msg = order.at("msg").get<std::string>();
|
||||
}
|
||||
} else if (!(order.at("fillSz").get<std::string>() == "0")) {
|
||||
utils::Uuid guid;
|
||||
std::string fill_id = guid.to_text_string();
|
||||
to_array(fill_id, &report.exchange_fill_id.value());
|
||||
}
|
||||
|
||||
// report.transact_time = ExchangeTimeStampVar(std::stoll(order.at("uTime").get<std::string>()));
|
||||
ExchangeTimeStamp64 now = ExchangeTimeStamp64(datetime::unix_nano_now());
|
||||
report.transact_time = now;
|
||||
std::string er = report.to_string();
|
||||
LOG(OrderMan, Info, "Created ExecutionReport: {}", er);
|
||||
}
|
||||
void on_close(const std::error_code &ec) {
|
||||
bts_assert_msgf(
|
||||
false, "Websocket stream client disconnected. {}", ec.message());
|
||||
}
|
||||
// fills execution report form basic_json and writes it to
|
||||
// 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_p = ws_exe_report_pool.construct();
|
||||
ExecutionReport &er = *er_p;
|
||||
get_exe_report(order, er);
|
||||
buffered_exec_reps.push(er_p);
|
||||
}
|
||||
void on_message(const std::string &msg_str) {
|
||||
LOG(OrderMan, Info, "okxlistener::on_message. {}", msg_str);
|
||||
nlohmann::json msg;
|
||||
try {
|
||||
msg = nlohmann::json::parse(msg_str);
|
||||
} catch (const std::exception &ex) {
|
||||
std::string s = ex.what();
|
||||
LOG(OrderMan, Error, "Failed to parse json. {}\n{}", s, msg_str);
|
||||
return;
|
||||
}
|
||||
const auto event_type_it = msg.find("event");
|
||||
if (event_type_it != msg.end()) {
|
||||
const auto event_type = event_type_it->get<std::string>();
|
||||
if (event_type == "login") {
|
||||
bts_assert(state == OkxExecutionState::STARTED);
|
||||
const auto code_it = msg.find("code");
|
||||
const auto code = code_it->get<std::string>();
|
||||
if (code == "0") {
|
||||
LOG(OrderMan, Info, "Succefully logged in {}", msg_str);
|
||||
state = OkxExecutionState::LOGGED_IN;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
if (event_type == "subscribe") {
|
||||
bts_assert(state == OkxExecutionState::LOGGED_IN);
|
||||
state = OkxExecutionState::SUBSCIRBED;
|
||||
return;
|
||||
}
|
||||
if (event_type == "channel-conn-count") { //ignore for now
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Handle order updates
|
||||
|
||||
const auto channel_it = msg.find("arg");
|
||||
if (channel_it == msg.end() ||
|
||||
channel_it->at("channel").get<std::string>() != "orders") {
|
||||
LOG(OrderMan, Error, "Unexpected response id:\n{}", msg_str);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &data = msg.at("data");
|
||||
|
||||
for (const nlohmann::basic_json<> &order : data) {
|
||||
_push_execution_report(order);
|
||||
}
|
||||
}
|
||||
void start() {
|
||||
bts_assert(!ws_thread);
|
||||
state = OkxExecutionState::STARTED;
|
||||
ws_thread.emplace([this]() {
|
||||
bts::threading::configure_current_thread("okx_execution");
|
||||
start_impl();
|
||||
});
|
||||
}
|
||||
|
||||
void start_impl() {
|
||||
size_t attempt = 0;
|
||||
while (state != OkxExecutionState::OFF) {
|
||||
try {
|
||||
LOG(OrderMan, Warn,
|
||||
"OkxExecutionAdapter::start Attempting login (Attempt: {})",
|
||||
attempt + 1);
|
||||
ws_client.connect({}, true);
|
||||
sleep(2);
|
||||
if (ws_ping_thread.has_value()) {
|
||||
if (ws_ping_thread->joinable()) {
|
||||
ws_ping_thread
|
||||
->join(); // Ensure the previous thread exits cleanly
|
||||
}
|
||||
ws_ping_thread
|
||||
.reset(); // Reset the optional before creating a new thread
|
||||
}
|
||||
|
||||
ws_ping_thread.emplace([&]() {
|
||||
LOG(OrderMan, Warn,
|
||||
"OkxExecutionAdapter::ws_ping_thread thread starting. url: "
|
||||
"{}",
|
||||
url);
|
||||
auto last_ping_time = core::TimeStamp::now();
|
||||
auto ping_interval = core::TimeSpan::from_seconds(
|
||||
config::get<config::OkxExeCfg>().ws_ping_interval_seconds());
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::milliseconds(ping_interval.millis()));
|
||||
|
||||
while (ws_client.is_connected()) {
|
||||
const auto now = bts::core::TimeStamp::now();
|
||||
if ((now - last_ping_time) > ping_interval) {
|
||||
LOG(OrderMan, Warn,
|
||||
"OkxExecutionAdapter::ws_ping_thread Pinging "
|
||||
"websocket. url: {}",
|
||||
url);
|
||||
ws_client.ping();
|
||||
last_ping_time = now;
|
||||
}
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::seconds(ping_interval.seconds() / 10));
|
||||
}
|
||||
LOG(OrderMan, Warn,
|
||||
"OkxExecutionAdapter::ws_ping_thread thread exiting. url: "
|
||||
"{}",
|
||||
url);
|
||||
});
|
||||
|
||||
auto [timestamp, sign] = OkxUtils::getLoginSignTime(secret_key);
|
||||
static constexpr const char *AUTH_REQ_TEMPLATE =
|
||||
R"({{"op": "login", "args": [{{ "apiKey": "{}", "passphrase": "{}", "timestamp": "{}", "sign": "{}" }}] }})";
|
||||
|
||||
const std::string auth_req = fmt::format(
|
||||
AUTH_REQ_TEMPLATE, api_key, passphrase, timestamp, sign);
|
||||
LOG(OrderMan, Warn,
|
||||
"OkxExecutionAdapter::start Attempting login (req: {})",
|
||||
auth_req);
|
||||
const auto auth_res = ws_client.send_text(auth_req);
|
||||
LOG(OrderMan, Warn,
|
||||
"OkxExecutionAdapter::start Attempting login {}, {}, {}",
|
||||
std::to_string(auth_res.value()),
|
||||
std::string(auth_res.category().name()), auth_res.message());
|
||||
if (auth_res.value()) {
|
||||
LOG(md, Info, "okx authentication failed. {}",
|
||||
auth_res.message());
|
||||
attempt++;
|
||||
throw std::runtime_error(
|
||||
"okx authentication failed: " + auth_res.message());
|
||||
}
|
||||
while (state == OkxExecutionState::STARTED) {
|
||||
LOG(OrderMan, Warn,
|
||||
"OkxExecutionAdapter::start waiting to login");
|
||||
std::this_thread::sleep_for(1s);
|
||||
}
|
||||
LOG(OrderMan, Warn,
|
||||
"OkxExecutionAdapter::start Login successful {}",
|
||||
auth_res.message());
|
||||
std::string subscribe_req = R"({
|
||||
"op": "subscribe",
|
||||
"args": [{"channel": "orders", "instType": "ANY"}]
|
||||
})";
|
||||
LOG(OrderMan, Warn,
|
||||
"OkxExecutionAdapter::start AGttempting sub (req: {})",
|
||||
subscribe_req);
|
||||
const auto auth_res2 = ws_client.send_text(subscribe_req);
|
||||
LOG(OrderMan, Warn, "OkxExecutionAdapter::subscribing to orders{}",
|
||||
auth_res2.message());
|
||||
while (state != OkxExecutionState::SUBSCIRBED) {
|
||||
LOG(OrderMan, Warn,
|
||||
"OkxExecutionAdapter::start waiting to subscribe");
|
||||
|
||||
std::this_thread::sleep_for(1s);
|
||||
}
|
||||
LOG(OrderMan, Warn,
|
||||
"OkxExecutionAdapter::subscribing to orders successful{}",
|
||||
auth_res2.message());
|
||||
on_gw_status_change(true);
|
||||
break; // Exit loop on success
|
||||
} catch (const std::exception &ex) {
|
||||
const std::string err = ex.what();
|
||||
LOG(OrderMan, Error,
|
||||
"OkxExecutionAdapter::start Login failed: {}. Retrying in {} "
|
||||
"seconds.",
|
||||
err,
|
||||
config::get<config::OkxExeCfg>()
|
||||
.ws_reconnect_interval_seconds());
|
||||
|
||||
size_t maxRetries =
|
||||
config::get<config::OkxExeCfg>().ws_reconnect_max_count();
|
||||
if (maxRetries > 0 && ++attempt >= maxRetries) {
|
||||
LOG(OrderMan, Error,
|
||||
"OkxExecutionAdapter::start Exceeded max retries ({}). "
|
||||
"Aborting.",
|
||||
maxRetries);
|
||||
OkxUtils::handleOkException("OkxExecutionAdapter::start", err);
|
||||
break; // Exit if retries are exhausted
|
||||
}
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::seconds(config::get<config::OkxExeCfg>()
|
||||
.ws_reconnect_interval_seconds()));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
void OkxExecutionGateway::start() { me_->start(); }
|
||||
OnGwStatusChange &OkxExecutionGateway::on_gw_status_change() {
|
||||
return me_->on_gw_status_change;
|
||||
}
|
||||
void OkxExecutionGateway::modify_order(
|
||||
const CancelReplaceOrderMsg &msg) { //confirm will have everything
|
||||
try {
|
||||
LOG(OrderMan, Warn, "OkxExecutionAdapter::modify_order modifying order{}",
|
||||
msg);
|
||||
http::HttpResponse res;
|
||||
// static constexpr const char *body_template = R"({"instId": "BTC-USDT", "tdMode": "cash", "side": "buy", "ordType": "limit", "px": "1000", "sz": "0.01"})";
|
||||
static constexpr const char *body_template =
|
||||
R"({{"instId": "{}", "clordId": "{}", "newPx": "{}", "newSz": "{}"}})";
|
||||
const std::string REQUEST_PATH = "/api/v5/trade/amend-order";
|
||||
const std::string body = fmt::format(
|
||||
body_template, msg.create_fields.security->okx().security_desc(),
|
||||
msg.create_fields.key, msg.create_fields.price,
|
||||
msg.create_fields.quantity.full_value());
|
||||
auto headers = OkxUtils::createHeaders(
|
||||
me_->api_key, me_->passphrase, me_->secret_key, "POST", REQUEST_PATH,
|
||||
me_->simulated_trading,body);
|
||||
// Generate and log the curl command
|
||||
std::string curlCommand = OkxUtils::generateCurlCommand(
|
||||
config::get<config::OkxExeCfg>().rest_url() + REQUEST_PATH, headers,
|
||||
body);
|
||||
LOG(OrderMan, Warn, "Curl Command: \n{}", curlCommand);
|
||||
http::HttpClient::post(
|
||||
config::get<config::OkxExeCfg>().rest_url() + REQUEST_PATH, body,
|
||||
headers, &res);
|
||||
const auto &[suc, top_msg] = OkxUtils::orderAccepted(res.body);
|
||||
|
||||
if (suc) {
|
||||
LOG(md, Info, "Http get succeeded {}", res.body);
|
||||
} else {
|
||||
LOG(md, Info, "Http get failed code: {}, errors: {}, body: {}", res.code, res.errors, res.body);
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
const std::string err = ex.what();
|
||||
OkxUtils::handleOkException("Amend order", err);
|
||||
}
|
||||
}
|
||||
void OkxExecutionGateway::dispatch_incoming_fix_messages() {
|
||||
if (me_->buffered_exec_reps.empty()) {
|
||||
return;
|
||||
}
|
||||
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(*er, cid);
|
||||
}
|
||||
}
|
||||
OnExeReport &OkxExecutionGateway::on_exe_report() { return me_->cb; }
|
||||
OnCancelReject &OkxExecutionGateway::on_cancel_reject() {
|
||||
static OnCancelReject default_cancel_reject;
|
||||
return default_cancel_reject;
|
||||
}
|
||||
OnExchangePositionState &OkxExecutionGateway::on_exchange_position_state() {
|
||||
static OnExchangePositionState default_exchange_position_state;
|
||||
return default_exchange_position_state;
|
||||
}
|
||||
OnExchangeOpenOrdersState &OkxExecutionGateway::on_exchange_open_orders() {
|
||||
static OnExchangeOpenOrdersState default_exchange_open_orders;
|
||||
return default_exchange_open_orders;
|
||||
}
|
||||
void OkxExecutionGateway::launch_new_order(const CreateOrderMsg &msg) {
|
||||
LOG(OrderMan, Warn, "launch order {}", msg);
|
||||
try {
|
||||
LOG(OrderMan, Warn,
|
||||
"OkxExecutionAdapter::launch_new_order creating order");
|
||||
static constexpr const char *body_template =
|
||||
R"({{"instId": "{}", "tdMode": "{}", "side": "{}", "ordType": "{}", "px": "{}", "sz": "{}", "clordId": "{}"}})";
|
||||
const std::string REQUEST_PATH = "/api/v5/trade/order";
|
||||
const std::string side = msg.side == api::Side::SIDE_BUY ? "buy" : "sell";
|
||||
const std::string quantity = std::to_string(msg.quantity.full_value());
|
||||
const std::string price = std::to_string(msg.price.value());
|
||||
std::string suffix = "ORDER_TYPE_";
|
||||
auto ord_full_name = NativeOrderType_Name(msg.order_type);
|
||||
auto pos = ord_full_name.find(suffix);
|
||||
const std::string ord_type =
|
||||
FIX::string_toLower(ord_full_name.substr(pos + suffix.size()));
|
||||
const std::string body = fmt::format(
|
||||
body_template, msg.security->okx().security_desc(),me_->tdmode, side, ord_type,
|
||||
price, quantity, msg.key);
|
||||
http::HttpResponse res;
|
||||
auto headers = OkxUtils::createHeaders(
|
||||
me_->api_key, me_->passphrase, me_->secret_key, "POST", REQUEST_PATH,
|
||||
me_->simulated_trading,body);
|
||||
// Generate and log the curl command
|
||||
std::string curlCommand = OkxUtils::generateCurlCommand(
|
||||
config::get<config::OkxExeCfg>().rest_url() + REQUEST_PATH, headers,
|
||||
body);
|
||||
LOG(OrderMan, Warn, "Curl Command: \n{}", curlCommand);
|
||||
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);
|
||||
const auto &[suc, top_msg] = OkxUtils::orderAccepted(res.body);
|
||||
|
||||
OkxUtils::populateCreateOrderFields(er, msg);
|
||||
er.price = msg.price;
|
||||
er.quantity = msg.quantity;
|
||||
er.filled_quantity = 0;
|
||||
er.last_shares = 0;
|
||||
er.avg_price = Price(0);
|
||||
ExchangeTimeStamp64 now = ExchangeTimeStamp64(datetime::unix_nano_now());
|
||||
er.transact_time = now;
|
||||
|
||||
er.order_key = msg.key;
|
||||
const auto &[id, ts, ex_msg] = OkxUtils::extractOrderInfo(res.body);
|
||||
er.exchange_order_id = ExchangeOrderID{OkxUtils::stringToFixedArray(id)};
|
||||
|
||||
if (suc) {
|
||||
LOG(md, Info, "Http get succeeded {}", res.body);
|
||||
er.exec_type = api::orderman::ExecType::EXEC_TYPE_PENDING_NEW;
|
||||
er.order_status = api::orderman::NATIVE_ORDER_STATUS_PENDING_NEW;
|
||||
} else {
|
||||
LOG(md, Info, "Http get failed code: {}, errors: {}, body: {}", res.code, res.errors, res.body);
|
||||
er.exec_type = api::orderman::ExecType::EXEC_TYPE_REJECTED;
|
||||
er.order_status = api::orderman::NATIVE_ORDER_STATUS_REJECTED;
|
||||
er.msg = ex_msg + " " + top_msg;
|
||||
}
|
||||
me_->buffered_exec_reps.push(er_p);
|
||||
}
|
||||
// Shut down all reactors if they exist.
|
||||
catch (const std::exception &ex) {
|
||||
const std::string err = ex.what();
|
||||
OkxUtils::handleOkException("Launch order", err);
|
||||
}
|
||||
}
|
||||
void OkxExecutionGateway::register_callback(
|
||||
[[maybe_unused]] const OnExeReport &cb) {
|
||||
LOG(OrderMan, Warn,
|
||||
"OkxExecutionAdapter::register_callback callback registered");
|
||||
me_->setCallback(cb);
|
||||
}
|
||||
|
||||
void OkxExecutionGateway::cancel_order(const CancelOrderMsg &msg) {
|
||||
LOG(OrderMan, Warn, "cancel order {}", msg);
|
||||
try {
|
||||
http::HttpResponse res;
|
||||
// static constexpr const char *body_template = R"({"instId": "BTC-USDT", "tdMode": "cash", "side": "buy", "ordType": "limit", "px": "1000", "sz": "0.01"})";
|
||||
static constexpr const char *body_template =
|
||||
R"({{"instId": "{}", "clordId": "{}"}})";
|
||||
const std::string REQUEST_PATH = "/api/v5/trade/cancel-order";
|
||||
const std::string body = fmt::format(
|
||||
body_template, msg.security->okx().security_desc(), msg.key);
|
||||
auto headers = OkxUtils::createHeaders(
|
||||
me_->api_key, me_->passphrase, me_->secret_key, "POST", REQUEST_PATH,
|
||||
me_->simulated_trading,body);
|
||||
// Generate and log the curl command
|
||||
std::string curlCommand = OkxUtils::generateCurlCommand(
|
||||
config::get<config::OkxExeCfg>().rest_url() + REQUEST_PATH, headers,
|
||||
body);
|
||||
LOG(OrderMan, Warn, "Curl Command: \n{}", curlCommand);
|
||||
http::HttpClient::post(
|
||||
config::get<config::OkxExeCfg>().rest_url() + REQUEST_PATH, body,
|
||||
headers, &res);
|
||||
const auto &[suc, top_msg] = OkxUtils::orderAccepted(res.body);
|
||||
// ExecutionReport *er_p = me_->exe_report_pool.construct();
|
||||
// ExecutionReport &er = *er_p;
|
||||
// er.security = msg.security;
|
||||
// er.side = msg.side;
|
||||
// er.order_key = msg.key;
|
||||
// to_array(msg.routing_record->account(), &er.account_name.value());
|
||||
// to_array(msg.routing_record->operator_id(), &er.operator_id.value());
|
||||
// er.exchange = msg.security->exchange();
|
||||
// er.order_key = msg.key;
|
||||
// const auto &[id, ts, ex_msg] = OkxUtils::extractOrderInfo(res.body);
|
||||
if (suc) {
|
||||
LOG(md, Info, "Http get succeeded {}", res.body);
|
||||
// er.exec_type = api::orderman::ExecType::EXEC_TYPE_PENDING_CANCEL;
|
||||
// er.order_status = api::orderman::NATIVE_ORDER_STATUS_PENDING_CANCEL;
|
||||
} else {
|
||||
LOG(md, Info, "Http get failed code: {}, errors: {}, body: {}", res.code, res.errors, res.body);
|
||||
// er.exec_type = api::orderman::ExecType::EXEC_TYPE_REJECTED;
|
||||
// er.order_status = api::orderman::NATIVE_ORDER_STATUS_REJECTED;
|
||||
// er.msg = ex_msg + " " + top_msg;
|
||||
}
|
||||
// me_->cb(er, {});
|
||||
}
|
||||
// Shut down all reactors if they exist.
|
||||
catch (const std::exception &ex) {
|
||||
const std::string err = ex.what();
|
||||
OkxUtils::handleOkException("Cacnel order", err);
|
||||
}
|
||||
}
|
||||
} // namespace bts::orderman::okxexe
|
||||
|
||||
|
||||
// ExecutionReport OkxExecutionAdapter::fire_dummy_modify_order_report(CancelReplaceOrderMsg msg) {
|
||||
// ExecutionReport *er_p = me_->create_exe_ptr();
|
||||
// ExecutionReport &er = *er_p;
|
||||
// std::string account_name = config::get<config::OkxExeCfg>().account();
|
||||
// std::string ops_id = config::get<config::OkxExeCfg>().ops_id();
|
||||
|
||||
// std::array<char, 12> account_array = {};
|
||||
// std::copy_n(
|
||||
// account_name.begin(),
|
||||
// std::min(account_name.size(), account_array.size()),
|
||||
// account_array.begin());
|
||||
// er.account_name = AccountName(account_array);
|
||||
|
||||
// to_array(ops_id, &er.operator_id.value());
|
||||
// er.order_key = msg.create_fields.key;
|
||||
// er.msg_seq_num = 0;
|
||||
// er.security = msg.create_fields.security;
|
||||
// er.leg_security = nullptr;
|
||||
// er.side = msg.create_fields.side;
|
||||
// er.time_in_force = msg.create_fields.time_in_force;
|
||||
// er.order_type = msg.create_fields.order_type;
|
||||
// er.exec_type = api::orderman::EXEC_TYPE_REPLACED;
|
||||
// er.order_status = api::orderman::NATIVE_ORDER_STATUS_REPLACED;
|
||||
// er.manual_order_indicator = api::orderman::MANUAL_ORDER_INDICATOR_NO;
|
||||
// auto s = std::to_string(msg.create_fields.key.value());
|
||||
// std::array<char, 17> idAsArray{};
|
||||
// std::copy_n(s.begin(), std::min(s.size()-1, idAsArray.size() - 1), idAsArray.begin());
|
||||
// er.exchange_order_id = ExchangeOrderID{idAsArray};
|
||||
// er.price = msg.create_fields.price;
|
||||
// er.quantity = msg.create_fields.quantity;
|
||||
// er.filled_quantity = Quantity(0);
|
||||
// er.last_shares = Quantity(0);
|
||||
// er.msg = "";
|
||||
// er.exchange = msg.create_fields.security->exchange();
|
||||
// // Set transaction time
|
||||
// ExchangeTimeStamp64 now = ExchangeTimeStamp64(datetime::unix_nano_now());
|
||||
// er.transact_time = now;
|
||||
// me_->cb(er, {});
|
||||
// return er;
|
||||
// }
|
||||
// ExecutionReport OkxExecutionAdapter::fire_dummy_cancel_order_report(CancelOrderMsg msg) {
|
||||
// ExecutionReport *er_p = me_->create_exe_ptr();
|
||||
// ExecutionReport &er = *er_p;
|
||||
// std::string account_name = config::get<config::OkxExeCfg>().account();
|
||||
// std::string ops_id = config::get<config::OkxExeCfg>().ops_id();
|
||||
|
||||
// std::array<char, 12> account_array = {};
|
||||
// std::copy_n(
|
||||
// account_name.begin(),
|
||||
// std::min(account_name.size(), account_array.size()),
|
||||
// account_array.begin());
|
||||
// er.account_name = AccountName(account_array);
|
||||
|
||||
// to_array(ops_id, &er.operator_id.value());
|
||||
// er.order_key = msg.key;
|
||||
// er.msg_seq_num = 0;
|
||||
// er.security = msg.security;
|
||||
// er.leg_security = nullptr;
|
||||
// er.side = msg.side;
|
||||
// er.time_in_force = api::orderman::TIME_IN_FORCE_GTC;
|
||||
// er.order_type = api::orderman::NATIVE_ORDER_TYPE_LIMIT;
|
||||
// er.exec_type = api::orderman::EXEC_TYPE_TRADE_CANCEL;
|
||||
// er.order_status = api::orderman::NATIVE_ORDER_STATUS_CANCELED;
|
||||
// er.manual_order_indicator = api::orderman::MANUAL_ORDER_INDICATOR_NO;
|
||||
// auto s = std::to_string(msg.key.value());
|
||||
// std::array<char, 17> idAsArray{};
|
||||
// std::copy_n(s.begin(), std::min(s.size()-1, idAsArray.size() - 1), idAsArray.begin());
|
||||
// er.exchange_order_id = ExchangeOrderID{idAsArray};
|
||||
// er.filled_quantity = Quantity(0);
|
||||
// er.last_shares = Quantity(0);
|
||||
// er.msg = "";
|
||||
// er.exchange = msg.security->exchange();
|
||||
// // Set transaction time
|
||||
// ExchangeTimeStamp64 now = ExchangeTimeStamp64(datetime::unix_nano_now());
|
||||
// er.transact_time = now;
|
||||
// me_->cb(er, {});
|
||||
// return er;
|
||||
// }
|
||||
// ExecutionReport OkxExecutionAdapter::fire_dummy_new_order_report(CreateOrderMsg msg) {
|
||||
// ExecutionReport *er_p = me_->create_exe_ptr();
|
||||
// ExecutionReport &er = *er_p;
|
||||
// std::string account_name = config::get<config::OkxExeCfg>().account();
|
||||
// std::string ops_id = config::get<config::OkxExeCfg>().ops_id();
|
||||
|
||||
// std::array<char, 12> account_array = {};
|
||||
// std::copy_n(
|
||||
// account_name.begin(),
|
||||
// std::min(account_name.size(), account_array.size()),
|
||||
// account_array.begin());
|
||||
// er.account_name = AccountName(account_array);
|
||||
|
||||
// to_array(ops_id, &er.operator_id.value());
|
||||
// er.order_key = msg.key;
|
||||
// er.msg_seq_num = 0;
|
||||
// er.security = msg.security;
|
||||
// er.leg_security = nullptr;
|
||||
// er.side = msg.side;
|
||||
// er.time_in_force = api::orderman::TIME_IN_FORCE_GTC;
|
||||
// er.order_type = msg.order_type;
|
||||
// er.exec_type = api::orderman::EXEC_TYPE_NEW;
|
||||
// er.order_status = api::orderman::NATIVE_ORDER_STATUS_NEW;
|
||||
// er.manual_order_indicator = api::orderman::MANUAL_ORDER_INDICATOR_NO;
|
||||
// auto s = std::to_string(msg.key.value());
|
||||
// std::array<char, 17> idAsArray{};
|
||||
// std::copy_n(s.begin(), std::min(s.size()-1, idAsArray.size() - 1), idAsArray.begin());
|
||||
// er.exchange_order_id = ExchangeOrderID{idAsArray};
|
||||
// er.price = msg.price;
|
||||
// er.quantity = msg.quantity;
|
||||
// er.filled_quantity = Quantity(0);
|
||||
// er.last_shares = Quantity(0);
|
||||
// er.msg = "";
|
||||
// er.exchange = msg.security->exchange();
|
||||
// // Set transaction time
|
||||
// ExchangeTimeStamp64 now = ExchangeTimeStamp64(datetime::unix_nano_now());
|
||||
// er.transact_time = now;
|
||||
// me_->cb(er, {});
|
||||
// return er;
|
||||
// }
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/md/SecurityOracle.h"
|
||||
#include "lib/orderman/gateways/IExecutionGateway.h"
|
||||
namespace bts::orderman {
|
||||
|
||||
class OkxExecutionGateway : public IExecutionGateway {
|
||||
public:
|
||||
OkxExecutionGateway(md::SecurityOracle sec_oracle);
|
||||
~OkxExecutionGateway() override;
|
||||
void register_callback(const OnExeReport &report);
|
||||
void start() override;
|
||||
void dispatch_incoming_fix_messages() override;
|
||||
void on_securities(const mercury::Update<NewSecuritiesEvent> &) override {
|
||||
LOG(md, Info,"Not implemented");
|
||||
};
|
||||
void on_admin_data(const mercury::Update<NewAdminDataEvent> &) override{
|
||||
LOG(md, Info,"Not implemented");
|
||||
};
|
||||
void send_fix(const jetfix::OutFIXPtr &, trace::CorID) override{
|
||||
LOG(md, Info,"Not implemented");
|
||||
}
|
||||
void send_msgs(std::vector<OrderMsg> const &, trace::CorID) override;
|
||||
OnExeReport &on_exe_report() override;
|
||||
OnGwStatusChange &on_gw_status_change() override;
|
||||
OnCancelReject &on_cancel_reject() override;
|
||||
OnExchangePositionState &on_exchange_position_state() override;
|
||||
OnExchangeOpenOrdersState &on_exchange_open_orders() override;
|
||||
|
||||
private:
|
||||
struct PImpl;
|
||||
std::unique_ptr<PImpl> me_;
|
||||
void launch_new_order(const CreateOrderMsg &msg);
|
||||
void cancel_order(const CancelOrderMsg &msg);
|
||||
void modify_order(const CancelReplaceOrderMsg &);
|
||||
};
|
||||
|
||||
} // namespace bts::orderman
|
||||
@@ -238,6 +238,9 @@ void populate_common_order_fields(Message &order, const CreateOrderMsg &msg) {
|
||||
.set(OnixS::FIX::FIX42::Tags::Currency, msg.security->currency());
|
||||
}
|
||||
}
|
||||
void OnixFIXAdapter::dispatch_incoming_fix_messages() {
|
||||
listener.dispatch_incoming_fix_messages();
|
||||
}
|
||||
void OnixFIXAdapter::modify_order(
|
||||
const CancelReplaceOrderMsg &msg) { //confirm will have everything
|
||||
try {
|
||||
|
||||
@@ -12,6 +12,7 @@ class OnixFIXAdapter : public IExecutionGenericAdapter {
|
||||
public:
|
||||
OnixFIXAdapter(md::SecurityOracle sec_oracle);
|
||||
~OnixFIXAdapter() override;
|
||||
void dispatch_incoming_fix_messages() override;
|
||||
void register_callback(const OnExecutionReport &report) override;
|
||||
void launch_new_order(const CreateOrderMsg &msg) override;
|
||||
void cancel_order(const CancelOrderMsg &msg) override;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#include <string>
|
||||
|
||||
namespace bts::orderman {
|
||||
ExecutionReport OnixFixListener::fire_dummy_report(CreateOrderMsg msg) {
|
||||
void OnixFixListener::fire_dummy_report(CreateOrderMsg msg) {
|
||||
ExecutionReport *er_p = exe_report_pool.construct();
|
||||
ExecutionReport &er = *er_p;
|
||||
std::string account_name = "BTSIM";
|
||||
@@ -51,13 +51,18 @@ ExecutionReport OnixFixListener::fire_dummy_report(CreateOrderMsg msg) {
|
||||
// Set transaction time
|
||||
ExchangeTimeStamp64 now = ExchangeTimeStamp64(datetime::unix_nano_now());
|
||||
er.transact_time = now;
|
||||
cb(er,{});
|
||||
return er;
|
||||
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(er_p);
|
||||
return;
|
||||
}
|
||||
|
||||
namespace {
|
||||
ExecutionReport createDefaultExecutionReport() {
|
||||
ExecutionReport er;
|
||||
void createDefaultExecutionReport(ExecutionReport &er) {
|
||||
er.exchange = api::VENUE_CBOE;
|
||||
std::string account_name = "BTSIM";
|
||||
std::string ops_id = "op_id_bts";
|
||||
@@ -67,7 +72,6 @@ namespace {
|
||||
er.account_name = AccountName(account_array);
|
||||
|
||||
to_array(ops_id, &er.operator_id.value());
|
||||
return er;
|
||||
}
|
||||
|
||||
void populateExecTypeAndOrderStatus(ExecutionReport &er, int exec_type_value) {
|
||||
@@ -120,9 +124,7 @@ namespace {
|
||||
return idAsArray;
|
||||
}
|
||||
}
|
||||
ExecutionReport OnixFixListener::get_exe_report(Message msg) {
|
||||
ExecutionReport *er_p = exe_report_pool.construct();
|
||||
ExecutionReport &er = *er_p;
|
||||
void OnixFixListener::get_exe_report(Message msg, ExecutionReport &er) {
|
||||
|
||||
auto msg_str = msg.toString();
|
||||
LOG(OrderMan,Info, "OnixFixListener::get_exe_report {}", msg_str);
|
||||
@@ -135,7 +137,7 @@ ExecutionReport OnixFixListener::get_exe_report(Message msg) {
|
||||
populateCreateOrderFields(er, m.value());
|
||||
}else{ //handle this later
|
||||
LOG(OrderMan,Error, "OnixFixListener::get_exe_report {} could not find create order message", msg_str);
|
||||
er = createDefaultExecutionReport();
|
||||
createDefaultExecutionReport(er);
|
||||
}
|
||||
// Populate fields from the message
|
||||
|
||||
@@ -186,8 +188,6 @@ ExecutionReport OnixFixListener::get_exe_report(Message msg) {
|
||||
// Set transaction time
|
||||
ExchangeTimeStamp64 now = ExchangeTimeStamp64(datetime::unix_nano_now());
|
||||
er.transact_time = now;
|
||||
|
||||
return er;
|
||||
}
|
||||
|
||||
void OnixFixListener::onInboundApplicationMsg(Message &msg, Session * /*sn*/) {
|
||||
@@ -210,13 +210,16 @@ void OnixFixListener::onInboundApplicationMsg(Message &msg, Session * /*sn*/) {
|
||||
case '8':
|
||||
{
|
||||
//exection report
|
||||
auto er = get_exe_report(msg);
|
||||
ExecutionReport *er_p = exe_report_pool.construct();
|
||||
ExecutionReport &er = *er_p;
|
||||
get_exe_report(msg,er);
|
||||
if(er.security==nullptr){
|
||||
LOG(OrderMan, Error,"DID not have sec");
|
||||
return;
|
||||
LOG(OrderMan, Error,"DID not have sec");
|
||||
exe_report_pool.destroy(&er);
|
||||
return;
|
||||
}
|
||||
LOG(OrderMan, Error,"msg {}",er.msg);
|
||||
cb(er,{});
|
||||
LOG(OrderMan, Info,"msg {}",er.msg);
|
||||
buffered_exec_reps.push(er_p);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,7 +229,20 @@ void OnixFixListener::onInboundApplicationMsg(Message &msg, Session * /*sn*/) {
|
||||
|
||||
// cb(*er, {});
|
||||
}
|
||||
|
||||
void OnixFixListener::dispatch_incoming_fix_messages() {
|
||||
if (buffered_exec_reps.empty()) {
|
||||
return;
|
||||
}
|
||||
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(*er, cid);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void OnixFixListener::onInboundSessionMsg(Message & msg, Session * /*sn*/)
|
||||
{
|
||||
|
||||
@@ -26,6 +26,7 @@ class OnixFixListener : public OnixS::FIX::ISessionListener {
|
||||
LOG(OrderMan, Info, "OnixFixListener::setCallback callback registered");
|
||||
cb = callback;
|
||||
}
|
||||
void dispatch_incoming_fix_messages();
|
||||
/// Is called when the session-level message is received from the counterparty.
|
||||
void onInboundSessionMsg(Message &msg, Session *sn) ONIXS_FIXENGINE_FINAL;
|
||||
void onStateChange(
|
||||
@@ -33,8 +34,9 @@ class OnixFixListener : public OnixS::FIX::ISessionListener {
|
||||
SessionState::Enum prevState,
|
||||
Session *sn) ONIXS_FIXENGINE_FINAL;
|
||||
OnExeReport cb;
|
||||
ExecutionReport fire_dummy_report(CreateOrderMsg msg);
|
||||
ExecutionReport get_exe_report(Message msg);
|
||||
tbb::concurrent_queue<ExecutionReport *> buffered_exec_reps;
|
||||
void fire_dummy_report(CreateOrderMsg msg);
|
||||
void get_exe_report(Message msg, ExecutionReport &er);
|
||||
static void dummy(const ExecutionReport &, const trace::CorID &) {
|
||||
LOG(OrderMan, Error, "OnixFixListener::Dummy fired");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/domain_model/Security.pb.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/md/SecurityOracle.h"
|
||||
#include "lib/orderman/CreateOrderMsg.h"
|
||||
#include "lib/orderman/gateways/IExecutionGateway.h"
|
||||
#include "lib/orderman/orderman_utils/ExecutionReport.h"
|
||||
#include "lib/utils/bts_assert.h"
|
||||
#include <OnixS/FIXEngine/FIX/Exception.h>
|
||||
#include <OnixS/FIXEngine/FIX/FIX42.h>
|
||||
#include <OnixS/FIXEngine/FIX/Message.h>
|
||||
#include <OnixS/FIXEngine/FIX/Numeric.h>
|
||||
#include <OnixS/FIXEngine/FIX/ProtocolVersion.h>
|
||||
#include <boost/pool/object_pool.hpp>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <ctime>
|
||||
#include <exception>
|
||||
#include <fmt/core.h>
|
||||
#include <map>
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
#include <openssl/buffer.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/hmac.h>
|
||||
#include <quickfix/FixFields.h>
|
||||
#include <quickfix/Utility.h>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <unistd.h>
|
||||
#include <unordered_map>
|
||||
namespace bts::orderman {
|
||||
using json = nlohmann::json;
|
||||
class OkxUtils {
|
||||
public:
|
||||
// Function template to get a value from a JSON object by key
|
||||
template <typename T>
|
||||
static inline T getJsonValue(
|
||||
const json &jsonObject,
|
||||
const std::string &key,
|
||||
const T &defaultValue = T()) {
|
||||
if (jsonObject.contains(key) && jsonObject[key].is_primitive()) {
|
||||
return jsonObject.value(key, defaultValue);
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// Function to parse JSON safely
|
||||
static inline std::optional<json> parseJsonSafely(
|
||||
const std::string &jsonString) {
|
||||
try {
|
||||
return json::parse(jsonString);
|
||||
} catch (const std::exception &e) {
|
||||
std::string s = e.what();
|
||||
LOG(OrderMan, bts::Error, "Failed to parse JSON: {}", s);
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
struct OrderResponse {
|
||||
bool accepted;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
static inline OrderResponse orderAccepted(const std::string &jsonString) {
|
||||
auto body = parseJsonSafely(jsonString);
|
||||
if (!body)
|
||||
return {false, "NO Body"};
|
||||
auto &jsonResponse = *body;
|
||||
|
||||
return {
|
||||
jsonResponse.value("code", "1") == "0",
|
||||
jsonResponse.value("msg", "NO code or message")};
|
||||
}
|
||||
|
||||
// Function to extract order information from JSON
|
||||
static inline std::tuple<std::string, std::string, std::string> extractOrderInfo(
|
||||
const std::string &jsonString) {
|
||||
auto body = parseJsonSafely(jsonString);
|
||||
if (!body.has_value()) {
|
||||
return {"", "", "json parsing error no body"};
|
||||
}
|
||||
auto jsonResponse = body.value();
|
||||
if (jsonResponse.contains("data") && jsonResponse["data"].is_array()) {
|
||||
const auto &dataArray = jsonResponse["data"];
|
||||
if (!dataArray.empty() && dataArray[0].is_object()) {
|
||||
const auto &firstItem = dataArray[0];
|
||||
|
||||
// Extract values using the templated function
|
||||
std::string ordId =
|
||||
OkxUtils::getJsonValue(firstItem, "ordId", std::string());
|
||||
std::string timestamp =
|
||||
OkxUtils::getJsonValue(firstItem, "ts", std::string());
|
||||
std::string sMsg =
|
||||
OkxUtils::getJsonValue(firstItem, "sMsg", std::string());
|
||||
|
||||
return {ordId, timestamp, sMsg};
|
||||
}
|
||||
}
|
||||
return {"", "", "json parsing error"};
|
||||
}
|
||||
// Function to encode a byte array to Base64
|
||||
static std::string base64Encode(const unsigned char *buffer, size_t length) {
|
||||
BIO *bmem = BIO_new(BIO_s_mem());
|
||||
BIO *b64 = BIO_new(BIO_f_base64());
|
||||
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); // No newlines
|
||||
BIO_push(b64, bmem);
|
||||
BIO_write(b64, buffer, static_cast<int>(length));
|
||||
BIO_flush(b64);
|
||||
|
||||
BUF_MEM *bptr;
|
||||
BIO_get_mem_ptr(b64, &bptr);
|
||||
std::string result(bptr->data, bptr->length);
|
||||
|
||||
BIO_free_all(b64);
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline std::vector<unsigned char> generateSignature(
|
||||
const std::string &prehash, const std::string &secretKey) {
|
||||
unsigned int len = EVP_MAX_MD_SIZE;
|
||||
std::vector<unsigned char> result(len);
|
||||
HMAC(
|
||||
EVP_sha256(), secretKey.c_str(), static_cast<int>(secretKey.length()),
|
||||
reinterpret_cast<const unsigned char *>(prehash.c_str()),
|
||||
prehash.length(), result.data(), &len);
|
||||
result.resize(len);
|
||||
return result;
|
||||
}
|
||||
static inline std::string getSign(
|
||||
std::string message, const std::string &secret_key = "your_secret_key") {
|
||||
auto signature = generateSignature(message, secret_key);
|
||||
std::string sign = base64Encode(signature.data(), signature.size());
|
||||
|
||||
return sign;
|
||||
}
|
||||
static std::tuple<long, std::string> getLoginSignTime(
|
||||
const std::string &secret_key = "your_secret_key",
|
||||
const std::string &method = "GET",
|
||||
const std::string &path = "/users/self/verify") {
|
||||
auto seconds = bts::core::TimeStamp::now().seconds();
|
||||
std::string message = std::to_string(seconds) + method + path;
|
||||
return {seconds, getSign(message, secret_key)};
|
||||
}
|
||||
static std::string getUTCTimestamp(bts::core::TimeStamp t) {
|
||||
auto rawTimestamp =
|
||||
t.to_iso_string() +
|
||||
"Z"; //gives 20241213T175033.840123Z should be 2024-12-13T17:50:33.840Z
|
||||
// Use regex to parse and reformat
|
||||
std::regex re(
|
||||
R"((\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})\.(\d{3})\d*Z)");
|
||||
std::string formattedTimestamp =
|
||||
std::regex_replace(rawTimestamp, re, "$1-$2-$3T$4:$5:$6.$7Z");
|
||||
|
||||
return formattedTimestamp;
|
||||
}
|
||||
static std::array<char, 17> stringToFixedArray(const std::string &s) {
|
||||
std::array<char, 17> idAsArray{};
|
||||
std::copy_n(
|
||||
s.begin(), std::min(s.size(), idAsArray.size() - 1),
|
||||
idAsArray.begin());
|
||||
return idAsArray;
|
||||
}
|
||||
static std::unordered_map<std::string, std::string> createHeaders(
|
||||
const std::string &apiKey,
|
||||
const std::string &passphrase,
|
||||
const std::string &secretKey,
|
||||
const std::string &method,
|
||||
const std::string &requestPath,
|
||||
const bool simulated_trading,
|
||||
const std::string &body = "") {
|
||||
auto ts = bts::core::TimeStamp::now();
|
||||
std::string timestamp = getUTCTimestamp(ts);
|
||||
std::string prehash = timestamp + method + requestPath + body;
|
||||
auto signature = getSign(prehash, secretKey);
|
||||
LOG(OrderMan, bts::Info, "prehash '{}' , key '{}'", prehash, signature);
|
||||
// Prepare headers
|
||||
std::unordered_map<std::string, std::string> headers = {
|
||||
{"accept", "application/json"},
|
||||
{"Content-Type", "application/json"},
|
||||
{"OK-ACCESS-KEY", apiKey},
|
||||
{"OK-ACCESS-SIGN", signature},
|
||||
{"OK-ACCESS-TIMESTAMP", timestamp},
|
||||
{"OK-ACCESS-PASSPHRASE", passphrase}};
|
||||
if (simulated_trading){
|
||||
headers.insert({"x-simulated-trading", "1"});
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
static std::string generateCurlCommand(
|
||||
const std::string &url,
|
||||
const std::unordered_map<std::string, std::string> &headers,
|
||||
const std::string &body) {
|
||||
std::ostringstream oss;
|
||||
|
||||
// Start with the curl command
|
||||
std::string curlCommand = "curl -X POST " + url + " \\\n";
|
||||
|
||||
// Add headers to the curl command
|
||||
for (const auto &header : headers) {
|
||||
curlCommand +=
|
||||
" -H \"" + header.first + ": " + header.second + "\" \\\n";
|
||||
}
|
||||
|
||||
// Add bodyl
|
||||
curlCommand += " -d '" + body + "'";
|
||||
|
||||
return curlCommand;
|
||||
}
|
||||
static inline api::SecurityType getInstType(const std::string &type) {
|
||||
static const std::unordered_map<std::string, api::SecurityType> type_map =
|
||||
{{"SPOT", api::SECURITYTYPE_SPOT},
|
||||
{"OPTION", api::SECURITYTYPE_OPTION},
|
||||
{"FUTURES", api::SECURITYTYPE_FUTURE},
|
||||
{"SWAP", api::SECURITYTYPE_FUTURE}};
|
||||
|
||||
auto it = type_map.find(type);
|
||||
return it != type_map.end() ? it->second : api::SECURITYTYPE_UNSET;
|
||||
}
|
||||
static inline api::orderman::NativeOrderType getOrdType(const std::string &type) {
|
||||
static const std::unordered_map<std::string, api::orderman::NativeOrderType> type_map =
|
||||
{{"limit", api::orderman::NATIVE_ORDER_TYPE_LIMIT},
|
||||
{"market", api::orderman::NATIVE_ORDER_TYPE_MARKET}};
|
||||
|
||||
auto it = type_map.find(type);
|
||||
return it != type_map.end() ? it->second : api::orderman::NativeOrderType::NATIVE_ORDER_TYPE_UNDEFINED;
|
||||
}
|
||||
static void populateCreateOrderFields(
|
||||
ExecutionReport &er, const CreateOrderMsg &create_order_msg) {
|
||||
er.security = create_order_msg.security;
|
||||
er.order_type = create_order_msg.order_type;
|
||||
er.time_in_force = create_order_msg.time_in_force;
|
||||
er.manual_order_indicator = create_order_msg.manual_order_indicator;
|
||||
er.side = create_order_msg.side;
|
||||
er.order_key = create_order_msg.key;
|
||||
to_array(
|
||||
create_order_msg.routing_record->account(), &er.account_name.value());
|
||||
to_array(
|
||||
create_order_msg.routing_record->operator_id(),
|
||||
&er.operator_id.value());
|
||||
er.exchange = create_order_msg.security->exchange();
|
||||
}
|
||||
static void populateExecTypeAndOrderStatus(
|
||||
ExecutionReport &er, const std::string &state) {
|
||||
if (state == "canceled") {
|
||||
er.exec_type = api::orderman::ExecType::EXEC_TYPE_CANCELED;
|
||||
er.order_status = api::orderman::NATIVE_ORDER_STATUS_CANCELED;
|
||||
} else if (state == "live") {
|
||||
er.exec_type = api::orderman::ExecType::EXEC_TYPE_NEW;
|
||||
er.order_status = api::orderman::NATIVE_ORDER_STATUS_NEW;
|
||||
} else if (state == "partially_filled") {
|
||||
er.exec_type = api::orderman::ExecType::EXEC_TYPE_PARTIAL_FILL;
|
||||
er.order_status = api::orderman::NATIVE_ORDER_STATUS_PARTIALLY_FILLED;
|
||||
} else if (state == "filled") {
|
||||
er.exec_type = api::orderman::ExecType::EXEC_TYPE_FILL;
|
||||
er.order_status = api::orderman::NATIVE_ORDER_STATUS_FILLED;
|
||||
} else {
|
||||
er.exec_type = api::orderman::ExecType::EXEC_TYPE_UNDEFINED;
|
||||
er.order_status = api::orderman::NATIVE_ORDER_STATUS_UNDEFINED;
|
||||
}
|
||||
}
|
||||
static inline void handleOkException(
|
||||
const std::string &context, const std::string &error) {
|
||||
bts_assert_msgf(false, "{} Exception: {}", context, error);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace bts::orderman::okxexe
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "lib/config/protos/spreader.pb.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/orderman/events/SettlementUpdateEvent.h"
|
||||
#include "lib/orderman/orderman_utils/orderman_common.h"
|
||||
#include "lib/orderman/orders/spreader_orders/spreader/LegPropertyData.h"
|
||||
#include "lib/orderman/orders/spreader_orders/spreader/NLegsHedgeRecompData.h"
|
||||
#include "lib/orderman/orders/spreader_orders/spreader/SpreaderBookkeepingData.h"
|
||||
@@ -491,15 +492,18 @@ void SpreaderOrder::_comp_and_report_state() {
|
||||
reported_state.set_status(status);
|
||||
reported_state.set_price(bookkeeping_data.input_price().value());
|
||||
reported_state.set_quantity(bookkeeping_data.quantity().value());
|
||||
reported_state.set_filled_quantity(filled_qty.value());
|
||||
bts::orderman::set_quantity(
|
||||
reported_state, StateQuantityType::FilledQuantity,
|
||||
filled_qty.full_value());
|
||||
//reported_state.set_filled_quantity(filled_qty.value());
|
||||
reported_state.set_avg_price(avg_filled_price.value());
|
||||
reported_state.set_last_price(avg_filled_price.value());
|
||||
}
|
||||
// if order is cons'ed from disc, with inited_new==false,
|
||||
// we just publish what was given to us
|
||||
LOG(OrderMan, Info,
|
||||
"SpreaderOrder::_report_state key:{} status:{} "
|
||||
"price:{} qty:{} filled:{} avg_price:{}",
|
||||
"SpreaderOrder::_report_state key: {} status: {} "
|
||||
"price: {} qty: {} filled: {} avg_price: {}",
|
||||
key(), status, reported_state.price(), reported_state.quantity(),
|
||||
reported_state.filled_quantity(), reported_state.avg_price());
|
||||
auto rerported_state_adj = reported_state;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "SpreaderBookkeepingData.h"
|
||||
#include "lib/domain_model/common.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/orderman/orders/spreader_orders/spreader/LegPropertyData.h"
|
||||
#include "lib/orderman/orders/spreader_orders/spreader/SpreaderFractionalQtyHelper.h"
|
||||
#include "lib/os/os.h"
|
||||
@@ -25,7 +27,9 @@ SpreaderBookkeepingData::SpreaderBookkeepingData(
|
||||
.spreader()
|
||||
.execution_strategy()
|
||||
.hedge_pricing()),
|
||||
_fqty_helper(spreader_order_key, legs) {
|
||||
_fqty_helper(
|
||||
spreader_order_key, order_def.custom_def().spreader(), legs) {
|
||||
//_order_def.custom_def().spreader()
|
||||
LOG(OrderMan, Info,
|
||||
"SpreaderBookkeepingData.cons started "
|
||||
"key: {} n_legs: {}",
|
||||
@@ -97,8 +101,12 @@ void SpreaderBookkeepingData::total_fills(
|
||||
avg_price += leg_fill_price.value() *
|
||||
leg_data.leg_property_data().price_multiplier();
|
||||
}
|
||||
LOG(OrderMan, Info,
|
||||
"SpreaderBookkeepingData::total_fills avg_price: {} fills: {}",
|
||||
avg_price, fills);
|
||||
|
||||
avg_price = fills == 0 ? 0.0 : avg_price; // should it be nan?
|
||||
out_quantity = Quantity(fills);
|
||||
out_quantity = Quantity::create_new(fills);
|
||||
out_price = Price(avg_price);
|
||||
}
|
||||
} // namespace orderman
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "lib/md/PriceSize.h"
|
||||
#include "lib/orderman/events/BookUpdateEvent.h"
|
||||
#include "lib/orderman/events/OrderStateUpdateEvent.h"
|
||||
//#include "lib/orderman/orders/spreader_orders/SpreaderOrder.h"
|
||||
#include "lib/orderman/orders/spreader_orders/spreader/LegPropertyData.h"
|
||||
#include "lib/orderman/orders/spreader_orders/spreader/SpreaderUtils.h"
|
||||
#include "lib/os/os.h"
|
||||
@@ -36,15 +37,6 @@ bool has_crypto_leg(const std::vector<LegPropertyData> &legs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool has_notional_leg(const std::vector<LegPropertyData> &legs) {
|
||||
return has_crypto_leg(legs);
|
||||
// TODO
|
||||
// for (const auto &l : legs) {
|
||||
// return l.is_notional();
|
||||
// }
|
||||
// return false;
|
||||
}
|
||||
|
||||
struct QtyTickInfo {
|
||||
public:
|
||||
const double tick_base;
|
||||
@@ -76,17 +68,13 @@ struct SpreaderFractionalQtyHelper ::PImpl {
|
||||
const QtyTickInfo def_qty_tick_info = QtyTickInfo(10000.0, 1.0);
|
||||
std::map<bts::SecurityKey, QtyTickInfo> qty_info_map = {};
|
||||
|
||||
PImpl(const std::vector<LegPropertyData> &lpd_vec)
|
||||
: passthrough(has_crypto_leg(lpd_vec) ? false : true),
|
||||
// in_qty_mult(
|
||||
// has_crypto_leg(lpd_vec)
|
||||
// ? static_cast<double>(
|
||||
// config::get<bts::config::Spreader>().qty_multiplier())
|
||||
// : 1.0),
|
||||
is_notional(has_notional_leg(lpd_vec)) {
|
||||
// LOG(OrderMan, Info,
|
||||
// "SpreaderFractionalQtyHelper::cons, in_qty_mult: {} is_notional: {}",
|
||||
// in_qty_mult, is_notional);
|
||||
PImpl(
|
||||
const api::orderman::SpreaderOrderDefinition &spreader_order_def,
|
||||
const std::vector<LegPropertyData> &lpd_vec)
|
||||
: passthrough(!has_crypto_leg(lpd_vec)),
|
||||
is_notional(
|
||||
spreader_order_def.security().synthetic().price_calc() ==
|
||||
api::SyntheticContract_PriceCalc_PRICECALC_NOTIONAL_LEG) {
|
||||
auto i = 0;
|
||||
for (const auto &l : lpd_vec) {
|
||||
const auto &qiv = l.security().contract_spec().quantity_infos();
|
||||
@@ -126,19 +114,26 @@ std::vector<LegPropertyData> SpreaderFractionalQtyHelper::get_transformed_legs(
|
||||
}
|
||||
|
||||
SpreaderFractionalQtyHelper::SpreaderFractionalQtyHelper(
|
||||
OrderKey /*spreader_ord_key*/, const std::vector<LegPropertyData> &lpd_vec)
|
||||
: me(new PImpl(lpd_vec)) {}
|
||||
OrderKey spreader_ord_key,
|
||||
const api::orderman::SpreaderOrderDefinition &spreader_order_def,
|
||||
const std::vector<LegPropertyData> &lpd_vec)
|
||||
: me(new PImpl(spreader_order_def, lpd_vec)) {
|
||||
LOG(OrderMan, Info,
|
||||
"SpreaderFractionalQtyHelper::cons spreader_ord_key: {} passthrough: {} "
|
||||
"is_notional: {}",
|
||||
spreader_ord_key, me->passthrough, me->is_notional);
|
||||
}
|
||||
|
||||
const Book &SpreaderFractionalQtyHelper::in_transform_book(
|
||||
const Book &bk) const {
|
||||
if (me->passthrough) {
|
||||
LOG(OrderMan, Info,
|
||||
LOG(OrderMan, Diag,
|
||||
"SpreaderFractionalQtyHelper::in_transform_book passthrough");
|
||||
return bk;
|
||||
}
|
||||
if (me->is_notional) {
|
||||
SpreaderUtils::compute_notional_book(bk, me->op_book);
|
||||
LOG(OrderMan, Info,
|
||||
LOG(OrderMan, Diag,
|
||||
"SpreaderFractionalQtyHelper::in_transform_book sec: {} bk: {} "
|
||||
"notional: {}",
|
||||
bk.security_key().value(), bk, me->op_book);
|
||||
@@ -155,7 +150,7 @@ const Book &SpreaderFractionalQtyHelper::in_transform_book(
|
||||
ps = bts::md::PriceSize(ps.int_price(), ps.size() * qm);
|
||||
}
|
||||
// mult by leg_mult
|
||||
LOG(OrderMan, Info,
|
||||
LOG(OrderMan, Diag,
|
||||
"SpreaderFractionalQtyHelper::in_transform_book bk: {} output: {}", bk,
|
||||
me->op_book);
|
||||
return me->op_book;
|
||||
@@ -177,14 +172,16 @@ SpreaderFractionalQtyHelper::in_transform_order_event(
|
||||
log_ev(sec_key, ev, ev);
|
||||
return ev;
|
||||
}
|
||||
me->op_ord = ev;
|
||||
// if notional, then order qtys needs to be converted to notional, since spreader sees books
|
||||
// in notionals and fire orders in notionals
|
||||
me->op_ord.quantity =
|
||||
SpreaderUtils::to_notional(Price(ev.price), ev.quantity);
|
||||
me->op_ord.filled_quantity =
|
||||
SpreaderUtils::to_notional(Price(ev.avg_price), ev.filled_quantity);
|
||||
me->op_ord.filled_at_attach_time_quantity = os::nan();
|
||||
me->op_ord = ev;
|
||||
if (me->is_notional) {
|
||||
me->op_ord.quantity =
|
||||
SpreaderUtils::to_notional(Price(ev.price), ev.quantity);
|
||||
me->op_ord.filled_quantity =
|
||||
SpreaderUtils::to_notional(Price(ev.avg_price), ev.filled_quantity);
|
||||
me->op_ord.filled_at_attach_time_quantity = os::nan();
|
||||
}
|
||||
// factor mult
|
||||
const auto &q_tinfo =
|
||||
get_qty_tick_info(me->qty_info_map, sec_key, me->def_qty_tick_info);
|
||||
@@ -236,7 +233,12 @@ Quantity SpreaderFractionalQtyHelper::out_transform_leg_order_qty(
|
||||
}
|
||||
|
||||
void SpreaderFractionalQtyHelper::out_transform_order_state(
|
||||
bts::api::orderman::OrderState &) const {
|
||||
bts::api::orderman::OrderState &s) const {
|
||||
auto s_txt = utils::pb::to_string(s);
|
||||
LOG(OrderMan, Info,
|
||||
"SpreaderFractionalQtyHelper::out_transform_order_state state: "
|
||||
"{} filled_qty: {} avg_price: {}",
|
||||
s_txt, s.filled_quantity(), s.avg_price());
|
||||
// todo:
|
||||
// this needs to divide the filled qty of the spreader
|
||||
// if (me->passthrough)
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
//#include "SpreaderLegData.h"
|
||||
//#include "lib/domain_model/common.h"
|
||||
//#include "lib/orderman/orderman_utils/orderman_common.h"
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/domain_model/SecurityKey.h"
|
||||
//#include "lib/orderman/orders/spreader_orders/SpreaderOrder.h"
|
||||
#include "lib/orderman/orders/spreader_orders/spreader/LegPropertyData.h"
|
||||
|
||||
namespace bts::api::orderman {
|
||||
@@ -19,7 +21,9 @@ class OrderStateUpdateEvent;
|
||||
class SpreaderFractionalQtyHelper {
|
||||
public:
|
||||
SpreaderFractionalQtyHelper(
|
||||
OrderKey spreader_ord_key, const std::vector<LegPropertyData> &legs);
|
||||
OrderKey spreader_ord_key,
|
||||
const api::orderman::SpreaderOrderDefinition &spreader_order_def,
|
||||
const std::vector<LegPropertyData> &legs);
|
||||
// the leg qtys need to be translated
|
||||
std::vector<LegPropertyData> get_transformed_legs(
|
||||
const std::vector<LegPropertyData> &legs) const;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
#ifndef NO_CPP0X
|
||||
|
||||
#include "Dispatcher.h"
|
||||
#include "../os/os.h"
|
||||
#include "defer.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/utils/bts_assert.h"
|
||||
#include "lib/utils/bts_chrono.h"
|
||||
#include "lib/utils/thread_manager.h"
|
||||
#include "lib/utils/type_name.h"
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <boost/thread.hpp>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ static OrderServiceServer create_order_service_server(
|
||||
static void execute() {
|
||||
using namespace bts;
|
||||
LOG(Gen, Warn,
|
||||
"------------------------------------ Sparkd v2.2.3-1 launched "
|
||||
"------------------------------------ Sparkd v2.2.5.4 launched "
|
||||
"--------------------------------");
|
||||
|
||||
auto qb_cfg = config::get<config::QuickbusCfg>().node();
|
||||
|
||||
@@ -374,7 +374,9 @@ struct BinanceSubscription::PImpl {
|
||||
}
|
||||
|
||||
listener->on_securities(sec_ptrs);
|
||||
batch_subscribe_market_data(sec_descs,4);
|
||||
// batch_subscribe_market_data(sec_descs,4);
|
||||
bts_assert(sec_descs.size() < 256);
|
||||
subscribe_market_data(sec_descs);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "DatabentoDataHandler.h"
|
||||
#include "databento/datetime.hpp"
|
||||
#include "databento/dbn_encoder.hpp"
|
||||
#include "databento/enums.hpp"
|
||||
#include "databento/exceptions.hpp"
|
||||
#include "databento/live_blocking.hpp"
|
||||
@@ -7,8 +8,8 @@
|
||||
#include "databento/record.hpp"
|
||||
#include "databento/timeseries.hpp"
|
||||
#include "lib/api/domain/Venue.pb.h"
|
||||
#include "lib/api/md/MarketDataConfig.pb.h"
|
||||
#include "lib/config/config.h"
|
||||
#include "lib/config/protos/system_config.pb.h"
|
||||
#include "lib/core/TimeStamp.h"
|
||||
#include "lib/domain/AggressorSide.h"
|
||||
#include "lib/domain/Book.h"
|
||||
@@ -22,14 +23,21 @@
|
||||
#include "lib/utils/TimeStamp.h"
|
||||
#include "lib/utils/bts_assert.h"
|
||||
#include "lib/utils/bts_chrono.h"
|
||||
#include "lib/utils/datetime.h"
|
||||
#include "lib/utils/helper_macros.h"
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <databento/constants.hpp>
|
||||
#include <databento/historical.hpp>
|
||||
#include <databento/live.hpp>
|
||||
#include <optional>
|
||||
#include <ratio>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace bts::md::DatabentoHandler {
|
||||
@@ -61,7 +69,87 @@ static SecurityType parse_sec_type(std::string_view str) {
|
||||
}
|
||||
return SEC_TYPE(UNSPECIFIED);
|
||||
}
|
||||
void update_file(
|
||||
const std::string &key,
|
||||
const std::string &dataset,
|
||||
const std::vector<std::string> &parent_symbols,
|
||||
const uint time_to_run,
|
||||
std::string file_path) {
|
||||
|
||||
auto client =
|
||||
databento::LiveBuilder{}
|
||||
.SetKey(key)
|
||||
.SetDataset(dataset)
|
||||
.SetUpgradePolicy(databento::VersionUpgradePolicy::UpgradeToV2)
|
||||
.BuildBlocking();
|
||||
|
||||
client.Subscribe(
|
||||
parent_symbols, databento::Schema::Definition, databento::SType::Parent,
|
||||
databento::UnixNanos{});
|
||||
// We start streaming
|
||||
databento::Metadata metadata = client.Start();
|
||||
{
|
||||
// We open a file for writing and encode the metadata
|
||||
databento::OutFileStream out_file{file_path};
|
||||
databento::DbnEncoder encoder{metadata, &out_file};
|
||||
// Wait for at least one record and 5 seconds before closing the connection
|
||||
const std::chrono::seconds timeout{time_to_run};
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
while (auto *record = client.NextRecord(timeout)) {
|
||||
encoder.EncodeRecord(*record);
|
||||
if (std::chrono::steady_clock::now() - start >= timeout) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
void log_time_statistics(
|
||||
const std::vector<std::vector<std::tuple<uint64_t, std::string>>>
|
||||
&all_times) {
|
||||
std::unordered_map<std::string, std::vector<uint64_t>> subpart_times;
|
||||
|
||||
// Collect all timing values per subpart
|
||||
for (const auto &entry : all_times) {
|
||||
for (const auto &[time, label] : entry) {
|
||||
subpart_times[label].push_back(time);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to compute statistics
|
||||
auto compute_statistics = [](std::vector<uint64_t> &values)
|
||||
-> std::tuple<double, double, double, double, double, double, double> {
|
||||
if (values.empty())
|
||||
return std::make_tuple(0.0, 0.0, 0, 0, 0, 0, 0);
|
||||
|
||||
std::sort(values.begin(), values.end());
|
||||
|
||||
double sum = std::accumulate(values.begin(), values.end(), 0.0);
|
||||
double avg = sum / values.size();
|
||||
|
||||
double sq_sum =
|
||||
std::inner_product(values.begin(), values.end(), values.begin(), 0.0);
|
||||
double stddev = std::sqrt(sq_sum / values.size() - avg * avg);
|
||||
|
||||
auto percentile = [&](double p) -> uint64_t {
|
||||
size_t idx = static_cast<size_t>(p * (values.size() - 1));
|
||||
return values[idx];
|
||||
};
|
||||
|
||||
return std::make_tuple(
|
||||
avg, stddev, percentile(0.00), percentile(0.25), percentile(0.50),
|
||||
percentile(0.75), percentile(1.00));
|
||||
};
|
||||
|
||||
// Compute and log statistics
|
||||
for (auto &[label, times] : subpart_times) {
|
||||
auto [avg, stddev, p0, p25, p50, p75, p100] = compute_statistics(times);
|
||||
LOG(md, Sys,
|
||||
"Subpart: {}, Avg: {:.2f} ns, Std: {:.2f} ns, P0: {} ns, P25: {} ns, "
|
||||
"P50: {} ns, P75: {} ns, P100: {} ns",
|
||||
label, avg, stddev, p0, p25, p50, p75, p100);
|
||||
}
|
||||
}
|
||||
void DatabentoDatahandler::run(
|
||||
bool use_file,
|
||||
std::shared_ptr<IMarketDataListener> listener,
|
||||
@@ -74,59 +162,71 @@ void DatabentoDatahandler::run(
|
||||
const uint filter_securities_upto_n_months,
|
||||
const bool load_reference_data,
|
||||
const std::chrono::duration<long double> sleep_duration) {
|
||||
|
||||
uint DEBUG_SEC_ID = 0;
|
||||
if (venue == api::domain::VENUE_CBOE)
|
||||
DEBUG_SEC_ID = config::get<bts::api::md::MarketDataConfig>()
|
||||
.cboe()
|
||||
.at(0)
|
||||
.databento_settings()
|
||||
.debug_sec();
|
||||
auto sleep_duration_updated = sleep_duration;
|
||||
|
||||
std::vector<std::vector<std::tuple<uint64_t, std::string>>> all_times;
|
||||
std::unordered_map<uint32_t, bts::domain::SecurityID> sec_id_map;
|
||||
if (!use_file) {
|
||||
sleep_duration_updated = std::chrono::duration<
|
||||
long double>::zero(); // Set to zero for live data
|
||||
}
|
||||
auto process_security = [&](const databento::Record &record) {
|
||||
const auto &msg = record.Get<databento::InstrumentDefMsg>();
|
||||
std::string id = remove_spaces(msg.RawSymbol());
|
||||
std::string symbol;
|
||||
|
||||
auto sec_filter = [&](const core::TimeStamp expiration_time,
|
||||
const std::string &symbol,
|
||||
const api::domain::Venue &venue, uint strike) {
|
||||
uint months = filter_securities_upto_n_months;
|
||||
switch (venue) {
|
||||
case api::domain::Venue::VENUE_CBOE: {
|
||||
symbol = remove_spaces(
|
||||
std::string(msg.underlying.data(), msg.underlying.size()));
|
||||
if (strike < 3800 || strike > 8000) {
|
||||
return false;
|
||||
}
|
||||
if (symbol == "SPX")
|
||||
months = 6;
|
||||
else if (symbol == "SPXW")
|
||||
months = 1;
|
||||
break;
|
||||
}
|
||||
case api::domain::Venue::VENUE_GLBX:
|
||||
if (id.length() > 5)
|
||||
return databento::KeepGoing::Continue;
|
||||
symbol = id.substr(0, 2);
|
||||
// sec.security_common.cpp = msg.unit_of_measure_qty;
|
||||
|
||||
break;
|
||||
return true;
|
||||
default:
|
||||
bts_assert_msg(false, "Exchange not supported on databento");
|
||||
return databento::KeepGoing::Stop;
|
||||
}
|
||||
|
||||
auto duration = months * 32 * 24 * 60 * 60;
|
||||
return expiration_time.seconds() <
|
||||
bts::core::TimeStamp::now().seconds() + duration;
|
||||
};
|
||||
auto process_security = [&](const databento::Record &record) -> bool {
|
||||
const auto &msg = record.Get<databento::InstrumentDefMsg>();
|
||||
const auto exp_nanos = msg.expiration;
|
||||
std::string exp_string = databento::ToIso8601(exp_nanos);
|
||||
const auto expiration_time =
|
||||
bts::core::TimeStamp::from_iso_string(exp_string);
|
||||
if (!sec_filter(
|
||||
expiration_time, msg.Underlying(), venue,
|
||||
msg.strike_price / 1000000000)) {
|
||||
return false;
|
||||
}
|
||||
bts::domain::Security sec;
|
||||
sec.security_common = domain::SecurityCommon();
|
||||
sec.security_common.venue = venue;
|
||||
sec.security_common.exchange_sec_id = msg.hd.instrument_id;
|
||||
sec.security_common.exchange_sec_id = std::to_string(msg.hd.instrument_id);
|
||||
sec.security_common.desc = msg.RawSymbol();
|
||||
sec.security_common.symbol = msg.Asset();
|
||||
sec.security_common.symbol = msg.Underlying();
|
||||
sec.security_common.asset = msg.Asset();
|
||||
sec.security_common.group = msg.Group();
|
||||
sec.security_common.min_price_increment = 0.01;
|
||||
sec.security_common.min_quantity = 0.01;
|
||||
sec.security_common.min_quantity_increment = 0.01;
|
||||
sec.security_common.currency = {msg.currency.data(), msg.currency.size()};
|
||||
// sec.security_common.cpp = msg.hd.
|
||||
|
||||
const SecurityType sec_type =
|
||||
parse_sec_type({msg.security_type.data(), msg.security_type.size()});
|
||||
const auto exp_nanos = msg.expiration;
|
||||
std::string exp_string = databento::ToIso8601(exp_nanos);
|
||||
const auto expiration_time =
|
||||
bts::core::TimeStamp::from_iso_string(exp_string);
|
||||
auto duration = filter_securities_upto_n_months*30*24*60*60;
|
||||
if( expiration_time.seconds() >= bts::core::TimeStamp::now().seconds() + duration){
|
||||
return databento::KeepGoing::Continue;
|
||||
}
|
||||
|
||||
switch (sec_type) {
|
||||
case SEC_TYPE(FUTURE):
|
||||
@@ -134,11 +234,10 @@ void DatabentoDatahandler::run(
|
||||
break;
|
||||
case SEC_TYPE(OPTION): {
|
||||
auto call_put = CallPut::PUT;
|
||||
if (msg.instrument_class == 'C') {
|
||||
if (msg.instrument_class == 'C')
|
||||
call_put = CallPut::CALL;
|
||||
} else {
|
||||
else
|
||||
bts_assert(msg.instrument_class == 'P');
|
||||
}
|
||||
sec.security.emplace<bts::domain::OptionSecurity>(
|
||||
expiration_time, bts::domain::Price(msg.strike_price, -9),
|
||||
call_put);
|
||||
@@ -147,36 +246,62 @@ void DatabentoDatahandler::run(
|
||||
default:
|
||||
bts_assert_msg(
|
||||
false, "Only futures and options supported on databento");
|
||||
return databento::KeepGoing::Stop;
|
||||
}
|
||||
|
||||
*sec.security_common.id = sec_id_store->get_or_add(
|
||||
venue, sec_type, std::to_string(msg.hd.instrument_id));
|
||||
listener->on_securities({&sec});
|
||||
return databento::KeepGoing::Continue;
|
||||
sec_id_map.insert({msg.hd.instrument_id, sec.security_common.id});
|
||||
return true;
|
||||
};
|
||||
auto debug_log = [&](const databento::Mbp1Msg &msg) {
|
||||
if (DEBUG_SEC_ID != msg.hd.instrument_id)
|
||||
return;
|
||||
const double denom = 1000000000;
|
||||
// auto delta = std::to_string(std::chrono::duration<double, std::micro>(msg.ts_in_delta).count());
|
||||
auto seq_num = msg.sequence;
|
||||
auto ap = msg.levels[0].ask_px / denom;
|
||||
auto bp = msg.levels[0].bid_px / denom;
|
||||
auto as = msg.levels[0].ask_sz;
|
||||
auto bs = msg.levels[0].bid_sz;
|
||||
LOG(md, Sys, "ID: {}, seq_num: {}, B_P: {}, B_S: {}, A_P: {}, A_S: {}",
|
||||
msg.hd.instrument_id, seq_num, bp, bs, ap, as);
|
||||
};
|
||||
|
||||
auto process_bbo = [&](const databento::Record &record) {
|
||||
const auto &msg = record.Get<databento::Mbp1Msg>();
|
||||
debug_log(msg);
|
||||
|
||||
// std::vector<std::tuple<uint64_t,std::string>>& times = all_times.emplace_back();
|
||||
// times.reserve(8);
|
||||
// return databento::KeepGoing::Continue; //only added so the same functions work with the threaded builder, not really need in the blocking version
|
||||
// The security type to is needed to get the id. Since the sec type isn't known,
|
||||
// check for an id using both types (OPTION and FUTURE). This is safe to do since
|
||||
// options and futures never have common names (instrument ids).
|
||||
const auto name = std::to_string(msg.hd.instrument_id);
|
||||
auto sec_id_opt =
|
||||
sec_id_store->get_id(venue, SEC_TYPE(OPTION), name, false);
|
||||
if (!sec_id_opt.has_value()) {
|
||||
sec_id_opt =
|
||||
sec_id_store->get_id(venue, SEC_TYPE(FUTURE), name, false);
|
||||
if (!sec_id_opt.has_value()) {
|
||||
return databento::KeepGoing::Continue;
|
||||
}
|
||||
}
|
||||
// auto current = bts::datetime::unix_nano_now();
|
||||
// auto prev = current;
|
||||
// times.push_back({current-prev,"Start Process bbo"});
|
||||
|
||||
// prev = current;
|
||||
// current = bts::datetime::unix_nano_now();
|
||||
// times.push_back({current-prev,"MISC to check log time"});
|
||||
if (!sec_id_map.contains(msg.hd.instrument_id))
|
||||
return databento::KeepGoing::Continue;
|
||||
bts::domain::SecurityID sec_id = sec_id_map.at(msg.hd.instrument_id);
|
||||
|
||||
// prev = current;
|
||||
// current = bts::datetime::unix_nano_now();
|
||||
// times.push_back({current-prev,"Sec_ID done"});
|
||||
|
||||
// sleep_for(sleep_duration_updated);
|
||||
// prev = current;
|
||||
// current = bts::datetime::unix_nano_now();
|
||||
// times.push_back({current-prev,"Sleep done"});
|
||||
|
||||
sleep_for(sleep_duration_updated);
|
||||
bts::domain::SecurityID sec_id =
|
||||
bts::domain::SecurityID(sec_id_opt.value());
|
||||
domain::Book book;
|
||||
book.set_security_id(sec_id);
|
||||
// prev = current;
|
||||
// current = bts::datetime::unix_nano_now();
|
||||
// times.push_back({current-prev,"Empty Book Made"});
|
||||
|
||||
auto &bids = book.bids();
|
||||
auto &asks = book.asks();
|
||||
@@ -186,130 +311,287 @@ void DatabentoDatahandler::run(
|
||||
asks.begin()->price = bts::domain::Price(msg.levels[0].ask_px, -9);
|
||||
asks.begin()->total_quantity =
|
||||
bts::domain::Quantity(msg.levels[0].ask_sz);
|
||||
// prev = current;
|
||||
// current = bts::datetime::unix_nano_now();
|
||||
// times.push_back({current-prev,"Complete book done"});
|
||||
|
||||
auto recv_time = bts::core::TimeStamp::from_iso_string(
|
||||
databento::ToIso8601(msg.ts_recv));
|
||||
// prev = current;
|
||||
// current = bts::datetime::unix_nano_now();
|
||||
// times.push_back({current-prev,"Time converted"});
|
||||
|
||||
listener->on_book(
|
||||
book,
|
||||
utils::TimeStamp{datetime::ptime_to_timestamp(recv_time.to_ptime())});
|
||||
return databento::KeepGoing::Continue;
|
||||
// prev = current;
|
||||
// current = bts::datetime::unix_nano_now();
|
||||
// times.push_back({current-prev,"On book done"});
|
||||
|
||||
return databento::KeepGoing::
|
||||
Continue; //only added so the same functions work with the threaded builder, not really need in the blocking version
|
||||
};
|
||||
auto process_tbbo = [&](const databento::Record &record) {
|
||||
const auto &msg = record.Get<databento::TbboMsg>();
|
||||
debug_log(msg);
|
||||
|
||||
// std::vector<std::tuple<uint64_t,std::string>>& times = all_times.emplace_back();
|
||||
// times.reserve(8);
|
||||
// return databento::KeepGoing::Continue; //only added so the same functions work with the threaded builder, not really need in the blocking version
|
||||
// The security type to is needed to get the id. Since the sec type isn't known,
|
||||
// check for an id using both types (OPTION and FUTURE). This is safe to do since
|
||||
// options and futures never have common names (instrument ids).
|
||||
// auto current = bts::datetime::unix_nano_now();
|
||||
// auto prev = current;
|
||||
// times.push_back({current-prev,"Start Process bbo"});
|
||||
|
||||
// prev = current;
|
||||
// current = bts::datetime::unix_nano_now();
|
||||
// times.push_back({current-prev,"MISC to check log time"});
|
||||
if (!sec_id_map.contains(msg.hd.instrument_id))
|
||||
return databento::KeepGoing::Continue;
|
||||
bts::domain::SecurityID sec_id = sec_id_map.at(msg.hd.instrument_id);
|
||||
|
||||
// prev = current;
|
||||
// current = bts::datetime::unix_nano_now();
|
||||
// times.push_back({current-prev,"Sec_ID done"});
|
||||
|
||||
// sleep_for(sleep_duration_updated);
|
||||
// prev = current;
|
||||
// current = bts::datetime::unix_nano_now();
|
||||
// times.push_back({current-prev,"Sleep done"});
|
||||
|
||||
domain::Book book;
|
||||
book.set_security_id(sec_id);
|
||||
// prev = current;
|
||||
// current = bts::datetime::unix_nano_now();
|
||||
// times.push_back({current-prev,"Empty Book Made"});
|
||||
|
||||
auto &bids = book.bids();
|
||||
auto &asks = book.asks();
|
||||
bids.begin()->price = bts::domain::Price(msg.levels[0].bid_px, -9);
|
||||
bids.begin()->total_quantity =
|
||||
bts::domain::Quantity(msg.levels[0].bid_sz);
|
||||
asks.begin()->price = bts::domain::Price(msg.levels[0].ask_px, -9);
|
||||
asks.begin()->total_quantity =
|
||||
bts::domain::Quantity(msg.levels[0].ask_sz);
|
||||
// prev = current;
|
||||
// current = bts::datetime::unix_nano_now();
|
||||
// times.push_back({current-prev,"Complete book done"});
|
||||
|
||||
auto recv_time = bts::core::TimeStamp::from_iso_string(
|
||||
databento::ToIso8601(msg.ts_recv));
|
||||
// prev = current;
|
||||
// current = bts::datetime::unix_nano_now();
|
||||
// times.push_back({current-prev,"Time converted"});
|
||||
|
||||
listener->on_book(
|
||||
book,
|
||||
utils::TimeStamp{datetime::ptime_to_timestamp(recv_time.to_ptime())});
|
||||
|
||||
|
||||
domain::TradeRecord trade;
|
||||
trade.trade_price = bts::domain::Price(msg.price, -9);
|
||||
trade.trade_quantity = bts::domain::Quantity(msg.size);
|
||||
trade.sec_id = sec_id;
|
||||
switch (msg.side) {
|
||||
case databento::Side::Ask:
|
||||
trade.agg_side = domain::AggressorSide::Sell;
|
||||
break;
|
||||
case databento::Side::Bid:
|
||||
trade.agg_side = domain::AggressorSide::Buy;
|
||||
break;
|
||||
case databento::Side::None:
|
||||
trade.agg_side = domain::AggressorSide::None;
|
||||
break;
|
||||
}
|
||||
trade.time_stamp = bts::core::TimeStamp::from_iso_string(
|
||||
databento::ToIso8601(msg.ts_recv));
|
||||
std::vector<const domain::TradeRecord *> trade_ptrs;
|
||||
trade_ptrs.push_back(&trade);
|
||||
listener->on_trades(trade_ptrs);
|
||||
|
||||
// prev = current;
|
||||
// current = bts::datetime::unix_nano_now();
|
||||
// times.push_back({current-prev,"On book done"});
|
||||
|
||||
return databento::KeepGoing::
|
||||
Continue; //only added so the same functions work with the threaded builder, not really need in the blocking version
|
||||
};
|
||||
|
||||
auto process_trades = [&](const databento::Record &record) {
|
||||
const auto &msg = record.Get<databento::TradeMsg>();
|
||||
// The security type to is needed to get the id. Since the sec type isn't known,
|
||||
// check for an id using both types (OPTION and FUTURE). This is safe to do since
|
||||
// options and futures never have common names (instrument ids).
|
||||
const auto name = std::to_string(msg.hd.instrument_id);
|
||||
auto sec_id_opt =
|
||||
sec_id_store->get_id(venue, SEC_TYPE(OPTION), name, false);
|
||||
if (!sec_id_opt.has_value()) {
|
||||
sec_id_opt =
|
||||
sec_id_store->get_id(venue, SEC_TYPE(FUTURE), name, false);
|
||||
if (!sec_id_opt.has_value()) {
|
||||
return databento::KeepGoing::Continue;
|
||||
}
|
||||
}
|
||||
if (!sec_id_map.contains(msg.hd.instrument_id))
|
||||
return databento::KeepGoing::Continue;
|
||||
bts::domain::SecurityID sec_id = sec_id_map.at(msg.hd.instrument_id);
|
||||
|
||||
sleep_for(sleep_duration_updated);
|
||||
bts::domain::SecurityID sec_id =
|
||||
bts::domain::SecurityID(sec_id_opt.value());
|
||||
// sleep_for(sleep_duration_updated);
|
||||
domain::TradeRecord trade;
|
||||
trade.trade_price = bts::domain::Price(msg.price, -9);
|
||||
trade.trade_quantity = bts::domain::Quantity(msg.size);
|
||||
trade.sec_id = sec_id;
|
||||
switch(msg.side){
|
||||
case databento::Side::Ask:
|
||||
trade.agg_side = domain::AggressorSide::Sell;
|
||||
break;
|
||||
case databento::Side::Bid:
|
||||
trade.agg_side = domain::AggressorSide::Buy;
|
||||
break;
|
||||
case databento::Side::None:
|
||||
trade.agg_side = domain::AggressorSide::None;
|
||||
break;
|
||||
trade.sec_id = sec_id;
|
||||
switch (msg.side) {
|
||||
case databento::Side::Ask:
|
||||
trade.agg_side = domain::AggressorSide::Sell;
|
||||
break;
|
||||
case databento::Side::Bid:
|
||||
trade.agg_side = domain::AggressorSide::Buy;
|
||||
break;
|
||||
case databento::Side::None:
|
||||
trade.agg_side = domain::AggressorSide::None;
|
||||
break;
|
||||
}
|
||||
trade.time_stamp = bts::core::TimeStamp::from_iso_string(
|
||||
databento::ToIso8601(msg.ts_recv));
|
||||
std::vector<const domain::TradeRecord *> trade_ptrs;
|
||||
trade_ptrs.push_back(&trade);
|
||||
listener->on_trades(trade_ptrs);
|
||||
|
||||
return databento::KeepGoing::Continue;
|
||||
|
||||
return databento::KeepGoing::
|
||||
Continue; //only added so the same functions work with the threaded builder, not really need in the blocking version
|
||||
};
|
||||
if (use_file) {
|
||||
auto client = databento::HistoricalBuilder{}.SetKey(key).Build();
|
||||
databento::DbnFileStore dbn_file_store(securities_path);
|
||||
dbn_file_store.Replay(process_security);
|
||||
// dbn_file_store.Replay(process_security); // fix this
|
||||
databento::DbnFileStore dbn_file_store2(books_path);
|
||||
dbn_file_store2.Replay(process_bbo);
|
||||
}else {
|
||||
bts_assert(dataset!="");
|
||||
// auto client = databento::LiveBuilder{}
|
||||
// .SetKey(key)
|
||||
// .SetDataset(dataset)
|
||||
// .BuildThreaded();
|
||||
auto client = databento::LiveBlocking{
|
||||
databento::ILogReceiver::Default(),
|
||||
key,
|
||||
dataset,
|
||||
true,
|
||||
databento::VersionUpgradePolicy::UpgradeToV2,
|
||||
{}
|
||||
};
|
||||
//hardcoding for now will update later
|
||||
std::vector<std::string> symbols;
|
||||
switch (venue) {
|
||||
case api::domain::VENUE_CBOE: symbols= {"SPX.OPT","SPXW.OPT"}; break;
|
||||
case api::domain::VENUE_GLBX: symbols= {"ES.FUT"}; break;
|
||||
default:bts_assert_msg(false, "ONLY GLBX AND CBOE SUPPORTED FOR NOW");
|
||||
}
|
||||
if(load_reference_data)
|
||||
client.Subscribe(symbols, databento::Schema::Definition, databento::SType::Parent,databento::UnixNanos{});
|
||||
else client.Subscribe(symbols, databento::Schema::Definition, databento::SType::Parent);
|
||||
client.Subscribe(symbols, databento::Schema::Mbp1, databento::SType::Parent);
|
||||
client.Subscribe(symbols, databento::Schema::Trades, databento::SType::Parent);
|
||||
client.Start();
|
||||
while (true) {
|
||||
try {
|
||||
auto* record = client.NextRecord(std::chrono::seconds{500});
|
||||
if (record == nullptr) {
|
||||
LOG(md, Error, "Timed out waiting for data");
|
||||
break;
|
||||
}
|
||||
if (record->Holds<databento::Mbp1Msg>()) {
|
||||
process_bbo(*record);
|
||||
}
|
||||
if (record->Holds<databento::InstrumentDefMsg>()) {
|
||||
process_security(*record);
|
||||
}
|
||||
if (record->Holds<databento::TradeMsg>()) {
|
||||
process_trades(*record);
|
||||
}
|
||||
} catch (const databento::TcpError& error) {
|
||||
std::string err = error.what();
|
||||
LOG(md, Error, "Caught TCP error: {} \nAttempting reconnect\n",err);
|
||||
client.Reconnect();
|
||||
} catch(const databento::DbnResponseError& err){
|
||||
std::string msg = err.Message();
|
||||
std::string what = err.what();
|
||||
LOG(md, Error, "Caught DbnResponseError what: {} ,msg:{} \nShutting down\n",msg, what);
|
||||
client.Stop();
|
||||
// bts_assert(false);
|
||||
}
|
||||
catch(const databento::Exception& excp){
|
||||
std::string err = excp.Message();
|
||||
std::string err2 = excp.what();
|
||||
LOG(md, Error, "Caught unknown dadtabento::exception what: {}, msg: {} \nShutting down\n",err, err2);
|
||||
client.Stop();
|
||||
// bts_assert(false);
|
||||
}catch (const std::exception& exc) {
|
||||
std::string err = exc.what();
|
||||
LOG(md, Error, "Caught unknown exception: {} \nShutting down\n",err);
|
||||
client.Stop();
|
||||
// bts_assert(false);
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
bts_assert(dataset != "");
|
||||
|
||||
//hardcoding for now will update later
|
||||
std::vector<std::string> symbols;
|
||||
switch (venue) {
|
||||
case api::domain::VENUE_CBOE:
|
||||
symbols = {"SPX.OPT", "SPXW.OPT"};
|
||||
break;
|
||||
case api::domain::VENUE_GLBX:
|
||||
symbols = {"ES.FUT"};
|
||||
break;
|
||||
default:
|
||||
bts_assert_msg(false, "ONLY GLBX AND CBOE SUPPORTED FOR NOW");
|
||||
}
|
||||
//ocassionaly run security writer to update security dbn
|
||||
if (!boost::filesystem::exists(securities_path) || load_reference_data) {
|
||||
update_file(key, dataset, symbols, 15 * 60, securities_path);
|
||||
}
|
||||
|
||||
//regular flow
|
||||
//read from security dbn, filter contracts that we need and subscribe to those in batches of two thousand
|
||||
LOG(md, Info, "starting loading symbols");
|
||||
databento::DbnFileStore store{securities_path};
|
||||
std::vector<std::vector<std::string>> all_sec;
|
||||
|
||||
all_sec.emplace_back();
|
||||
all_sec.back().reserve(2000);
|
||||
size_t total_count = 0;
|
||||
|
||||
while (auto *record = store.NextRecord()) {
|
||||
if (auto *def = record->GetIf<databento::InstrumentDefMsg>()) {
|
||||
std::string s = def->RawSymbol();
|
||||
if (process_security(*record)) {
|
||||
++total_count;
|
||||
// Check if the last vector is full
|
||||
if (all_sec.back().size() == 2000) {
|
||||
all_sec.emplace_back();
|
||||
all_sec.back().reserve(2000);
|
||||
LOG(md, Info, "{} symbols added", total_count);
|
||||
}
|
||||
all_sec.back().push_back(std::move(s)); // Move to avoid copying
|
||||
}
|
||||
}
|
||||
}
|
||||
// std::ostringstream output;
|
||||
|
||||
// for (const auto& sec_group : all_sec) {
|
||||
// for (size_t i = 0; i < sec_group.size(); ++i) {
|
||||
// if (i > 0) output << ", ";
|
||||
// output << sec_group[i];
|
||||
// }
|
||||
// output << '\n'; // Newline after each group
|
||||
// }
|
||||
|
||||
// // Store the final output in a stringls
|
||||
|
||||
// std::string final_output = output.str();
|
||||
// size_t size = all_sec.size();
|
||||
LOG(md, Info, "total {} secs loaded", total_count);
|
||||
|
||||
bool first = true;
|
||||
auto client = databento::LiveBlocking{
|
||||
databento::ILogReceiver::Default(), key, dataset, true,
|
||||
databento::VersionUpgradePolicy::UpgradeToV2, {}};
|
||||
for (std::vector vec : all_sec) {
|
||||
// client.Subscribe(
|
||||
// vec, databento::Schema::Tbbo, databento::SType::RawSymbol);
|
||||
// client.Subscribe(
|
||||
// vec, databento::Schema::Mbp1, databento::SType::RawSymbol);
|
||||
// client.Subscribe(
|
||||
// vec, databento::Schema::Trades, databento::SType::RawSymbol);
|
||||
}
|
||||
client.Subscribe(symbols, databento::Schema::Definition, databento::SType::Parent,databento::UnixNanos{});
|
||||
|
||||
client.Start();
|
||||
while (true) {
|
||||
try {
|
||||
auto *record = client.NextRecord(std::chrono::seconds{500});
|
||||
if (first) {
|
||||
LOG(md, Info, "first record");
|
||||
first = false;
|
||||
}
|
||||
if (record == nullptr) {
|
||||
LOG(md, Error, "Timed out waiting for data");
|
||||
break;
|
||||
}
|
||||
if (record->Holds<databento::Mbp1Msg>()) {
|
||||
process_bbo(*record);
|
||||
}
|
||||
if (record->Holds<databento::TbboMsg>()) {
|
||||
process_tbbo(*record);
|
||||
}
|
||||
if (all_times.size() == 1001) {
|
||||
log_time_statistics(all_times);
|
||||
client.Stop();
|
||||
}
|
||||
if (record->Holds<databento::InstrumentDefMsg>()) {
|
||||
process_security(*record);
|
||||
}
|
||||
if (record->Holds<databento::TradeMsg>()) {
|
||||
process_trades(*record);
|
||||
}
|
||||
} catch (const databento::TcpError &error) {
|
||||
std::string err = error.what();
|
||||
LOG(md, Error, "Caught TCP error: {} \nAttempting reconnect\n",
|
||||
err);
|
||||
client.Reconnect();
|
||||
} catch (const databento::DbnResponseError &err) {
|
||||
std::string msg = err.Message();
|
||||
std::string what = err.what();
|
||||
LOG(md, Error,
|
||||
"Caught DbnResponseError what: {} ,msg:{} \nShutting down\n",
|
||||
msg, what);
|
||||
client.Stop();
|
||||
// bts_assert(false);
|
||||
} catch (const databento::Exception &excp) {
|
||||
std::string err = excp.Message();
|
||||
std::string err2 = excp.what();
|
||||
LOG(md, Error,
|
||||
"Caught unknown dadtabento::exception what: {}, msg: {} "
|
||||
"\nShutting down\n",
|
||||
err, err2);
|
||||
client.Stop();
|
||||
// bts_assert(false);
|
||||
} catch (const std::exception &exc) {
|
||||
std::string err = exc.what();
|
||||
LOG(md, Error, "Caught unknown exception: {} \nShutting down\n",
|
||||
err);
|
||||
client.Stop();
|
||||
// bts_assert(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,8 +41,8 @@ static void instrument_to_sec(const OkxInstrument &oi, Security &sec) {
|
||||
scomm.exchange_sec_id = oi.instId;
|
||||
scomm.desc = oi.instId;
|
||||
scomm.min_price_increment = oi.tickSz;
|
||||
scomm.min_quantity = oi.lotSz;
|
||||
scomm.min_quantity_increment = oi.minSz;
|
||||
scomm.min_quantity = oi.minSz;
|
||||
scomm.min_quantity_increment = oi.lotSz;
|
||||
scomm.currency = oi.quoteCcy;
|
||||
|
||||
switch (oi.instType.value) {
|
||||
@@ -51,9 +51,13 @@ static void instrument_to_sec(const OkxInstrument &oi, Security &sec) {
|
||||
sec.security.emplace<SpotSecurity>();
|
||||
break;
|
||||
case bts::md::okx::OkxInstrumentType::SWAP:{
|
||||
sec.security_common.symbol = oi.settleCcy;
|
||||
auto &fut = sec.security.emplace<FutureSecurity>();
|
||||
core::TimeStamp::from_ymd(3000, 1, 1,&fut.expiration);
|
||||
if(oi.instId.find("USDT")!=std::string::npos || oi.instId.find("USDC")!=std::string::npos){
|
||||
sec.security_common.symbol = oi.instId.substr(0,oi.instId.find_first_of("-"));
|
||||
break;
|
||||
}
|
||||
sec.security_common.symbol = oi.settleCcy;
|
||||
break;
|
||||
}
|
||||
case OkxInstrumentType::FUTURES: {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
BINANCE_USDS_FUTURES_PROD_URL="https://fapi.binance.com"
|
||||
BINANCE_USDS_FUTURES_DEV_URL="https://testnet.binancefuture.com"
|
||||
BINANCE_USDS_FUTURES_EXCHANGE_INFO_PATH="/fapi/v1/exchangeInfo"
|
||||
|
||||
BINANCE_USDS_FUTURES_DEPTH_PATH="/fapi/v1/depth"
|
||||
BINANCE_USDS_FUTURES_DEPTH_PARAMS='symbol=${SYMBOL}&limit=${LIMIT:-5}'
|
||||
|
||||
BINANCE_USDS_FUTURES_RECENT_TRADES_PATH="/fapi/v1/trades"
|
||||
BINANCE_USDS_FUTURES_RECENT_TRADES_PARAMS='symbol=${SYMBOL}&limit=${LIMIT:-500}'
|
||||
|
||||
BINANCE_USDS_FUTURES_HISTORICAL_TRADES_PATH="/fapi/v1/historicalTrades"
|
||||
BINANCE_USDS_FUTURES_HISTORICAL_TRADES_PARAMS='symbol=${SYMBOL}&limit=${LIMIT:-500}'
|
||||
|
||||
BINANCE_USDS_FUTURES_MARK_PRICE_PATH="/fapi/v1/premiumIndex"
|
||||
BINANCE_USDS_FUTURES_MARK_PRICE_PARAMS='symbol=${SYMBOL}'
|
||||
|
||||
BINANCE_USDS_FUTURES_PRICE_TICKER_PATH="/fapi/v1/ticker/price"
|
||||
BINANCE_USDS_FUTURES_PRICE_TICKER_PARAMS='symbol=${SYMBOL}'
|
||||
|
||||
BINANCE_USDS_FUTURES_BBO_PATH="/fapi/v1/ticker/bookTicker"
|
||||
BINANCE_USDS_FUTURES_BBO_PARAMS='symbol=${SYMBOL}'
|
||||
|
||||
BINANCE_USDS_FUTURES_OPEN_INTEREST_PATH="/dapi/v1/openInterest"
|
||||
BINANCE_USDS_FUTURES_OPEN_INTEREST_PARAMS='symbol=${SYMBOL}'
|
||||
|
||||
BINANCE_USDS_FUTURES_LONG_SHORT_RATIO_PATH="/futures/data/globalLongShortAccountRatio"
|
||||
BINANCE_USDS_FUTURES_LONG_SHORT_RATIO_PATH='symbol=${SYMBOL}&period=${PERIOD:-30}&limit=${LIMIT:-10}'
|
||||
|
||||
BINANCE_COIN_FUTURES_PROD_URL="https://dapi.binance.com"
|
||||
BINANCE_COIN_FUTURES_DEV_URL="https://testnet.binancefuture.com"
|
||||
BINANCE_COIN_FUTURES_EXCHANGE_INFO_PATH="/dapi/v1/exchangeInfo"
|
||||
BINANCE_COIN_FUTURES_DEPTH_PATH="/dapi/v1/depth"
|
||||
BINANCE_COIN_FUTURES_RECENT_TRADES_PATH="/dapi/v1/trades"
|
||||
BINANCE_COIN_FUTURES_HISTORICAL_TRADES_PATH="/dapi/v1/historicalTrades"
|
||||
BINANCE_COIN_FUTURES_MARK_PRICE_PATH="/dapi/v1/premiumIndex"
|
||||
BINANCE_COIN_FUTURES_PRICE_TICKER_PATH="/dapi/v1/ticker/price"
|
||||
BINANCE_COIN_FUTURES_BBO_PATH="/dapi/v1/ticker/bookTicker"
|
||||
BINANCE_COIN_FUTURES_OPEN_INTEREST_PATH="/dapi/v1/openInterest"
|
||||
BINANCE_COIN_FUTURES_LONG_SHORT_RATIO_PATH="/futures/data/globalLongShortAccountRatio"
|
||||
|
||||
BINANCE_PROD_URL="https://api.binance.com"
|
||||
BINANCE_DEV_URL="https://testnet.binance.vision"
|
||||
BINANCE_PATH="/api/v3/depth"
|
||||
Executable
+141
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
help_text="\
|
||||
Get Binance market data.
|
||||
|
||||
Usage: get_binance_md.sh [OPTIONS]
|
||||
|
||||
Options:
|
||||
--usds-future Indicates the symbol is a USDS Future
|
||||
--coin-future Indicates the symbol is a Coin Future
|
||||
--spot Indicates the symbol is a Spot
|
||||
--us Indicates the symbol is a Binance US Spot
|
||||
--prod Use production url
|
||||
--dev Use development url
|
||||
--dry Print the command to console without actually running it
|
||||
--data DEPTH | RECENT_TRADES | HISTORICAL_TRADES | MARK_PRICE | PRICE_TICKER
|
||||
--<key>=<value> Query parameter (e.g., --symbol=btcusdt, --limit=5)
|
||||
|
||||
"
|
||||
|
||||
function print_help_and_exit() {
|
||||
echo "$help_text"
|
||||
exit "$1"
|
||||
}
|
||||
|
||||
declare this_dir="$(readlink -f $(dirname ${BASH_SOURCE[0]}))"
|
||||
source "$this_dir/binance.env"
|
||||
|
||||
env_name="binance"
|
||||
venue="BINANCE"
|
||||
declare -u mode
|
||||
declare -u data
|
||||
declare -A query_params
|
||||
declare -i dry
|
||||
dry=0
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--usds-future)
|
||||
venue="BINANCE_USDS_FUTURES"
|
||||
shift 1
|
||||
;;
|
||||
--coin-future)
|
||||
venue="BINANCE_COIN_FUTURES"
|
||||
shift 1
|
||||
;;
|
||||
--spot)
|
||||
venue="BINANCE"
|
||||
shift 1
|
||||
;;
|
||||
--us)
|
||||
venue="BINANCE_US"
|
||||
shift 1
|
||||
;;
|
||||
--prod)
|
||||
mode="PROD"
|
||||
shift 1
|
||||
;;
|
||||
--dev)
|
||||
mode="DEV"
|
||||
shift 1
|
||||
;;
|
||||
--data)
|
||||
if [[ -n "$data" ]]; then
|
||||
echo "Unexpected argument '$1'"
|
||||
print_help_and_exit 1
|
||||
elif [[ -z "$2" ]]; then
|
||||
echo "Data (--data) must be non-empty"
|
||||
print_help_and_exit 1
|
||||
fi
|
||||
data="$2"
|
||||
shift 2
|
||||
;;
|
||||
--*=*)
|
||||
key="${1%%=*}"
|
||||
key="${key#--}"
|
||||
value="${1#*=}"
|
||||
if [[ -z "$key" ]]; then
|
||||
echo "Invalid --key=value arg (key is empty). $1"
|
||||
print_help_and_exit 1
|
||||
fi
|
||||
query_params["$key"]="$value"
|
||||
shift 1
|
||||
;;
|
||||
--dry)
|
||||
dry=1
|
||||
shift 1
|
||||
;;
|
||||
-h)
|
||||
;&
|
||||
--help)
|
||||
print_help_and_exit 0
|
||||
;;
|
||||
*)
|
||||
if [[ -z "$1" ]]; then
|
||||
echo "Invalid positional argument '$1'"
|
||||
print_help_and_exit 1
|
||||
elif [[ -z "$symbol" ]]; then
|
||||
symbol="$1"
|
||||
else
|
||||
echo "Unexpected argument '$1'"
|
||||
print_help_and_exit 1
|
||||
fi
|
||||
shift 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$mode" ]; then
|
||||
mode="DEV"
|
||||
fi
|
||||
if [ -z "$data" ]; then
|
||||
data="DEPTH"
|
||||
fi
|
||||
|
||||
base_url_name="${venue}_${mode}_URL"
|
||||
base_url_name="${venue}_${mode}_URL"
|
||||
path_name="${venue}_${data}_PATH"
|
||||
params_name="${venue}_${data}_PARAMS"
|
||||
if [ ! -f "$this_dir/$env_name.env" ]; then
|
||||
echo "Env file '$this_dir/$env_name.env' does not exist"
|
||||
print_help_and_exit 1
|
||||
fi
|
||||
|
||||
for key in "${!query_params[@]}"; do
|
||||
val=${query_params[$key]}
|
||||
declare -u keyu
|
||||
keyu="$key"
|
||||
eval "$keyu=$val"
|
||||
done
|
||||
|
||||
source "$this_dir/$env_name.env"
|
||||
|
||||
params_template=${!params_name}
|
||||
eval "params=\"$params_template\""
|
||||
url="${!base_url_name}${!path_name}?${params}"
|
||||
if [ $dry -ne 0 ]; then
|
||||
echo curl --get $url
|
||||
else
|
||||
curl --get $url
|
||||
fi
|
||||
Reference in new issue
Block a user