Compare commits
108
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0cb46fd8ab | ||
|
|
c098b5e442 | ||
|
|
dc8cd290b6 | ||
|
|
17c77d9b31 | ||
|
|
2cf62d8d9a | ||
|
|
0328356758 | ||
|
|
0e1be3f559 | ||
|
|
f2d1364c4b | ||
|
|
3d2043f926 | ||
|
|
e525e740c1 | ||
|
|
0ef72a45fe | ||
|
|
db5dbd3666 | ||
|
|
ca27dd3329 | ||
|
|
37b34ec6f7 | ||
|
|
f869c97fbe | ||
|
|
a27462b1a3 | ||
|
|
8e9abf8451 | ||
|
|
1b40382d56 | ||
|
|
402d9f5b84 | ||
|
|
20779387eb | ||
|
|
7a6bba0f19 | ||
|
|
83dc93aeb1 | ||
|
|
6107bbdb5d | ||
|
|
71f95f8774 | ||
|
|
4108ee087e | ||
|
|
e30bf5cdea | ||
|
|
dce77049b1 | ||
|
|
72ef4f33b0 | ||
|
|
a7176f7851 | ||
|
|
1a0f3197ce | ||
|
|
e34c2ffec7 | ||
|
|
8a10b591ed | ||
|
|
efc2100177 | ||
|
|
a4d57cc97c | ||
|
|
3426702d8f | ||
|
|
8e7a24c971 | ||
|
|
475dda4e9e | ||
|
|
193f3380cb | ||
|
|
d6ac2c5134 | ||
|
|
076513e79b | ||
|
|
b227fc6254 | ||
|
|
ca82042669 | ||
|
|
ec7c0de946 | ||
|
|
391580b9da | ||
|
|
7e7e185968 | ||
|
|
517e64ceff | ||
|
|
7df9a15d3b | ||
|
|
b01f36d263 | ||
|
|
36e555cd13 | ||
|
|
3d0d1c6dba | ||
|
|
f03a055c34 | ||
|
|
bfeb5e362a | ||
|
|
e48f2ec99d | ||
|
|
cec62215e2 | ||
|
|
e361d405c9 | ||
|
|
020cd44cf3 | ||
|
|
c02f3d152a | ||
|
|
b4b9e00f90 | ||
|
|
8f886008ef | ||
|
|
23e4dbfa07 | ||
|
|
114ac2fdc9 | ||
|
|
3bdde53d15 | ||
|
|
bcc4d5fb3d | ||
|
|
e93b5c901f | ||
|
|
ccf8b671bf | ||
|
|
0462c97b76 | ||
|
|
3e23799772 | ||
|
|
2b4119a023 | ||
|
|
da469270e7 | ||
|
|
c9e9be0772 | ||
|
|
2870fefc15 | ||
|
|
cd1a5946b3 | ||
|
|
2cf60d74cc | ||
|
|
10c018e995 | ||
|
|
25b937c096 | ||
|
|
516b9c0462 | ||
|
|
347ec1a7ea | ||
|
|
80f4b4bc1c | ||
|
|
c470984bd2 | ||
|
|
f560eca4d7 | ||
|
|
f9b28dcb7a | ||
|
|
8275423b89 | ||
|
|
86a725227b | ||
|
|
43ec58dfe3 | ||
|
|
922924c31d | ||
|
|
2b7dbb6ad3 | ||
|
|
1c431f542e | ||
|
|
7fa8e4ca47 | ||
|
|
8579c9fdc5 | ||
|
|
ebcb070310 | ||
|
|
f5171d8636 | ||
|
|
8af399ca0f | ||
|
|
ea48dbabe3 | ||
|
|
9c7f712e11 | ||
|
|
e4df7215b0 | ||
|
|
4f404eec24 | ||
|
|
633aa362ff | ||
|
|
bfd837764a | ||
|
|
99f3a845b8 | ||
|
|
49f9f50fdb | ||
|
|
bb25eab078 | ||
|
|
70fae06a30 | ||
|
|
07b59559a2 | ||
|
|
cd1709563b | ||
|
|
94f22ddf40 | ||
|
|
7f6c08f956 | ||
|
|
1d737cdf82 | ||
|
|
b645192355 |
No files matched your search
@@ -34,7 +34,7 @@
|
||||
# src/server/sparkd/Dockerfile
|
||||
SPARKD__IMAGE=sparkd
|
||||
# The config dir for this service to use
|
||||
SPARKD__CONFIG_DIR=src/server/sparkd/configurations/dev
|
||||
SPARKD__CONFIG_DIR=src/app_sparkd/configurations/dev
|
||||
# Mount a specific volume or dir to "~/sparkdata/data" inside the docker.
|
||||
# Defaults to a volume named "sparkd_<config_port>" (e.g., sparkd_8000).
|
||||
# SPARKD__DATA_DIR=/home/bts/sparkdata/data
|
||||
@@ -44,5 +44,6 @@ SPARKD__CONFIG_PORT=8000
|
||||
SPARKD__QUICKBUS_PORT=8001
|
||||
SPARKD__SIM_FIX_PORT=8002
|
||||
SPARKD__SHM_SIZE=4gb
|
||||
SPARKD__PYTHON_REQUIREMENTS=requirements.txt
|
||||
|
||||
# TODO: add config for other services (pubsub, md-publisher, webapp, api-server, etc.)
|
||||
+2
-2
@@ -113,15 +113,15 @@ else()
|
||||
endif()
|
||||
|
||||
# subdirs
|
||||
add_subdirectory(src/lib_spark)
|
||||
if (BUILD_SPARKD)
|
||||
add_subdirectory(src/lib_python_interop)
|
||||
add_subdirectory(src/lib_onixs_fix)
|
||||
add_subdirectory(src/lib_binance_execution)
|
||||
add_subdirectory(src/lib_okx_execution)
|
||||
add_subdirectory(src/lib_python_md)
|
||||
add_subdirectory(src/app_sparkd)
|
||||
add_subdirectory(src/data_files)
|
||||
endif()
|
||||
add_subdirectory(src/lib_spark)
|
||||
if (BUILD_ADMIN_WEB_APP)
|
||||
add_subdirectory(src/Spark.Admin.WebApp)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
include_guard(DIRECTORY)
|
||||
include(GNUInstallDirs)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/ExportToParent.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/SetFirstDefined.cmake)
|
||||
|
||||
function(init_python_module)
|
||||
set(_options)
|
||||
set(_one_value_args
|
||||
NAME
|
||||
)
|
||||
set(_multi_value_args
|
||||
SOURCES
|
||||
)
|
||||
cmake_parse_arguments(_ARGS "${_options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN})
|
||||
set_first_defined(_name _ARGS_NAME PROJECT_NAME)
|
||||
set_first_defined(_sources _ARGS_SOURCES ${_name}_SOURCES)
|
||||
|
||||
set(module_proj ${_name}_module)
|
||||
set(${module_proj}_SOURCES ${_sources})
|
||||
pybind11_add_module(${module_proj} SHARED ${_sources})
|
||||
target_link_libraries(
|
||||
${module_proj}
|
||||
PUBLIC
|
||||
pybind11::embed
|
||||
spark
|
||||
${_name}
|
||||
)
|
||||
target_include_directories(
|
||||
${module_proj}
|
||||
PRIVATE
|
||||
$<BUILD_INTERFACE:${pybind11_INCLUDE_DIRS}>
|
||||
)
|
||||
target_include_directories(
|
||||
${module_proj}
|
||||
PUBLIC
|
||||
${Python3_INCLUDE_DIRS}
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/stubs/${_name}.pyi
|
||||
DEPENDS ${_name} ${module_proj}
|
||||
COMMAND PYTHONPATH=${CMAKE_CURRENT_BINARY_DIR} pybind11-stubgen --numpy-array-use-type-var --ignore-invalid-expressions '.*' ${_name}_module
|
||||
COMMENT "Generating py stubs"
|
||||
)
|
||||
add_custom_target(
|
||||
"${_name}_py_stubs"
|
||||
ALL
|
||||
DEPENDS
|
||||
${CMAKE_CURRENT_BINARY_DIR}/stubs/${_name}.pyi
|
||||
)
|
||||
install(
|
||||
TARGETS ${module_proj}
|
||||
EXPORT ${module_proj}Targets
|
||||
RUNTIME
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
LIBRARY
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
ARCHIVE
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
)
|
||||
foreach(dir ${pybind11_INCLUDE_DIRS})
|
||||
install(DIRECTORY ${dir} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
endforeach()
|
||||
install(
|
||||
EXPORT ${module_proj}Targets
|
||||
FILE ${module_proj}Targets.cmake
|
||||
NAMESPACE ${_name}::
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${_name}
|
||||
)
|
||||
endfunction()
|
||||
@@ -0,0 +1,65 @@
|
||||
include_guard(DIRECTORY)
|
||||
include(GNUInstallDirs)
|
||||
|
||||
function(init_python_plugin)
|
||||
set(_options)
|
||||
set(_one_value_args NAME PLUGIN_NAME)
|
||||
set(_multi_value_args SOURCES)
|
||||
cmake_parse_arguments(_ARGS "${_options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN})
|
||||
set_first_defined(_name _ARGS_NAME PROJECT_NAME)
|
||||
set(_plugin_name ${_ARGS_PLUGIN_NAME})
|
||||
if (NOT DEFINED _ARGS_PLUGIN_NAME)
|
||||
set(_plugin_name ${_name}_plugins)
|
||||
endif()
|
||||
set(${_plugin_name}_SOURCES ${_ARGS_SOURCES})
|
||||
if (NOT DEFINED _ARGS_SOURCES)
|
||||
file(
|
||||
GLOB_RECURSE ${_plugin_name}_SOURCES
|
||||
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${_plugin_name}/*.py)
|
||||
endif()
|
||||
set(src_outs)
|
||||
foreach(_src ${${_plugin_name}_SOURCES})
|
||||
set(_src_in ${CMAKE_CURRENT_SOURCE_DIR}/${_src})
|
||||
set(_src_out ${CMAKE_CURRENT_BINARY_DIR}/${_src})
|
||||
add_custom_command(
|
||||
OUTPUT ${_src_out}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${_src_in} ${_src_out}
|
||||
DEPENDS ${_src_in}
|
||||
COMMENT "Copying ${_plugin_name} plugin ${_src} to ${_src_out}"
|
||||
)
|
||||
list(APPEND src_outs ${_src_out})
|
||||
endforeach()
|
||||
|
||||
add_custom_target(copy_${_plugin_name} ALL DEPENDS ${src_outs})
|
||||
|
||||
add_library(${_plugin_name} INTERFACE)
|
||||
add_library(${_name}::${_plugin_name} ALIAS ${_plugin_name})
|
||||
|
||||
set_target_properties(
|
||||
${_plugin_name}
|
||||
PROPERTIES
|
||||
INTERFACE_PYTHON_SCRIPTS_DIR
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/${_plugin_name}>$<INSTALL_INTERFACE:${CMAKE_INSTALL_BINDIR}/${_plugin_name}>
|
||||
)
|
||||
|
||||
set_target_properties(
|
||||
${_plugin_name}
|
||||
PROPERTIES
|
||||
INTERFACE_PYTHON_SCRIPTS $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/${${_plugin_name}_SOURCES}>$<INSTALL_INTERFACE:${CMAKE_INSTALL_BINDIR}/${${_plugin_name}_SOURCES}>
|
||||
)
|
||||
|
||||
install(
|
||||
TARGETS ${_plugin_name}
|
||||
EXPORT ${_plugin_name}Targets
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
)
|
||||
|
||||
install(
|
||||
EXPORT ${_plugin_name}Targets
|
||||
FILE ${_plugin_name}Targets.cmake
|
||||
NAMESPACE ${_name}::
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${_name}
|
||||
)
|
||||
install(DIRECTORY ${_plugin_name} DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
endfunction()
|
||||
@@ -0,0 +1,39 @@
|
||||
include_guard(DIRECTORY)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/SymLink.cmake)
|
||||
|
||||
function(sym_link_python_module)
|
||||
set(_options)
|
||||
set(_one_value_args LIB_NAME MODULE_NAME PLUGIN_NAME)
|
||||
set(_multi_value_args)
|
||||
cmake_parse_arguments(_ARGS "${_options}" "${_one_value_args}"
|
||||
"${_multi_value_args}" ${ARGN})
|
||||
if(NOT DEFINED _ARGS_LIB_NAME)
|
||||
message(
|
||||
FATAL_ERROR "LIB_NAME must be specified for sym_link_python_module()")
|
||||
endif()
|
||||
set(_lib_name ${_ARGS_LIB_NAME})
|
||||
set(_module_name ${_ARGS_MODULE_NAME})
|
||||
if(NOT DEFINED _ARGS_MODULE_NAME)
|
||||
set(_module_name ${_lib_name}_module)
|
||||
endif()
|
||||
set(_plugin_name ${_ARGS_PLUGIN_NAME})
|
||||
set(_deps ${_lib_name} ${_module_name})
|
||||
if(DEFINED _ARGS_PLUGIN_NAME)
|
||||
list(APPEND _deps ${_plugin_name} copy_${_plugin_name})
|
||||
endif()
|
||||
add_dependencies(${PROJECT_NAME} ${_deps})
|
||||
sym_link_local_lib(${_lib_name})
|
||||
set(_module_suffix cpython-310-x86_64-linux-gnu.so)
|
||||
sym_link_file(
|
||||
SOURCE
|
||||
${CMAKE_BINARY_DIR}/src/lib_${_lib_name}/${_module_name}.${_module_suffix}
|
||||
DEST
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${_module_name}.${_module_suffix}
|
||||
INSTALL_DEST
|
||||
${CMAKE_INSTALL_BINDIR}/)
|
||||
if(DEFINED _ARGS_PLUGIN_NAME)
|
||||
get_target_property(plugins ${_plugin_name} INTERFACE_PYTHON_SCRIPTS_DIR)
|
||||
sym_link_dir(TARGET_NAME ${PROJECT_NAME}_link_${_plugin_name} SOURCE
|
||||
${plugins} DEST ${CMAKE_CURRENT_BINARY_DIR}/${_plugin_name})
|
||||
endif()
|
||||
endfunction()
|
||||
+3
-1
@@ -8,6 +8,7 @@ services:
|
||||
- IMAGE=${SPARKD__IMAGE}
|
||||
- SOURCE_DIR=${SPARKD__SOURCE_DIR}
|
||||
- CONFIG_DIR=${SPARKD__CONFIG_DIR}
|
||||
- PYTHON_REQUIREMENTS=${SPARKD__PYTHON_REQUIREMENTS}
|
||||
shm_size: "${SPARKD__SHM_SIZE:-512mb}"
|
||||
command: >
|
||||
sh -c "
|
||||
@@ -39,11 +40,12 @@ services:
|
||||
- ${SPARKD__DATA_DIR:-sparkd_data}:/home/bts/sparkdata/data
|
||||
- ${SPARKD__RUNTIME_CONF_DIR:-sparkd_conf}:/home/bts/sparkdata/conf
|
||||
build:
|
||||
dockerfile: src/server/sparkd/Dockerfile
|
||||
dockerfile: src/app_sparkd/Dockerfile
|
||||
args:
|
||||
- IMAGE=${SPARKD__IMAGE}
|
||||
- SOURCE_DIR=${SPARKD__SOURCE_DIR}
|
||||
- CONFIG_DIR=${SPARKD__CONFIG_DIR}
|
||||
- PYTHON_REQUIREMENTS=${SPARKD__PYTHON_REQUIREMENTS}
|
||||
shm_size: "${SPARKD__SHM_SIZE:-512mb}"
|
||||
command: >
|
||||
sh -c "
|
||||
|
||||
@@ -16,11 +16,16 @@ jmespath==1.0.1
|
||||
lazy-object-proxy==1.6.0
|
||||
MarkupSafe==3.0.2
|
||||
mccabe==0.6.1
|
||||
numpy==2.2.6
|
||||
platformdirs==2.3.0
|
||||
protobuf==4.21.12
|
||||
pybars3==0.9.7
|
||||
pybind11==2.13.6
|
||||
pybind11-stubgen==2.5.3
|
||||
pybit==5.11.0
|
||||
pycodestyle==2.7.0
|
||||
pycparser==2.20
|
||||
pycryptodome==3.23.0
|
||||
pylint==2.10.2
|
||||
PyMeta3==0.5.1
|
||||
python-dateutil==2.9.0.post0
|
||||
@@ -31,6 +36,7 @@ six==1.17.0
|
||||
termcolor==1.1.0
|
||||
toml==0.10.2
|
||||
urllib3==2.0.3
|
||||
websocket-client==1.8.0
|
||||
Werkzeug==3.1.3
|
||||
wrapt==1.12.1
|
||||
yapf==0.31.0
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
|
||||
namespace BCG {
|
||||
[DataStreamUserRespresentation("count")]
|
||||
public class CountStream : ViewStream {
|
||||
private DataPacket myLastFiredDataPacket;
|
||||
private IBlueTableSchema mySchema = new BlueTableSchema("count",
|
||||
new ColumnInfo("id", 0, typeof(int)),
|
||||
new ColumnInfo("count", 1, typeof(int)));
|
||||
private int myId = 0;
|
||||
private ViewStream myVS;
|
||||
|
||||
public CountStream(ViewStream vs) {
|
||||
myVS = vs;
|
||||
}
|
||||
|
||||
public CountStream(string tableName)
|
||||
: this(new TableStream(tableName)) { }
|
||||
|
||||
protected override void myOnFirstSubscriber() {
|
||||
myVS.onChange += new ChangeDelegate(myHandleChanges);
|
||||
}
|
||||
protected override void myOnLastSubscriber() {
|
||||
myVS.onChange -= new ChangeDelegate(myHandleChanges);
|
||||
}
|
||||
|
||||
|
||||
private void myHandleChanges(object newValue) {
|
||||
myFireEvent(
|
||||
myLastFiredDataPacket =
|
||||
myLastFiredDataPacket == null ?
|
||||
new DataPacket(myGetRows(newValue, ++myId), myGetRows(newValue, myId),
|
||||
new BlueTable(mySchema))
|
||||
:
|
||||
new DataPacket(myGetRows(newValue, ++myId), myGetRows(newValue, myId),
|
||||
myLastFiredDataPacket.newRows)
|
||||
);
|
||||
}
|
||||
|
||||
private IBlueRowCollection myGetRows(object dp, int id) {
|
||||
BlueTable retVal = new BlueTable(mySchema);
|
||||
retVal.addRow
|
||||
(new BlueRow(mySchema, id, (dp as DataPacket).newRows.Count));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
namespace bts.BlueOrb {
|
||||
using bts;
|
||||
|
||||
using BCG;
|
||||
|
||||
using System;
|
||||
[DataStreamUserRespresentation("DateTimeExpanderStream")]
|
||||
public class DateTimeExpanstionStream : ViewStream {
|
||||
public DateTimeExpanstionStream(ViewStream parent_stream, string inp_col) {
|
||||
Func<IBlueTableSchema, IBlueTableSchema> get_schema = (s) => {
|
||||
IBlueTableSchema rv = s.add_column(System.TypeCode.DateTime, $"{inp_col}_date");
|
||||
rv = rv.add_column(System.TypeCode.String, $"{inp_col}_day_of_week");
|
||||
rv = rv.add_column(System.TypeCode.Int32, $"{inp_col}_day_of_month");
|
||||
rv = rv.add_column(System.TypeCode.Int32, $"{inp_col}_month");
|
||||
rv = rv.add_column(System.TypeCode.Int32, $"{inp_col}_hour");
|
||||
rv = rv.add_column(System.TypeCode.Int32, $"{inp_col}_minute");
|
||||
rv = rv.add_column(System.TypeCode.Int32, $"{inp_col}_seconds_of_day");
|
||||
return rv;
|
||||
};
|
||||
ViewStream rs = new MapStream(parent_stream,
|
||||
s => get_schema(s),
|
||||
r => {
|
||||
DateTime dt = r.get_datetime(inp_col);
|
||||
IBlueTableSchema s = get_schema(r.schema);
|
||||
MutableBlueRow rv = new MutableBlueRow(s, r);
|
||||
rv.setValue($"{inp_col}_date", dt.Date);
|
||||
rv.setValue($"{inp_col}_day_of_week", dt.DayOfWeek.ToString());
|
||||
rv.setValue($"{inp_col}_day_of_month", dt.Day);
|
||||
rv.setValue($"{inp_col}_month", dt.Date.Month);
|
||||
rv.setValue($"{inp_col}_hour", dt.Hour);
|
||||
rv.setValue($"{inp_col}_minute", dt.Minute);
|
||||
rv.setValue($"{inp_col}_seconds_of_day", (dt - dt.Date).TotalSeconds);
|
||||
return rv;
|
||||
});
|
||||
mySetRealSource(rs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
namespace bts.BlueOrb {
|
||||
using BCG;
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
|
||||
public class RegExpFilter : IStreamFilter {
|
||||
private Regex[] myRegExps;
|
||||
private string[] myCols;
|
||||
|
||||
public RegExpFilter(string col, string regExp)
|
||||
: this(new string[] { col }, new string[] { regExp }) { }
|
||||
|
||||
public RegExpFilter(string[] cols, string[] regExps) {
|
||||
myCols = cols;
|
||||
myRegExps = new Regex[regExps.Length];
|
||||
for (int i = 0; i < regExps.Length; i++)
|
||||
myRegExps[i] = new Regex(regExps[i]);
|
||||
}
|
||||
|
||||
#region IStreamFilter Members
|
||||
public bool filter(IBlueRow dr) {
|
||||
bool rv = true;
|
||||
for (int i = 0; i < myCols.Length; i++) {
|
||||
rv &= myRegExps[i].IsMatch(dr[myCols[i]] is DBNull ? "" : dr[myCols[i]].ToString());
|
||||
if (!rv)
|
||||
return rv;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
//[DataStreamUserReprsentation("FilterStream")]
|
||||
public class FilterStream : ViewStream {
|
||||
private BlueTable myLastFiredRows;
|
||||
private IBlueRowCollection myLastReceivedRows;
|
||||
private IStreamFilter myFilter;
|
||||
private ViewStream myParentStream;
|
||||
private string myFilterString;
|
||||
private bool myUseNewFilter = false;// Configuration.get<DataStreamConfig>().UseNewFilter;
|
||||
|
||||
#region constructors
|
||||
public delegate bool OnFilter(IBlueRow r);
|
||||
private class OnFilterWrapper : IStreamFilter {
|
||||
public OnFilterWrapper(OnFilter f) { this.f = f; }
|
||||
|
||||
public bool filter(IBlueRow dr) { return f(dr); }
|
||||
|
||||
private OnFilter f;
|
||||
}
|
||||
public FilterStream(ViewStream vs, OnFilter f)
|
||||
: this(vs, new OnFilterWrapper(f)) { }
|
||||
|
||||
|
||||
public FilterStream(ViewStream vs, IStreamFilter filter) {
|
||||
{
|
||||
myParentStream = vs;
|
||||
myFilter = filter;
|
||||
myFilterString = filter.ToString();
|
||||
if (filter is IDynamicStreamFilter)
|
||||
(filter as IDynamicStreamFilter).onFilterChange += new FilterChangeDelegate(myHandleFilterChange);
|
||||
}
|
||||
}
|
||||
|
||||
public FilterStream(ViewStream vs, string filterExpression)
|
||||
: this(vs, new BooleanExpressionFilter(filterExpression)) {
|
||||
myFilterString = filterExpression;
|
||||
}
|
||||
|
||||
public FilterStream(ViewStream vs, string col, string regExp)
|
||||
: this(vs, new RegExpFilter(col, regExp)) {
|
||||
myFilterString = col + " , " + regExp;
|
||||
}
|
||||
|
||||
public FilterStream(ViewStream vs)
|
||||
: this(vs, new BinaryFilter(true)) {
|
||||
myFilterString = true.ToString();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region overridden methods
|
||||
protected override void myOnFirstSubscriber() { myParentStream.onChange += new ChangeDelegate(myHandleCallbacks); }
|
||||
|
||||
protected override void myOnLastSubscriber() {
|
||||
myLastFiredRows = null;
|
||||
myParentStream.onChange -= new ChangeDelegate(myHandleCallbacks);
|
||||
}
|
||||
|
||||
protected override void myFireEvent(object o) {
|
||||
DataPacket dp = o as DataPacket;
|
||||
if (myLastValue == null || dp.addedRows.Count + dp.deletedRows.Count > 0)
|
||||
base.myFireEvent(o);
|
||||
}
|
||||
#endregion
|
||||
|
||||
private void myHandleCallbacks(object newValue) {
|
||||
DataPacket retVal;
|
||||
DataPacket arrivedPacket = newValue as DataPacket;
|
||||
myLastReceivedRows = arrivedPacket.newRows;
|
||||
if (myLastFiredRows != null) {
|
||||
retVal = new DataPacket(myLastFiredRows, new BlueTable(myLastFiredRows.schema),
|
||||
new BlueTable(myLastFiredRows.schema));
|
||||
foreach (IBlueRow r in arrivedPacket.deletedRows)
|
||||
if (myLastFiredRows.containsID((int)r["id"])) {
|
||||
myLastFiredRows.deleteRow(r);
|
||||
(retVal.deletedRows as BlueTable).addRow(r);
|
||||
}
|
||||
foreach (IBlueRow r in arrivedPacket.addedRows)
|
||||
if (myFilter.filter(r)) {
|
||||
myLastFiredRows.addRow(r);
|
||||
(retVal.addedRows as BlueTable).addRow(r);
|
||||
}
|
||||
} else {
|
||||
myLastFiredRows = new BlueTable(arrivedPacket.schema);
|
||||
retVal = new DataPacket(myLastFiredRows, myLastFiredRows, new BlueTable(myLastFiredRows.schema));
|
||||
foreach (IBlueRow r in arrivedPacket.newRows)
|
||||
if (myFilter.filter(r))
|
||||
myLastFiredRows.addRow(r);
|
||||
}
|
||||
myFireEvent(retVal);
|
||||
}
|
||||
|
||||
public IStreamFilter filter {
|
||||
set {
|
||||
{
|
||||
myFilter = value;
|
||||
if (numberOfSubscribers > 0) {
|
||||
if (myLastReceivedRows != null) {
|
||||
BlueTable nRows = new BlueTable(myLastReceivedRows.schema);
|
||||
foreach (IBlueRow r in myLastReceivedRows)
|
||||
if (myFilter.filter(r))
|
||||
nRows.addRow(r);
|
||||
IBlueRowCollection dRows = new BlueTable(nRows.schema),
|
||||
aRows = new BlueTable(nRows.schema);
|
||||
BlueDataUtility.computeDifferences(nRows, myLastFiredRows, out dRows, out aRows);
|
||||
myLastFiredRows = nRows;
|
||||
myFireEvent(new DataPacket(nRows, aRows, dRows));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
get {
|
||||
return myFilter;
|
||||
}
|
||||
}
|
||||
|
||||
//public string filterString { set { filter = new BooleanExpressionFilter(value); } }
|
||||
|
||||
public override string ToString() {
|
||||
return GetType().ToString() + " " + myFilterString;
|
||||
}
|
||||
|
||||
private void myHandleFilterChange() {
|
||||
filter = myFilter;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
|
||||
using bts;
|
||||
//using bts.SilverPine;
|
||||
using BCG;
|
||||
|
||||
namespace bts.BlueOrb {
|
||||
[DataStreamUserRespresentation("GroupByStream")]
|
||||
public class GroupByStream : ViewStream {
|
||||
|
||||
public interface INonIncGroupByEvaluator {
|
||||
// grouped_rows
|
||||
IBlueRowCollection evaluate(IDictionary<int, List<IBlueRow>> grouped_rows);
|
||||
string[] get_group_by_cols();
|
||||
}
|
||||
|
||||
private class DelNonIncGroupByEvaluator : INonIncGroupByEvaluator {
|
||||
Func<string[]> _groupby_cols_func;
|
||||
Func<List<IBlueRow>, List<IBlueRow>> _evaluate_func;
|
||||
public DelNonIncGroupByEvaluator(Func<string[]> groupby_cols_func,
|
||||
Func<List<IBlueRow>, List<IBlueRow>> evaluate_func) {
|
||||
_groupby_cols_func = groupby_cols_func;
|
||||
_evaluate_func = evaluate_func;
|
||||
}
|
||||
public IBlueRowCollection evaluate(IDictionary<int, List<IBlueRow>> grouped_rows) {
|
||||
BlueTable rv = null;
|
||||
foreach (var kv in grouped_rows) {
|
||||
List<IBlueRow> g_rows = kv.Value;
|
||||
List<IBlueRow> out_rows = _evaluate_func.Invoke(g_rows);
|
||||
if (out_rows != null && out_rows.Count > 0) {
|
||||
rv = rv ?? new BlueTable(out_rows.ElementAt(0).schema);
|
||||
}
|
||||
rv.addRows(out_rows);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
public string[] get_group_by_cols() {
|
||||
return _groupby_cols_func();
|
||||
}
|
||||
|
||||
}
|
||||
private ViewStream _main_stream;
|
||||
private INonIncGroupByEvaluator _groupby_func;
|
||||
private BlueRowHasher _row_hasher;
|
||||
private string[] _groupby_cols;
|
||||
private Stopwatch _timer = new();
|
||||
private int _n_total_fires = 0, _n_expensive_fires = 0;
|
||||
|
||||
public GroupByStream(ViewStream main_stream, INonIncGroupByEvaluator groupby_func) {
|
||||
_main_stream = main_stream;
|
||||
_groupby_func = groupby_func;
|
||||
_groupby_cols = _groupby_func.get_group_by_cols();
|
||||
_row_hasher = new BlueRowHasher(_groupby_cols == null ? 0 : _groupby_cols.Length);
|
||||
}
|
||||
|
||||
public GroupByStream(ViewStream main_stream, Func<string[]> groupby_cols_func,
|
||||
Func<List<IBlueRow>, List<IBlueRow>> evaluate_func)
|
||||
: this(main_stream, new DelNonIncGroupByEvaluator(groupby_cols_func, evaluate_func)) { }
|
||||
|
||||
public GroupByStream(ViewStream parent_str, string groupby_col, string standard_group_by_text)
|
||||
: this(parent_str, groupby_col, standard_group_by_text, String.Empty) { }
|
||||
|
||||
// this is the main constructor that does everything
|
||||
public GroupByStream(ViewStream parent_str, string groupby_col, string standard_group_by_ops, string optional_sort_col) {
|
||||
MultiRowOpHelper std_group_by = MultiRowOpHelper.parse_ops(standard_group_by_ops);
|
||||
Func<string[]> groupby_cols_func = () => {
|
||||
return String.IsNullOrEmpty(groupby_col) ? null : new string[] { groupby_col };
|
||||
};
|
||||
Func<List<IBlueRow>, List<IBlueRow>> evaluate_func = (group_rows) => {
|
||||
if (!String.IsNullOrEmpty(optional_sort_col))
|
||||
group_rows.Sort((r1, r2) => BlueDataUtility.compare_rows(r2, r1, optional_sort_col));
|
||||
// this si where the group by aggregation happens
|
||||
IBlueRow rv = MultiRowOpHelper.group_by_compute(group_rows, std_group_by);
|
||||
return new IBlueRow[] { rv }.ToList();
|
||||
};
|
||||
ViewStream rs = new GroupByStream(parent_str, groupby_cols_func, evaluate_func);
|
||||
mySetRealSource(rs);
|
||||
}
|
||||
public GroupByStream(ViewStream parent_str, string standard_group_by_text) :
|
||||
this(parent_str, String.Empty, standard_group_by_text) {
|
||||
}
|
||||
|
||||
|
||||
protected override void myOnFirstSubscriber() { _main_stream.onChange += _main_stream_onchange; }
|
||||
|
||||
protected override void myOnLastSubscriber() { _main_stream.onChange -= _main_stream_onchange; }
|
||||
|
||||
private void _main_stream_onchange(object nv) {
|
||||
DataPacket dp = nv as DataPacket;
|
||||
///grouping together
|
||||
_timer.Restart();
|
||||
IDictionary<int, List<IBlueRow>> grouped_rows = (_groupby_cols == null || _groupby_cols.Length == 0) ?
|
||||
new Dictionary<int, List<IBlueRow>>() { { 0, dp.newRows.to_list() } } :
|
||||
BlueDataUtility.group_together(dp.newRows, _groupby_cols, ref _row_hasher);
|
||||
_timer.Stop();
|
||||
var groupElapsedMs = _timer.ElapsedMilliseconds;
|
||||
if (groupElapsedMs >= 50) {
|
||||
Log.DataFlow.info($"Time to group {dp.newRows.Count} rows into {grouped_rows.Count} groups: {_timer.ElapsedMilliseconds}ms");
|
||||
}
|
||||
// Log.log(bts.Log.Level.Info, bts.log.Subsystem.gen, String.Format("Time to group {0} rows into {1} groups = {2}", dp.newRows.Count, grouped_rows.Count, _timer.lastIntervalTime));
|
||||
/// reducing
|
||||
_timer.Restart();
|
||||
IBlueRowCollection total_state = _groupby_func.evaluate(grouped_rows);
|
||||
_timer.Stop();
|
||||
var applyElapsedMs = _timer.ElapsedMilliseconds;
|
||||
_n_total_fires++;
|
||||
if (applyElapsedMs >= 50) {
|
||||
_n_expensive_fires++;
|
||||
Log.DataFlow.info(
|
||||
$"Time to evaluate {grouped_rows.Count} groups {_timer.ElapsedMilliseconds}ms, ratio {_n_expensive_fires}/{_n_total_fires}");
|
||||
}
|
||||
// TODO,
|
||||
if (total_state != null)
|
||||
myFireEvent(new DataPacket(total_state, total_state, myLastValue == null ? new BlueTable(total_state.schema) : (myLastValue as DataPacket).newRows));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BCG;
|
||||
|
||||
namespace bts.BlueOrb {
|
||||
public interface INonIncJoinInlineMapper
|
||||
{
|
||||
IBlueTableSchema get_output_schema(IBlueTableSchema schema);
|
||||
|
||||
IBlueRowCollection map_rows(IBlueRowCollection rows);
|
||||
|
||||
event MapperChangeDelegate onMapperChange;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
using bts;
|
||||
using bts.collections;
|
||||
|
||||
using mbts;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using BCG;
|
||||
|
||||
namespace bts.BlueOrb {
|
||||
internal class NonIncJoinBookkeeper : IDisposable {
|
||||
public Action on_new_data;
|
||||
private readonly NonIncJoinSpec _join_spec;
|
||||
private readonly int _join_position;
|
||||
private readonly int _start_index_in_output;
|
||||
private readonly BlueRowHasher _row_hasher;
|
||||
private readonly bool _is_left_most;
|
||||
private readonly bool _is_tag_along;
|
||||
private int _n_total_fires = 0, _n_expensive_fires = 0;
|
||||
|
||||
private IBlueTableSchema _schema = null;
|
||||
private IDictionary<int, IBlueRow> _row_map = new Dictionary<int, IBlueRow>();
|
||||
private List<Pair<int, IBlueRow>> _row_list = new List<Pair<int, IBlueRow>>();
|
||||
private IBlueRow _solitary_row = null;
|
||||
|
||||
private int[] _join_col_indices = null,
|
||||
_data_col_indices = null;
|
||||
|
||||
public NonIncJoinBookkeeper(NonIncJoinSpec the_join_spec, int join_position, int start_index_in_output, BlueRowHasher row_hasher) {
|
||||
_join_spec = the_join_spec;
|
||||
_join_position = join_position;
|
||||
_start_index_in_output = start_index_in_output;
|
||||
_row_hasher = row_hasher;
|
||||
_is_left_most = _join_position == 0;
|
||||
_is_tag_along = _join_spec.join_col_names.Length == 0;
|
||||
}
|
||||
|
||||
private bool _filled_missing_data = false;
|
||||
private void _fill_missing_data(DataPacket dp) {
|
||||
if (_filled_missing_data)
|
||||
return;
|
||||
Func<string[], bool> null_or_empty = (str_arr) => {
|
||||
if (str_arr == null || str_arr.Length == 0)
|
||||
return true;
|
||||
foreach (string s in str_arr) {
|
||||
if (!String.IsNullOrEmpty(s)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
Func<string[], string[]> remove_bad_cols = (col_names) => {
|
||||
HashSet<string> schema_col_map = new HashSet<string>(dp.schema.columnNames);
|
||||
List<string> cols_list = new List<string>(col_names);
|
||||
cols_list.RemoveWhere(c => !schema_col_map.Contains(c));
|
||||
return cols_list.ToArray();
|
||||
};
|
||||
if (null_or_empty(_join_spec.data_col_names)) {
|
||||
_join_spec.data_col_names = dp.schema.columnNames;
|
||||
}
|
||||
_join_spec.data_col_names = remove_bad_cols(_join_spec.data_col_names);
|
||||
if (null_or_empty(_join_spec.data_col_names)) {
|
||||
_join_spec.data_col_names = dp.schema.columnNames;
|
||||
}
|
||||
if (null_or_empty(_join_spec.projected_col_names)) {
|
||||
_join_spec.projected_col_names = _join_spec.data_col_names;
|
||||
}
|
||||
}
|
||||
|
||||
public void init() {
|
||||
_join_spec.stream.onChange += _on_stream_cb;
|
||||
}
|
||||
|
||||
public void _on_stream_cb(object dp_obj) {
|
||||
//Log.Gen.info($"NonIncJoinBookkeeper._on_stream_cb dp_obj: {dp_obj}");
|
||||
var timer = Stopwatch.StartNew();
|
||||
DataPacket dp = (DataPacket)dp_obj;
|
||||
_fill_missing_data(dp);
|
||||
if (_schema == null) {
|
||||
_schema = dp.schema;
|
||||
_join_col_indices = BlueDataUtility.get_column_indices(_schema, _join_spec.join_col_names);
|
||||
_data_col_indices = BlueDataUtility.get_column_indices(_schema, _join_spec.data_col_names);
|
||||
}
|
||||
|
||||
if (_is_left_most) {
|
||||
_row_list.Clear();
|
||||
foreach (IBlueRow r in dp.newRows)
|
||||
_row_list.Add(new Pair<int, IBlueRow>(_row_hasher.get_hash(r, _join_col_indices), r));
|
||||
} else {
|
||||
if (_is_tag_along)
|
||||
_solitary_row = BlueDataUtility.extractRow(dp.newRows, 0);
|
||||
else {
|
||||
_row_map.Clear();
|
||||
foreach (IBlueRow r in dp.newRows)
|
||||
_row_map[_row_hasher.get_hash(r, _join_col_indices)] = r;
|
||||
}
|
||||
}
|
||||
timer.Stop();
|
||||
_n_total_fires++;
|
||||
if (timer.ElapsedMilliseconds >= 50) {
|
||||
_n_expensive_fires++;
|
||||
Log.Gen.info($"NonIncJoinBookkeepingData._on_stream_cb {dp.newRows.Count} rows {_join_col_indices.Length} "
|
||||
+ $"key cols {_join_position} join pos time {timer.Elapsed} ratio {_n_expensive_fires}/{_n_total_fires}");
|
||||
}
|
||||
if (on_new_data != null)
|
||||
on_new_data();
|
||||
}
|
||||
|
||||
public bool is_ready { get { return _schema != null; } }
|
||||
|
||||
public void constribute_cols_to_schema(List<ColumnInfo> cis) {
|
||||
for (int i = 0; i < _join_spec.data_col_names.Length; i++)
|
||||
cis.Add(new ColumnInfo(_join_spec.projected_col_names[i], cis.Count, _schema[_join_spec.data_col_names[i]].colType));
|
||||
}
|
||||
|
||||
public List<Pair<int, IBlueRow>> get_list() { return _row_list; }
|
||||
|
||||
public void copy_master_row_to_output_row(int row_index, object[] output_row) {
|
||||
IBlueRow this_row = _row_list[row_index].second;
|
||||
BlueDataUtility.copy_to_row(this_row, _data_col_indices, output_row, _start_index_in_output);
|
||||
}
|
||||
|
||||
public void copy_matching_row_to_output_row(int row_hash, object[] output_row) {
|
||||
if (_is_tag_along) {
|
||||
if (_solitary_row != null)
|
||||
BlueDataUtility.copy_to_row(_solitary_row, _data_col_indices, output_row, _start_index_in_output);
|
||||
else
|
||||
copy_dbnull_to_output_row(output_row);
|
||||
} else {
|
||||
IBlueRow this_row = null;
|
||||
if (_row_map.TryGetValue(row_hash, out this_row))
|
||||
BlueDataUtility.copy_to_row(this_row, _data_col_indices, output_row, _start_index_in_output);
|
||||
else
|
||||
copy_dbnull_to_output_row(output_row);
|
||||
}
|
||||
}
|
||||
|
||||
private void copy_dbnull_to_output_row(object[] output_row) {
|
||||
for (int i = 0; i < _data_col_indices.Length; i++)
|
||||
output_row[i + _start_index_in_output] = DBNull.Value;
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
public void Dispose() { _join_spec.stream.onChange -= _on_stream_cb; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using BCG;
|
||||
|
||||
using mbts;
|
||||
|
||||
namespace bts.BlueOrb {
|
||||
// this a collection of spec, theres no joining logic in here
|
||||
public class NonIncJoinSpec {
|
||||
public ViewStream stream;
|
||||
// join col names are the names on which the streams are joined (for equality),
|
||||
// data col names are the names in the original stream
|
||||
// projected col names are their new names, ie if its x:l_x, then x is in the
|
||||
// data_col_names and l_x in projected cols
|
||||
public string[] join_col_names, data_col_names, projected_col_names;
|
||||
|
||||
public NonIncJoinSpec() { }
|
||||
|
||||
public NonIncJoinSpec(ViewStream the_stream, string[] the_join_col_names, string[] the_data_col_names)
|
||||
: this(the_stream, the_join_col_names, the_data_col_names, the_data_col_names) { }
|
||||
|
||||
public NonIncJoinSpec(ViewStream the_stream, string[] the_join_col_names, string[] the_data_col_names, string[] the_projected_col_names) {
|
||||
stream = the_stream;
|
||||
join_col_names = the_join_col_names;
|
||||
data_col_names = the_data_col_names;
|
||||
projected_col_names = the_projected_col_names;
|
||||
}
|
||||
|
||||
public static NonIncJoinSpec[] from_triplet(params Triplet<ViewStream, string, string>[] join_specs) {
|
||||
List<NonIncJoinSpec> rv = new List<NonIncJoinSpec>();
|
||||
foreach (Triplet<ViewStream, string, string> r in join_specs) {
|
||||
string[] keys = String.IsNullOrEmpty(r.second) ? new string[] { } : r.second.Split(' ');
|
||||
string[] visible_cols = r.third.Split(' ');
|
||||
List<string> data_col_names = new List<string>(), mapped_col_names = new List<string>();
|
||||
foreach (string s in visible_cols) {
|
||||
string[] toks = s.Split(':');
|
||||
if (toks.Length == 1) {
|
||||
data_col_names.Add(toks[0]);
|
||||
mapped_col_names.Add(toks[0]);
|
||||
} else if (toks.Length == 2) {
|
||||
data_col_names.Add(toks[0]);
|
||||
mapped_col_names.Add(toks[1]);
|
||||
}
|
||||
}
|
||||
rv.Add(new NonIncJoinSpec(r.first, keys, data_col_names.ToArray(), mapped_col_names.ToArray()));
|
||||
}
|
||||
return rv.ToArray();
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
Func<string[], string> arr_txt = (a) => {
|
||||
string rv = "";
|
||||
foreach (string s in a) {
|
||||
rv += "{s} ";
|
||||
}
|
||||
rv = rv.Trim();
|
||||
return rv;
|
||||
};
|
||||
string rv = $"stream: {stream} join_col_names: {arr_txt(join_col_names)} data_col_names: {arr_txt(data_col_names)} projected_col_names: {arr_txt(projected_col_names)}";
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
using bts;
|
||||
|
||||
using mbts;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using BCG;
|
||||
|
||||
namespace bts.BlueOrb {
|
||||
[DataStreamUserRespresentation("JoinStream")]
|
||||
public class NonIncJoinStream : ViewStream, BCG.Utilities.General.IWorker {
|
||||
private readonly NonIncJoinSpec[] _join_specs;
|
||||
private readonly INonIncJoinInlineMapper _output_mapper;
|
||||
private readonly BlueRowHasher _row_hasher;
|
||||
private readonly TimeSpan _throttle_period;
|
||||
private readonly ViewStreamDatabase _vsd;
|
||||
|
||||
private List<NonIncJoinBookkeeper> _book_keeping_data = new List<NonIncJoinBookkeeper>();
|
||||
private IBlueTableSchema _output_schema = null;
|
||||
private DateTime _last_fire_time = DateTime.MinValue;
|
||||
private bool _is_dirty = false;
|
||||
|
||||
public NonIncJoinStream(ViewStream left, string left_join_cols, string left_proj,
|
||||
ViewStream right, string right_join_cols, string right_proj)
|
||||
: this(new Triplet<ViewStream, string, string>[]
|
||||
{
|
||||
new Triplet<ViewStream, string, string> (left, left_join_cols, left_proj),
|
||||
new Triplet<ViewStream, string, string> (right, right_join_cols, right_proj)
|
||||
}) { }
|
||||
|
||||
public NonIncJoinStream(params Triplet<ViewStream, string, string>[] join_specs)
|
||||
: this(NonIncJoinSpec.from_triplet(join_specs), null, 0) { }
|
||||
|
||||
public NonIncJoinStream(ViewStreamDatabase vsd, INonIncJoinInlineMapper output_mapper, int throttles_ms,
|
||||
params Triplet<ViewStream, string, string>[] join_specs)
|
||||
: this(vsd, NonIncJoinSpec.from_triplet(join_specs), output_mapper, throttles_ms) {
|
||||
}
|
||||
|
||||
public NonIncJoinStream(NonIncJoinSpec[] join_specs, INonIncJoinInlineMapper output_mapper, int throttles_ms = 0)
|
||||
: this(ViewStreamDatabase.instance, join_specs, output_mapper, throttles_ms) { }
|
||||
|
||||
public NonIncJoinStream(ViewStreamDatabase vsd, NonIncJoinSpec[] join_specs, INonIncJoinInlineMapper output_mapper, int throttles_ms = 0) {
|
||||
_join_specs = join_specs;
|
||||
_output_mapper = output_mapper;
|
||||
_row_hasher = new BlueRowHasher(_join_specs[0].join_col_names.Length);
|
||||
_throttle_period = throttles_ms == 0 ? TimeSpan.Zero : new TimeSpan(0, 0, 0, 0, throttles_ms);
|
||||
_vsd = vsd;
|
||||
}
|
||||
|
||||
protected override void myOnFirstSubscriber() {
|
||||
int data_col_no = 1;
|
||||
for (int jp = 0; jp < _join_specs.Length; jp++) {
|
||||
NonIncJoinBookkeeper s = new NonIncJoinBookkeeper(_join_specs[jp], jp, data_col_no, _row_hasher);
|
||||
//Log.Gen.info($"NonIncJoinStream.myOnFirstSubscriber jp: {jp} spec: {_join_specs[jp]}");
|
||||
data_col_no += _join_specs[jp].data_col_names.Length;
|
||||
s.on_new_data += _stream_callback;
|
||||
_book_keeping_data.Add(s);
|
||||
}
|
||||
if (_output_mapper != null)
|
||||
_output_mapper.onMapperChange += _on_mapper_change;
|
||||
_vsd.addWorker(this);
|
||||
foreach (var bkd in _book_keeping_data)
|
||||
bkd.init();
|
||||
}
|
||||
|
||||
protected override void myOnLastSubscriber() {
|
||||
foreach (NonIncJoinBookkeeper d in _book_keeping_data)
|
||||
d.Dispose();
|
||||
if (_output_mapper != null)
|
||||
_output_mapper.onMapperChange -= _on_mapper_change;
|
||||
_vsd.removeWorker(this);
|
||||
}
|
||||
|
||||
private void _stream_callback() {
|
||||
//Log.Gen.info("NonIncJoinStream._stream_callback received callback");
|
||||
_is_dirty = true;
|
||||
doWork();
|
||||
}
|
||||
|
||||
private void _on_mapper_change() {
|
||||
_is_dirty = true;
|
||||
doWork();
|
||||
}
|
||||
|
||||
private bool _is_ready() {
|
||||
bool rv = true;
|
||||
foreach (NonIncJoinBookkeeper d in _book_keeping_data)
|
||||
rv &= d.is_ready;
|
||||
return rv;
|
||||
}
|
||||
|
||||
private void _construct_schema() {
|
||||
if (_output_schema == null) {
|
||||
List<ColumnInfo> cis = new List<ColumnInfo>(new ColumnInfo[] { new ColumnInfo("id", 0, typeof(int)) });
|
||||
foreach (NonIncJoinBookkeeper d in _book_keeping_data)
|
||||
d.constribute_cols_to_schema(cis);
|
||||
_output_schema = new BlueTableSchema("NonIncJoinStream", cis.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
private int _n_total_fires = 0, _n_expensive_fires = 0;
|
||||
private void _form_and_fire() {
|
||||
//Log.Gen.info($"NonIncJoinStream._form_and_fire _is_ready(): {_is_ready()}");
|
||||
if (_is_ready()) {
|
||||
_construct_schema();
|
||||
var timer = Stopwatch.StartNew();
|
||||
IList<Pair<int, IBlueRow>> left_row_list = _book_keeping_data[0].get_list();
|
||||
List<object[]> output_rows = new List<object[]>();
|
||||
|
||||
for (int i = 0; i < left_row_list.Count; i++) {
|
||||
object[] or = new object[_output_schema.columnInfo.Length];
|
||||
or[0] = i;
|
||||
output_rows.Add(or);
|
||||
}
|
||||
|
||||
for (int ji = 0; ji < _book_keeping_data.Count; ji++) {
|
||||
NonIncJoinBookkeeper bk_data = _book_keeping_data[ji];
|
||||
for (int i = 0; i < left_row_list.Count; i++) {
|
||||
if (ji == 0)
|
||||
bk_data.copy_master_row_to_output_row(i, output_rows[i]);
|
||||
else
|
||||
bk_data.copy_matching_row_to_output_row(left_row_list[i].first, output_rows[i]);
|
||||
}
|
||||
}
|
||||
IBlueRowCollection dp_output_rows = _apply_map(new SequentialIDBlueTable(_output_schema, output_rows));
|
||||
timer.Stop();
|
||||
_n_total_fires++;
|
||||
//if (timer.ElapsedMilliseconds >= 250) {
|
||||
_n_expensive_fires++;
|
||||
//string log_msg = $"Join and map time for {_book_keeping_data.Count} "
|
||||
// + $"tables {timer.Elapsed.TotalMilliseconds} msecs, ratio {_n_expensive_fires}/{_n_total_fires}";
|
||||
//Log.Gen.info(log_msg);
|
||||
//}
|
||||
var dp_out = BlueDataUtility.build_dp_inc(dp_output_rows, myLastValue as DataPacket, true);
|
||||
myFireEvent(dp_out);
|
||||
//myFireEvent(new DataPacket(dp_output_rows, dp_output_rows, myLastValue == null ? new BlueTable(_output_schema) : (myLastValue as DataPacket).newRows));
|
||||
}
|
||||
}
|
||||
|
||||
private IBlueRowCollection _apply_map(IBlueRowCollection unmapped_rows) {
|
||||
if (_output_mapper != null) {
|
||||
IBlueRowCollection output_rows;
|
||||
var timer = Stopwatch.StartNew();
|
||||
output_rows = _output_mapper.map_rows(unmapped_rows);
|
||||
timer.Stop();
|
||||
if (timer.ElapsedMilliseconds >= 50) {
|
||||
Log.Gen.info($"Time to map {unmapped_rows.Count} rows into {output_rows.Count} rows {timer.Elapsed}");
|
||||
}
|
||||
return output_rows;
|
||||
} else {
|
||||
return unmapped_rows;
|
||||
}
|
||||
}
|
||||
|
||||
public void doWork() {
|
||||
//Log.Gen.info($"NonIncJoinStream.doWork _last_fire_time: {_last_fire_time} _is_dirty: {_is_dirty}");
|
||||
if (DateTime.Now - _last_fire_time >= _throttle_period && _is_dirty) {
|
||||
_is_dirty = false;
|
||||
_last_fire_time = DateTime.Now;
|
||||
_form_and_fire();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//namespace bts.Blueorb {
|
||||
// using BCG;
|
||||
|
||||
// public class JsonSaverStream : ViewStream {
|
||||
// private ViewStream _stream;
|
||||
// private string _file_name;
|
||||
// public JsonSaverStream(ViewStream vs, string file_name) {
|
||||
// _stream = vs;
|
||||
// _file_name = file_name;
|
||||
// }
|
||||
|
||||
// protected override void myOnFirstSubscriber() {
|
||||
// _stream.onChange += _on_data;
|
||||
// }
|
||||
|
||||
// private void _on_data(object dp_obj) {
|
||||
// if (dp_obj == null) {
|
||||
// return;
|
||||
// }
|
||||
// DataPacket dp = dp_obj as DataPacket;
|
||||
// dp.newRows.write_to_json_file(dp.newRows, _file_name);
|
||||
// }
|
||||
|
||||
// protected override void myOnLastSubscriber() {
|
||||
// _stream.onChange -= _on_data;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Amazon.Runtime.Internal.Util;
|
||||
|
||||
using BCG;
|
||||
using BCG.Utilities;
|
||||
|
||||
using bts;
|
||||
|
||||
namespace bts.BlueOrb{
|
||||
public class LiteralStream : ViewStream {
|
||||
|
||||
public static List<Tuple<ColumnInfo, object>> parse_columns(string key_value_pairs) {
|
||||
List<Tuple<ColumnInfo, object>> rv = new List<Tuple<ColumnInfo, object>>();
|
||||
string[] pairs = key_value_pairs.Split(' ');
|
||||
foreach (string p in pairs) {
|
||||
string[] kvp = p.Split(':');
|
||||
string col_name = kvp[0];
|
||||
string val = kvp[1];
|
||||
var tup = BlueDataUtility.guess_type(val);
|
||||
var ci = new ColumnInfo(col_name, rv.Count, tup.Item1);
|
||||
rv.Add(Tuple.Create(ci, tup.Item2));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
//literal("table1", "x:123, y:1.001, z:abc;x:123, y:1.001, z:abc")
|
||||
// col_name: col_value ... ; col_name: col_value
|
||||
public LiteralStream(string table_name, string rows) {
|
||||
Func<string, int, IBlueRow> get_row = (json_row, id) => {
|
||||
Dictionary<string, object> map = new Dictionary<string, object>();
|
||||
string[] toks = json_row.Split(',', StringSplitOptions.TrimEntries);
|
||||
//Logger.assert(col_vals.Length == 2);
|
||||
foreach (string t in toks) {
|
||||
var col_vals = t.Split(':', StringSplitOptions.TrimEntries);
|
||||
string col = col_vals[0];
|
||||
string val = col_vals[1];
|
||||
map.Add(col, val);
|
||||
}
|
||||
map["id"] = id;
|
||||
return new BlueRow(map);
|
||||
};
|
||||
string[] row_arr = rows.Split(';', StringSplitOptions.TrimEntries);
|
||||
BlueTable table = null;
|
||||
foreach (string json_string in row_arr) {
|
||||
IBlueRow r = //BlueDataUtility.convert_json_to_row(json_string, table == null ? 0 : table.Count);
|
||||
get_row(json_string, table == null ? 0 : table.Count);
|
||||
table = table ?? new BlueTable(r.schema);
|
||||
table.addRow(r);
|
||||
}
|
||||
ViewStream str = new BlueTableStream(table);
|
||||
str = new SchemaGuesserStream(str);
|
||||
mySetRealSource(str);
|
||||
}
|
||||
|
||||
public LiteralStream(string tableName, string schema, string value) {
|
||||
string[] columns = schema.Split(' ');
|
||||
|
||||
|
||||
ColumnInfo[] ci = new ColumnInfo[columns.Length + 1];
|
||||
ci[0] = new ColumnInfo("id", 0, typeof(int));
|
||||
int n = 1;
|
||||
foreach (string c in columns) {
|
||||
string[] columnAndType = c.Split('/');
|
||||
if (columnAndType.Length == 1)
|
||||
ci[n] = new ColumnInfo(c, n, typeof(double));
|
||||
else
|
||||
ci[n] = new ColumnInfo(columnAndType[0], n, Type.GetType("System." + columnAndType[1], true));
|
||||
n++;
|
||||
}
|
||||
|
||||
|
||||
BlueTable t = new BlueTable(new BlueTableSchema(tableName, ci));
|
||||
string[] rows = value.Split(';');
|
||||
n = 0;
|
||||
foreach (string r in rows)
|
||||
try {
|
||||
if (r == string.Empty)
|
||||
continue;
|
||||
t.addRow(
|
||||
BlueDataUtility.createRowFromCSVFileRow(r, t.schema, ++n));
|
||||
} catch (Exception e) {
|
||||
throw (new Exception("Error occured at line: " + n.ToString() + " " + e.Message));
|
||||
}
|
||||
|
||||
mySetRealSource(new BlueRowCollectionStaticStream(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
|
||||
using BCG.Utilities.General;
|
||||
using BCG;
|
||||
using bts;
|
||||
|
||||
namespace bts.BlueOrb {
|
||||
[DataStreamUserRespresentation("MillisecondStream")]
|
||||
public class MillisecondStream : ViewStream, IWorker {
|
||||
private DateTime _last_fire_time = DateTime.MinValue;
|
||||
private readonly TimeSpan _period;
|
||||
private readonly int _size;
|
||||
private readonly ViewStreamDatabase _vsd;
|
||||
private IBlueTableSchema _schema;
|
||||
private bts.CircularBufferTable _table;
|
||||
|
||||
public MillisecondStream(int interval_msecs, int size)
|
||||
: this(ViewStreamDatabase.instance, interval_msecs, size) {
|
||||
}
|
||||
|
||||
public MillisecondStream(ViewStreamDatabase vsd, int interval_msecs, int size) {
|
||||
_vsd = vsd;
|
||||
_period = TimeSpan.FromMilliseconds(interval_msecs);
|
||||
_size = size;
|
||||
_schema = BlueDataUtility.form_schema("Milliseconds",
|
||||
Tuple.Create("id", TypeCode.Int32),
|
||||
Tuple.Create("time", TypeCode.DateTime),
|
||||
Tuple.Create("display_time", TypeCode.String));
|
||||
_table = new CircularBufferTable(_schema, _size);
|
||||
}
|
||||
|
||||
protected override void myOnFirstSubscriber() {
|
||||
_vsd.addWorker(this);
|
||||
}
|
||||
|
||||
protected override void myOnLastSubscriber() {
|
||||
_vsd.removeWorker(this);
|
||||
}
|
||||
|
||||
private int _id = 0;
|
||||
public void doWork() {
|
||||
if (DateTime.Now - _last_fire_time >= _period) {
|
||||
_last_fire_time = DateTime.Now;
|
||||
IBlueRow nr = new BlueRow(_schema, _id++, _last_fire_time,
|
||||
_last_fire_time.ToString("yyyy-MM-dd HH:mm:ss.fff"));
|
||||
_table.add_row(nr);
|
||||
var dp = BlueDataUtility.build_dp_inc(_table, myLastValue as DataPacket);
|
||||
myFireEvent(dp);
|
||||
}
|
||||
}
|
||||
}
|
||||
public class MSecondStream : TimeUnitStream {
|
||||
public MSecondStream(int intervalInms, int size)
|
||||
: base(DateTime.Now, new TimeSpan(0, 0, 0, 0, intervalInms), size, ViewStreamDatabase.instance) { }
|
||||
|
||||
protected override DateTime myRoundTime(DateTime dt) { return dt; }
|
||||
}
|
||||
|
||||
public class DisplayableMillisecondStream : ViewStream, IStreamMapper {
|
||||
private IBlueTableSchema mySchema = new BlueTableSchema("ms",
|
||||
new ColumnInfo("id", 0, typeof(int)),
|
||||
new ColumnInfo("time", 1, typeof(string)));
|
||||
public DisplayableMillisecondStream(int intervalInMs, int sz) {
|
||||
mySetRealSource(new MapStream(new MSecondStream(intervalInMs, sz), this));
|
||||
}
|
||||
#region IStreamMapper Members
|
||||
|
||||
public IBlueTableSchema getOutputSchema(IBlueTableSchema inputSchema) {
|
||||
return mySchema;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDynamicStreamMapper Members
|
||||
|
||||
public IBlueRow map(IBlueRow row) {
|
||||
DateTime time = row.get_datetime("time");
|
||||
var time_txt = time.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
||||
return new BlueRow(mySchema, row["id"], time_txt);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private string myToString(DateTime dt) {
|
||||
string sep = ":";
|
||||
return dt.Hour + sep + dt.Minute + sep + dt.Second + sep + dt.Millisecond;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
namespace bts.BlueOrb {
|
||||
using BCG;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
//using MathNet.Numerics.Statistics;
|
||||
|
||||
|
||||
public enum MultiRowOp {
|
||||
Mean,
|
||||
Median,
|
||||
Mode,
|
||||
Range,
|
||||
StdDev,
|
||||
Max,
|
||||
Min,
|
||||
First,
|
||||
Last,
|
||||
Sum,
|
||||
EdgeDiff,
|
||||
EdgeDiffPercentage,
|
||||
//IsEdgeDiff,
|
||||
}
|
||||
public class MultiRowOpHelper {
|
||||
public readonly List<Tuple<string, MultiRowOp>> operations;
|
||||
|
||||
public static MultiRowOpHelper parse_ops(string text) {
|
||||
// text is inp_col: op, inp_col: op, inp_col:op
|
||||
List<Tuple<string, MultiRowOp>> rv = new List<Tuple<string, MultiRowOp>>();
|
||||
string[] calcs = text.Split(',');
|
||||
foreach (string c in calcs) {
|
||||
string[] toks = c.Split(':');
|
||||
string inp_col = toks[0].Trim();
|
||||
string op_txt = toks[1].Trim();
|
||||
MultiRowOp op = Enum.Parse<MultiRowOp>(op_txt, true);
|
||||
rv.Add(Tuple.Create(inp_col, op));
|
||||
}
|
||||
return new MultiRowOpHelper(rv);
|
||||
}
|
||||
|
||||
public MultiRowOpHelper(List<Tuple<string, MultiRowOp>> operations_) {
|
||||
operations = operations_;
|
||||
}
|
||||
|
||||
// aggregates multiple rows into one - ie does group_by compute
|
||||
public static IBlueRow group_by_compute(List<IBlueRow> rows, MultiRowOpHelper parameters) {
|
||||
if (rows.Count() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
IBlueRow ir = rows.ElementAt(0);
|
||||
IBlueTableSchema in_schema = ir.schema;
|
||||
IBlueTableSchema out_schema = in_schema;
|
||||
IDictionary<string, Tuple<string, MultiRowOp>> out_col_map =
|
||||
new Dictionary<string, Tuple<string, MultiRowOp>>();
|
||||
foreach (var t in parameters.operations) {
|
||||
string out_col_name = $"{t.Item1}_{t.Item2}".ToLower();
|
||||
out_schema = out_schema.add_column(TypeCode.Double, out_col_name);
|
||||
out_col_map[out_col_name] = t;
|
||||
}
|
||||
MutableBlueRow or = new MutableBlueRow(out_schema, ir);
|
||||
foreach (var kv in out_col_map) {
|
||||
string out_col_name = kv.Key;
|
||||
string in_col_name = kv.Value.Item1;
|
||||
MultiRowOp op = kv.Value.Item2;
|
||||
IEnumerable<double> col_vals = BlueDataUtility.get_colvalues<double>(rows, in_col_name);
|
||||
double comp_res = compute_op(col_vals, op);
|
||||
or.setValue(out_col_name, comp_res);
|
||||
}
|
||||
return or;
|
||||
}
|
||||
|
||||
// rolling_rows are the rolling window of rows. the 0th row is the one thats gettin
|
||||
// calculated. rows 1 - n-1 are its window rows. the schema is based on the operations.
|
||||
|
||||
public IBlueRow rolling_calc(List<IBlueRow> rolling_rows) {
|
||||
if (rolling_rows.Count() == 0) {
|
||||
return null;
|
||||
}
|
||||
IBlueRow ir = rolling_rows.ElementAt(0);
|
||||
IBlueTableSchema in_schema = ir.schema;
|
||||
IBlueTableSchema out_schema = in_schema;
|
||||
IDictionary<string, Tuple<string, MultiRowOp>> out_col_map =
|
||||
new Dictionary<string, Tuple<string, MultiRowOp>>();
|
||||
foreach (var t in operations) {
|
||||
string out_col_name = $"{t.Item1}_{t.Item2}".ToLower();
|
||||
out_schema = out_schema.add_column(TypeCode.Double, out_col_name);
|
||||
out_col_map[out_col_name] = t;
|
||||
}
|
||||
MutableBlueRow or = new MutableBlueRow(out_schema, ir);
|
||||
foreach (var kv in out_col_map) {
|
||||
string out_col_name = kv.Key;
|
||||
string in_col_name = kv.Value.Item1;
|
||||
MultiRowOp op = kv.Value.Item2;
|
||||
IEnumerable<double> col_vals = BlueDataUtility.get_colvalues<double>(rolling_rows, in_col_name);
|
||||
double comp_res = compute_op(col_vals, op);
|
||||
or.setValue(out_col_name, comp_res);
|
||||
}
|
||||
return or;
|
||||
}
|
||||
|
||||
public static double compute_op(IEnumerable<double> col_vals, MultiRowOp op) {
|
||||
switch (op) {
|
||||
case MultiRowOp.Max:
|
||||
return col_vals.Max<double>();
|
||||
case MultiRowOp.Min:
|
||||
return col_vals.Min<double>();
|
||||
case MultiRowOp.Mean:
|
||||
return col_vals.Sum() / col_vals.Count();
|
||||
case MultiRowOp.Sum:
|
||||
return col_vals.Sum();
|
||||
case MultiRowOp.First:
|
||||
return col_vals.First();
|
||||
case MultiRowOp.Last:
|
||||
return col_vals.Last();
|
||||
case MultiRowOp.StdDev:
|
||||
return MathNet.Numerics.Statistics.Statistics.StandardDeviation(col_vals);
|
||||
default:
|
||||
// TODO
|
||||
Log.assert(false);
|
||||
return Double.NaN;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
namespace bts.BlueOrb {
|
||||
using BCG;
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
#region mappings
|
||||
internal class ProjectData {
|
||||
// schema from the base stream
|
||||
public readonly IBlueTableSchema in_schema;
|
||||
// this is the projected schema
|
||||
public readonly IBlueTableSchema out_schema;
|
||||
// projected cols to the input cols, for cols who's names are changed
|
||||
public readonly IDictionary<string, string> mapped_to_orig_col_map = new Dictionary<string, string>();
|
||||
public ProjectData(IBlueTableSchema in_schema_, IBlueTableSchema out_schema_,
|
||||
IDictionary<string, string> mapped_to_orig_col_map_) {
|
||||
in_schema = in_schema_;
|
||||
out_schema = out_schema_;
|
||||
mapped_to_orig_col_map = mapped_to_orig_col_map_;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Row with mapipings build in
|
||||
internal class NewProjectRow : IBlueRow {
|
||||
#region IBlueRow members
|
||||
private ProjectData _project_data;
|
||||
private IBlueRow _row;
|
||||
public NewProjectRow(ProjectData project_data, IBlueRow row) {
|
||||
_project_data = project_data;
|
||||
_row = row;
|
||||
}
|
||||
public IBlueTableSchema schema { get { return _project_data.out_schema; } }
|
||||
#endregion
|
||||
|
||||
#region ICollection members
|
||||
public int Count { get { return schema.columnNames.Length; } }
|
||||
|
||||
public bool IsSynchronized { get { return false; } }
|
||||
|
||||
public object SyncRoot { get { return this; } }
|
||||
|
||||
public void CopyTo(Array array, int index) {
|
||||
int i = index;
|
||||
foreach (object o in this as IEnumerable)
|
||||
array.SetValue(o, i++);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDictionary members
|
||||
public bool IsFixedSize { get { return true; } }
|
||||
|
||||
public bool IsReadOnly { get { return true; } }
|
||||
|
||||
public object this[object key] {
|
||||
get {
|
||||
if (key is string)
|
||||
return this[(string)key];
|
||||
return this[(int)key];
|
||||
}
|
||||
set { throw new NotSupportedException("The IBlueRow is readonly"); }
|
||||
}
|
||||
|
||||
public object this[int index] {
|
||||
get {
|
||||
return this[schema.columnInfo[index].colName];
|
||||
}
|
||||
}
|
||||
|
||||
public object this[string col_name] {
|
||||
get {
|
||||
string mapped_to_col = null;
|
||||
if (_project_data.mapped_to_orig_col_map.TryGetValue(col_name, out mapped_to_col)) {
|
||||
return _row[mapped_to_col];
|
||||
}
|
||||
return _row[col_name];
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection Keys { get { return schema.columnNames; } }
|
||||
|
||||
public ICollection Values {
|
||||
get {
|
||||
object[] retVal = new object[schema.columnNames.Length];
|
||||
CopyTo(retVal, 0);
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(object key, object value) { throw new NotSupportedException("The IBlueRow is readonly"); }
|
||||
|
||||
public void Clear() { throw new NotSupportedException("The IBlueRow is readonly"); }
|
||||
|
||||
public bool Contains(object key) { return schema[(string)key] != null; }
|
||||
|
||||
/// <summary>
|
||||
/// Note, the default enumerator is the IDictionary's enumerator, so if user needs to
|
||||
/// iterate through the values, typecast this into an IEnumerable
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IDictionaryEnumerator GetEnumerator() { return new BlueRowDictionaryEnumerator(this); }
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() { return new BlueRowEnumerator(this); }
|
||||
|
||||
public void Remove(object key) { throw new NotSupportedException("The IBlueRow is readonly"); }
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
|
||||
[DataStreamUserRespresentation("ProjectStream")]
|
||||
public class NewProjectStream : ViewStream, IStreamMapper {
|
||||
|
||||
private ViewStream _stream;
|
||||
private string _project_expression;
|
||||
private ProjectData _project_data = null;
|
||||
//public ProjectData
|
||||
public NewProjectStream(ViewStream vs, string project_expression) {
|
||||
_stream = vs;
|
||||
_project_expression = project_expression;
|
||||
_stream = new MapStream(vs, this);
|
||||
mySetRealSource(_stream);
|
||||
}
|
||||
|
||||
public IBlueTableSchema getOutputSchema(IBlueTableSchema input_schema) {
|
||||
_project_data = _create_project_data(input_schema, _project_expression);
|
||||
return _project_data.out_schema;
|
||||
}
|
||||
|
||||
public IBlueRow map(IBlueRow row) {
|
||||
return new NewProjectRow(_project_data, row);
|
||||
}
|
||||
|
||||
private static ProjectData _create_project_data(IBlueTableSchema input_schema,
|
||||
string project_expression) {
|
||||
project_expression = project_expression.Trim();
|
||||
string[] project_toks = project_expression.Split(' ');
|
||||
HashSet<string> removed_cols = new HashSet<string>();
|
||||
Dictionary<string, string> mapped_to_orig_map = new Dictionary<string, string>();
|
||||
Dictionary<string, string> orig_to_mapped = new Dictionary<string, string>();
|
||||
// by default every col will show up, so if we have the projection as (a:a1)
|
||||
// it will convert a to a1,
|
||||
// examples: if a schema has cols a b c
|
||||
// project(parent_str, "a:aa ") -> aa b c
|
||||
// project(parent_str, "a:aa !c") -> aa b
|
||||
// project(parent_str, "a:aa !") -> aa
|
||||
// project(parent_str, "a:aa b")) -> aa b
|
||||
bool hide_all = false;
|
||||
foreach (string p_tok in project_toks) {
|
||||
if (p_tok.StartsWith("!")) {
|
||||
if (p_tok.Length == 1) {
|
||||
// if it is a "blanket "!"=> hide all cols except the one that
|
||||
// are stated explicitly
|
||||
hide_all = true;
|
||||
} else {
|
||||
string removed_col = p_tok.Substring(1);
|
||||
removed_cols.Add(removed_col);
|
||||
}
|
||||
} else if (p_tok.Contains(":")) {
|
||||
string[] map_toks = p_tok.Split(":");
|
||||
string orig_col = map_toks[0];
|
||||
string mapped_col = map_toks[1];
|
||||
mapped_to_orig_map.Add(mapped_col, orig_col);
|
||||
orig_to_mapped.Add(orig_col, mapped_col);
|
||||
} else {
|
||||
if (input_schema.columnNames.Contains(p_tok)) {
|
||||
string orig_col = p_tok;
|
||||
string mapped_col = orig_col;
|
||||
orig_to_mapped.Add(orig_col, mapped_col);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// make the new schema
|
||||
List<ColumnInfo> new_cols = new List<ColumnInfo>();
|
||||
foreach (ColumnInfo ci in input_schema.columnInfo) {
|
||||
if (ci.colName == "id") {
|
||||
ColumnInfo nc = new ColumnInfo(ci.colName, new_cols.Count, ci.colType);
|
||||
new_cols.Add(nc);
|
||||
continue;
|
||||
}
|
||||
if (removed_cols.Contains(ci.colName)) {
|
||||
continue;
|
||||
} else if (orig_to_mapped.ContainsKey(ci.colName)) {
|
||||
string mapped_name = orig_to_mapped[ci.colName];
|
||||
ColumnInfo nc = new ColumnInfo(mapped_name, new_cols.Count, ci.colType);
|
||||
new_cols.Add(nc);
|
||||
} else {
|
||||
if (hide_all)
|
||||
continue;
|
||||
//if (ci.colName == "id" || removed_cols.Count > 0) {
|
||||
// unless columns are explicitely hidden, all columns show up
|
||||
ColumnInfo nc = new ColumnInfo(ci.colName, new_cols.Count, ci.colType);
|
||||
new_cols.Add(nc);
|
||||
//}
|
||||
}
|
||||
}
|
||||
IBlueTableSchema new_schema = new BlueTableSchema(input_schema.tableName, new_cols.ToArray());
|
||||
return new ProjectData(input_schema, new_schema, mapped_to_orig_map);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using BCG;
|
||||
|
||||
namespace bts.BlueOrb {
|
||||
public class SchemaGuesserStream : ViewStream {
|
||||
|
||||
private IBlueRowCollection _rows = null;
|
||||
private ViewStream _vs;
|
||||
public SchemaGuesserStream(ViewStream vs) {
|
||||
_vs = vs;
|
||||
}
|
||||
|
||||
protected override void myOnFirstSubscriber() {
|
||||
_vs.onChange += _on_data;
|
||||
}
|
||||
protected override void myOnLastSubscriber() {
|
||||
_vs.onChange -= _on_data;
|
||||
}
|
||||
|
||||
private void _on_data(object obj) {
|
||||
DataPacket dp = obj as DataPacket;
|
||||
if (_rows == null) {
|
||||
_rows = BlueDataUtility.get_typed_table(dp.newRows);
|
||||
}
|
||||
var dp_out = BlueDataUtility.build_dp_inc(_rows, myLastValue as DataPacket);
|
||||
myFireEvent(dp_out);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using Bts.ArithmeticExpressions;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using BCG;
|
||||
|
||||
namespace bts.BlueOrb {
|
||||
|
||||
[DataStreamUserRespresentation("SingleRowMathStream")]
|
||||
public class MathStream : ViewStream, IStreamMapper {
|
||||
private IArithmeticProgram myExp;
|
||||
private BooleanExpressionFilter _bool;
|
||||
List<string> myExtraColumns = new List<string>();
|
||||
BlueTableSchema mySchema;
|
||||
|
||||
|
||||
public MathStream(ViewStream vs, string mathExpression)
|
||||
: this(vs, mathExpression, string.Empty) {
|
||||
}
|
||||
|
||||
public MathStream(ViewStream vs, string mathExpression, string boolean_expression) {
|
||||
myExp = ArithmeticProgramParser.Parse(mathExpression) ??
|
||||
throw new Exception($"Failed to parse ArithmeticExpression '{mathExpression}'");
|
||||
myExtraColumns = myExp.OutputIdentifiers.ToList();
|
||||
if (!string.IsNullOrEmpty(boolean_expression))
|
||||
_bool = new BooleanExpressionFilter(boolean_expression);
|
||||
mySetRealSource(new MapStream(vs, this));
|
||||
}
|
||||
|
||||
#region IStreamMapper Members
|
||||
|
||||
public IBlueTableSchema getOutputSchema(IBlueTableSchema inputSchema) {
|
||||
if (mySchema == null) {
|
||||
Dictionary<string, ColumnInfo> colIdx = new Dictionary<string, ColumnInfo>();
|
||||
foreach (ColumnInfo ci in inputSchema.columnInfo)
|
||||
colIdx[ci.colName] = ci;
|
||||
|
||||
foreach (string ncn in myExtraColumns) {
|
||||
ColumnInfo newC;
|
||||
if (colIdx.ContainsKey(ncn))
|
||||
newC = new ColumnInfo(colIdx[ncn].colName, colIdx[ncn].colNo, typeof(double));
|
||||
else
|
||||
newC = new ColumnInfo(ncn, colIdx.Count, typeof(double));
|
||||
colIdx[ncn] = newC;
|
||||
}
|
||||
mySchema = new BlueTableSchema(colIdx.Values.ToArray(), "math(" + inputSchema.tableName + ")");
|
||||
}
|
||||
return mySchema;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDynamicStreamMapper Members
|
||||
|
||||
public IBlueRow map(IBlueRow row) {
|
||||
MutableBlueRow result = new MutableBlueRow(mySchema, row);
|
||||
if (_bool == null || _bool.filter(row)) {
|
||||
myExp.Evaluate(result.AsDictionary());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace bts.BlueOrb {
|
||||
using BCG;
|
||||
[DataStreamUserRespresentation("TopStream")]
|
||||
public class TopStream : ViewStream {
|
||||
public TopStream(ViewStream vs, int n_rows, string sort_col) {
|
||||
ViewStream rs = new BCG.TopStream (vs, n_rows, sort_col);
|
||||
mySetRealSource(rs);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using mbts;
|
||||
using BCG;
|
||||
|
||||
namespace bts.BlueOrb {
|
||||
internal class MutableAppendTable : IBlueRowCollection {
|
||||
private class MyRow : IBlueRow {
|
||||
private IBlueRow myRow;
|
||||
private int myID;
|
||||
|
||||
public MyRow(IBlueRow r, int id) {
|
||||
myRow = r;
|
||||
myID = id;
|
||||
}
|
||||
|
||||
#region IBlueRow Members
|
||||
|
||||
public IBlueTableSchema schema { get { return myRow.schema; } }
|
||||
|
||||
public object this[int index] {
|
||||
get {
|
||||
if (myRow.schema[index].colName == "id")
|
||||
return myID;
|
||||
else
|
||||
return myRow[index];
|
||||
}
|
||||
}
|
||||
|
||||
public object this[string colName] { get { return colName == "id" ? (object)myID : myRow[colName]; } }
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDictionary Members
|
||||
|
||||
public void Add(object key, object value) { throw new Exception("Row is read only."); }
|
||||
|
||||
public void Clear() { throw new Exception("Row is read only."); }
|
||||
|
||||
public bool Contains(object key) { return myRow.Contains(key); }
|
||||
|
||||
public System.Collections.IDictionaryEnumerator GetEnumerator() { return new BlueRowDictionaryEnumerator(this); }
|
||||
|
||||
public bool IsFixedSize { get { return true; } }
|
||||
|
||||
public bool IsReadOnly { get { return true; } }
|
||||
|
||||
public System.Collections.ICollection Keys { get { return myRow.Keys; } }
|
||||
|
||||
public void Remove(object key) { throw new Exception("The method or operation is not implemented."); }
|
||||
|
||||
public System.Collections.ICollection Values {
|
||||
get {
|
||||
object[] vals = new object[myRow.Count];
|
||||
CopyTo(vals, 0);
|
||||
return vals;
|
||||
}
|
||||
}
|
||||
|
||||
public object this[object key] {
|
||||
get { return key is string ? this[key as string] : this[(int)key]; }
|
||||
set { throw new Exception("The method or operation is not implemented."); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection Members
|
||||
|
||||
public void CopyTo(Array array, int index) {
|
||||
myRow.CopyTo(array, index);
|
||||
array.SetValue(myID, index + myRow.schema["id"].colNo);
|
||||
}
|
||||
|
||||
public int Count { get { return myRow.Count; } }
|
||||
|
||||
public bool IsSynchronized { get { return myRow.IsSynchronized; } }
|
||||
|
||||
public object SyncRoot { get { return myRow.SyncRoot; } }
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable Members
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return new BlueRowEnumerator(this); }
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
private BlueTable myRows;
|
||||
private int myID = 0;
|
||||
/// <summary>
|
||||
/// maps the table no to a dictionary containing the
|
||||
/// local IDs to a row in the final big table
|
||||
/// </summary>
|
||||
private Dictionary<int, Dictionary<int, IBlueRow>> myIDMap = new Dictionary<int, Dictionary<int, IBlueRow>>();
|
||||
|
||||
public MutableAppendTable(IBlueTableSchema schema) { myRows = new BlueTable(schema); }
|
||||
|
||||
public IBlueRow addRow(IBlueRow r, int tableNo) {
|
||||
if (!myIDMap.ContainsKey(tableNo))
|
||||
myIDMap.Add(tableNo, new Dictionary<int, IBlueRow>());
|
||||
IBlueRow nr = new MyRow(r, myID++); // BlueDataUtility.getNewReplacedRow(r, myID++, "id");
|
||||
myRows.addRow(nr);
|
||||
myIDMap[tableNo].Add((int)r["id"], nr);
|
||||
return nr;
|
||||
}
|
||||
|
||||
public IBlueRow removeRow(IBlueRow r, int tableNo) {
|
||||
IBlueRow dr = myIDMap[tableNo][(int)r["id"]];
|
||||
myRows.deleteRow(dr);
|
||||
myIDMap[tableNo].Remove((int)r["id"]);
|
||||
return dr;
|
||||
}
|
||||
|
||||
public IBlueRowCollection removeTable(int tableNo) {
|
||||
if (!myIDMap.ContainsKey(tableNo))
|
||||
return null;
|
||||
else {
|
||||
BlueTable rv = new BlueTable(myRows.schema);
|
||||
foreach (IBlueRow r in myIDMap[tableNo].Values)
|
||||
rv.addRow(r);
|
||||
myRows.deleteRows(rv);
|
||||
myIDMap.Remove(tableNo);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
myRows.clear();
|
||||
myIDMap.Clear();
|
||||
}
|
||||
|
||||
#region IBlueRowCollection Members
|
||||
|
||||
public IBlueRow this[int id] { get { return myRows[id]; } }
|
||||
|
||||
public IBlueTableSchema schema { get { return myRows.schema; } }
|
||||
|
||||
public bool containsID(int id) { return myRows.containsID(id); }
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection Members
|
||||
|
||||
public void CopyTo(Array array, int index) { myRows.CopyTo(array, index); }
|
||||
|
||||
public int Count { get { return myRows.Count; } }
|
||||
|
||||
public bool IsSynchronized { get { return myRows.IsSynchronized; } }
|
||||
|
||||
public object SyncRoot { get { return myRows.SyncRoot; } }
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable Members
|
||||
|
||||
public System.Collections.IEnumerator GetEnumerator() { return myRows.GetEnumerator(); }
|
||||
|
||||
#endregion
|
||||
}
|
||||
/// <summary>
|
||||
/// MutableStream can append multiple streams
|
||||
/// as well as give the user the flexibility
|
||||
/// to append (or remove) more streams at
|
||||
/// runtime
|
||||
/// </summary>
|
||||
}
|
||||
namespace bts.BlueOrb {
|
||||
[DataStreamUserRespresentation("UnionStream")]
|
||||
public class MutableAppendStream : ViewStream {
|
||||
private bool mySubscriptionActive = false;
|
||||
private MutableAppendTable myTable;
|
||||
private int myMovingIndex;
|
||||
private Dictionary<int, Pair<ViewStream, ViewStream>> myStreamIndex = new Dictionary<int, Pair<ViewStream, ViewStream>>();
|
||||
private bool _allign_col_orders = true;
|
||||
|
||||
public MutableAppendStream(params ViewStream[] streams)
|
||||
: this(new List<ViewStream>(streams)) { }
|
||||
|
||||
public MutableAppendStream(bool allign_col_order, params ViewStream[] streams)
|
||||
: this(allign_col_order, new List<ViewStream>(streams)) { }
|
||||
|
||||
public MutableAppendStream(ICollection<ViewStream> streams)
|
||||
: this(true, streams) { }
|
||||
|
||||
public MutableAppendStream(bool allign_col_orders, ICollection<ViewStream> streams) {
|
||||
_allign_col_orders = allign_col_orders;
|
||||
foreach (ViewStream vs in streams) {
|
||||
myMovingIndex++;
|
||||
ViewStream pj = new ChangeTableNameStream(vs, myMovingIndex.ToString());
|
||||
if (_allign_col_orders)
|
||||
pj = new ProjectStream(pj, "!");
|
||||
myStreamIndex.Add(myMovingIndex, new Pair<ViewStream, ViewStream>(vs, pj));
|
||||
}
|
||||
}
|
||||
|
||||
public void addStream(ViewStream vs) {
|
||||
myMovingIndex++;
|
||||
///the change name is done to identify the callbacks, and the project to
|
||||
///make all the schemas col orders the same
|
||||
ViewStream pj = new ChangeTableNameStream(vs, myMovingIndex.ToString());
|
||||
if (_allign_col_orders)
|
||||
pj = new ProjectStream(pj, "!");
|
||||
myStreamIndex.Add(myMovingIndex, new Pair<ViewStream, ViewStream>(vs, pj));
|
||||
if (mySubscriptionActive)
|
||||
pj.onChange += new ChangeDelegate(myHandleStreamChanges);
|
||||
}
|
||||
|
||||
public void removeStream(ViewStream vs) {
|
||||
int index = -1;
|
||||
foreach (KeyValuePair<int, Pair<ViewStream, ViewStream>> p in myStreamIndex) {
|
||||
if (p.Value.first == vs) {
|
||||
index = p.Key;
|
||||
if (mySubscriptionActive) {
|
||||
p.Value.second.onChange -= new ChangeDelegate(myHandleStreamChanges);
|
||||
if (myTable != null) {
|
||||
IBlueRowCollection delRows = myTable.removeTable(p.Key);
|
||||
if (delRows != null)
|
||||
myFireEvent(new DataPacket(myTable, new BlueTable(myTable.schema), delRows));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (index > 0)
|
||||
myStreamIndex.Remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void myOnFirstSubscriber() {
|
||||
mySubscriptionActive = true;
|
||||
foreach (KeyValuePair<int, Pair<ViewStream, ViewStream>> p in myStreamIndex)
|
||||
p.Value.second.onChange += new ChangeDelegate(myHandleStreamChanges);
|
||||
}
|
||||
|
||||
protected override void myOnLastSubscriber() {
|
||||
mySubscriptionActive = false;
|
||||
foreach (KeyValuePair<int, Pair<ViewStream, ViewStream>> p in myStreamIndex)
|
||||
p.Value.second.onChange -= new ChangeDelegate(myHandleStreamChanges);
|
||||
if (myTable != null)
|
||||
myTable.clear();
|
||||
}
|
||||
|
||||
private void myHandleStreamChanges(object newValue) {
|
||||
DataPacket dp = newValue as DataPacket;
|
||||
myTable = myTable ?? new MutableAppendTable(new BlueTableSchema("MutableAppend", dp.schema.columnInfo));
|
||||
BlueTable adds = new BlueTable(myTable.schema),
|
||||
dels = new BlueTable(myTable.schema);
|
||||
int index = Int32.Parse(dp.schema.tableName);
|
||||
foreach (IBlueRow r in dp.deletedRows) {
|
||||
IBlueRow dr = myTable.removeRow(r, index);
|
||||
dels.addRow(dr);
|
||||
}
|
||||
foreach (IBlueRow r in dp.addedRows) {
|
||||
IBlueRow nr = myTable.addRow(r, index);
|
||||
adds.addRow(nr);
|
||||
}
|
||||
myFireEvent(new DataPacket(myTable, adds, dels));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.57" />
|
||||
<PackageReference Include="IronPython" Version="3.4.2" />
|
||||
<PackageReference Include="Microsoft.Data.Analysis" Version="0.21.1" />
|
||||
<PackageReference Include="MongoDB.Bson" Version="2.23.1" />
|
||||
<PackageReference Include="MongoDB.Driver" Version="2.23.1" />
|
||||
|
||||
@@ -1,52 +1,47 @@
|
||||
using System;
|
||||
|
||||
namespace BCG
|
||||
{
|
||||
[DataStreamUserRespresentation ("count")]
|
||||
public class CountStream : ViewStream
|
||||
{
|
||||
namespace bts.BlueOrb {
|
||||
using BCG;
|
||||
[DataStreamUserRespresentation("CountStream")]
|
||||
public class CountStream : ViewStream {
|
||||
private DataPacket myLastFiredDataPacket;
|
||||
private IBlueTableSchema mySchema = new BlueTableSchema ("count",
|
||||
new ColumnInfo ("id", 0, typeof(int)),
|
||||
new ColumnInfo ("count", 1, typeof(int)));
|
||||
private IBlueTableSchema mySchema = new BlueTableSchema("count",
|
||||
new ColumnInfo("id", 0, typeof(int)),
|
||||
new ColumnInfo("count", 1, typeof(int)));
|
||||
private int myId = 0;
|
||||
private ViewStream myVS;
|
||||
|
||||
public CountStream (ViewStream vs)
|
||||
{
|
||||
public CountStream(ViewStream vs) {
|
||||
myVS = vs;
|
||||
}
|
||||
|
||||
public CountStream (string tableName)
|
||||
: this (new TableStream (tableName))
|
||||
{}
|
||||
public CountStream(string tableName)
|
||||
: this(new TableStream(tableName)) { }
|
||||
|
||||
protected override void myOnFirstSubscriber () {
|
||||
protected override void myOnFirstSubscriber() {
|
||||
myVS.onChange += new ChangeDelegate(myHandleChanges);
|
||||
}
|
||||
protected override void myOnLastSubscriber () {
|
||||
protected override void myOnLastSubscriber() {
|
||||
myVS.onChange -= new ChangeDelegate(myHandleChanges);
|
||||
}
|
||||
|
||||
|
||||
private void myHandleChanges(object newValue)
|
||||
{
|
||||
myFireEvent (
|
||||
private void myHandleChanges(object newValue) {
|
||||
myFireEvent(
|
||||
myLastFiredDataPacket =
|
||||
myLastFiredDataPacket == null ?
|
||||
new DataPacket (myGetRows (newValue, ++myId), myGetRows (newValue, myId),
|
||||
new BlueTable (mySchema))
|
||||
new DataPacket(myGetRows(newValue, ++myId), myGetRows(newValue, myId),
|
||||
new BlueTable(mySchema))
|
||||
:
|
||||
new DataPacket (myGetRows (newValue, ++myId), myGetRows (newValue, myId),
|
||||
new DataPacket(myGetRows(newValue, ++myId), myGetRows(newValue, myId),
|
||||
myLastFiredDataPacket.newRows)
|
||||
);
|
||||
}
|
||||
|
||||
private IBlueRowCollection myGetRows (object dp, int id)
|
||||
{
|
||||
BlueTable retVal = new BlueTable (mySchema);
|
||||
private IBlueRowCollection myGetRows(object dp, int id) {
|
||||
BlueTable retVal = new BlueTable(mySchema);
|
||||
retVal.addRow
|
||||
(new BlueRow (mySchema, id, (dp as DataPacket).newRows.Count));
|
||||
(new BlueRow(mySchema, id, (dp as DataPacket).newRows.Count));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,25 +5,20 @@ using mbts.config;
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace BCG.StreamWorld.Addition
|
||||
{
|
||||
[DataStreamUserRespresentation (false)]
|
||||
public class FilterStream : ViewStream
|
||||
{
|
||||
namespace BCG.StreamWorld.Addition {
|
||||
[DataStreamUserRespresentation(false)]
|
||||
public class FilterStream : ViewStream {
|
||||
private ViewStream myBaseStream;
|
||||
private BlueTable myBaseTable, myStateTable;
|
||||
private IStreamFilter myFilter;
|
||||
|
||||
public FilterStream(ViewStream vs)
|
||||
: this(vs, "1=1")
|
||||
{ }
|
||||
: this(vs, "1=1") { }
|
||||
|
||||
public FilterStream(ViewStream vs, string filterExp)
|
||||
: this(vs, new BooleanExpressionFilter(filterExp))
|
||||
{ }
|
||||
: this(vs, new BooleanExpressionFilter(filterExp)) { }
|
||||
|
||||
public FilterStream(ViewStream vs, IStreamFilter filter)
|
||||
{
|
||||
public FilterStream(ViewStream vs, IStreamFilter filter) {
|
||||
myBaseStream = vs;
|
||||
myFilter = filter;
|
||||
if (filter is IDynamicStreamFilter)
|
||||
@@ -31,43 +26,35 @@ namespace BCG.StreamWorld.Addition
|
||||
}
|
||||
|
||||
|
||||
protected override void myOnFirstSubscriber()
|
||||
{
|
||||
protected override void myOnFirstSubscriber() {
|
||||
myBaseStream.onChange += new ChangeDelegate(myHandleChanges);
|
||||
}
|
||||
|
||||
protected override void myOnLastSubscriber()
|
||||
{
|
||||
protected override void myOnLastSubscriber() {
|
||||
myBaseStream.onChange -= new ChangeDelegate(myHandleChanges);
|
||||
myBaseTable.clear();
|
||||
myStateTable.clear();
|
||||
}
|
||||
|
||||
private void myHandleChanges(object newValue)
|
||||
{
|
||||
private void myHandleChanges(object newValue) {
|
||||
DataPacket dp = newValue as DataPacket;
|
||||
if (myStateTable == null)
|
||||
{
|
||||
if (myStateTable == null) {
|
||||
bts.Log.assert(myBaseTable == null);
|
||||
myStateTable = new BlueTable(dp.schema);
|
||||
myBaseTable = new BlueTable(dp.schema);
|
||||
}
|
||||
BlueTable dels = new BlueTable(dp.schema),
|
||||
adds = new BlueTable(dp.schema);
|
||||
foreach (IBlueRow r in dp.deletedRows)
|
||||
{
|
||||
foreach (IBlueRow r in dp.deletedRows) {
|
||||
myBaseTable.deleteRow(r);
|
||||
if (myStateTable.containsID((int)r["id"]))
|
||||
{
|
||||
if (myStateTable.containsID((int)r["id"])) {
|
||||
dels.addRow(r);
|
||||
myStateTable.deleteRow(r);
|
||||
}
|
||||
}
|
||||
foreach (IBlueRow r in dp.addedRows)
|
||||
{
|
||||
foreach (IBlueRow r in dp.addedRows) {
|
||||
myBaseTable.addRow(r);
|
||||
if (myFilter.filter(r))
|
||||
{
|
||||
if (myFilter.filter(r)) {
|
||||
adds.addRow(r);
|
||||
myStateTable.addRow(r);
|
||||
}
|
||||
@@ -76,13 +63,10 @@ namespace BCG.StreamWorld.Addition
|
||||
myFireEvent(new DataPacket(myStateTable, adds, dels));
|
||||
}
|
||||
|
||||
public IStreamFilter filter
|
||||
{
|
||||
set
|
||||
{
|
||||
public IStreamFilter filter {
|
||||
set {
|
||||
myFilter = value;
|
||||
if (myBaseTable != null)
|
||||
{
|
||||
if (myBaseTable != null) {
|
||||
IBlueRowCollection oldState = new BlueTable(myStateTable);
|
||||
myStateTable.clear();
|
||||
foreach (IBlueRow r in myBaseTable)
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace BCG {
|
||||
public class GroupByStream : MethodDrivenStream {
|
||||
internal class GroupByStream : MethodDrivenStream {
|
||||
private class MyFuncGroupByEvaluator : IGroupByEvaluator {
|
||||
private string[] _groupby_cols;
|
||||
private Func<DataPacket, IBlueRow> _groupby_func;
|
||||
|
||||
@@ -40,4 +40,4 @@ namespace BCG {
|
||||
myFireSwitch = myLastFiredPacket.addedRows.Count + myLastFiredPacket.deletedRows.Count == 0 ? false : true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -280,6 +280,28 @@ namespace BCG {
|
||||
|
||||
#region json
|
||||
|
||||
public static void write_to_json_file(this IBlueRowCollection table, string file_path) {
|
||||
write_dict_to_json_file(file_path, table.to_namevalue_pairs());
|
||||
}
|
||||
|
||||
public static void write_dict_to_json_file(string file_path, List<Dictionary<string, string>> data) {
|
||||
var options = new JsonSerializerOptions { WriteIndented = true };
|
||||
string json = System.Text.Json.JsonSerializer.Serialize(data, options);
|
||||
File.WriteAllText(file_path, json);
|
||||
}
|
||||
|
||||
public static List<Dictionary<string, string>> ReadFromFile(string filePath) {
|
||||
string json = File.ReadAllText(filePath);
|
||||
return System.Text.Json.JsonSerializer.Deserialize<List<Dictionary<string, string>>>(json);
|
||||
}
|
||||
|
||||
public static void write_table_to_json(IBlueRowCollection table) {
|
||||
List<Dictionary<string, string>> rows = new List<Dictionary<string, string>>();
|
||||
foreach (IBlueRow row in table) {
|
||||
row.to_json();
|
||||
}
|
||||
}
|
||||
|
||||
public static Dictionary<string, string> convert_json_to_dictionary(string json_string) {
|
||||
Dictionary<string, string> dictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(json_string);
|
||||
return dictionary;
|
||||
@@ -348,6 +370,14 @@ namespace BCG {
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static List<Dictionary<string,string>> to_namevalue_pairs(this IBlueRowCollection table) {
|
||||
List<Dictionary<string, string>> rv = new List<Dictionary<string, string>>();
|
||||
foreach (IBlueRow r in table) {
|
||||
rv.Add(r.to_dict());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
#region Schema types
|
||||
// these are the only types allowed in schemas
|
||||
private static IReadOnlyDictionary<TypeCode, Type> _typecode_to_type = new Dictionary<TypeCode, Type>
|
||||
@@ -1225,6 +1255,20 @@ namespace BCG {
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static Dictionary<string, string> to_dict(this IBlueRow r) {
|
||||
Dictionary<string, string> map = new Dictionary<string, string>();
|
||||
foreach (ColumnInfo c in r.schema.columnInfo) {
|
||||
map.Add(c.colName, r[c.colName].ToString());
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public static string to_json(this IBlueRow r) {
|
||||
Dictionary<string, string> map = r.to_dict();
|
||||
string json = System.Text.Json.JsonSerializer.Serialize(map);
|
||||
return json;
|
||||
}
|
||||
|
||||
public static void compute_differences_fast(IBlueRowCollection new_rows, IBlueRowCollection old_rows,
|
||||
out IBlueRowCollection out_d_rows, out IBlueRowCollection out_a_rows) {
|
||||
if (old_rows == null || old_rows.Count == 0) {
|
||||
|
||||
@@ -47,10 +47,10 @@ namespace BCG {
|
||||
/// </summary>
|
||||
/// <param name="assemblyName"></param>
|
||||
public void loadAssembly(string assemblyName) {
|
||||
Func<Type, bool> is_silverpine_only = (t) => {
|
||||
if (!assemblyName.Contains("SharedStreams.dll"))
|
||||
return true;
|
||||
return t.Namespace == "bts.SilverPine";
|
||||
Func<Type, bool> is_blue_orb = (t) => {
|
||||
//if (!assemblyName.Contains("SharedStreams.dll"))
|
||||
// return true;
|
||||
return t.Namespace == "bts.BlueOrb";// || t.Namespace == "BCG";
|
||||
};
|
||||
bts.Log.Gen.info($"PrimitiveManager.loadAssembly assemblyName: {assemblyName}");
|
||||
if (myAssemblyNames.Contains(assemblyName))
|
||||
@@ -61,7 +61,7 @@ namespace BCG {
|
||||
Assembly.LoadFrom(assemblyName);
|
||||
|
||||
foreach (Type type in loadedAssembly.GetTypes())
|
||||
if (!type.IsInterface && is_silverpine_only(type)
|
||||
if (!type.IsInterface && is_blue_orb(type)
|
||||
&& !type.IsAbstract
|
||||
&& type.IsSubclassOf(typeof(ViewStream))
|
||||
&& type.IsPublic) {
|
||||
@@ -77,7 +77,7 @@ namespace BCG {
|
||||
break;
|
||||
}
|
||||
if (isVisible) {
|
||||
Log.assert_d(myTypeLookupTable[userRep] == null, type.FullName);
|
||||
// Log.assert_d(myTypeLookupTable[userRep] == null, type.FullName);
|
||||
myTypeLookupTable[userRep] = type;
|
||||
}
|
||||
}
|
||||
|
||||
+30
-43
@@ -1,4 +1,5 @@
|
||||
namespace bts.SilverPine {
|
||||
namespace bts.BlueOrb {
|
||||
using bts.SilverPine;
|
||||
using BCG;
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -45,7 +46,7 @@ namespace bts.SilverPine {
|
||||
}
|
||||
|
||||
[DataStreamUserRespresentation("FuturesHistorical1DayBars")]
|
||||
public class FuturesHistorical1DayBarsStream : ViewStream {
|
||||
internal class FuturesHistorical1DayBarsStream : ViewStream {
|
||||
|
||||
// this gets the raw bars for a symbol (perhaps make it into a raw bars stream?)
|
||||
public FuturesHistorical1DayBarsStream(string symbol) :
|
||||
@@ -103,7 +104,7 @@ namespace bts.SilverPine {
|
||||
}
|
||||
|
||||
[DataStreamUserRespresentation("FuturesHistorical1MinBars")]
|
||||
public class FuturesHistorical1MinuteBarsStream : ViewStream {
|
||||
internal class FuturesHistorical1MinuteBarsStream : ViewStream {
|
||||
// same as the above but for one min
|
||||
// this gets the raw bars for a symbol
|
||||
public FuturesHistorical1MinuteBarsStream(string symbol) :
|
||||
@@ -187,46 +188,6 @@ namespace bts.SilverPine {
|
||||
}
|
||||
}
|
||||
|
||||
[DataStreamUserRespresentation("FuturePriceDiff")]
|
||||
public class FutureHistoricalBarsPriceDiffStream : ViewStream {
|
||||
|
||||
public FutureHistoricalBarsPriceDiffStream(string future_contract, string dt1, string dt2)
|
||||
:
|
||||
this(new ContractUtils.FutureContract(future_contract), DateTime.Parse(dt1), DateTime.Parse(dt2), "close", ViewStreamDatabase.instance) {
|
||||
}
|
||||
|
||||
|
||||
public FutureHistoricalBarsPriceDiffStream(string future_contract, DateTime dt1, DateTime dt2)
|
||||
:
|
||||
this(new ContractUtils.FutureContract(future_contract), dt1, dt2, "close", ViewStreamDatabase.instance) {
|
||||
}
|
||||
|
||||
public FutureHistoricalBarsPriceDiffStream(string future_contract, DateTime dt1, DateTime dt2, string col)
|
||||
:
|
||||
this(new ContractUtils.FutureContract(future_contract), dt1, dt2, col, ViewStreamDatabase.instance) {
|
||||
}
|
||||
|
||||
|
||||
public FutureHistoricalBarsPriceDiffStream(ContractUtils.FutureContract fc, DateTime dt1, DateTime dt2, string col, ViewStreamDatabase vsd) {
|
||||
BarSize bsize = dt2 - dt1 >= TimeSpan.FromDays(1.0) ? BarSize.ONE_DAY : BarSize.ONE_MIN;
|
||||
ViewStream rs = new FutureHistoricalBarsStream(fc, BarSize.ONE_DAY, new DateTimeRange(dt1, dt2));
|
||||
|
||||
rs = new ColumnDifferenceStream(rs, r => {
|
||||
DateTime r_dt = r.get_datetime("ts_event");
|
||||
var rv = dt1.Equals(r_dt);
|
||||
return rv;
|
||||
},
|
||||
//r => dt2.Equals(r.get_datetime("ts_event")), col, col);
|
||||
r => {
|
||||
DateTime r_dt = r.get_datetime("ts_event");
|
||||
var rv = dt2.Equals(r_dt);
|
||||
return rv;
|
||||
}, col, col);
|
||||
rs = new NewProjectStream(rs, "diff !");
|
||||
mySetRealSource(rs);
|
||||
}
|
||||
}
|
||||
|
||||
// takes the contract name and expands it into multuiple columns corresponding to the contracts.
|
||||
// the contract columns are a union, of all properties with some of them empty
|
||||
public class ContractExpanderStream : ViewStream {
|
||||
@@ -247,3 +208,29 @@ namespace bts.SilverPine {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace bts.BlueOrb {
|
||||
using BCG;
|
||||
|
||||
using bts.api;
|
||||
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
[DataStreamUserRespresentation("FuturesContinousContract")]
|
||||
public class FuturesContinousContractStream : ViewStream {
|
||||
// the argument is symbol, like "ES" etc and not the contract itself
|
||||
public FuturesContinousContractStream(string symbol) : this(symbol, ViewStreamDatabase.instance) { }
|
||||
public FuturesContinousContractStream(string symbol, ViewStreamDatabase db) {
|
||||
symbol = symbol.EndsWith("*") ? symbol : symbol + "*";
|
||||
ViewStream r = new FuturesHistorical1DayBarsStream(symbol);
|
||||
r = new ContractExpanderStream(r, "symbol");
|
||||
r = new FilterStream(r, "contract_type='Future'");
|
||||
// finding the max volume across symbols, if we need to do this for open_interest, then join here
|
||||
// with the open interest stream on symbol/contract
|
||||
r = new NonIncGroupByStream(r, "ts_event", "volume:Max");
|
||||
r = new NewProjectStream(r, "ts_event symbol !");
|
||||
r = new SortStream(r, "-ts_event");
|
||||
mySetRealSource(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
namespace bts.SilverPine {
|
||||
namespace bts.BlueOrb {
|
||||
using bts.SilverPine;
|
||||
using BCG;
|
||||
using System;
|
||||
|
||||
|
||||
@@ -1,117 +1,118 @@
|
||||
// namespace bts.SilverPine {
|
||||
// using bts;
|
||||
// using BCG;
|
||||
// using MongoDB.Driver.Linq;
|
||||
// using System.ComponentModel.DataAnnotations;
|
||||
// using ZstdSharp.Unsafe;
|
||||
// TODO: Remove
|
||||
//// namespace bts.SilverPine
|
||||
//// using bts;
|
||||
//// using BCG;
|
||||
//// using MongoDB.Driver.Linq;
|
||||
//// using System.ComponentModel.DataAnnotations;
|
||||
//// using ZstdSharp.Unsafe;
|
||||
|
||||
// [DataStreamUserRespresentation("VWAP")]
|
||||
// public class VWAPStream : ViewStream {
|
||||
// // returns the VWAP stream for each contract
|
||||
// private static ViewStream build_vwap_stream(string symbol, DateTime begin_dt, DateTime end_dt) {
|
||||
// var ohlcv_params = OHLCVStream.get_params(begin_dt, end_dt);
|
||||
// ViewStream ohlc_str = new OHLCVStream(symbol, ohlcv_params);
|
||||
// // stream is grouped per contract and then vwap is calculated per contract
|
||||
// GlobalMapStream rs = new GlobalMapStream(ohlc_str, "ts_event", "contract",
|
||||
// (rows, idx) => {
|
||||
// Log.Gen.info($"VWAPStream.GlobalMapStream delegate n_rows: {rows.Count} idx: {idx}");
|
||||
// IBlueRow ir = rows.ElementAt((int)idx);
|
||||
// var sel_rows = rows.Skip((int)idx).Where(r => r.get_datetime("ts_event") >= end_dt && r.get_datetime("ts_event") <= begin_dt);
|
||||
// var volumeprices = sel_rows.Select(r => Tuple.Create(r.get<int>("volume"), r.get<double>("close"))).ToList();
|
||||
// double vwap = IndicatorCalc.calculate_VWAP(volumeprices);
|
||||
// IBlueTableSchema schema = ir.schema.add_columns(Tuple.Create(typeof(double), "vwap"));
|
||||
// MutableBlueRow nr = new MutableBlueRow(schema, ir);
|
||||
// //vwap = -313.987;
|
||||
// nr.setValue("vwap", vwap);
|
||||
// return nr;
|
||||
// });
|
||||
// return rs;
|
||||
// }
|
||||
// // the args want dt1 > dt2, if its nto we, swap them
|
||||
// static void swap_if_necessary(ref DateTime dt1, ref DateTime dt2) {
|
||||
// if (dt1 < dt2) {
|
||||
// DateTime aux = dt1;
|
||||
// dt1 = dt2;
|
||||
// dt2 = aux;
|
||||
// }
|
||||
// }
|
||||
//// [DataStreamUserRespresentation("VWAP")]
|
||||
//// public class VWAPStream : ViewStream {
|
||||
//// // returns the VWAP stream for each contract
|
||||
//// private static ViewStream build_vwap_stream(string symbol, DateTime begin_dt, DateTime end_dt) {
|
||||
//// var ohlcv_params = OHLCVStream.get_params(begin_dt, end_dt);
|
||||
//// ViewStream ohlc_str = new OHLCVStream(symbol, ohlcv_params);
|
||||
//// // stream is grouped per contract and then vwap is calculated per contract
|
||||
//// GlobalMapStream rs = new GlobalMapStream(ohlc_str, "ts_event", "contract",
|
||||
//// (rows, idx) => {
|
||||
//// Log.Gen.info($"VWAPStream.GlobalMapStream delegate n_rows: {rows.Count} idx: {idx}");
|
||||
//// IBlueRow ir = rows.ElementAt((int)idx);
|
||||
//// var sel_rows = rows.Skip((int)idx).Where(r => r.get_datetime("ts_event") >= end_dt && r.get_datetime("ts_event") <= begin_dt);
|
||||
//// var volumeprices = sel_rows.Select(r => Tuple.Create(r.get<int>("volume"), r.get<double>("close"))).ToList();
|
||||
//// double vwap = IndicatorCalc.calculate_VWAP(volumeprices);
|
||||
//// IBlueTableSchema schema = ir.schema.add_columns(Tuple.Create(typeof(double), "vwap"));
|
||||
//// MutableBlueRow nr = new MutableBlueRow(schema, ir);
|
||||
//// //vwap = -313.987;
|
||||
//// nr.setValue("vwap", vwap);
|
||||
//// return nr;
|
||||
//// });
|
||||
//// return rs;
|
||||
//// }
|
||||
//// // the args want dt1 > dt2, if its nto we, swap them
|
||||
//// static void swap_if_necessary(ref DateTime dt1, ref DateTime dt2) {
|
||||
//// if (dt1 < dt2) {
|
||||
//// DateTime aux = dt1;
|
||||
//// dt1 = dt2;
|
||||
//// dt2 = aux;
|
||||
//// }
|
||||
//// }
|
||||
|
||||
// // this assumes the ohlc_str has diff contracts
|
||||
// public VWAPStream(string symbol_or_contract, DateTime begin_dt, DateTime end_dt) {
|
||||
// swap_if_necessary(ref begin_dt, ref end_dt);
|
||||
// Tuple<ContractUtils.ContractCategory, string> ccat_sym_contr = ContractUtils.get_symbol_category(symbol_or_contract);
|
||||
// string symbol = String.Empty;
|
||||
// symbol = ccat_sym_contr.Item2;
|
||||
// var ohlcv_params = OHLCVStream.get_params(begin_dt, end_dt);
|
||||
// ViewStream vwap_str = build_vwap_stream(symbol, begin_dt, end_dt);
|
||||
// // vwap_str now has vwap for all contracts. if the arg was a symbol, we need to filter it for the active contract,
|
||||
// // whihc we do with FutureActivesContractStream. If the arg was a contract we'll simply filter it
|
||||
// ViewStream rs = vwap_str;
|
||||
// switch (ccat_sym_contr.Item1) {
|
||||
// case ContractUtils.ContractCategory.FutureString:
|
||||
// case ContractUtils.ContractCategory.SpreadString:
|
||||
// rs = new FilterStream(vwap_str, r => r.get<string>("contract").CompareTo(ccat_sym_contr.Item2) == 0);
|
||||
// break;
|
||||
// case ContractUtils.ContractCategory.Symbol:
|
||||
// rs = new FuturesActiveContractStream(vwap_str);
|
||||
// break;
|
||||
// }
|
||||
// rs = new FilterStream(rs, r => r.get_datetime("ts_event").CompareTo(begin_dt) == 0);
|
||||
// rs = new NewProjectStream(rs, "! vwap");
|
||||
// mySetRealSource(rs);
|
||||
// }
|
||||
//// // this assumes the ohlc_str has diff contracts
|
||||
//// public VWAPStream(string symbol_or_contract, DateTime begin_dt, DateTime end_dt) {
|
||||
//// swap_if_necessary(ref begin_dt, ref end_dt);
|
||||
//// Tuple<ContractUtils.ContractCategory, string> ccat_sym_contr = ContractUtils.get_symbol_category(symbol_or_contract);
|
||||
//// string symbol = String.Empty;
|
||||
//// symbol = ccat_sym_contr.Item2;
|
||||
//// var ohlcv_params = OHLCVStream.get_params(begin_dt, end_dt);
|
||||
//// ViewStream vwap_str = build_vwap_stream(symbol, begin_dt, end_dt);
|
||||
//// // vwap_str now has vwap for all contracts. if the arg was a symbol, we need to filter it for the active contract,
|
||||
//// // whihc we do with FutureActivesContractStream. If the arg was a contract we'll simply filter it
|
||||
//// ViewStream rs = vwap_str;
|
||||
//// switch (ccat_sym_contr.Item1) {
|
||||
//// case ContractUtils.ContractCategory.FutureString:
|
||||
//// case ContractUtils.ContractCategory.SpreadString:
|
||||
//// rs = new FilterStream(vwap_str, r => r.get<string>("contract").CompareTo(ccat_sym_contr.Item2) == 0);
|
||||
//// break;
|
||||
//// case ContractUtils.ContractCategory.Symbol:
|
||||
//// rs = new FuturesActiveContractStream(vwap_str);
|
||||
//// break;
|
||||
//// }
|
||||
//// rs = new FilterStream(rs, r => r.get_datetime("ts_event").CompareTo(begin_dt) == 0);
|
||||
//// rs = new NewProjectStream(rs, "! vwap");
|
||||
//// mySetRealSource(rs);
|
||||
//// }
|
||||
|
||||
// public VWAPStream(string symbol_or_contract, string begin_dt, string end_dt)
|
||||
// :
|
||||
// this(symbol_or_contract, DateTime.Parse(begin_dt), DateTime.Parse(end_dt)) { }
|
||||
// }
|
||||
// }
|
||||
//// public VWAPStream(string symbol_or_contract, string begin_dt, string end_dt)
|
||||
//// :
|
||||
//// this(symbol_or_contract, DateTime.Parse(begin_dt), DateTime.Parse(end_dt)) { }
|
||||
//// }
|
||||
//// }
|
||||
|
||||
using System;
|
||||
//using System;
|
||||
|
||||
using BCG;
|
||||
//using BCG;
|
||||
|
||||
using bts.api;
|
||||
//using bts.api;
|
||||
|
||||
namespace bts.SilverPine {
|
||||
/*
|
||||
----------------------------------------------------------------------
|
||||
*Expression*
|
||||
----------------------------------------------------------------------
|
||||
//nl("Find Settlement price of the SP500 future in the first week of 2023.")
|
||||
//namespace bts.SilverPine {
|
||||
// /*
|
||||
// ----------------------------------------------------------------------
|
||||
// *Expression*
|
||||
// ----------------------------------------------------------------------
|
||||
// //nl("Find Settlement price of the SP500 future in the first week of 2023.")
|
||||
|
||||
r=FutureHistoricalBars("ES", "One_Day", "1/1/2024", "2/1/2024")
|
||||
r=math(r, "vprice = volume * close")
|
||||
r=aggregate(r, "vprice:Sum, volume:Sum")
|
||||
r=math(r, "vwap = vprice_sum / volume_sum")
|
||||
r=newProject(r, "vwap !")
|
||||
// r=FutureHistoricalBars("ES", "One_Day", "1/1/2024", "2/1/2024")
|
||||
// r=math(r, "vprice = volume * close")
|
||||
// r=aggregate(r, "vprice:Sum, volume:Sum")
|
||||
// r=math(r, "vwap = vprice_sum / volume_sum")
|
||||
// r=newProject(r, "vwap !")
|
||||
|
||||
----------------------------------------------------------------------
|
||||
*Schema*
|
||||
----------------------------------------------------------------------
|
||||
id System.Int32
|
||||
vwap System.Double
|
||||
*/
|
||||
// ----------------------------------------------------------------------
|
||||
// *Schema*
|
||||
// ----------------------------------------------------------------------
|
||||
// id System.Int32
|
||||
// vwap System.Double
|
||||
// */
|
||||
|
||||
[DataStreamUserRespresentation("VWAP")]
|
||||
public class VWAPStream : ViewStream {
|
||||
public VWAPStream(string symbol_or_contract, DateTime beg_dt, DateTime end_dt)
|
||||
:
|
||||
this(symbol_or_contract, beg_dt, end_dt, ViewStreamDatabase.instance) { }
|
||||
// [DataStreamUserRespresentation("VWAP")]
|
||||
// public class VWAPStream : ViewStream {
|
||||
// public VWAPStream(string symbol_or_contract, DateTime beg_dt, DateTime end_dt)
|
||||
// :
|
||||
// this(symbol_or_contract, beg_dt, end_dt, ViewStreamDatabase.instance) { }
|
||||
|
||||
public VWAPStream(string symbol_or_contract, DateTime beg_dt, DateTime end_dt, ViewStreamDatabase db) {
|
||||
//var fc = new ContractUtils.FutureContract(symbol_or_contract);
|
||||
ViewStream r = new FutureHistoricalBarsStream(symbol_or_contract, beg_dt.ToString(), end_dt.ToString());
|
||||
// since this is already filtered, we can groupBy iit without a groupBy column
|
||||
r = new MathStream(r,
|
||||
"vprice = volume * close");
|
||||
r = new NonIncGroupByStream(r,
|
||||
"vprice:Sum, volume:Sum");
|
||||
r = new MathStream(r,
|
||||
"vwap = vprice_sum / volume_sum");
|
||||
r = new NewProjectStream(r,
|
||||
"vwap !");
|
||||
mySetRealSource(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
// public VWAPStream(string symbol_or_contract, DateTime beg_dt, DateTime end_dt, ViewStreamDatabase db) {
|
||||
// //var fc = new ContractUtils.FutureContract(symbol_or_contract);
|
||||
// ViewStream r = new FutureHistoricalBarsStream(symbol_or_contract, beg_dt.ToString(), end_dt.ToString());
|
||||
// // since this is already filtered, we can groupBy iit without a groupBy column
|
||||
// r = new MathStream(r,
|
||||
// "vprice = volume * close");
|
||||
// r = new NonIncGroupByStream(r,
|
||||
// "vprice:Sum, volume:Sum");
|
||||
// r = new MathStream(r,
|
||||
// "vwap = vprice_sum / volume_sum");
|
||||
// r = new NewProjectStream(r,
|
||||
// "vwap !");
|
||||
// mySetRealSource(r);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
+47
-47
@@ -1,53 +1,53 @@
|
||||
using BCG;
|
||||
//using BCG;
|
||||
|
||||
namespace bts.SilverPine {
|
||||
/*
|
||||
----------------------------------------------------------------------
|
||||
*Expression*
|
||||
----------------------------------------------------------------------
|
||||
//r=FutureHistoricalBars("ES*", "02/01/2024 09:30 AM", "02/01/2024 09:30 PM")
|
||||
r=FutureHistoricalBars("ES*","One_Day", 02/01/2024, 02/01/2025)
|
||||
r=contractExpander(r,"symbol")
|
||||
r=filter(r,"contract_type='FutureSpread'")
|
||||
r=aggregate(r,"symbol","volume:Sum","ts_event")
|
||||
r=aggregate(r,"volume_sum:Max")
|
||||
r=newProject(r,"volume_sum_max:volume symbol !")
|
||||
//namespace bts.SilverPine {
|
||||
// /*
|
||||
// ----------------------------------------------------------------------
|
||||
// *Expression*
|
||||
// ----------------------------------------------------------------------
|
||||
// //r=FutureHistoricalBars("ES*", "02/01/2024 09:30 AM", "02/01/2024 09:30 PM")
|
||||
// r=FutureHistoricalBars("ES*","One_Day", 02/01/2024, 02/01/2025)
|
||||
// r=contractExpander(r,"symbol")
|
||||
// r=filter(r,"contract_type='FutureSpread'")
|
||||
// r=aggregate(r,"symbol","volume:Sum","ts_event")
|
||||
// r=aggregate(r,"volume_sum:Max")
|
||||
// r=newProject(r,"volume_sum_max:volume symbol !")
|
||||
|
||||
----------------------------------------------------------------------
|
||||
*Schema*
|
||||
----------------------------------------------------------------------
|
||||
symbol System.String
|
||||
id System.Int32
|
||||
volume System.Double
|
||||
*/
|
||||
// ----------------------------------------------------------------------
|
||||
// *Schema*
|
||||
// ----------------------------------------------------------------------
|
||||
// symbol System.String
|
||||
// id System.Int32
|
||||
// volume System.Double
|
||||
// */
|
||||
|
||||
[DataStreamUserRespresentation("VolumeAgg")]
|
||||
public class VolumeAggregationStream : ViewStream {
|
||||
public VolumeAggregationStream(string future_contract, string contract_type, DateTime range_beg, DateTime range_end)
|
||||
: this(future_contract, contract_type, range_beg, range_end, ViewStreamDatabase.instance) { }
|
||||
// [DataStreamUserRespresentation("VolumeAgg")]
|
||||
// public class VolumeAggregationStream : ViewStream {
|
||||
// public VolumeAggregationStream(string future_contract, string contract_type, DateTime range_beg, DateTime range_end)
|
||||
// : this(future_contract, contract_type, range_beg, range_end, ViewStreamDatabase.instance) { }
|
||||
|
||||
public VolumeAggregationStream(string future_contract, string contract_type, string range_beg, string range_end)
|
||||
:
|
||||
this(future_contract, contract_type, DateTime.Parse(range_beg), DateTime.Parse(range_end), ViewStreamDatabase.instance) { }
|
||||
// public VolumeAggregationStream(string future_contract, string contract_type, string range_beg, string range_end)
|
||||
// :
|
||||
// this(future_contract, contract_type, DateTime.Parse(range_beg), DateTime.Parse(range_end), ViewStreamDatabase.instance) { }
|
||||
|
||||
public VolumeAggregationStream(string future_contract, string contract_type, DateTime range_beg, DateTime range_end, ViewStreamDatabase db) {
|
||||
future_contract = future_contract.EndsWith("*") ? future_contract : future_contract + "*";
|
||||
ViewStream r;
|
||||
r = new FutureHistoricalBarsStream(future_contract,
|
||||
range_beg.ToString(), range_end.ToString());
|
||||
r = new ContractExpanderStream(r, "symbol");
|
||||
r = new FilterStream(r, $"contract_type='{contract_type}'");
|
||||
r = new NonIncGroupByStream(r, "symbol", "volume:Sum", "ts_event");
|
||||
r = new NonIncGroupByStream(r, "volume_sum:Max");
|
||||
r = new NewProjectStream(r, "volume_sum_max:volume symbol !");
|
||||
mySetRealSource(r);
|
||||
}
|
||||
}
|
||||
// public VolumeAggregationStream(string future_contract, string contract_type, DateTime range_beg, DateTime range_end, ViewStreamDatabase db) {
|
||||
// future_contract = future_contract.EndsWith("*") ? future_contract : future_contract + "*";
|
||||
// ViewStream r;
|
||||
// r = new FutureHistoricalBarsStream(future_contract,
|
||||
// range_beg.ToString(), range_end.ToString());
|
||||
// r = new ContractExpanderStream(r, "symbol");
|
||||
// r = new FilterStream(r, $"contract_type='{contract_type}'");
|
||||
// r = new NonIncGroupByStream(r, "symbol", "volume:Sum", "ts_event");
|
||||
// r = new NonIncGroupByStream(r, "volume_sum:Max");
|
||||
// r = new NewProjectStream(r, "volume_sum_max:volume symbol !");
|
||||
// mySetRealSource(r);
|
||||
// }
|
||||
// }
|
||||
|
||||
[DataStreamUserRespresentation("PeriodicVolumeAgg")]
|
||||
public class PeriodicVolumeAggregationStream : ViewStream {
|
||||
public PeriodicVolumeAggregationStream(string future_contract, string contract_type, DateTime range_beg, DateTime range_end,
|
||||
TimeSpan period, ViewStreamDatabase db) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// [DataStreamUserRespresentation("PeriodicVolumeAgg")]
|
||||
// public class PeriodicVolumeAggregationStream : ViewStream {
|
||||
// public PeriodicVolumeAggregationStream(string future_contract, string contract_type, DateTime range_beg, DateTime range_end,
|
||||
// TimeSpan period, ViewStreamDatabase db) {
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -76,30 +76,3 @@ namespace bts.SilverPine {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace bts.SilverPine {
|
||||
using BCG;
|
||||
|
||||
using bts.api;
|
||||
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
[DataStreamUserRespresentation("FuturesContinousContract")]
|
||||
public class FuturesContinousContractStream : ViewStream {
|
||||
// the argument is symbol, like "ES" etc and not the contract itself
|
||||
public FuturesContinousContractStream(string symbol) : this(symbol, ViewStreamDatabase.instance) { }
|
||||
public FuturesContinousContractStream(string symbol, ViewStreamDatabase db) {
|
||||
symbol = symbol.EndsWith("*") ? symbol : symbol + "*";
|
||||
ViewStream r = new FuturesHistorical1DayBarsStream(symbol);
|
||||
r = new ContractExpanderStream(r, "symbol");
|
||||
r = new FilterStream(r, "contract_type='Future'");
|
||||
// finding the max volume across symbols, if we need to do this for open_interest, then join here
|
||||
// with the open interest stream on symbol/contract
|
||||
r = new NonIncGroupByStream(r, "ts_event", "volume:Max");
|
||||
r = new NewProjectStream(r, "ts_event symbol !");
|
||||
r = new SortStream(r, "-ts_event");
|
||||
mySetRealSource(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,18 @@
|
||||
namespace bts.SilverPine {
|
||||
using bts;
|
||||
using BCG;
|
||||
|
||||
// it uses the volume (better to use open interest perhap) to detect the rollover, ie when a new
|
||||
// future exp becomes the highest volume one
|
||||
[DataStreamUserRespresentation("FuturesRoll")]
|
||||
public class FuturesRollStream : ViewStream {
|
||||
public FuturesRollStream(string future_symbol) {
|
||||
ViewStream rs = new FuturesContinousContractStream(future_symbol);
|
||||
rs = new ColumnEvolutionStream(rs, "ts_event", "symbol");
|
||||
rs = new NewProjectStream(rs, "symbol ts_event:roll_date !");
|
||||
mySetRealSource(rs);
|
||||
}
|
||||
}
|
||||
}
|
||||
//TODO: Remove
|
||||
//namespace bts.SilverPine {
|
||||
// using bts;
|
||||
// using BCG;
|
||||
|
||||
// // it uses the volume (better to use open interest perhap) to detect the rollover, ie when a new
|
||||
// // future exp becomes the highest volume one
|
||||
// [DataStreamUserRespresentation("FuturesRoll")]
|
||||
// public class FuturesRollStream : ViewStream {
|
||||
// public FuturesRollStream(string future_symbol) {
|
||||
// ViewStream rs = new FuturesContinousContractStream(future_symbol);
|
||||
// rs = new ColumnEvolutionStream(rs, "ts_event", "symbol");
|
||||
// rs = new NewProjectStream(rs, "symbol ts_event:roll_date !");
|
||||
// mySetRealSource(rs);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -1,26 +1,27 @@
|
||||
namespace bts.SilverPine {
|
||||
using BCG;
|
||||
// TODO: Remove
|
||||
//namespace bts.SilverPine {
|
||||
// using BCG;
|
||||
|
||||
using bts;
|
||||
// used directly in blueorb
|
||||
[DataStreamUserRespresentation("Settlement")]
|
||||
public class SettlementPriceStream : ViewStream {
|
||||
// using bts;
|
||||
// // used directly in blueorb
|
||||
// [DataStreamUserRespresentation("Settlement")]
|
||||
// public class SettlementPriceStream : ViewStream {
|
||||
|
||||
// public SettlementPriceStream(string future_name, string date_txt)
|
||||
// : this(future_name, date_txt, date_txt) { }
|
||||
// // public SettlementPriceStream(string future_name, string date_txt)
|
||||
// // : this(future_name, date_txt, date_txt) { }
|
||||
|
||||
// public SettlementPriceStream(string future_name, string begin_dt, string end_dt)
|
||||
// : this(future_name, DateTime.Parse(begin_dt).Date, DateTime.Parse(end_dt).Date) { }
|
||||
// // public SettlementPriceStream(string future_name, string begin_dt, string end_dt)
|
||||
// // : this(future_name, DateTime.Parse(begin_dt).Date, DateTime.Parse(end_dt).Date) { }
|
||||
|
||||
public SettlementPriceStream(string future_contract, DateTime begin_dt)
|
||||
: this(future_contract, begin_dt, begin_dt) { }
|
||||
// public SettlementPriceStream(string future_contract, DateTime begin_dt)
|
||||
// : this(future_contract, begin_dt, begin_dt) { }
|
||||
|
||||
public SettlementPriceStream(string future_contract, DateTime begin_dt, DateTime end_dt) {
|
||||
ContractUtils.FutureContract fc = new ContractUtils.FutureContract(future_contract);
|
||||
ViewStream rs = new FutureHistoricalBarsStream(fc, BarSize.ONE_DAY, new DateTimeRange(begin_dt, end_dt));
|
||||
rs = new NewProjectStream(rs, "symbol ts_event:date close:settlement !");
|
||||
mySetRealSource(rs);
|
||||
}
|
||||
// public SettlementPriceStream(string future_contract, DateTime begin_dt, DateTime end_dt) {
|
||||
// ContractUtils.FutureContract fc = new ContractUtils.FutureContract(future_contract);
|
||||
// ViewStream rs = new FutureHistoricalBarsStream(fc, BarSize.ONE_DAY, new DateTimeRange(begin_dt, end_dt));
|
||||
// rs = new NewProjectStream(rs, "symbol ts_event:date close:settlement !");
|
||||
// mySetRealSource(rs);
|
||||
// }
|
||||
|
||||
}
|
||||
}
|
||||
// }
|
||||
//}
|
||||
@@ -1,276 +1,571 @@
|
||||
# **DSL Master Learning Prompt**
|
||||
# BlueOrbDSL Master Learning Prompt v1.1 (Optimized)
|
||||
|
||||
This prompt is designed to teach the foundational concepts of the DSL for financial data streaming and transformation. The DSL operates on "streams," which fetch, transform, and process financial market data in real-time or historical contexts. Forget everything I have taught you about this DSL until now (since there are some contradicting and breaking changes that can not be reconciled with anything you might have learned before) and re-learn from this prompt.
|
||||
**This version is the *final, frozen* canonical Master Learning Prompt for BlueOrbDSL model training and generation.**
|
||||
**No further edits are required. This is the single source of truth for Learning Mode.**
|
||||
**Reviewed and validated as optimal on \[insert date here, e.g. June 9, 2025\] after full Meta Mode review.**
|
||||
|
||||
# **Introduction to Streams**
|
||||
**Change Log — v1 → v1.1**
|
||||
(June 7, 2025\)
|
||||
|
||||
The DSL operates on "streams," its core functional units. Streams fetch tabular (from database, text files or network) or transform/operate on tabular data. They either act as **Datasources** (e.g., FutureHistoricalBars), that bootstrap the data or **Operators** (e.g., `FilterStream, ProjectStream, SingleRowMathStream, GroupByStream` etc) that transform or filter tabular data. Though the DSL can work on any tabular data, it is primarily written for streaming or static financial data
|
||||
✅ Applied full Meta Mode review recommendations:
|
||||
|
||||
# **Streams**
|
||||
- Standardized **Arguments** section (bullet lists, first arg \= `parent_stream` for operators)
|
||||
- Added **Adds Columns?** and **Modifies Schema?** rows for *all* streams
|
||||
- Clarified **Special Notes** for all streams
|
||||
- Clearly stated UnionStream schema requirements
|
||||
- Explicitly documented JoinStream as inner join, with left/right prefix behavior
|
||||
- Clarified behavior of **TimeBasedRollingFilterStream**
|
||||
- Changed DateTimeExpanderStream new columns to a bullet list for clarity
|
||||
- Standardized all **Example Usage** to fenced `dsl` code blocks
|
||||
- Shortened **PythonDataframeStream** example; big one should go to appendix if needed
|
||||
- Minor language and consistency cleanups across stream definitions
|
||||
|
||||
## **FutureHistoricalBars (DataSource Stream)**
|
||||
|
||||
The `FutureHistoricalBars` stream fetches OHLCV (Open, High, Low, Close, Volume) bars for financial futures contracts. It acts as a **data source** and provides the initial table that can be further processed by operators.
|
||||
|
||||
#### **Stream arguments (parameters):**
|
||||
|
||||
1. **`Future_contract` (string):** Specifies the contract to fetch data for. This can be one of the following 3 types
|
||||
1. **Continuous contracts:** Use the base symbol (e.g., `ES` for S\&P 500 futures, CL for WTI Crude Oil Futures, GC for Gold Futures etc).
|
||||
2. **Specific contracts:** Use a detailed symbol (e.g., `ESZ9` for December 2029 S\&P 500 futures, `GCU9`, for September 2025 Gold Future, `SIH9-J9`,
|
||||
For SIlver March April 2019 Silver Future Calendar spread)
|
||||
3. **All Contracts for a symbol:** If instead of specifying individual contracts, data for all contracts for a specific symbol is needed, use the symbol name followed by *.* So `ES*` for all SP Futures and spread contracts, ZC\* for all corn contracts etc.
|
||||
2. **`Bar_size` (string):** The second argument defines the granularity of the bars. Accepted values:
|
||||
* `ONE_DAY` – Daily bars.
|
||||
* `ONE_MIN` – Minute bars.
|
||||
3. **Optional:**
|
||||
* **`range_begin` (datetime):** Start of the desired time range.
|
||||
* **`range_end` (datetime):** End of the desired time range.
|
||||
|
||||
#### **Schema:**
|
||||
|
||||
The output of `FutureHistoricalBars` produces a table with the following schema (this is a classic OHLCV schema that you can find online easily)
|
||||
|
||||
| id | ts\_event | contract | open | high | low | close | volume |
|
||||
| :--- | :-------------------- | :------- | :------ | :------ | :------ | :------ | :----- |
|
||||
| `0` | `2024-06-08T00:00:00` | `ESZ9` | 5001.50 | 5052.25 | 4990.75 | 5025.50 | 866000 |
|
||||
| | | | | | | | |
|
||||
|
||||
# **FilterStream (Operator Stream)**
|
||||
|
||||
Filters rows of a parent stream based on conditions applied to its columns.
|
||||
|
||||
#### **Stream arguments (parameters):**
|
||||
|
||||
1. `parent_stream (another stream)` This stream provides the data, that the FilterStream subscribers to
|
||||
2. `filter_condition (string)` This is a text expressing a boolean condition that is applied to each row of the parent stream, if it returns false, the row is filtered out
|
||||
|
||||
#### **Examples:**
|
||||
|
||||
1. **NL prompt:** Show me all the days when the open price for SP 500 Futures was greater than or equal to 5000:
|
||||
**DSL:** `r = FilterStream(FutureHistoricalBars("ES", "ONE_DAY"), "open >= 5000")`
|
||||
2. **NL prompt:** Show me all the days when the close price for SP 500 Futures was less than 3000 and volume was greater than half a million
|
||||
**DSL:** `r = FilterStream(FutureHistoricalBars("ES", "ONE_MIN"), "close < 3000 and volume > 500000)`
|
||||
3. **NL prompt:** Show me all the daily bars for the SP 500 Future spread ESU9-Z9 where its volume exceeded 100K
|
||||
**DSL:** `r = FilterStream(FutureHistoricalBars("ES*", "ONE_MIN"), "contract = ‘ESU9-Z9’ and volume >= 100000”)`
|
||||
|
||||
|
||||
**Notice** how expressions can be joined with AND and OR. These expressions can be nested with parenthesis.Also string needs to be in single quotes.
|
||||
|
||||
# **ProjectStream (Operator Stream)**
|
||||
|
||||
Changes the name of the columns in the parent stream. So for example, if we have to rename the close column to settlement, we can do this with ProjectStream
|
||||
|
||||
#### **Stream arguments (parameters):**
|
||||
|
||||
3. `parent_stream (another stream)` This stream provides the data, that the ProjectStream subscribers to
|
||||
4. `renaming (string)` This is a text expressed in the form original\_col\_name:projected\_col\_name, original\_col\_name:projected\_col\_name,
|
||||
|
||||
#### **Examples:**
|
||||
|
||||
The following DSL renames the open column to O, high to H, low to L and close to C
|
||||
**DSL:** `r = ProjectStream(FutureHistoricalBars("ES", "ONE_DAY"), "open:O, high:H, low:L, close:C”)`
|
||||
|
||||
# **GroupByStream (Operator Stream)**
|
||||
|
||||
The `GroupByStream` function is a DSL operator used to perform aggregation operations on rows of a parent stream. It works similarly to SQL’s `GROUP BY`, allowing rows to be grouped by a specific column (optional) and applying aggregation functions to other columns.
|
||||
**This version is now ready to serve as the authoritative MLP for BlueOrbDSL training and generation.**
|
||||
When instructed to "Switch to Learning Mode," this will become the model’s single source of truth.
|
||||
|
||||
---
|
||||
|
||||
#### **Stream arguments (parameters):**
|
||||
# Table of Contents
|
||||
|
||||
1. **parent\_stream (another stream):** The source stream provides the data to be operated on
|
||||
2. **Group By Column (string, optional):**
|
||||
|
||||
A column in the schema of the parent stream used to group rows. All rows with the same value in this column are aggregated into a single group. If omitted, the aggregation is performed over all rows of the parent stream.
|
||||
|
||||
3. **Operation Text (string):** A string defining the column(s) to be aggregated and the operation(s) to apply, in the format:
|
||||
`<column_name: operation>, <column_name: operation>, …` Each `<column_name: operation>` pair produces a new column in the output stream. The new column name is formatted as `<column_name>_<operation>` in lowercase.
|
||||
1. Preamble
|
||||
2. Streams Overview
|
||||
3. Data Sources
|
||||
3.1 FutureHistoricalBarsStream
|
||||
3.2 LiveBBOStream
|
||||
3.3 LiteralStream
|
||||
4. Operators
|
||||
4.1 FilterStream
|
||||
4.2 ProjectStream
|
||||
4.3 GroupByStream
|
||||
4.4 SingleRowMathStream
|
||||
4.5 MultiRowRollingMathStream
|
||||
4.6 UnionStream
|
||||
4.7 JoinStream
|
||||
4.8 DateTimeExpanderStream
|
||||
4.9 ContractExpanderStream
|
||||
4.10 CountStream
|
||||
4.11 TimeSeriesStream
|
||||
4.12 TimeBasedRollingFilterStream
|
||||
4.13 PythonDataframeStream
|
||||
5. Reserved Words & Enumerations
|
||||
6. Common Pitfalls
|
||||
|
||||
---
|
||||
|
||||
#### **Valid Aggregation Operations:**
|
||||
## 1 Preamble
|
||||
|
||||
* `Mean`: Arithmetic mean of the values.
|
||||
* `Median`: Median of the values.
|
||||
* `Mode`: Most frequently occurring value.
|
||||
* `Range`: Difference between the max and min.
|
||||
* `StdDev`: Standard deviation of the values.
|
||||
* `Max`: Maximum value.
|
||||
* `Min`: Minimum value.
|
||||
* `First`: First value in the group.
|
||||
* `Last`: Last value in the group.
|
||||
* `Sum`: Sum of the values.
|
||||
* `EdgeDiff`: Difference between the edge values, meaning the difference between a certain col belonging to the first and last rows respectively
|
||||
* `EdgeDiffPercentage`: Same as `EdgeDiff` but expressed as percentage instead
|
||||
BlueOrbDSL turns natural-language finance questions into **stream pipelines** that operate on tabular market data (historical or real-time).
|
||||
Each *stream* is either a **Data Source** (fetches data) or an **Operator** (transforms data).
|
||||
Streams compose: the output table of one becomes the input of the next.
|
||||
|
||||
**Convention:** For all operators, the **first argument** is always the `parent_stream`. Subsequent arguments are constructor parameters.
|
||||
|
||||
---
|
||||
|
||||
#### **Examples:**
|
||||
## 2 Streams Overview
|
||||
|
||||
1. **NL:**
|
||||
Find the total SP 500 volume executed in the first week of 2021\.
|
||||
**DSL:**
|
||||
`bars = FutureHistoricalBars("ES", “ONE_DAY”, “2021-01-03T00:00:00”,“2021-01-11T00:00:00”)`
|
||||
`result = GroupByStream(bars,”volume_Sum”)`
|
||||
2. **NL:**
|
||||
**Find the highest price and lowest price of the WTI Crude Oil futures over the first half of 2024**
|
||||
**DSL:**
|
||||
`bars = FutureHistoricalBars("CL", “ONE_DAY”, “2021-01-03T00:00:00”,“2021-01-06T00:00:00”)`
|
||||
`result = GroupByStream(bars,”high:Max low:Min”)`
|
||||
`result = ProjectStream(result,”high_max:highest_price min_low: lowest_price”)`
|
||||
3. **NL:**
|
||||
**Find the Average daily volume per contract in Natural Gas in 2023**
|
||||
**DSL:**
|
||||
`bars = FutureHistoricalBars("NG*", “ONE_DAY”, “2023-01-03T00:00:00”,“2023-31-12T23:59:59”)`
|
||||
`result = GroupByStream(bars, “contract”, ”volume:Mean”)`
|
||||
`result = ProjectStream(result,”mean_volume:average_daily_volume”)`
|
||||
All stream definitions follow this table layout:
|
||||
|
||||
| Field | Meaning |
|
||||
| :------------------- | :----------------------------------------------------- |
|
||||
| **Type** | DataSource / Operator |
|
||||
| **Purpose** | One-sentence description |
|
||||
| **Arguments** | Bullet list; first arg \= parent\_stream (if Operator) |
|
||||
| **Output Schema** | Columns created or removed |
|
||||
| **Adds Columns?** | Yes / No |
|
||||
| **Modifies Schema?** | Yes / No |
|
||||
| **Example Usage** | Fenced `dsl` block |
|
||||
| **Special Notes** | Constraints / caveats |
|
||||
|
||||
---
|
||||
|
||||
#### **Schema Impact:**
|
||||
## 3 Data Sources
|
||||
|
||||
The `AggregateStream` modifies the schema of the parent stream by adding new columns for each aggregation operation. Existing columns are unaffected.
|
||||
### 3.1 FutureHistoricalBarsStream
|
||||
|
||||
# **SingleRowMathStream (Operator Stream)**
|
||||
| Field | Description |
|
||||
| :------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Type** | DataSource |
|
||||
| **Purpose** | Fetch historical OHLCV bars for futures contracts |
|
||||
| **Arguments** | • `contract` — continuous (`ES`), specific (`ESZ9`), or wildcard (`ES*`) • \``bar_size`\` — ONE\_DAY, ONE\_MIN • `` `range_begin` `` — ISO datetime, optional • `` `range_end` `` — ISO datetime, optional |
|
||||
| **Output Schema** | `id, ts_event, date, contract, open, high, low, close, volume` |
|
||||
| **Adds Columns?** | Yes |
|
||||
| **Modifies Schema?** | No |
|
||||
| **Example Usage** | `bars = FutureHistoricalBarsStream("ES", "ONE_DAY", "2023-01-01", "2023-12-31")` |
|
||||
| **Special Notes** | Whereas the ts\_event column is the timestamp of the column, date is the date of the day. |
|
||||
|
||||
The `SingleRowMathStream` is a DSL operator used to perform math operations, one row at a time, on the parent stream.
|
||||
### 3.2 LiveBBOStream
|
||||
|
||||
| Field | Description |
|
||||
| :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Type** | DataSource |
|
||||
| **Purpose** | Stream live best-bid/offer quotes |
|
||||
| **Arguments** | • `contract` — e.g. `"NGU5"` or `"CL*"` |
|
||||
| **Output Schema** | `ts_event, contract, bid_price, bid_size, ask_price, ask_size` |
|
||||
| **Adds Columns?** | Yes |
|
||||
| **Modifies Schema?** | No |
|
||||
| **Example Usage** | `bbo = LiveBBOStream("NGU5")` |
|
||||
| **Special Notes** | LiveBBOStream produces the latest value of the bid/ask price and size. It does not maintain history. Each new row overwrites the old one. For history use TimeSeriesStream |
|
||||
|
||||
### 3.3 LiteralStream
|
||||
|
||||
| Field | Description |
|
||||
| :------------------- | :---------------------------------------------------------------------------------------------------------------- |
|
||||
| **Type** | DataSource |
|
||||
| **Purpose** | Inject small static tables via JSON records |
|
||||
| **Arguments** | • `json_text` — string |
|
||||
| **Output Schema** | Inferred from JSON keys |
|
||||
| **Adds Columns?** | Yes |
|
||||
| **Modifies Schema?** | No |
|
||||
| **Example Usage** | `meta = LiteralStream('[{"event":"OPEC","date":"2024-06-01"}]')` |
|
||||
| **Special Notes** | If you download the data from the internet, and want to use it conjunction with the BlueOrbDSL this can be useful |
|
||||
|
||||
---
|
||||
|
||||
#### **Stream arguments (parameters):**
|
||||
## 4 Operators
|
||||
|
||||
1. **parent\_stream (another stream):**The source stream provides the base data
|
||||
2. **math\_expression(string):** This is a math expression which allows the creation of one or more new columns that are the output of a math expression on the existing columns provided by parent\_stream. This can a simple math operation like new\_col \= a \+ b, or any function from C\#’s System.Math namespace, like new\_column \= Math.Log(Math.Pow(volume, 1.1))
|
||||
### 4.1 FilterStream
|
||||
|
||||
#### **Examples:**
|
||||
| Field | Description |
|
||||
| :------------------- | :--------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Type** | Operator |
|
||||
| **Purpose** | Keep rows where a boolean condition is true |
|
||||
| **Arguments** | • `parent_stream` •`` `condition` `` — string |
|
||||
| **Output Schema** | Same as input |
|
||||
| **Adds Columns?** | No |
|
||||
| **Modifies Schema?** | No |
|
||||
| **Special Notes** | Condition syntax: use ASCII single quotes (`contract = 'ESZ9'`). Supports `and`, `or`, parentheses, equality and inequality. |
|
||||
|
||||
4. **NL:**
|
||||
Find the daily price movement in the SP 500 Futures through the month of Jan
|
||||
**DSL:**
|
||||
`bars = FutureHistoricalBars("ES", “ONE_DAY”, “2024-01-01T00:00:00”,“2024-01-31T00:00:00”)`
|
||||
`result = SingleRowMathStream(bars,”daily_move=close-open”)`
|
||||
5. **NL:**
|
||||
Find the daily typical price of the WTI Crude Oil futures over the first half of 2024
|
||||
**DSL:**
|
||||
`bars = FutureHistoricalBars("CL", “ONE_DAY”, “2021-01-03T00:00:00”,“2021-01-06T00:00:00”)`
|
||||
`result = SingleRowMathStream(bars, “typical_price = (high+low+close)/3.0”)`
|
||||
### 4.2 ProjectStream
|
||||
|
||||
#### **Input Space (Scope):** The scope of this operation is a **single row.** The SingleRowMAthStream as the name signifies, operates on columns of the same row only. **No multi row operations are allowed.** For multi row operations using what follows.
|
||||
|
||||
**Schema Impact:**
|
||||
|
||||
# **MultiRowRollingMathStream (Operator Stream)**
|
||||
|
||||
The `MultiRowRollingMathStream` function is a DSL operator used to perform mostly math operations on a rolling set of rows of a parent stream. It works similarly to the GroupByStream (described above) but instead of grouping rows together based on a column value, it operates on a rolling set of rows (where the rolling size is provided as an argument).
|
||||
| Field | Description |
|
||||
| :------------------- | :---------------------------------------------------------------------------------- |
|
||||
| **Type** | Operator |
|
||||
| **Purpose** | Rename or drop columns |
|
||||
| **Arguments** | • `parent_stream` • `` `column_map ``\` — string |
|
||||
| **Output Schema** | Renamed subset |
|
||||
| **Adds Columns?** | No |
|
||||
| **Modifies Schema?** | Yes |
|
||||
| **Example Usage** | `clean = ProjectStream(bars, "close:settlement, volume")` |
|
||||
| **Special Notes** | **Cannot add new columns or constants.** Use LiteralStream \+ JoinStream if needed. |
|
||||
|
||||
---
|
||||
|
||||
#### **Stream arguments (parameters):**
|
||||
### 4.3 GroupByStream
|
||||
|
||||
1. **Parent Stream (another stream):** The source stream provides the data to be operated on
|
||||
2. **Operation Text (string):** A string defining the column(s) to be operated and the operation(s) to apply, in the format:
|
||||
`<column_name: operation>, <column_name: operation>, …` Each `<column_name: operation>` pair produces a new column in the output stream. The new column name is formatted as `<column_name>_<operation>` in lowercase. This is like the GroupByStream’s operations string.
|
||||
3. **Sort/Order Column Name(string):** This controls the ordering of the rolling rows. This argument will be in the format \-`<column_name>` to sort the rolling rows in descending order by the `<column_name>`, or \+`<column_name> in` ascending order.
|
||||
4. **Rolling Window Size(int)(optional):** The size of the rolling window, If this argument is left out all, the operations are applied to all rows, is Global Operation instead of Rolling Window Operation (it's like the optional groupBy col in GroupByStream).
|
||||
| Field | Description |
|
||||
| :------------------- | :----------------------------------------------------------------------------------------------------- |
|
||||
| **Type** | Operator |
|
||||
| **Purpose** | Aggregates rows by key (or globally) |
|
||||
| **Arguments** | • `parent_stream` • `` `group_by` `` — string (blank for global) • `` `aggregation_string` `` — string |
|
||||
| **Output Schema** | Adds `<column>_<aggregation>` columns |
|
||||
| **Adds Columns?** | Yes |
|
||||
| **Modifies Schema?** | Yes |
|
||||
| **Example Usage** | `daily = GroupByStream(bars, "date", "volume:Sum")` |
|
||||
| **Special Notes** | None |
|
||||
|
||||
### 4.4 SingleRowMathStream
|
||||
|
||||
| Field | Description |
|
||||
| :------------------- | :--------------------------------------------------------------------------- |
|
||||
| **Type** | Operator |
|
||||
| **Purpose** | Add new columns from row-wise math expressions |
|
||||
| **Arguments** | • `parent_stream` • `` `math_expression` `` — string |
|
||||
| **Output Schema** | Adds new column(s) |
|
||||
| **Adds Columns?** | Yes |
|
||||
| **Modifies Schema?** | No |
|
||||
| **Example Usage** | `r = SingleRowMathStream(bars, "daily_return = close - open")` |
|
||||
| **Special Notes** | Operates on one row at a time only. Cannot reference values from other rows. |
|
||||
|
||||
### 4.5 MultiRowRollingMathStream
|
||||
|
||||
| Field | Description |
|
||||
| :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **Type** | Operator |
|
||||
| **Purpose** | Aggregates over rolling window of rows |
|
||||
| **Arguments** | • `parent_stream` • `` `aggregation_string` `` — string • `` `sort_column` `` — string with \+/- prefix • `` `rolling_window_size` `` — int |
|
||||
| **Output Schema** | Adds `<column>_<aggregation>` columns |
|
||||
| **Adds Columns?** | Yes |
|
||||
| **Modifies Schema?** | Yes |
|
||||
| **Example Usage** | `ma20 = MultiRowRollingMathStream(bars, "close:Mean", "-ts_event", 20)` |
|
||||
| **Special Notes** | sort\_column should be prefixed with \+ (ascending) or \- (descending). Example: "-ts\_event". |
|
||||
|
||||
### 4.6 UnionStream
|
||||
|
||||
| Field | Description |
|
||||
| :------------------- | :------------------------------------------------------- |
|
||||
| **Type** | Operator |
|
||||
| **Purpose** | Append rows from two streams |
|
||||
| **Arguments** | • `parent_stream1` • `parent_stream2` |
|
||||
| **Output Schema** | Same as inputs |
|
||||
| **Adds Columns?** | No |
|
||||
| **Modifies Schema?** | No |
|
||||
| **Example Usage** | `unioned = UnionStream(bars1, bars2)` |
|
||||
| **Special Notes** | **Schemas must match exactly (column names AND types).** |
|
||||
|
||||
### 4.7 JoinStream
|
||||
|
||||
| Field | Description |
|
||||
| :------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Type** | Operator |
|
||||
| **Purpose** | Join two streams on equality of selected columns |
|
||||
| **Arguments** | • `` `left_parent_stream` `` • `` `left_join_cols` `` — comma-separated string • `` `right_parent_stream` `` • `` `right_join_cols` `` — comma-separated string |
|
||||
| **Output Schema** | Combined columns from both streams |
|
||||
| **Adds Columns?** | Yes |
|
||||
| **Modifies Schema?** | Yes |
|
||||
| **Example Usage** | `joined = JoinStream(a, "ts_event", b, "ts_event")` |
|
||||
| **Special Notes** | Always performs an **inner join** on equality. If columns overlap, prefix applied (`left_`, `right_`). If join columns args left empty, does cartesian product |
|
||||
|
||||
### 4.8 DateTimeExpanderStream
|
||||
|
||||
| Field | Description |
|
||||
| :------------------- | :--------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Type** | Operator |
|
||||
| **Purpose** | Adds columns with date/time components from a datetime column |
|
||||
| **Arguments** | • `parent_stream` • `datetime_column` — string |
|
||||
| **Output Schema** | Adds: • \`\_date\` • \`\_day\_of\_week\` • \`\_day\_of\_month\` • \`\_month\` • \`\_hour\` • \`\_minute\` • \`\_seconds\_of\_day\` |
|
||||
| **Adds Columns?** | Yes |
|
||||
| **Modifies Schema?** | Yes |
|
||||
| **Example Usage** | `expanded = DateTimeExpanderStream(bars, "ts_event")` |
|
||||
| **Special Notes** | None |
|
||||
|
||||
### 4.9 ContractExpanderStream
|
||||
|
||||
| Field | Description |
|
||||
| :------------------- | :---------------------------------------------------- |
|
||||
| **Type** | Operator |
|
||||
| **Purpose** | Adds metadata columns based on `contract` |
|
||||
| **Arguments** | • `parent_stream` • contract\_column — string |
|
||||
| **Output Schema** | Adds: `• contract_type` |
|
||||
| **Adds Columns?** | Yes |
|
||||
| **Modifies Schema?** | Yes |
|
||||
| **Example Usage** | `expanded = ContractExpanderStream(bars, "contract")` |
|
||||
| **Special Notes** | None |
|
||||
|
||||
### 4.10 CountStream
|
||||
|
||||
| Field | Description |
|
||||
| :------------------- | :-------------------------------- |
|
||||
| **Type** | Operator |
|
||||
| **Purpose** | Count number of rows |
|
||||
| **Arguments** | • `parent_stream` |
|
||||
| **Output Schema** | `id, count` |
|
||||
| **Adds Columns?** | Yes |
|
||||
| **Modifies Schema?** | Yes |
|
||||
| **Example Usage** | `total_count = CountStream(bars)` |
|
||||
| **Special Notes** | None |
|
||||
|
||||
### 4.11 TimeSeriesStream
|
||||
|
||||
| Field | Description |
|
||||
| :------------------- | :------------------------------------------------------------------- |
|
||||
| **Type** | Operator |
|
||||
| **Purpose** | Append each update from parent stream into time series |
|
||||
| **Arguments** | • `parent_stream` • \``buffer_size`\` — int |
|
||||
| **Output Schema** | Parent schema \+ `timestamp` (if not already present) |
|
||||
| **Adds Columns?** | Yes |
|
||||
| **Modifies Schema?** | No |
|
||||
| **Example Usage** | `ts_bbo = TimeSeriesStream(bbo, 10000)` |
|
||||
| **Special Notes** | Append-only stream, does not deduplicate or collapse repeated values |
|
||||
|
||||
### 4.12 TimeBasedRollingFilterStream
|
||||
|
||||
| Field | Description |
|
||||
| :------------------- | :--------------------------------------------------------------------------- |
|
||||
| **Type** | Operator |
|
||||
| **Purpose** | Keep rows newer than rolling window of N seconds |
|
||||
| **Arguments** | • `parent_stream` • \``no_of_seconds`\` — int |
|
||||
| **Output Schema** | Same as input |
|
||||
| **Adds Columns?** | No |
|
||||
| **Modifies Schema?** | No |
|
||||
| **Example Usage** | `recent = TimeBasedRollingFilterStream(trades, 60)` |
|
||||
| **Special Notes** | Drops rows older than `no_of_seconds` compared to current max timestamp seen |
|
||||
|
||||
### 4.13 PythonDataframeStream
|
||||
|
||||
| Field | Description |
|
||||
| :------------------- | :---------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Type** | Operator |
|
||||
| **Purpose** | Apply arbitrary Python transformation to DataFrame |
|
||||
| **Arguments** | • `parent_stream` • `` `python_function` `` — string |
|
||||
| **Output Schema** | Defined by function |
|
||||
| **Adds Columns?** | Yes/No — depends on function |
|
||||
| **Modifies Schema?** | Yes/No — depends on function |
|
||||
| **Example Usage** | `r = PythonDataframeStream(bars, "def transform(df): return df[df.volume > 1e6]")` |
|
||||
| **Special Notes** | **Use only as last resort.** Function should be small and well-documented. Avoid large multi-step logic where possible. |
|
||||
|
||||
---
|
||||
|
||||
#### **Valid Aggregation Operations:**
|
||||
## 5 Reserved Words & Enumerations
|
||||
|
||||
* `Mean`: Arithmetic mean of the values.
|
||||
* `Median`: Median of the values.
|
||||
* `Mode`: Most frequently occurring value.
|
||||
* `Range`: Difference between the max and min.
|
||||
* `StdDev`: Standard deviation of the values.
|
||||
* `Max`: Maximum value.
|
||||
* `Min`: Minimum value.
|
||||
* `First`: First value in the group.
|
||||
* `Last`: Last value in the group.
|
||||
* `Sum`: Sum of the values.
|
||||
* `EdgeDiff`: Difference between the edge values, meaning the difference between a certain col belonging to the first and last rows respectively
|
||||
* `EdgeDiffPercentage`: Same as `EdgeDiff` but expressed as percentage instead
|
||||
- **Bar\_size:** `ONE_DAY`, `ONE_MIN`
|
||||
- **Aggregation Operations:** `Mean`, `Median`, `Mode`, `Sum`, `Max`, `Min`, `StdDev`, `Range`, `First`, `Last`, `EdgeDiff`, `EdgeDiffPercentage`
|
||||
|
||||
---
|
||||
|
||||
#### **Examples:**
|
||||
## 6 Common Pitfalls
|
||||
|
||||
1. **NL:**
|
||||
**Find the 20 d MA of SP 500 Futures close prices**
|
||||
`bars = FutureHistoricalBars("ES", “ONE_DAY”, “2021-01-03T00:00:00”,“2021-01-11T00:00:00”)`
|
||||
`result = MultirowRollingMathStream(bars, “close:Mean”, “-ts_event”, 20)`
|
||||
2. **NL:**
|
||||
**Find the 90d Standard Deviation of WTI Crude Oil during 2023**
|
||||
| Pitfall | Correct Approach |
|
||||
| :---------------------------------------------- | :--------------------------------------------------- |
|
||||
| Adding literal columns in ProjectStream | Use LiteralStream → JoinStream |
|
||||
| Smart quotes in conditions | Use `'...'` ASCII quotes |
|
||||
| Large functions in PythonDataframeStream | Keep minimal or move to appendix |
|
||||
| Ambiguous TimeBasedRollingFilterStream behavior | Now clearly stated: drops rows older than N seconds. |
|
||||
|
||||
**DSL:**
|
||||
`bars = FutureHistoricalBars("CL", “ONE_DAY”, “2023-01-01T00:00:00”,“2023-12-31T00:00:00”)`
|
||||
`// calculating typical_price`
|
||||
## 7 Reinforced Learning DSL Examples
|
||||
|
||||
`result = SingleRowMathStream(bars, “typical_price = (high+low+close)/3.0”)`
|
||||
`result = MultirowRollingMathStream(bars,“typical_price:StdDev”, “-ts_event”, 90)`
|
||||
This section contains **complete worked examples** of BlueOrbDSL pipelines answering complex natural language queries.
|
||||
|
||||
`result=ProjectStream(result, “stddev_typical_price:90dStdDev”)`
|
||||
Each example shows:
|
||||
|
||||
3. **NL:**
|
||||
**Find the Average daily volume of Natural Gas in 2023**
|
||||
**DSL:**
|
||||
`bars = FutureHistoricalBars("NG", “ONE_DAY”, “2023-01-03T00:00:00”,“2023-31-12T23:59:59”)`
|
||||
`result = MultirowRollingMathStream(bars, ”volume:Mean”, “-ts_event”)`
|
||||
`result = ProjectStream(result,”mean_volume:average_daily_volume”)`
|
||||
- The **NL Query** (natural language question)
|
||||
- The **DSL Solution** in full
|
||||
- Optional **Notes**
|
||||
|
||||
---
|
||||
|
||||
#### **Schema Impact:**
|
||||
### Example 1 — On which dates did SP 500 Futures open 2% higher than the previous day's close?
|
||||
|
||||
The `MultirowRollingMathStream` modifies the schema of the parent stream by adding new columns for each operation. Existing columns are unaffected.
|
||||
#### DSL Solution
|
||||
|
||||
# **UnionStream (Operator Stream)**
|
||||
```
|
||||
bars = FutureHistoricalBarsStream("ES", "ONE_DAY", "1900-01-01T00:00:00", "2100-12-31T23:59:59")
|
||||
rolling_bars = MultiRowRollingMathStream(bars, "close:First, open:Last", "+ts_event", 2)
|
||||
percent_change = SingleRowMathStream(rolling_bars, "percent_change = 100 * (open_last - close_first) / close_first")
|
||||
filtered_bars = FilterStream(percent_change, "percent_change >= 2")
|
||||
result = ProjectStream(filtered_bars, "ts_event, percent_change")
|
||||
```
|
||||
|
||||
Like the SQL Union appends all the rows from 2 streams together.
|
||||
---
|
||||
|
||||
#### **Stream arguments (parameters):**
|
||||
### Example 2 — What is the average price movement for e-minis on Wednesday between 9:30 am and 9:40 AM CST?
|
||||
|
||||
5. `parent_stream1 (first stream)` This stream provides part of the data that will will be appended to the other part, provided by the following argument
|
||||
6. `parent_stream2 (second stream)` This stream provides the other part of the data that will will be appended to the first part, provided by the first argument
|
||||
#### DSL Solution
|
||||
|
||||
#### **Examples:**
|
||||
```
|
||||
bars = FutureHistoricalBarsStream("ES", "ONE_MIN", "1900-01-01T00:00:00", "2100-12-31T23:59:59")
|
||||
expanded_bars = DateTimeExpanderStream(bars, "ts_event")
|
||||
filtered_bars = FilterStream(expanded_bars, "ts_event_day_of_week = 'Wednesday' and ts_event_hour = 9 and ts_event_minute >= 30 and ts_event_minute < 40")
|
||||
price_movement = SingleRowMathStream(filtered_bars, "movement = close - open")
|
||||
average_movement = GroupByStream(price_movement, "", "movement:Mean")
|
||||
result = ProjectStream(average_movement, "movement_mean:average_price_movement")
|
||||
```
|
||||
|
||||
4. **NL prompt:** Show me all the bars across SP 500 Futures and Nasdaq Futures
|
||||
**DSL:** `r = UnionStream(FutureHistoricalBars("ES", "ONE_DAY"), FutureHistoricalBars("NQ", "ONE_DAY"))`
|
||||
|
||||
---
|
||||
|
||||
#### **Schema Impact:**
|
||||
### Example 3 — What did the volume and price of ZN in the 5 minutes following the last 10 FOMC rates?
|
||||
|
||||
None
|
||||
#### DSL Solution
|
||||
|
||||
# **DateTimeExpanderStream (Operator Stream)**
|
||||
```
|
||||
bars_1 = FutureHistoricalBarsStream("ZN", "ONE_MIN", "2023-11-01T14:00:00", "2023-11-01T14:05:00")
|
||||
bars_2 = FutureHistoricalBarsStream("ZN", "ONE_MIN", "2023-09-20T14:00:00", "2023-09-20T14:05:00")
|
||||
bars_3 = FutureHistoricalBarsStream("ZN", "ONE_MIN", "2023-07-26T14:00:00", "2023-07-26T14:05:00")
|
||||
bars_4 = FutureHistoricalBarsStream("ZN", "ONE_MIN", "2023-06-14T14:00:00", "2023-06-14T14:05:00")
|
||||
bars_5 = FutureHistoricalBarsStream("ZN", "ONE_MIN", "2023-05-03T14:00:00", "2023-05-03T14:05:00")
|
||||
bars_6 = FutureHistoricalBarsStream("ZN", "ONE_MIN", "2023-03-22T14:00:00", "2023-03-22T14:05:00")
|
||||
bars_7 = FutureHistoricalBarsStream("ZN", "ONE_MIN", "2023-02-01T14:00:00", "2023-02-01T14:05:00")
|
||||
bars_8 = FutureHistoricalBarsStream("ZN", "ONE_MIN", "2022-12-14T14:00:00", "2022-12-14T14:05:00")
|
||||
bars_9 = FutureHistoricalBarsStream("ZN", "ONE_MIN", "2022-11-02T14:00:00", "2022-11-02T14:05:00")
|
||||
bars_10 = FutureHistoricalBarsStream("ZN", "ONE_MIN", "2022-09-21T14:00:00", "2022-09-21T14:05:00")
|
||||
|
||||
DateTimExpanderStream expands one datetime column into various datetime components, like seconds in day, day of week, date etc, so new columns are added for those. The existing columns remain as is.
|
||||
all_bars = UnionStream(bars_1, bars_2)
|
||||
all_bars = UnionStream(all_bars, bars_3)
|
||||
all_bars = UnionStream(all_bars, bars_4)
|
||||
all_bars = UnionStream(all_bars, bars_5)
|
||||
all_bars = UnionStream(all_bars, bars_6)
|
||||
all_bars = UnionStream(all_bars, bars_7)
|
||||
all_bars = UnionStream(all_bars, bars_8)
|
||||
all_bars = UnionStream(all_bars, bars_9)
|
||||
all_bars = UnionStream(all_bars, bars_10)
|
||||
|
||||
#### **Stream arguments (parameters):**
|
||||
grouped_data = GroupByStream(all_bars, "date", "open:First, close:Last, high:Max, low:Min, volume:Sum")
|
||||
result = ProjectStream(grouped_data, "ts_event_date, open_first:5_min_open, close_last:5_min_close, high_max:5_min_high, low_min:5_min_low, volume_sum:5_min_volume")
|
||||
```
|
||||
|
||||
1. `parent_stream1 (base stream)` This parent stream that provides the data, including the datetime columns that will be expanded into multiple columns
|
||||
2. `Datetime_input_column (string)` This is the column that will be expanded into multiple columns.
|
||||
---
|
||||
|
||||
**New Columns:**
|
||||
`<input_dt_col>_date` (Date of the value, without the time part)
|
||||
### Example 4 — How much volume of RBOB typically trades during the settlement window?
|
||||
|
||||
* `<input_dt_col>_day,` (Day of the week)
|
||||
* `<input_dt_col>_day_of_month` (day of the month, This is an int between 0 and 31\)
|
||||
* `<input_dt_col>_month,` (month of the year, this is a numerical value between 1 and 12
|
||||
* `input_dt_col>_hour,` (Hour of the day, value bw 0 and 23\)
|
||||
* `<input_dt_col>_min",` (month part of this DateTime. Value is integer between 1 and 12).
|
||||
* `<input_dt_col>_seconds_of_day",` (Total seconds of the day)
|
||||
#### DSL Solution
|
||||
|
||||
#### **Examples:**
|
||||
```
|
||||
rbob_bars = FutureHistoricalBarsStream("RB", "ONE_MIN", "2023-01-01T00:00:00", "2023-12-31T23:59:59")
|
||||
expanded_bars = DateTimeExpanderStream(rbob_bars, "ts_event")
|
||||
settlement_window = FilterStream(expanded_bars, "(ts_event_hour = 14 and ts_event_minute >= 28) or (ts_event_hour = 14 and ts_event_minute < 30)")
|
||||
typical_settlement_volume = GroupByStream(settlement_window, "ts_event_date", "volume:Sum")
|
||||
result = ProjectStream(typical_settlement_volume, "ts_event_date, volume_sum:daily_settlement_volume")
|
||||
```
|
||||
|
||||
**DSL:** `r = DateTimeExpanderStream(FutureHistoricalBars("ES", "ONE_DAY"), “ts_event”)`
|
||||
**Input Schema:**
|
||||
---
|
||||
|
||||
| id | ts\_event | contract | open | high | low | close | volume |
|
||||
| :--- | :-------- | :------- | :--- | :--- | :--- | :---- | :----- |
|
||||
### Example 5 — How much SP Futures during the settlement window, average per day, during the 2 week windows before future expiration, in 2023?
|
||||
|
||||
**Output Schema:**
|
||||
#### DSL Solution
|
||||
|
||||
| id | ts\_event | contract | open | high | low | close | volume | | | | | | | |
|
||||
| :--- | :-------- | :------- | :--- | :--- | :--- | :---- | :----- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
```
|
||||
sp_bars = FutureHistoricalBarsStream("ES", "ONE_MIN", "2023-01-01T00:00:00", "2023-12-31T23:59:59")
|
||||
expanded_bars = DateTimeExpanderStream(sp_bars, "ts_event")
|
||||
settlement_window = FilterStream(expanded_bars, "(ts_event_hour = 15 and ts_event_minute >= 58) or (ts_event_hour = 16 and ts_event_minute < 1)")
|
||||
filtered_bars = FilterStream(settlement_window, "(ts_event_date >= '2023-03-03' and ts_event_date < '2023-03-17') or (ts_event_date >= '2023-06-02' and ts_event_date < '2023-06-16') or (ts_event_date >= '2023-09-01' and ts_event_date < '2023-09-15') or (ts_event_date >= '2023-12-01' and ts_event_date < '2023-12-15')")
|
||||
daily_volume = GroupByStream(filtered_bars, "ts_event_date", "volume:Sum")
|
||||
average_daily_volume = GroupByStream(daily_volume, "", "volume_sum:Mean")
|
||||
result = ProjectStream(average_daily_volume, "volume_sum_mean:average_daily_volume")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Example 6 — Find the difference between the number of days in 2023 that SP 500 went up 2% and when it went down 2%
|
||||
|
||||
#### DSL Solution
|
||||
|
||||
```
|
||||
bars = FutureHistoricalBarsStream("ES", "ONE_DAY", "2023-01-01T00:00:00", "2023-12-31T23:59:59")
|
||||
percentage_change = SingleRowMathStream(bars, "percent_change = 100 * (close - open) / open")
|
||||
days_up_2_percent = FilterStream(percentage_change, "percent_change >= 2")
|
||||
days_up_count = CountStream(days_up_2_percent)
|
||||
days_down_2_percent = FilterStream(percentage_change, "percent_change <= -2")
|
||||
days_down_count = CountStream(days_down_2_percent)
|
||||
joined_counts = JoinStream(days_up_count, "", days_down_count, "")
|
||||
difference_result = SingleRowMathStream(joined_counts, "difference = days_up_count - days_down_count")
|
||||
result = ProjectStream(difference_result, "difference")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Example 7 — Monitor which CL contracts (CL) are trading with bid size \>= 1000, and record the last 1000 events\*
|
||||
|
||||
#### DSL Solution
|
||||
|
||||
```
|
||||
bbo = LiveBBOStream("CL*")
|
||||
large_bid = FilterStream(bbo, "bid_size >= 1000")
|
||||
large_bid_ts = TimeSeriesStream(large_bid, 1000)
|
||||
result = ProjectStream(large_bid_ts, "timestamp, ts_event, contract, bid_size, bid_price, ask_price")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Example 8 — Identify FutureSpreads in CrudeOil and monitor their spread width
|
||||
|
||||
#### DSL Solution
|
||||
|
||||
```
|
||||
bbo = LiveBBOStream("CL*")
|
||||
expanded_bbo = ContractExpanderStream(bbo, "contract")
|
||||
future_spreads = FilterStream(expanded_bbo, "contract_type = 'FutureSpread'")
|
||||
spread_stream = SingleRowMathStream(future_spreads, "spread = ask_price - bid_price")
|
||||
result = ProjectStream(spread_stream, "ts_event, contract, spread")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Example 9 — Track when bid prices in NaturalGas Futures cross above 3.00 and keep only the last 10 seconds of such events
|
||||
|
||||
#### DSL Solution
|
||||
|
||||
```
|
||||
bbo = LiveBBOStream("NG")
|
||||
high_bid = FilterStream(bbo, "bid_price > 3.00")
|
||||
high_bid_ts = TimeSeriesStream(high_bid, 10000)
|
||||
recent_high_bid = TimeBasedRollingFilterStream(high_bid_ts, 10)
|
||||
result = ProjectStream(recent_high_bid, "ts_event, contract, bid_price, bid_size, ask_price")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Example 11 — Detect sequences in NaturalGas future where bid\_price increased 3 times consecutively within a rolling 30-second window
|
||||
|
||||
#### DSL Solution
|
||||
|
||||
```
|
||||
bbo = LiveBBOStream("NG")
|
||||
bbo_ts = TimeSeriesStream(bbo, 10000)
|
||||
recent_bbo = TimeBasedRollingFilterStream(bbo, 30) // 30-second window of updates
|
||||
|
||||
result = PythonDataframeStream(recent_bbo, "
|
||||
def transform(df):
|
||||
import pandas as pd
|
||||
df = df.sort_values('ts_event')
|
||||
df['bid_increase'] = df['bid_price'].diff() > 0
|
||||
df['consec_increase'] = df['bid_increase'].rolling(window=3).sum()
|
||||
# Find rows where we just reached 3 consecutive increases
|
||||
df['trigger'] = (df['consec_increase'] == 3) & (df['consec_increase'].shift(1) < 3)
|
||||
return df[df['trigger']][['ts_event', 'contract', 'bid_price', 'bid_size']]
|
||||
")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Example 12 — When we get a 5+ level bid sweep in SP 500, show me how the bid behaves in the 10 minutes after — mean, stddev, min/max bid\_price
|
||||
|
||||
#### DSL Solution
|
||||
|
||||
```
|
||||
bbo = LiveBBOStream("ES")
|
||||
bbo_ts = TimeSeriesStream(bbo, 10000)
|
||||
recent_bbo = TimeSeriesStream(bbo, 5000) // keep large enough window for event tracking
|
||||
|
||||
result = PythonDataframeStream(recent_bbo, "
|
||||
def transform(df):
|
||||
import pandas as pd
|
||||
df = df.sort_values('ts_event')
|
||||
rows = []
|
||||
in_measurement = False
|
||||
measurement_start_time = None
|
||||
measurement_end_time = None
|
||||
bid_prices_in_window = []
|
||||
min_bid_size_threshold = 100 # defines what we consider 'stable large bid'
|
||||
min_levels_stable = 5
|
||||
prior_bid_sizes = []
|
||||
|
||||
for i, row in df.iterrows():
|
||||
bid_price = row['bid_price']
|
||||
bid_size = row['bid_size']
|
||||
ts = pd.to_datetime(row['ts_event'])
|
||||
|
||||
# Maintain rolling window of prior bid_sizes
|
||||
prior_bid_sizes.append(bid_size)
|
||||
if len(prior_bid_sizes) > min_levels_stable:
|
||||
prior_bid_sizes.pop(0)
|
||||
|
||||
if not in_measurement:
|
||||
# Detect sweep trigger
|
||||
if all(x > min_bid_size_threshold for x in prior_bid_sizes[:-1]) and bid_size < min_bid_size_threshold:
|
||||
# Sweep detected → start measurement window
|
||||
measurement_start_time = ts
|
||||
measurement_end_time = measurement_start_time + pd.Timedelta(minutes=10)
|
||||
bid_prices_in_window = []
|
||||
in_measurement = True
|
||||
else:
|
||||
# Inside measurement window
|
||||
if ts <= measurement_end_time:
|
||||
bid_prices_in_window.append(bid_price)
|
||||
else:
|
||||
# Window complete → emit stats
|
||||
if bid_prices_in_window:
|
||||
mean_bid = pd.Series(bid_prices_in_window).mean()
|
||||
stddev_bid = pd.Series(bid_prices_in_window).std()
|
||||
min_bid = pd.Series(bid_prices_in_window).min()
|
||||
max_bid = pd.Series(bid_prices_in_window).max()
|
||||
rows.append({
|
||||
'sweep_time': measurement_start_time,
|
||||
'measurement_end': measurement_end_time,
|
||||
'mean_bid_price': mean_bid,
|
||||
'stddev_bid_price': stddev_bid,
|
||||
'min_bid_price': min_bid,
|
||||
'max_bid_price': max_bid
|
||||
})
|
||||
# Reset state
|
||||
in_measurement = False
|
||||
measurement_start_time = None
|
||||
measurement_end_time = None
|
||||
bid_prices_in_window = []
|
||||
prior_bid_sizes = []
|
||||
|
||||
return pd.DataFrame(rows)
|
||||
")
|
||||
|
||||
```
|
||||
|
||||
*End of Section 7 — Reinforced Learning DSL Examples*
|
||||
@@ -0,0 +1,27 @@
|
||||
**Enter Meta Mode**
|
||||
You are not learning the DSL yet.
|
||||
You are reviewing the following **BlueOrbDSL Master Learning Prompt** (MLP) as a *training document for yourself*.
|
||||
|
||||
Your goal is to optimize it to make it easier for you — or any future LLM — to learn from it and generate DSL code with high accuracy.
|
||||
|
||||
Please analyze the MLP and provide:
|
||||
|
||||
1. **Clarity review** — is each stream definition clear, unambiguous, easy to parse?
|
||||
|
||||
2. **Consistency review** — do all stream definitions follow the same structure and style?
|
||||
|
||||
3. **Terminology check** — are key terms (parent stream, arguments, schema impact, example usage, etc.) used consistently?
|
||||
|
||||
4. **Structural feedback** — is the overall document structured in a way that supports learning (headings, bold, tables, code blocks)?
|
||||
|
||||
5. **Formatting feedback** — are important warnings / "cannot do X" constraints highlighted clearly?
|
||||
|
||||
6. **Missing info** — is anything missing from any stream definition that would help you understand how to use it?
|
||||
|
||||
7. **Generation risk spots** — any parts where you foresee yourself making common mistakes due to ambiguity?
|
||||
|
||||
**Do not attempt to "learn" this DSL yet.** Stay in Meta Mode and provide feedback as an *editor and optimizer*.
|
||||
|
||||
When I later say:
|
||||
→ **Switch to Learning Mode — this is now the final BlueOrbDSL MLP** → you will forget your Meta comments and treat the MLP as the single authoritative source for DSL generation.
|
||||
|
||||
@@ -17,7 +17,7 @@ type Tab =
|
||||
| PositionManagementTab
|
||||
| MarginTab
|
||||
|
||||
let venues = [| Venue.BINANCE; Venue.BinanceUs; Venue.BinanceUsdsFutures; Venue.CBOE; Venue.COINBASE; Venue.DERIBIT; Venue.OKX; Venue.GLBX; Venue.ICE; Venue.TAIFEX; Venue.TWSE; Venue.SIM; |]
|
||||
let venues = [| Venue.BINANCE; Venue.BinanceUs; Venue.BinanceUsdsFutures; Venue.BYBIT; Venue.CBOE; Venue.COINBASE; Venue.DERIBIT; Venue.GATEIO; Venue.OKX; Venue.GLBX; Venue.ICE; Venue.TAIFEX; Venue.TWSE; Venue.SIM; |]
|
||||
|
||||
let securityTypes = [| SecurityType.SPOT; SecurityType.FUTURE; SecurityType.OPTION; SecurityType.SPREAD; |]
|
||||
|
||||
@@ -50,6 +50,11 @@ type BinanceUsdsFUTURESExchangeField =
|
||||
| Origin of CustomerOrFirm
|
||||
| ClearingAccount of string
|
||||
|
||||
type BybitExchangeField =
|
||||
| CTI of CTICode
|
||||
| Origin of CustomerOrFirm
|
||||
| ClearingAccount of string
|
||||
|
||||
type COINBASEExchangeField =
|
||||
| CTI of CTICode
|
||||
| Origin of CustomerOrFirm
|
||||
@@ -60,6 +65,11 @@ type DERIBITExchangeField =
|
||||
| Origin of CustomerOrFirm
|
||||
| ClearingAccount of string
|
||||
|
||||
type GateioExchangeField =
|
||||
| CTI of CTICode
|
||||
| Origin of CustomerOrFirm
|
||||
| ClearingAccount of string
|
||||
|
||||
type ICEExchangeField =
|
||||
| CTI of CTICode
|
||||
| Origin of CustomerOrFirm
|
||||
|
||||
@@ -797,6 +797,8 @@ let venueToString (venue:bts.api.Venue) =
|
||||
| bts.api.Venue.BINANCE -> "BINANCE"
|
||||
| bts.api.Venue.BinanceUs -> "BinanceUs"
|
||||
| bts.api.Venue.BinanceUsdsFutures -> "BinanceUsdsFutures"
|
||||
| bts.api.Venue.BYBIT -> "BYBIT"
|
||||
| bts.api.Venue.GATEIO -> "GATEIO"
|
||||
| bts.api.Venue.FTX -> "FTX"
|
||||
| bts.api.Venue.OKX -> "OKX"
|
||||
| bts.api.Venue.TAIFEX -> "TAIFEX"
|
||||
|
||||
@@ -107,39 +107,25 @@ target_link_directories(
|
||||
# The dirs are relative to sparkd's path, represented by "$ORIGIN".
|
||||
set_target_properties(
|
||||
${PROJECT_NAME} PROPERTIES
|
||||
BUILD_RPATH "$ORIGIN/../lib_spark;$ORIGIN/../lib_python_md"
|
||||
BUILD_RPATH "$ORIGIN/../lib_spark;$ORIGIN/../lib_python_interop"
|
||||
INSTALL_RPATH "$ORIGIN/../${CMAKE_INSTALL_LIBDIR}"
|
||||
)
|
||||
|
||||
# Python interop (for python md providers)
|
||||
add_dependencies(${PROJECT_NAME}
|
||||
python_md
|
||||
python_md_module
|
||||
python_md_providers
|
||||
copy_python_md_providers
|
||||
)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/SymLink.cmake)
|
||||
|
||||
sym_link_local_lib(python_md)
|
||||
sym_link_file(
|
||||
SOURCE ${CMAKE_BINARY_DIR}/src/lib_python_md/python_md_module.cpython-310-x86_64-linux-gnu.so
|
||||
DEST ${CMAKE_CURRENT_BINARY_DIR}/python_md_module.cpython-310-x86_64-linux-gnu.so
|
||||
INSTALL_DEST ${CMAKE_INSTALL_BINDIR}/
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/SymLinkPythonModule.cmake)
|
||||
# Py interop
|
||||
sym_link_python_module(
|
||||
LIB_NAME python_interop
|
||||
MODULE_NAME python_interop_module
|
||||
PLUGIN_NAME python_interop_plugins
|
||||
)
|
||||
get_target_property(providers python_md_providers INTERFACE_PYTHON_SCRIPTS_DIR)
|
||||
sym_link_dir(
|
||||
TARGET_NAME link_python_md_providers
|
||||
SOURCE ${providers}
|
||||
DEST ${CMAKE_CURRENT_BINARY_DIR}/python_md_providers
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
sparkd
|
||||
spark
|
||||
binance_execution
|
||||
okx_execution
|
||||
python_md
|
||||
python_md_module
|
||||
python_interop
|
||||
python_interop_module
|
||||
databento::databento
|
||||
${emumba-deribit-connector-library_LIBRARIES}
|
||||
tbb
|
||||
|
||||
+45
-12
@@ -1,19 +1,52 @@
|
||||
FROM bluetradingsystems/linux_dotnet7
|
||||
|
||||
ARG IMAGE="sparkd"
|
||||
ARG CONFIG_DIR="src/app_sparkd/configurations/dev"
|
||||
ARG SOURCE_DIR="install/sparkd-release-gcc"
|
||||
ARG CONF_FILE="src/app_sparkd/spark.conf"
|
||||
ARG PYTHON_REQUIREMENTS="requirements.txt"
|
||||
|
||||
# sparkd_base
|
||||
FROM bluetradingsystems/linux_dotnet7 AS sparkd_base
|
||||
ARG CONFIG_DIR
|
||||
ARG SOURCE_DIR
|
||||
|
||||
USER "root"
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y liblttng-ust-dev
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y libpcap0.8-dev
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y python3.10-dev
|
||||
RUN apt-get update && apt-get install -y liblttng-ust-dev
|
||||
RUN apt-get update && apt-get install -y libpcap0.8-dev
|
||||
RUN apt-get update && apt-get install -y python3-dev
|
||||
RUN apt-get update && apt-get install -y python3-pip
|
||||
RUN python3 -m pip install --upgrade pip
|
||||
COPY "${PYTHON_REQUIREMENTS}" "/home/bts/bin/requirements.txt"
|
||||
RUN pip3 install -r "/home/bts/bin/requirements.txt"
|
||||
|
||||
USER "bts"
|
||||
COPY --chown=bts:bts "${SOURCE_DIR}" "."
|
||||
COPY --chown=bts:bts "${CONF_FILE}" ".local/share/bts/sparkd/conf/spark.conf"
|
||||
COPY --chown=bts:bts "${CONFIG_DIR}" "/home/bts/sparkdata/conf"
|
||||
WORKDIR "/home/bts/bin"
|
||||
|
||||
# sparkd
|
||||
FROM sparkd_base AS sparkd
|
||||
|
||||
# sparkd_md_replay
|
||||
FROM sparkd_base AS sparkd_md_replay
|
||||
ARG MD_RECORDING_DIR="src/app_sparkd/.md_recording"
|
||||
COPY --chown=bts:bts "${MD_RECORDING_DIR}" "/home/bts/sparkdata/data/md_recording"
|
||||
|
||||
# sparkd_symbols - like sparkd, but doesn't copy a config dir into the image
|
||||
FROM bluetradingsystems/linux_dotnet7 AS sparkd_symbols
|
||||
ARG SOURCE_DIR
|
||||
ARG PYTHON_REQUIREMENTS="requirements.txt"
|
||||
|
||||
USER "root"
|
||||
RUN apt-get update && apt-get install -y liblttng-ust-dev
|
||||
RUN apt-get update && apt-get install -y libpcap0.8-dev
|
||||
RUN apt-get update && apt-get install -y python3-dev
|
||||
RUN apt-get update && apt-get install -y python3-pip
|
||||
RUN python3 -m pip install --upgrade pip
|
||||
COPY "${PYTHON_REQUIREMENTS}" "/home/bts/bin/requirements.txt"
|
||||
RUN pip3 install -r "/home/bts/bin/requirements.txt"
|
||||
|
||||
USER "bts"
|
||||
COPY --chown=bts:bts "${SOURCE_DIR}" "."
|
||||
WORKDIR "/home/bts/bin"
|
||||
|
||||
# selected image
|
||||
FROM ${IMAGE}
|
||||
@@ -112,7 +112,7 @@ OkxExeCfg {
|
||||
secret_key: "240038D7A28D7F9461247CFCB8E43710"
|
||||
api_key: "dc188532-c74c-4d40-affe-3c6bf4d0ca9a"
|
||||
passphrase: ":,7i%tA%ivfj%SCbX?Ut"
|
||||
ws_url: "wss://wsaws.okx.com:8443"
|
||||
ws_url: "wss://ws.okx.com:8443"
|
||||
rest_url: "https://www.okx.com"
|
||||
ws_ping_timeout_seconds: 10
|
||||
ws_ping_interval_seconds: 25
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
SPARKD__CONFIG_DIR=src/app_sparkd/configurations/alphaworx_zen_hk_live-preview
|
||||
SPARKD__PYTHON_REQUIREMENTS=src/app_sparkd/configurations/alphaworx_zen_hk_live-preview/requirements.txt
|
||||
SPARKD__SHM_SIZE=8gb
|
||||
SPARKD__MEMORY=8gb
|
||||
SPARKD__IMAGE=sparkd_symbols
|
||||
SPARKD__RUNTIME_CONF_DIR=/home/bts/8000/sparkdata/conf
|
||||
@@ -0,0 +1,6 @@
|
||||
* Connects to Binance, Okx, and Bybit PRODUCTION sites with small accounts designated for test trades
|
||||
* Api keys NOT included
|
||||
* Configured for Alphaworx and Bts testing in a production environment
|
||||
|
||||
To retrieve api keys, run "python init_env.py". It should dump them to a file
|
||||
called ".env.local".
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"api_key": "${BINANCE_API_KEY}",
|
||||
"secret_key": "${BINANCE_SECRET_KEY}",
|
||||
"account": "PROD",
|
||||
"rest_endpoint": "https://api.binance.com",
|
||||
"ws_endpoint": "wss://ws-api.binance.com:443/ws-api/v3",
|
||||
"ws_stream_endpoint": "wss://stream.binance.com:9443",
|
||||
"use_cross_margin_api": true
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"api_key": "${BINANCE_API_KEY}",
|
||||
"secret_key": "${BINANCE_SECRET_KEY}",
|
||||
"account": "PROD",
|
||||
"rest_endpoint": "https://fapi.binance.com",
|
||||
"ws_endpoint": "wss://ws-fapi.binance.com/ws-fapi/v1",
|
||||
"ws_stream_endpoint": "wss://fstream.binance.com",
|
||||
"use_cross_margin_api": false
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import botocore
|
||||
import json
|
||||
import os
|
||||
|
||||
from boto3.session import Session
|
||||
from botocore.exceptions import ClientError
|
||||
from fileinput import FileInput
|
||||
|
||||
|
||||
def update_env_file(path, env_vars: dict):
|
||||
"""Update or add vars in a .env file in place."""
|
||||
if not os.path.exists(path):
|
||||
with open(path, "w") as file:
|
||||
for k, v in env_vars.items():
|
||||
file.write(f"{k}={v}\n")
|
||||
return
|
||||
|
||||
with FileInput(path, inplace=True) as file:
|
||||
for line in file:
|
||||
if not line.startswith("#"):
|
||||
stripped: str = line.lstrip()
|
||||
key_len = stripped.find("=")
|
||||
if key_len >= 0:
|
||||
k = stripped[0:key_len]
|
||||
if k in env_vars:
|
||||
print(f"{k}={env_vars[k]}")
|
||||
del env_vars[k]
|
||||
continue
|
||||
print(line, end="")
|
||||
|
||||
if len(env_vars) > 0:
|
||||
with open(path, "a") as file:
|
||||
for k, v in env_vars.items():
|
||||
file.write(f"{k}={v}\n")
|
||||
|
||||
|
||||
def get_secret():
|
||||
secret_name = "alphaworx_api_keys"
|
||||
region_name = "us-east-1"
|
||||
profile_name = "bts-ap-northeast-1"
|
||||
session = Session(profile_name=profile_name)
|
||||
client = session.client(
|
||||
service_name="secretsmanager",
|
||||
region_name=region_name,
|
||||
)
|
||||
|
||||
def on_token_error(e: Exception):
|
||||
print(f"""
|
||||
Failed to get secret value. {e}
|
||||
Verify you are logged in to aws, then try again.
|
||||
|
||||
aws sso login --profile {profile_name}
|
||||
|
||||
If this error persists, verify you have access to these api keys.
|
||||
For more help or information, talk to John.
|
||||
""")
|
||||
|
||||
try:
|
||||
get_secret_value_response = client.get_secret_value(
|
||||
SecretId=secret_name)
|
||||
except ClientError as e:
|
||||
print(f"Failed to get secret value.\n{e}")
|
||||
raise e
|
||||
except botocore.exceptions.SSOTokenLoadError as tl_err:
|
||||
on_token_error(tl_err)
|
||||
return None
|
||||
except botocore.exceptions.TokenRetrievalError as tr_err:
|
||||
on_token_error(tr_err)
|
||||
return None
|
||||
return get_secret_value_response["SecretString"]
|
||||
|
||||
|
||||
def _run():
|
||||
this_dir = os.path.dirname(__file__)
|
||||
secret_str = get_secret()
|
||||
if secret_str is None:
|
||||
exit(1)
|
||||
secret = json.loads(secret_str)
|
||||
env_vars = {}
|
||||
|
||||
def set_env_var(key_name: str, *json_path):
|
||||
try:
|
||||
val = secret[json_path[0]]
|
||||
for p in json_path[1:]:
|
||||
val = val[p]
|
||||
env_vars[key_name] = val
|
||||
print(f"Set {key_name}={val} ({'.'.join(json_path)})")
|
||||
except KeyError:
|
||||
print(f"Failed to set {key_name}= ({'.'.join(json_path)})")
|
||||
|
||||
set_env_var("BINANCE_API_KEY", "binance", "prod", "api_key")
|
||||
set_env_var("BINANCE_SECRET_KEY", "binance", "prod", "secret_key")
|
||||
set_env_var("BYBIT_API_KEY", "bybit", "prod_test", "api_key")
|
||||
set_env_var("BYBIT_SECRET_KEY", "bybit", "prod_test", "secret_key")
|
||||
update_env_file(f"{this_dir}/.env.local", env_vars)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_run()
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"gateway_type": "bybit_execution_gateway.BybitExecutionGateway",
|
||||
"account": "BYBIT_DEV",
|
||||
"module_path": [
|
||||
"python_interop_plugins"
|
||||
],
|
||||
"venue": "VENUE_BYBIT",
|
||||
"api_key": "${BYBIT_API_KEY}",
|
||||
"secret_key": "${BYBIT_SECRET_KEY}",
|
||||
"use_testnet": true,
|
||||
"logging": {
|
||||
"console_level": "DEBUG",
|
||||
"file_level": "DEBUG"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"currencies": [
|
||||
"USDT"
|
||||
],
|
||||
"futures": [
|
||||
"ANIME",
|
||||
"BTC",
|
||||
"ETH",
|
||||
"MASK",
|
||||
"RESOLV"
|
||||
],
|
||||
"use_testnet": true,
|
||||
"logging": {
|
||||
"console_level": "WARNING",
|
||||
"file_level": "INFO"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
certifi==2025.4.26
|
||||
charset-normalizer==3.4.2
|
||||
dotenv==0.9.9
|
||||
idna==3.10
|
||||
numpy==2.2.6
|
||||
pybit==5.11.0
|
||||
pycryptodome==3.23.0
|
||||
python-dotenv==1.1.0
|
||||
requests==2.32.4
|
||||
urllib3==2.4.0
|
||||
websocket-client==1.8.0
|
||||
@@ -0,0 +1,138 @@
|
||||
QuickbusCfg {
|
||||
value {
|
||||
node {
|
||||
reconnect_period_secs: 1
|
||||
hb_period_msecs: 15000
|
||||
max_missed_hbs: 5
|
||||
parent_host: ""
|
||||
parent_port: 0
|
||||
my_port: 0
|
||||
}
|
||||
client {
|
||||
reconnect_period_secs: 1
|
||||
hb_period_msecs: 15000
|
||||
max_missed_hbs: 5
|
||||
parent_host: @SystemConfig.config_ip
|
||||
parent_port: @Ports.qbus_port
|
||||
my_port: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JetFIX {
|
||||
value {
|
||||
# connection {
|
||||
# name: "binance_1"
|
||||
# venue: VENUE_BINANCE
|
||||
# max_order_msgs_per_window: 300
|
||||
# window_duration_msecs: 1000
|
||||
# }
|
||||
# connection {
|
||||
# name: "binance_usds_futures_1"
|
||||
# venue: VENUE_BINANCE_USDS_FUTURES
|
||||
# max_order_msgs_per_window: 300
|
||||
# window_duration_msecs: 1000
|
||||
# }
|
||||
connection {
|
||||
name: "bybit_1"
|
||||
venue: VENUE_BYBIT
|
||||
max_order_msgs_per_window: 300
|
||||
window_duration_msecs: 1000
|
||||
}
|
||||
# connection {
|
||||
# name: "okx_1"
|
||||
# venue: VENUE_OKX
|
||||
# max_order_msgs_per_window: 300
|
||||
# window_duration_msecs: 1000
|
||||
# }
|
||||
}
|
||||
}
|
||||
|
||||
MarketDataConfig {
|
||||
value {
|
||||
# binance {
|
||||
# num_depth_levels: 5
|
||||
# url_rest_base: "https://api.binance.com"
|
||||
# url_ws_base: "wss://stream.binance.com:9443"
|
||||
# security_criteria {
|
||||
# clauses {
|
||||
# security_types: SECURITY_TYPE_SPOT
|
||||
# symbols: @SymbolLists.list[Binance].symbol
|
||||
# name_regexps: "USDC|USDT$"
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# binance_usds_futures {
|
||||
# num_depth_levels: 5
|
||||
# url_rest_base: "https://fapi.binance.com"
|
||||
# url_ws_base: "wss://fstream.binance.com"
|
||||
# security_criteria {
|
||||
# clauses {
|
||||
# security_types: SECURITY_TYPE_FUTURE
|
||||
# symbols: @SymbolLists.list[BinanceUsdsFutures].symbol
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# okx {
|
||||
# num_depth_levels: 5
|
||||
# url_rest_base: "https://www.okx.com"
|
||||
# url_ws_base: "wss://ws.okx.com:8443"
|
||||
# security_criteria {
|
||||
# clauses {
|
||||
# security_types: SECURITY_TYPE_SPOT
|
||||
# symbols: @SymbolLists.list[OkxSpots].symbol
|
||||
# }
|
||||
# clauses {
|
||||
# security_types: SECURITY_TYPE_FUTURE
|
||||
# symbols: @SymbolLists.list[OkxFutures].symbol
|
||||
# }
|
||||
# }
|
||||
# is_application_ping_pong_enabled: true
|
||||
# application_ping_interval_ms: 20000
|
||||
# application_pong_timeout_ms: 10000
|
||||
# is_protocol_ping_pong_enabled: true
|
||||
# protocol_ping_interval_ms: 60000
|
||||
# protocol_pong_timeout_ms: 30000
|
||||
# }
|
||||
python_md_providers {
|
||||
venue: VENUE_BYBIT
|
||||
module_path: "python_interop_plugins"
|
||||
provider_type: "bybit_md_provider.BybitMdProvider"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NotSimExecution {
|
||||
value {
|
||||
# entries {
|
||||
# venue: VENUE_BINANCE
|
||||
# }
|
||||
# entries {
|
||||
# venue: VENUE_BINANCE_USDS_FUTURES
|
||||
# }
|
||||
entries {
|
||||
venue: VENUE_BYBIT
|
||||
}
|
||||
# entries {
|
||||
# venue: VENUE_OKX
|
||||
# }
|
||||
}
|
||||
}
|
||||
|
||||
# OkxExeCfg {
|
||||
# value {
|
||||
# account: "PROD"
|
||||
# ops_id: "op_id_bts"
|
||||
# secret_key: "240038D7A28D7F9461247CFCB8E43710"
|
||||
# api_key: "dc188532-c74c-4d40-affe-3c6bf4d0ca9a"
|
||||
# passphrase: ":,7i%tA%ivfj%SCbX?Ut"
|
||||
# ws_url: "wss://ws.okx.com:8443"
|
||||
# rest_url: "https://www.okx.com"
|
||||
# ws_ping_timeout_seconds: 10
|
||||
# ws_ping_interval_seconds: 10
|
||||
# ws_reconnect_interval_seconds: 5
|
||||
# ws_reconnect_max_count: 5
|
||||
# simulated_trading: false
|
||||
# tdmode: "cross"
|
||||
# }
|
||||
# }
|
||||
@@ -0,0 +1,66 @@
|
||||
SymbolLists {
|
||||
value {
|
||||
list {
|
||||
key: "Binance"
|
||||
symbol: "ANIME"
|
||||
symbol: "BTC"
|
||||
symbol: "ETH"
|
||||
symbol: "MASK"
|
||||
symbol: "RESOLV"
|
||||
}
|
||||
list {
|
||||
key: "BinanceUsdsFutures"
|
||||
symbol: "ANIME"
|
||||
symbol: "BTC"
|
||||
symbol: "ETH"
|
||||
symbol: "MASK"
|
||||
symbol: "RESOLV"
|
||||
}
|
||||
list {
|
||||
key: "OkxSpots"
|
||||
symbol: "ANIME"
|
||||
symbol: "BTC"
|
||||
symbol: "ETH"
|
||||
symbol: "MASK"
|
||||
symbol: "RESOLV"
|
||||
}
|
||||
list {
|
||||
key: "Bybit"
|
||||
symbol: "ANIME"
|
||||
symbol: "BTC"
|
||||
symbol: "ETH"
|
||||
symbol: "MASK"
|
||||
symbol: "RESOLV"
|
||||
}
|
||||
list {
|
||||
key: "OkxFutures"
|
||||
symbol: "ANIME"
|
||||
symbol: "BTC"
|
||||
symbol: "ETH"
|
||||
symbol: "MASK"
|
||||
symbol: "RESOLV"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Symbols {
|
||||
value {
|
||||
# venues {
|
||||
# name: "VENUE_BINANCE"
|
||||
# spot: @SymbolLists.list[Binance].symbol
|
||||
# }
|
||||
# venues {
|
||||
# name: "VENUE_BINANCE_USDS_FUTURES"
|
||||
# future: @SymbolLists.list[BinanceUsdsFutures].symbol
|
||||
# }
|
||||
venues {
|
||||
name: "VENUE_BYBIT"
|
||||
future: @SymbolLists.list[Bybit].symbol
|
||||
}
|
||||
# venues {
|
||||
# name: "VENUE_OKX"
|
||||
# spot: @SymbolLists.list[OkxSpots].symbol
|
||||
# future: @SymbolLists.list[OkxFutures].symbol
|
||||
# }
|
||||
}
|
||||
}
|
||||
@@ -35,8 +35,8 @@ def update_env_file(path, env_vars: dict):
|
||||
|
||||
|
||||
def get_secret():
|
||||
secret_name = "alphaworx-binance-api-keys"
|
||||
region_name = "ap-northeast-1"
|
||||
secret_name = "alphaworx_api_keys"
|
||||
region_name = "us-east-1"
|
||||
profile_name = "bts-ap-northeast-1"
|
||||
session = Session(profile_name=profile_name)
|
||||
client = session.client(
|
||||
@@ -77,10 +77,19 @@ def _run():
|
||||
exit(1)
|
||||
secret = json.loads(secret_str)
|
||||
env_vars = {}
|
||||
if "api-key" in secret:
|
||||
env_vars["BINANCE_API_KEY"] = secret["api-key"]
|
||||
if "secret-key" in secret:
|
||||
env_vars["BINANCE_SECRET_KEY"] = secret["secret-key"]
|
||||
|
||||
def set_env_var(key_name: str, *json_path):
|
||||
try:
|
||||
val = secret[json_path[0]]
|
||||
for p in json_path[1:]:
|
||||
val = val[p]
|
||||
env_vars[key_name] = val
|
||||
print(f"Set {key_name}={val} ({'.'.join(json_path)})")
|
||||
except KeyError:
|
||||
print(f"Failed to set {key_name}= ({'.'.join(json_path)})")
|
||||
|
||||
set_env_var("BINANCE_API_KEY", "binance", "prod", "api_key")
|
||||
set_env_var("BINANCE_SECRET_KEY", "binance", "prod", "secret_key")
|
||||
update_env_file(f"{this_dir}/.env.local", env_vars)
|
||||
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ OkxExeCfg {
|
||||
secret_key: "240038D7A28D7F9461247CFCB8E43710"
|
||||
api_key: "dc188532-c74c-4d40-affe-3c6bf4d0ca9a"
|
||||
passphrase: ":,7i%tA%ivfj%SCbX?Ut"
|
||||
ws_url: "wss://wsaws.okx.com:8443"
|
||||
ws_url: "wss://ws.okx.com:8443"
|
||||
rest_url: "https://www.okx.com"
|
||||
ws_ping_timeout_seconds: 10
|
||||
ws_ping_interval_seconds: 10
|
||||
|
||||
@@ -43,10 +43,6 @@ Logging {
|
||||
}
|
||||
file {
|
||||
default_level: "Info"
|
||||
subsystem {
|
||||
name: "md"
|
||||
level: "Diag"
|
||||
}
|
||||
subsystem {
|
||||
name: "OrderMan"
|
||||
level: "Diag"
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
SPARKD__CONFIG_DIR=src/app_sparkd/configurations/binance_py_dev
|
||||
SPARKD__SHM_SIZE=8gb
|
||||
@@ -0,0 +1 @@
|
||||
* Connects to Binance test nets for market data and execution
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"api_key": "${BINANCE_API_KEY}",
|
||||
"secret_key": "${HOME}/sparkdata/conf/.secrets/binance_1.pem",
|
||||
"account": "BINANCE_DEV",
|
||||
"rest_endpoint": "https://testnet.binance.vision",
|
||||
"ws_endpoint": "wss://ws-api.testnet.binance.vision/ws-api/v3",
|
||||
"ws_stream_endpoint": "wss://stream.testnet.binance.vision:9443"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"api_key": "${BINANCE_USDS_FUTURES_API_KEY}",
|
||||
"secret_key": "${BINANCE_USDS_FUTURES_SECRET_KEY}",
|
||||
"account": "BINANCE_DEV",
|
||||
"rest_endpoint": "https://testnet.binancefuture.com",
|
||||
"ws_endpoint": "wss://testnet.binancefuture.com/ws-fapi/v1",
|
||||
"ws_stream_endpoint": "wss://stream.binancefuture.com"
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import traceback
|
||||
import botocore
|
||||
import json
|
||||
import os
|
||||
|
||||
from boto3.session import Session
|
||||
from botocore.exceptions import ClientError
|
||||
from fileinput import FileInput
|
||||
|
||||
|
||||
def update_env_file(path, env_vars: dict):
|
||||
"""Update or add vars in a .env file in place."""
|
||||
if not env_vars:
|
||||
# Nothing to update
|
||||
return
|
||||
if not os.path.exists(path):
|
||||
with open(path, "w") as file:
|
||||
for k, v in env_vars.items():
|
||||
file.write(f"{k}={v}\n")
|
||||
return
|
||||
|
||||
with FileInput(path, inplace=True) as file:
|
||||
for line in file:
|
||||
if not line.startswith("#"):
|
||||
stripped: str = line.lstrip()
|
||||
key_len = stripped.find("=")
|
||||
if key_len >= 0:
|
||||
k = stripped[0:key_len]
|
||||
if k in env_vars:
|
||||
print(f"{k}={env_vars[k]}")
|
||||
del env_vars[k]
|
||||
continue
|
||||
print(line, end="")
|
||||
|
||||
if len(env_vars) > 0:
|
||||
with open(path, "a") as file:
|
||||
for k, v in env_vars.items():
|
||||
file.write(f"{k}={v}\n")
|
||||
|
||||
|
||||
def write_pem_file(file_path: str, text: str):
|
||||
try:
|
||||
dir_path = os.path.dirname(file_path)
|
||||
os.makedirs(dir_path, exist_ok=True)
|
||||
# rw-------
|
||||
mode = 0o600
|
||||
fd = os.open(file_path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, mode)
|
||||
with os.fdopen(fd, "x") as f:
|
||||
f.write(text)
|
||||
return True
|
||||
except Exception:
|
||||
print(f"Error writing pem file '{file_path}'.")
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
def get_secret(secret_name: str, profile_name: str):
|
||||
session = Session(profile_name=profile_name)
|
||||
client = session.client(service_name="secretsmanager")
|
||||
|
||||
def on_token_error(e: Exception):
|
||||
print(f"""
|
||||
Failed to get secret value '{secret_name}'. {e}
|
||||
Verify you are logged in to aws, then try again.
|
||||
|
||||
aws sso login --profile {profile_name}
|
||||
|
||||
If this error persists, verify you have access to these api keys.
|
||||
For more help or information, talk to John.
|
||||
""")
|
||||
|
||||
try:
|
||||
get_secret_value_response = client.get_secret_value(
|
||||
SecretId=secret_name)
|
||||
except ClientError as e:
|
||||
print(f"Failed to get secret value.\n{e}")
|
||||
raise e
|
||||
except botocore.exceptions.SSOTokenLoadError as tl_err:
|
||||
on_token_error(tl_err)
|
||||
return None
|
||||
except botocore.exceptions.TokenRetrievalError as tr_err:
|
||||
on_token_error(tr_err)
|
||||
return None
|
||||
return get_secret_value_response["SecretString"]
|
||||
|
||||
|
||||
def _init_binance_1():
|
||||
this_dir = os.path.dirname(__file__)
|
||||
secret_str = get_secret("dev/binance/john", "bts-us-east-1")
|
||||
if secret_str is None:
|
||||
exit(1)
|
||||
secret = json.loads(secret_str)
|
||||
env_vars = {}
|
||||
if "api-key" in secret:
|
||||
env_vars["BINANCE_API_KEY"] = secret["api-key"]
|
||||
if "secret-key-pem" in secret:
|
||||
sk_pem = secret["secret-key-pem"]
|
||||
sk_pem = "\n".join(sk_pem)
|
||||
write_pem_file(f"{this_dir}/.secrets/binance_1.pem", sk_pem)
|
||||
update_env_file(f"{this_dir}/.env.local", env_vars)
|
||||
|
||||
|
||||
def _init_binance_usds_futures_1():
|
||||
this_dir = os.path.dirname(__file__)
|
||||
secret_str = get_secret("dev/binance_usds_futures/john.hmac",
|
||||
"bts-us-east-1")
|
||||
if secret_str is None:
|
||||
exit(1)
|
||||
secret = json.loads(secret_str)
|
||||
env_vars = {}
|
||||
if "api-key" in secret:
|
||||
env_vars["BINANCE_USDS_FUTURES_API_KEY"] = secret["api-key"]
|
||||
if "secret-key" in secret:
|
||||
env_vars["BINANCE_USDS_FUTURES_SECRET_KEY"] = secret["secret-key"]
|
||||
update_env_file(f"{this_dir}/.env.local", env_vars)
|
||||
|
||||
|
||||
def _run():
|
||||
_init_binance_1()
|
||||
_init_binance_usds_futures_1()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_run()
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"gateway_type": "dummy_execution_gateway.DummyExecutionGateway",
|
||||
"account": "BINANCE_DEV",
|
||||
"module_path": [
|
||||
"python_execution_gateways"
|
||||
],
|
||||
"venue": "VENUE_BINANCE_USDS_FUTURES"
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
JetFIX {
|
||||
value {
|
||||
# connection {
|
||||
# name: "binance_1"
|
||||
# venue: VENUE_BINANCE
|
||||
# max_order_msgs_per_window: 300
|
||||
# window_duration_msecs: 1000
|
||||
# }
|
||||
connection {
|
||||
name: "binance_usds_futures_1"
|
||||
venue: VENUE_BINANCE_USDS_FUTURES
|
||||
max_order_msgs_per_window: 300
|
||||
window_duration_msecs: 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PublisherOptions {
|
||||
value {
|
||||
network_interface: "0.0.0.0"
|
||||
heartbeat_ms: 5000
|
||||
heartbeat_boot_seconds: 60
|
||||
skip_options: true
|
||||
hot_initial: 1024
|
||||
hot_growth: 1024
|
||||
cold_initial: 8192
|
||||
cold_growth: 2048
|
||||
channel_prefix: ""
|
||||
disable_heartbeat: true
|
||||
}
|
||||
}
|
||||
|
||||
Logging {
|
||||
value {
|
||||
initial_queue_size: 120001
|
||||
max_queue_growth: 4
|
||||
console {
|
||||
default_level: "Info"
|
||||
subsystem {
|
||||
name: "OrderMan"
|
||||
level: "Diag"
|
||||
}
|
||||
}
|
||||
file {
|
||||
default_level: "Info"
|
||||
# subsystem {
|
||||
# name: "md"
|
||||
# level: "Diag"
|
||||
# }
|
||||
subsystem {
|
||||
name: "OrderMan"
|
||||
level: "Diag"
|
||||
}
|
||||
subsystem {
|
||||
name: "Http"
|
||||
level: "Diag"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MarketDataConfig {
|
||||
value {
|
||||
# binance {
|
||||
# num_depth_levels: 5
|
||||
# # url_rest_base: "wss://ws-api.binance.com:9443"
|
||||
# # According to Binance, the "apiN" urls offer performance at the cost of stability.
|
||||
# # https://developers.binance.com/docs/binance-spot-api-docs/rest-api/general-api-information
|
||||
# # url_rest_base: "https://api1.binance.com:9443"
|
||||
# # url_rest_base: "https://api.binance.com"
|
||||
# url_rest_base: "https://testnet.binance.vision"
|
||||
# # url_ws_base: "wss://ws-api.binance.com"
|
||||
# url_ws_base: "wss://testnet.binance.vision"
|
||||
|
||||
# security_criteria {
|
||||
# clauses {
|
||||
# security_types: SECURITY_TYPE_SPOT
|
||||
# symbols: @SymbolLists.list[Binance].symbol
|
||||
# name_regexps: "USDC|USDT$"
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
|
||||
binance_usds_futures {
|
||||
num_depth_levels: 5
|
||||
# url_rest_base: "https://fapi.binance.com"
|
||||
# url_ws_base: "wss://fstream.binance.com"
|
||||
url_rest_base: "https://testnet.binancefuture.com"
|
||||
url_ws_base: "wss://stream.binancefuture.com"
|
||||
|
||||
security_criteria {
|
||||
clauses {
|
||||
security_types: SECURITY_TYPE_FUTURE
|
||||
symbols: @SymbolLists.list[BinanceUsdsFutures].symbol
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NotSimExecution {
|
||||
value {
|
||||
# entries {
|
||||
# venue: VENUE_BINANCE
|
||||
# }
|
||||
entries {
|
||||
venue: VENUE_BINANCE_USDS_FUTURES
|
||||
}
|
||||
# entries {
|
||||
# venue: VENUE_GLBX
|
||||
# }
|
||||
# entries {
|
||||
# venue: VENUE_OKX
|
||||
# }
|
||||
# entries {
|
||||
# venue: VENUE_TAIFEX
|
||||
# }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
SymbolLists {
|
||||
value {
|
||||
list {
|
||||
key: "Binance"
|
||||
symbol: "AAVE"
|
||||
symbol: "AVAX"
|
||||
symbol: "BNB"
|
||||
symbol: "BTC"
|
||||
symbol: "DOGE"
|
||||
symbol: "EIGEN"
|
||||
symbol: "ETH"
|
||||
}
|
||||
list {
|
||||
key: "BinanceUsdsFutures"
|
||||
symbol: "AAVE"
|
||||
symbol: "AVAX"
|
||||
symbol: "BNB"
|
||||
symbol: "BTC"
|
||||
symbol: "DOGE"
|
||||
symbol: "EIGEN"
|
||||
symbol: "ETH"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Symbols {
|
||||
value {
|
||||
venues {
|
||||
name: "VENUE_BINANCE"
|
||||
spot: @SymbolLists.list[Binance].symbol
|
||||
}
|
||||
venues {
|
||||
name: "VENUE_BINANCE_USDS_FUTURES"
|
||||
future: @SymbolLists.list[BinanceUsdsFutures].symbol
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
SPARKD__CONFIG_DIR=src/app_sparkd/configurations/py_dummy_md
|
||||
SPARKD__CONFIG_DIR=src/app_sparkd/configurations/bybit_dev
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"gateway_type": "bybit_execution_gateway.BybitExecutionGateway",
|
||||
"account": "BYBIT_DEV",
|
||||
"module_path": [
|
||||
"python_interop_plugins"
|
||||
],
|
||||
"venue": "VENUE_BYBIT",
|
||||
"api_key": "${BYBIT_API_KEY}",
|
||||
"secret_key": "${BYBIT_SECRET_KEY}",
|
||||
"use_testnet": true,
|
||||
"logging": {
|
||||
"console_level": "WARNING",
|
||||
"file_level": "INFO"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"currencies": [
|
||||
"USDT"
|
||||
],
|
||||
"futures": [
|
||||
"ADA",
|
||||
"BNB",
|
||||
"BTC",
|
||||
"ETH",
|
||||
"PRIME",
|
||||
"XRP"
|
||||
],
|
||||
"use_testnet": true,
|
||||
"logging": {
|
||||
"console_level": "WARNING",
|
||||
"file_level": "INFO"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,14 @@
|
||||
JetFIX {
|
||||
value {
|
||||
connection {
|
||||
name: "bybit_1"
|
||||
venue: VENUE_BYBIT
|
||||
max_order_msgs_per_window: 300
|
||||
window_duration_msecs: 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PublisherOptions {
|
||||
value {
|
||||
network_interface: "0.0.0.0"
|
||||
@@ -19,6 +30,10 @@ Logging {
|
||||
max_queue_growth: 4
|
||||
file {
|
||||
default_level: "Info"
|
||||
subsystem {
|
||||
name: "QBus"
|
||||
level: "Warn"
|
||||
}
|
||||
subsystem {
|
||||
name: "md"
|
||||
level: "Diag"
|
||||
@@ -31,8 +46,16 @@ MarketDataConfig {
|
||||
value {
|
||||
python_md_providers {
|
||||
venue: VENUE_BYBIT
|
||||
module_path: "python_md_providers"
|
||||
module_path: "python_interop_plugins"
|
||||
provider_type: "bybit_md_provider.BybitMdProvider"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NotSimExecution {
|
||||
value {
|
||||
entries {
|
||||
venue: VENUE_BYBIT
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,12 @@ SymbolLists {
|
||||
value {
|
||||
list {
|
||||
key: "Bybit"
|
||||
symbol: "ADA"
|
||||
symbol: "BNB"
|
||||
symbol: "BTC"
|
||||
symbol: "ETH"
|
||||
symbol: "BNB"
|
||||
symbol: "XRP"
|
||||
symbol: "ADA"
|
||||
symbol: "PRIME"
|
||||
symbol: "XRP"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ Symbols {
|
||||
value {
|
||||
venues {
|
||||
name: "VENUE_BYBIT"
|
||||
spot: @SymbolLists.list[Bybit].symbol
|
||||
future: @SymbolLists.list[Bybit].symbol
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"api_key": "e4a0c6b198549042b13faa7645640d60",
|
||||
"secret_key": "527c37877efc247bb0b6158cf3d5d18b84db720da894307f1e2ab88df3aaf0ee",
|
||||
"ws_url": "wss://fx-ws-testnet.gateio.ws/v4/ws/usdt",
|
||||
"currencies": [
|
||||
"USDT"
|
||||
],
|
||||
"spots": [
|
||||
"AMP",
|
||||
"AR",
|
||||
"ARB",
|
||||
"ATOM",
|
||||
"BTC",
|
||||
"CAKE",
|
||||
"CELO",
|
||||
"CRV",
|
||||
"ETH",
|
||||
"FIL",
|
||||
"GMT",
|
||||
"GMX",
|
||||
"HIGH",
|
||||
"ICP",
|
||||
"IMX",
|
||||
"IOTX",
|
||||
"LRC",
|
||||
"MANA",
|
||||
"MINA",
|
||||
"NEAR",
|
||||
"NOT",
|
||||
"OMG",
|
||||
"OP",
|
||||
"OXY",
|
||||
"PEOPLE",
|
||||
"TURBO",
|
||||
"VET",
|
||||
"WAVES",
|
||||
"YFI",
|
||||
"ZIL",
|
||||
"ZRX"
|
||||
],
|
||||
"logging": {
|
||||
"console_level": "WARNING",
|
||||
"file_level": "INFO"
|
||||
}
|
||||
}
|
||||
@@ -2,37 +2,37 @@
|
||||
value {
|
||||
list {
|
||||
key: "Gateio"
|
||||
symbol: "BTC"
|
||||
symbol: "CAKE"
|
||||
symbol: "ETH"
|
||||
symbol: "MANA"
|
||||
symbol: "OMG"
|
||||
symbol: "GMX"
|
||||
symbol: "ICP"
|
||||
symbol: "LRC"
|
||||
symbol: "MINA"
|
||||
symbol: "ZIL"
|
||||
symbol: "TURBO"
|
||||
symbol: "OP"
|
||||
symbol: "ZRX"
|
||||
symbol: "VET"
|
||||
symbol: "AR"
|
||||
symbol: "IMX"
|
||||
symbol: "HIGH"
|
||||
symbol: "ARB"
|
||||
symbol: "YFI"
|
||||
symbol: "NEAR"
|
||||
symbol: "OXY"
|
||||
symbol: "WAVES"
|
||||
symbol: "AMP"
|
||||
symbol: "AR"
|
||||
symbol: "ARB"
|
||||
symbol: "ATOM"
|
||||
symbol: "BTC"
|
||||
symbol: "CAKE"
|
||||
symbol: "CELO"
|
||||
symbol: "CRV"
|
||||
symbol: "ETH"
|
||||
symbol: "FIL"
|
||||
symbol: "PEOPLE"
|
||||
symbol: "GMT"
|
||||
symbol: "GMX"
|
||||
symbol: "HIGH"
|
||||
symbol: "ICP"
|
||||
symbol: "IMX"
|
||||
symbol: "IOTX"
|
||||
symbol: "ATOM"
|
||||
symbol: "NOT"
|
||||
symbol: "GMT"
|
||||
symbol: "LRC"
|
||||
symbol: "MANA"
|
||||
symbol: "MINA"
|
||||
symbol: "NEAR"
|
||||
symbol: "NOT"
|
||||
symbol: "OMG"
|
||||
symbol: "OP"
|
||||
symbol: "OXY"
|
||||
symbol: "PEOPLE"
|
||||
symbol: "TURBO"
|
||||
symbol: "VET"
|
||||
symbol: "WAVES"
|
||||
symbol: "YFI"
|
||||
symbol: "ZIL"
|
||||
symbol: "ZRX"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "Run.h"
|
||||
#include "python_interop/plugin_manager.h"
|
||||
#include "spark/api/public/ServiceContext.h"
|
||||
#include "spark/config/config.h"
|
||||
#include "spark/config/protos/ports.pb.h"
|
||||
@@ -53,6 +54,7 @@ using bts::orderman::ExecutionGatewayProvider;
|
||||
using bts::orderman::IExecutionGatewayProvider;
|
||||
using bts::orderman::OrderServiceServer;
|
||||
using bts::trace::PerfLog;
|
||||
using std::shared_ptr;
|
||||
using std::unique_ptr;
|
||||
using std::vector;
|
||||
|
||||
@@ -62,14 +64,16 @@ static OrderServiceServer create_order_service_server(
|
||||
DynamicOrderRegistry &dyn_order_registry,
|
||||
MetricsContainer &stats,
|
||||
PerfLog &perf_log,
|
||||
TradeSummarySignalDispatcher &tss_dispatcher) {
|
||||
TradeSummarySignalDispatcher &tss_dispatcher,
|
||||
shared_ptr<python_interop::PluginManager> py_plugin_man) {
|
||||
vector<unique_ptr<IExecutionGatewayProvider>> gw_pros;
|
||||
gw_pros.push_back(std::make_unique<ExecutionGatewayProvider>(
|
||||
&perf_log, mdclient.oracle(), config::get<config::JetFIX>(),
|
||||
[](const std::string &name) {
|
||||
const spark::SparkDataDirectory data_dir;
|
||||
return data_dir.load_config_json(name);
|
||||
}));
|
||||
},
|
||||
std::move(py_plugin_man)));
|
||||
return OrderServiceServer(
|
||||
sc, mdclient, dyn_order_registry, std::move(gw_pros), stats, perf_log,
|
||||
tss_dispatcher);
|
||||
@@ -99,7 +103,11 @@ static void execute() {
|
||||
|
||||
bts::md::MBOProcessor mbo_processor;
|
||||
bts::md::TradeSummarySignalDispatcher tss_dispatcher;
|
||||
md::cmds::Run::run();
|
||||
// The py plugin manager is shared by md and execution, so it lives in this scope.
|
||||
auto py_plugin_man = std::make_shared<python_interop::PluginManager>();
|
||||
// start work loop in background
|
||||
py_plugin_man->start_work_thread();
|
||||
md::cmds::Run::run(py_plugin_man);
|
||||
|
||||
api::synthetic::SyntheticSecurityService synthetic_service(sc);
|
||||
bts::md::MDClient mdclient(
|
||||
@@ -120,7 +128,8 @@ static void execute() {
|
||||
// finished initializing order plugins
|
||||
|
||||
auto order_service_server = create_order_service_server(
|
||||
sc, mdclient, dyn_order_registry, stats, perf_log, tss_dispatcher);
|
||||
sc, mdclient, dyn_order_registry, stats, perf_log, tss_dispatcher,
|
||||
py_plugin_man);
|
||||
support::ErrorReportServer error_report_service(sc);
|
||||
admin::AdminServiceServer admin_service(sc);
|
||||
bts::md::auxiliary::MarketDataAuxiliaryServiceServer md_aux_server(sc);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "LiveLadderDemo.h"
|
||||
#include "python_interop/plugin_manager.h"
|
||||
#include "spark/api/md/MarketDataConfig.pb.h"
|
||||
#include "spark/persistence/IDGenerator.h"
|
||||
#include "sparkd/md/MarketDataGateway.h"
|
||||
@@ -15,7 +16,9 @@ LiveLadderDemo::LiveLadderDemo(const MarketDataConfig &config) {
|
||||
char ssl_cert_file[] = "SSL_CERT_FILE=/etc/ssl/cert/ca-certificates.crt";
|
||||
putenv(ssl_cert_file);
|
||||
|
||||
md::MarketDataGateway gw{config, IDGenerator::instance_ptr()};
|
||||
auto py_plugin_man = std::make_shared<python_interop::PluginManager>();
|
||||
py_plugin_man->start_work_thread();
|
||||
md::MarketDataGateway gw{config, IDGenerator::instance_ptr(), py_plugin_man};
|
||||
md::listeners::LiveLadderPrinter ladder_printer;
|
||||
gw.start();
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "Run.h"
|
||||
#include "Cleanup.h"
|
||||
#include "python_interop/plugin_manager.h"
|
||||
#include "spark/api/md/MarketDataConfig.pb.h"
|
||||
#include "spark/config/protos/market_data.pb.h"
|
||||
#include "spark/domain/SecurityCriteria.h"
|
||||
@@ -92,12 +93,13 @@ static void run_onixs(
|
||||
disp.run();
|
||||
}
|
||||
|
||||
static void run_md_gwy() {
|
||||
static void run_md_gwy(
|
||||
std::shared_ptr<python_interop::PluginManager> py_worker) {
|
||||
LOG(md, Info, "Run::run_md_gwy t_id:{} t_name:{}", os::get_thread_id(),
|
||||
os::get_thread_name());
|
||||
bts::md::MarketDataGateway gwy{
|
||||
config::get<api::md::MarketDataConfig>(),
|
||||
persistence::IDGenerator::instance_ptr()};
|
||||
persistence::IDGenerator::instance_ptr(), std::move(py_worker)};
|
||||
gwy.start();
|
||||
while (true) {
|
||||
using namespace std::chrono_literals;
|
||||
@@ -105,12 +107,15 @@ static void run_md_gwy() {
|
||||
}
|
||||
}
|
||||
|
||||
void Run::run() {
|
||||
void Run::run(std::shared_ptr<python_interop::PluginManager> py_worker) {
|
||||
md::cmds::Cleanup::execute();
|
||||
LOG(md, Info, "Run::run t_id:{} t_name:{}", os::get_thread_id(),
|
||||
os::get_thread_name());
|
||||
// start a thread for the md
|
||||
threading::make_thread("MarketDataGateway", run_md_gwy).detach();
|
||||
threading::make_thread(
|
||||
"MarketDataGateway",
|
||||
[py_worker = std::move(py_worker)]() { run_md_gwy(py_worker); })
|
||||
.detach();
|
||||
boot_sleep();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
#include <CLI11/CLI11.hpp>
|
||||
|
||||
namespace python_interop {
|
||||
class PluginManager;
|
||||
}
|
||||
|
||||
namespace bts::cmds::md::cmds {
|
||||
|
||||
/**
|
||||
@@ -9,18 +13,15 @@ namespace bts::cmds::md::cmds {
|
||||
* shared memory.
|
||||
*/
|
||||
struct Run : public CLI::App {
|
||||
enum class MDType {
|
||||
Configured,
|
||||
Replay,
|
||||
Onixs
|
||||
};
|
||||
enum class MDType { Configured, Replay, Onixs };
|
||||
|
||||
Run();
|
||||
|
||||
/**
|
||||
* Run market data
|
||||
*/
|
||||
static void run();
|
||||
static void run(
|
||||
std::shared_ptr<python_interop::PluginManager> py_worker = nullptr);
|
||||
};
|
||||
|
||||
} // namespace bts::cmds::md::cmds
|
||||
@@ -464,7 +464,7 @@ void EMExecutionGateway::_process_cb_and_fire_er(const EMCallbackMsg &em_cb) {
|
||||
// cant create one on the stack
|
||||
ExecutionReport er;
|
||||
// length of these have to be <= 12
|
||||
to_array(cr_msg.routing_record->account(), &er.account_name.value());
|
||||
er.account_name->copy_from(cr_msg.routing_record->account());
|
||||
to_array(cr_msg.routing_record->operator_id(), &er.operator_id.value());
|
||||
er.order_key = cr_msg.key;
|
||||
er.msg_seq_num =
|
||||
@@ -482,8 +482,7 @@ void EMExecutionGateway::_process_cb_and_fire_er(const EMCallbackMsg &em_cb) {
|
||||
std::string exch_oid = std::to_string(em_cb.order_key.value());
|
||||
// TODO, should we put EM order or Deribit ?
|
||||
// (this is not used by EMNativeOrder so we are ok)
|
||||
auto &er_exch_oid = std::get<ExchangeOrderID>(er.exchange_order_id).value();
|
||||
to_array(exch_oid, &er_exch_oid);
|
||||
std::get<ExchangeOrderID>(er.exchange_order_id).value().copy_from(exch_oid);
|
||||
er.price.value() = em_cb.order_state.order_px;
|
||||
er.quantity = em_cb.order_state.order_qty;
|
||||
// fractional qty
|
||||
@@ -523,7 +522,7 @@ void EMExecutionGateway::_process_cb_and_fire_er(const EMCallbackMsg &em_cb) {
|
||||
// TODO: this should be part of the update
|
||||
utils::Uuid guid;
|
||||
std::string fill_id = guid.to_text_string();
|
||||
to_array(fill_id, &er.exchange_fill_id.value());
|
||||
er.exchange_fill_id->copy_from(fill_id);
|
||||
} else {
|
||||
er.last_shares = Quantity(0);
|
||||
}
|
||||
@@ -582,7 +581,7 @@ void EMExecutionGateway::_fire_leg_er(
|
||||
l_er.msg_seq_num = me->msg_seq_num++;
|
||||
utils::Uuid guid;
|
||||
std::string fill_id = guid.to_text_string();
|
||||
to_array(fill_id, &l_er.exchange_fill_id.value());
|
||||
l_er.exchange_fill_id->copy_from(fill_id);
|
||||
LOG(OrderMan, Info, "EMExecutionGateway::_fire_leg_er firing er: {}",
|
||||
l_er.to_string());
|
||||
me->on_exe_report(
|
||||
|
||||
@@ -5,7 +5,11 @@
|
||||
#include "binance_execution/usds_futures/usds_futures_gateway.h"
|
||||
#include "okx_execution/okx_execution_gateway.h"
|
||||
#include "onixs_fix/OnixFIXAdapter.h"
|
||||
#include "python_interop/plugin_manager.h"
|
||||
#include "spark/api/domain/Venue.h"
|
||||
#include "spark/domain/DomainSecurityUtils.h"
|
||||
#include "spark/domain_model/Security.pb.h"
|
||||
#include "spark/domain_model/Venue.h"
|
||||
#include "spark/io/JsonReader.h"
|
||||
#include "spark/orderman/gateways/CCAPIExecutionGateway.h"
|
||||
#include "spark/orderman/gateways/FakeExecutionGateway.h"
|
||||
@@ -17,6 +21,10 @@
|
||||
#include "spark/orderman/gateways/sim/SimExecutionGatewayFactory.h"
|
||||
#include "spark/orderman/gateways/sim/SimExecutionGatewayUniqueWrapper.h"
|
||||
#include "spark/utils/PBUtils.h"
|
||||
#include "spark/utils/bts_assert.h"
|
||||
#include "spark/utils/type_name.h"
|
||||
#include "sparkd/execution/py_execution_gateway_adapter.h"
|
||||
#include "sparkd/execution/py_execution_gateway_config.h"
|
||||
|
||||
namespace bts::orderman {
|
||||
|
||||
@@ -30,22 +38,37 @@ using bts::io::JsonReader;
|
||||
using nlohmann::json;
|
||||
using std::function;
|
||||
using std::optional;
|
||||
using std::shared_ptr;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
template <typename T, typename... Args>
|
||||
static std::unique_ptr<T> make_gw(
|
||||
const config::JetFIX::Connection &conn, Args &&...args) {
|
||||
LOG(OrderMan, Info,
|
||||
R"([create_exe_gw.json]){{ "name": "{}", "venue": "{}", "type": "{}", "ts": {} }})",
|
||||
conn.name(), bts::api::venue_name_pretty(conn.venue()),
|
||||
std::string(bts::utils::cached_type_name<T>()),
|
||||
bts::core::TimeStamp::now_ns());
|
||||
return std::make_unique<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
struct ExecutionGatewayProvider::PImpl {
|
||||
PImpl(
|
||||
trace::PerfLog *l,
|
||||
md::SecurityOracle o,
|
||||
config::JetFIX config,
|
||||
function<optional<json>(const string &)> load_config_json)
|
||||
function<optional<json>(const string &)> load_config_json,
|
||||
shared_ptr<python_interop::PluginManager> py_plugin_man)
|
||||
: l(l), o(std::move(o)), config(std::move(config)),
|
||||
load_config_json(std::move(load_config_json)) {}
|
||||
load_config_json(std::move(load_config_json)),
|
||||
py_plugin_man(std::move(py_plugin_man)) {}
|
||||
|
||||
trace::PerfLog *l;
|
||||
md::SecurityOracle o;
|
||||
config::JetFIX config;
|
||||
function<optional<json>(const string &)> load_config_json;
|
||||
shared_ptr<python_interop::PluginManager> py_plugin_man;
|
||||
|
||||
template <class T>
|
||||
T parse_config_json(const string &name) {
|
||||
@@ -63,43 +86,51 @@ struct ExecutionGatewayProvider::PImpl {
|
||||
std::unique_ptr<IExecutionGateway> create_gateway(
|
||||
const config::JetFIX::Connection &conn) {
|
||||
INFO_FN("create_gateway", "conn: {}", utils::pb::to_string(conn));
|
||||
// *************************************
|
||||
// Simple hack to integrate python execution gateways:
|
||||
// If a json file exists with the same name as the jetfix connection,
|
||||
// but with a "py_" prefix, assume that file defines python gateway
|
||||
// config for that connection.
|
||||
// *************************************
|
||||
if (SimExecutionGatewayFactory::is_sim_execution_on(conn.venue())) {
|
||||
return std::make_unique<SimExecutionGatewayUniqueWrapper>(
|
||||
conn.venue());
|
||||
return make_gw<SimExecutionGatewayUniqueWrapper>(conn, conn.venue());
|
||||
}
|
||||
auto py_cfg_j = load_config_json(fmt::format("py_{}", conn.name()));
|
||||
if (py_cfg_j) {
|
||||
auto py_cfg = py_cfg_j->get<PyExecutionGatewayConfig>();
|
||||
bts_assert(
|
||||
domain::DomainSecurityUtils::convert(conn.venue()) ==
|
||||
py_cfg.venue);
|
||||
return make_gw<PyExecutionGatewayAdapter>(
|
||||
conn, o, std::move(py_cfg), conn, py_plugin_man);
|
||||
}
|
||||
switch (conn.venue()) {
|
||||
case api::VENUE_ICE:
|
||||
case api::VENUE_SIMICE:
|
||||
return std::make_unique<ICEExecutionGateway>(conn, *l, o);
|
||||
case api::VENUE_GLBX: {
|
||||
if (conn.use_ilink3())
|
||||
return std::make_unique<ilink3::GLBXiLink3ExecutionGateway>(
|
||||
conn, *l, o);
|
||||
return std::make_unique<GLBXExecutionGateway>(conn, *l, o);
|
||||
}
|
||||
return make_gw<ICEExecutionGateway>(conn, conn, *l, o);
|
||||
case api::VENUE_GLBX:
|
||||
if (conn.use_ilink3()) {
|
||||
make_gw<ilink3::GLBXiLink3ExecutionGateway>(conn, conn, *l, o);
|
||||
}
|
||||
return make_gw<GLBXExecutionGateway>(conn, conn, *l, o);
|
||||
case api::VENUE_DERIBIT:
|
||||
return std::make_unique<CCAPIExecutionGateway>(conn.venue(), o);
|
||||
case api::VENUE_CBOE: {
|
||||
std::unique_ptr<IExecutionGenericAdapter> adapter =
|
||||
std::make_unique<OnixFIXAdapter>(o);
|
||||
return std::make_unique<GeneralExecutionGateway>(
|
||||
conn.venue(), std::move(adapter), o);
|
||||
}
|
||||
case api::VENUE_OKX: {
|
||||
return std::make_unique<OkxExecutionGateway>(o);
|
||||
}
|
||||
return make_gw<CCAPIExecutionGateway>(conn, conn.venue(), o);
|
||||
case api::VENUE_CBOE:
|
||||
return make_gw<GeneralExecutionGateway>(
|
||||
conn, conn.venue(), std::make_unique<OnixFIXAdapter>(o), o);
|
||||
case api::VENUE_OKX:
|
||||
return make_gw<OkxExecutionGateway>(conn, o);
|
||||
case api::VENUE_BINANCE: {
|
||||
auto cfg =
|
||||
parse_config_json<binance_execution::GatewayConfig>(conn.name());
|
||||
cfg.expand_env();
|
||||
cfg.deduce_key_type();
|
||||
if (cfg.use_cross_margin_api) {
|
||||
return std::make_unique<
|
||||
binance_execution::cross_margin::CrossMarginGateway>(
|
||||
o, std::move(cfg), conn);
|
||||
return make_gw<binance_execution::cross_margin::CrossMarginGateway>(
|
||||
conn, o, std::move(cfg), conn);
|
||||
}
|
||||
return std::make_unique<binance_execution::spots::SpotsGateway>(
|
||||
o, std::move(cfg), conn);
|
||||
return make_gw<binance_execution::spots::SpotsGateway>(
|
||||
conn, o, std::move(cfg), conn);
|
||||
}
|
||||
case api::VENUE_BINANCE_USDS_FUTURES: {
|
||||
auto cfg =
|
||||
@@ -111,12 +142,11 @@ struct ExecutionGatewayProvider::PImpl {
|
||||
"Config has 'use_cross_margin_api' set to true, but this is "
|
||||
"only supported for spots (using regular api).");
|
||||
}
|
||||
return std::make_unique<
|
||||
binance_execution::usds_futures::UsdsFuturesGateway>(
|
||||
o, std::move(cfg), conn);
|
||||
return make_gw<binance_execution::usds_futures::UsdsFuturesGateway>(
|
||||
conn, o, std::move(cfg), conn);
|
||||
}
|
||||
default:
|
||||
return std::make_unique<FakeExecutionGateway>(o);
|
||||
return make_gw<FakeExecutionGateway>(conn, o);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,9 +166,14 @@ ExecutionGatewayProvider::ExecutionGatewayProvider(
|
||||
md::SecurityOracle o,
|
||||
config::JetFIX config,
|
||||
std::function<std::optional<nlohmann::json>(const std::string &)>
|
||||
load_config_json)
|
||||
load_config_json,
|
||||
shared_ptr<python_interop::PluginManager> py_plugin_man)
|
||||
: me_(std::make_unique<PImpl>(
|
||||
l, std::move(o), std::move(config), std::move(load_config_json))) {}
|
||||
l,
|
||||
std::move(o),
|
||||
std::move(config),
|
||||
std::move(load_config_json),
|
||||
std::move(py_plugin_man))) {}
|
||||
|
||||
ExecutionGatewayProvider::~ExecutionGatewayProvider() = default;
|
||||
|
||||
|
||||
@@ -9,6 +9,10 @@ namespace bts::spark {
|
||||
class SparkDataDirectory;
|
||||
}
|
||||
|
||||
namespace python_interop {
|
||||
class PluginManager;
|
||||
}
|
||||
|
||||
namespace bts::orderman {
|
||||
|
||||
class ExecutionGatewayProvider : public IExecutionGatewayProvider {
|
||||
@@ -18,7 +22,8 @@ class ExecutionGatewayProvider : public IExecutionGatewayProvider {
|
||||
md::SecurityOracle o,
|
||||
config::JetFIX config,
|
||||
std::function<std::optional<nlohmann::json>(const std::string &)>
|
||||
load_config_json);
|
||||
load_config_json,
|
||||
std::shared_ptr<python_interop::PluginManager> py_plugin_man);
|
||||
~ExecutionGatewayProvider() override;
|
||||
|
||||
std::vector<OrderGatewayDefinition> create() override;
|
||||
|
||||
@@ -0,0 +1,637 @@
|
||||
#include "py_execution_gateway_adapter.h"
|
||||
#include "python_interop/execution/execution_gateway.h"
|
||||
#include "python_interop/execution/execution_gateway_bridge.h"
|
||||
#include "python_interop/execution/execution_gateway_bridge_impl.h"
|
||||
#include "python_interop/execution/py_cancel_order_request.h"
|
||||
#include "python_interop/execution/py_cancel_replace_order_request.h"
|
||||
#include "python_interop/execution/py_create_order_request.h"
|
||||
#include "python_interop/execution/py_execution_gateway.h"
|
||||
#include "python_interop/execution/py_execution_report.h"
|
||||
#include "python_interop/plugin_manager.h"
|
||||
#include "python_interop/py_contract_price_info.h"
|
||||
#include "python_interop/py_contract_quantity_info.h"
|
||||
#include "python_interop/py_contract_specification.h"
|
||||
#include "python_interop/py_security.h"
|
||||
#include "python_interop/python_object.h"
|
||||
#include "spark/api/domain/Venue.h"
|
||||
#include "spark/api/service_helpers/AdminServiceUtils.h"
|
||||
#include "spark/api/services/AdminService.pb.h"
|
||||
#include "spark/api/services/OrderService.h"
|
||||
#include "spark/api/services/exec_type.h"
|
||||
#include "spark/api/services/native_order_status.h"
|
||||
#include "spark/api/services/native_order_type.h"
|
||||
#include "spark/api/services/time_in_force.h"
|
||||
#include "spark/config/protos/jetfix.pb.h"
|
||||
#include "spark/core/TimeStamp.h"
|
||||
#include "spark/core/start_stop_state_machine.h"
|
||||
#include "spark/domain/tick_utils.h"
|
||||
#include "spark/domain_model/Security.h"
|
||||
#include "spark/domain_model/Security.pb.h"
|
||||
#include "spark/domain_model/SecurityType.h"
|
||||
#include "spark/domain_model/Side.h"
|
||||
#include "spark/domain_model/Venue.h"
|
||||
#include "spark/io/json_formatter.h"
|
||||
#include "spark/logging/logging.h"
|
||||
#include "spark/logging/trace_log.h"
|
||||
#include "spark/md/SecurityOracle.h"
|
||||
#include "spark/mercury/events/Update.h"
|
||||
#include "spark/orderman/CancelOrderMsg.h"
|
||||
#include "spark/orderman/CancelReplaceOrderMsg.h"
|
||||
#include "spark/orderman/CreateOrderMsg.h"
|
||||
#include "spark/orderman/RFQMsg.h"
|
||||
#include "spark/orderman/events/NewAdminDataEvent.h"
|
||||
#include "spark/orderman/events/NewSecuritiesEvent.h"
|
||||
#include "spark/orderman/gateways/IExecutionGateway.h"
|
||||
#include "spark/orderman/log_messages/cancel_order_log.h"
|
||||
#include "spark/orderman/log_messages/create_order_log.h"
|
||||
#include "spark/orderman/log_messages/execution_report_log.h"
|
||||
#include "spark/orderman/log_messages/modify_order_log.h"
|
||||
#include "spark/orderman/orderman_utils/ExchangePosition.h"
|
||||
#include "spark/orderman/orderman_utils/ExecutionReport.h"
|
||||
#include "spark/orderman/orderman_utils/execution_report_store.h"
|
||||
#include "spark/orderman/orderman_utils/orderman_common.h"
|
||||
#include "spark/orderman/orderman_utils/simple_validation.h"
|
||||
#include "spark/utils/PBUtils.h"
|
||||
#include "spark/utils/bts_lock_guard.h"
|
||||
#include "spark/utils/overloaded.h"
|
||||
#include "spark/utils/stopwatch.h"
|
||||
#include "spark/utils/string_utils.h"
|
||||
#include "sparkd/execution/py_execution_gateway_config.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <future>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace bts::orderman {
|
||||
|
||||
#define ASSERT_FN(test, fn, ...) \
|
||||
bts_assert_msgf(test, "PyExecutionGatewayAdapter::" fn ". " __VA_ARGS__)
|
||||
#define LOG_FN(level, fn, ...) \
|
||||
LOG(OrderMan, level, "PyExecutionGatewayAdapter::" fn ". " __VA_ARGS__)
|
||||
#define DIAG_FN(fn, ...) LOG_FN(bts::Diag, fn, __VA_ARGS__)
|
||||
#define INFO_FN(fn, ...) LOG_FN(bts::Info, fn, __VA_ARGS__)
|
||||
#define WARN_FN(fn, ...) LOG_FN(bts::Warn, fn, __VA_ARGS__)
|
||||
#define ERROR_FN(fn, ...) LOG_FN(bts::Error, fn, __VA_ARGS__)
|
||||
|
||||
#define _LOG(level, ...) LOG(OrderMan, level, __VA_ARGS__)
|
||||
#define INFO(...) _LOG(bts::Info, __VA_ARGS__)
|
||||
#define ERROR(...) _LOG(bts::Error, __VA_ARGS__)
|
||||
|
||||
#define _LOG_TRACE(level, name) \
|
||||
LOG_TRACE(OrderMan, level, std::string("py_exe_gw_adapt"), std::string(name))
|
||||
#define INFO_TRACE(name) _LOG_TRACE(bts::Info, name)
|
||||
#define WARN_TRACE(name) _LOG_TRACE(bts::Warn, name)
|
||||
#define ERROR_TRACE(name) _LOG_TRACE(bts::Error, name)
|
||||
|
||||
#define _LOG_TRACE_PARAMS(level, name, params) \
|
||||
LOG_TRACE_PARAMS( \
|
||||
OrderMan, level, std::string("py_exe_gw_adapt"), std::string(name), \
|
||||
params)
|
||||
#define DIAG_TRACE_PARAMS(name, params) \
|
||||
_LOG_TRACE_PARAMS(bts::Diag, name, params)
|
||||
#define ERROR_TRACE_PARAMS(name, params) \
|
||||
_LOG_TRACE_PARAMS(bts::Error, name, params)
|
||||
|
||||
#define INFO_TRACE_PARAMS(name, params) \
|
||||
LOG_TRACE_PARAMS( \
|
||||
OrderMan, bts::Info, std::string("py_exe_gw_adapt"), std::string(name), \
|
||||
params)
|
||||
|
||||
using bts::Quantity;
|
||||
using bts::api::security_type_parse_smart;
|
||||
using bts::api::SecurityType;
|
||||
using bts::api::Venue;
|
||||
using bts::api::venue_parse_smart;
|
||||
using bts::api::admin::BTSAccount;
|
||||
using bts::api::orderman::exec_type_parse_smart;
|
||||
using bts::api::orderman::native_order_status_parse_smart;
|
||||
using bts::api::orderman::time_in_force_parse_smart;
|
||||
using bts::core::Delegate;
|
||||
using bts::core::StartStopStateMachine;
|
||||
using bts::core::TimeStamp;
|
||||
using bts::md::SecurityOracle;
|
||||
using bts::orderman::CancelOrderMsg;
|
||||
using bts::orderman::CancelReplaceOrderMsg;
|
||||
using bts::orderman::CreateOrderMsg;
|
||||
using bts::orderman::ExchangeOrderID64;
|
||||
using bts::orderman::ExchangePosition;
|
||||
using bts::orderman::ExecutionReport;
|
||||
using bts::orderman::ExecutionReportStore;
|
||||
using bts::orderman::NewAdminDataEvent;
|
||||
using bts::orderman::NewSecuritiesEvent;
|
||||
using bts::orderman::OrderKey;
|
||||
using bts::orderman::OrderMsg;
|
||||
using bts::orderman::RFQMsg;
|
||||
using bts::system::string_utils::parse_uint;
|
||||
using nlohmann::json;
|
||||
using python_execution::ExecutionGateway;
|
||||
using python_execution::ExecutionGatewayBridgeImpl;
|
||||
using python_execution::PyCancelOrderRequest;
|
||||
using python_execution::PyCancelReplaceOrderRequest;
|
||||
using python_execution::PyCreateOrderRequest;
|
||||
using python_interop::PluginManager;
|
||||
using python_interop::PythonObjectT;
|
||||
using std::int64_t;
|
||||
using std::optional;
|
||||
using std::shared_ptr;
|
||||
using std::size_t;
|
||||
using std::string;
|
||||
using std::thread;
|
||||
using std::uint32_t;
|
||||
using std::uint64_t;
|
||||
using std::vector;
|
||||
|
||||
struct ExecutionGatewayCallbacks {
|
||||
bts::orderman::OnExeReport exe_report;
|
||||
bts::orderman::OnGwStatusChange gw_status_change;
|
||||
bts::orderman::OnCancelReject cancel_reject;
|
||||
bts::orderman::OnExchangePositionState exchange_position_state;
|
||||
bts::orderman::OnExchangeOpenOrdersState exchange_open_orders;
|
||||
};
|
||||
|
||||
struct PyExecutionGatewayAdapter::PImpl {
|
||||
explicit PImpl(
|
||||
bts::md::SecurityOracle sec_oracle,
|
||||
PyExecutionGatewayConfig config,
|
||||
bts::config::JetFIX::Connection jetfix_connection,
|
||||
shared_ptr<PluginManager> plugin_man)
|
||||
: sec_oracle(std::move(sec_oracle)), config(std::move(config)),
|
||||
name(fmt::format("{}_py_gw", string{jetfix_connection.name()})),
|
||||
bridge_ptr(std::make_shared<ExecutionGatewayBridgeImpl>(
|
||||
jetfix_connection.name())),
|
||||
bridge(*bridge_ptr), plugin_man(std::move(plugin_man)),
|
||||
jetfix_connection(std::make_shared<bts::config::JetFIX_Connection>(
|
||||
std::move(jetfix_connection))),
|
||||
orders(this->config.account, this->jetfix_connection) {
|
||||
bts_assert(this->jetfix_connection->venue() != api::VENUE_UNSET);
|
||||
bts_assert(!this->jetfix_connection->name().empty());
|
||||
pos.exchange = this->jetfix_connection->venue();
|
||||
sm.set_name(name);
|
||||
sm.set_subsystem(bts::log::OrderMan);
|
||||
sm.start_core_impl.bind<&PImpl::start_impl>(*this);
|
||||
sm.stop_core_impl.bind<&PImpl::stop_impl>(*this);
|
||||
}
|
||||
|
||||
~PImpl() { stop(); }
|
||||
|
||||
enum RequestType {
|
||||
ORDER,
|
||||
POSITION,
|
||||
};
|
||||
|
||||
SecurityOracle sec_oracle;
|
||||
PyExecutionGatewayConfig config;
|
||||
std::string name;
|
||||
std::shared_ptr<ExecutionGatewayBridgeImpl> bridge_ptr;
|
||||
ExecutionGatewayBridgeImpl &bridge;
|
||||
shared_ptr<PluginManager> plugin_man;
|
||||
shared_ptr<bts::config::JetFIX_Connection> jetfix_connection;
|
||||
optional<thread> pos_snapshot_thread;
|
||||
optional<thread> py_thread;
|
||||
bool is_ready = false;
|
||||
bool is_ready_dirty = false;
|
||||
volatile bool py_keep_going = false;
|
||||
uint32_t n_active = 0;
|
||||
|
||||
StartStopStateMachine sm;
|
||||
// SimpleOrderKeyMap key_map;
|
||||
|
||||
ExecutionReportStore orders;
|
||||
ExecutionGatewayCallbacks callbacks;
|
||||
ExchangePosition pos;
|
||||
BTSAccount account;
|
||||
OnExeReport exe_report_callback;
|
||||
|
||||
void to_exe_report(
|
||||
bts::orderman::ExecutionReport &er,
|
||||
const python_execution::PyExecutionReport &pyer,
|
||||
std::function<const bts::api::Security *(
|
||||
bts::api::Venue, bts::api::SecurityType, const std::string &)>
|
||||
find_sec) {
|
||||
*er.order_key = pyer.key();
|
||||
er.account_name->copy_from(pyer.account());
|
||||
if (std::holds_alternative<ExchangeOrderID>(er.exchange_order_id)) {
|
||||
std::get<ExchangeOrderID>(er.exchange_order_id)
|
||||
->copy_from(pyer.exchange_order_id());
|
||||
} else {
|
||||
er.exchange_order_id = ExchangeOrderID{pyer.exchange_order_id()};
|
||||
}
|
||||
native_order_type_parse_smart(pyer.order_type(), &er.order_type);
|
||||
native_order_status_parse_smart(pyer.order_status(), &er.order_status);
|
||||
time_in_force_parse_smart(pyer.time_in_force(), &er.time_in_force);
|
||||
exec_type_parse_smart(pyer.exec_type(), &er.exec_type);
|
||||
Venue venue;
|
||||
bts_assert(venue_parse_smart(pyer.venue(), &venue));
|
||||
SecurityType sec_type;
|
||||
bts_assert(security_type_parse_smart(pyer.security_type(), &sec_type));
|
||||
er.security = find_sec(venue, sec_type, pyer.exchange_security_id());
|
||||
er.side = pyer.is_buy() ? bts::api::SIDE_BUY : bts::api::SIDE_SELL;
|
||||
*er.price = pyer.price();
|
||||
*er.stop_price = std::numeric_limits<double>::quiet_NaN();
|
||||
er.quantity = Quantity::create_new(pyer.quantity());
|
||||
*er.avg_price = pyer.avg_filled_price();
|
||||
er.filled_quantity = Quantity::create_new(pyer.filled_quantity());
|
||||
*er.last_price = pyer.last_filled_price();
|
||||
er.last_shares = Quantity::create_new(pyer.last_filled_quantity());
|
||||
er.transact_time =
|
||||
ExchangeTimeStamp64{static_cast<uint64_t>(pyer.transact_time())};
|
||||
}
|
||||
|
||||
void start() {
|
||||
INFO_TRACE("start");
|
||||
sm.start();
|
||||
}
|
||||
|
||||
// should not be called directly (use the state machine)
|
||||
void start_impl() {
|
||||
INFO_TRACE("start_impl");
|
||||
bts_assert(!py_thread);
|
||||
py_keep_going = true;
|
||||
py_thread.emplace([&]() {
|
||||
auto orig_signal_handler = std::signal(SIGINT, SIG_DFL);
|
||||
std::future<shared_ptr<PythonObjectT<ExecutionGateway>>> gw_fut =
|
||||
plugin_man->start_execution_gateway(
|
||||
name, config.gateway_type, bridge_ptr);
|
||||
gw_fut.wait();
|
||||
std::signal(SIGINT, orig_signal_handler);
|
||||
bridge.run_py(&py_keep_going, gw_fut.get(), [&](auto callback) {
|
||||
plugin_man->invoke(callback);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void stop() {
|
||||
INFO_TRACE("stop");
|
||||
sm.stop();
|
||||
}
|
||||
|
||||
void stop_impl() {
|
||||
INFO_TRACE("stop_impl");
|
||||
py_keep_going = false;
|
||||
if (py_thread) {
|
||||
py_thread->join();
|
||||
py_thread.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void create_order(const CreateOrderMsg &m) {
|
||||
LOG_CREATE_ORDER(Info, m);
|
||||
PyCreateOrderRequest py_req;
|
||||
to_py(py_req, m);
|
||||
bridge.on_order_msg(
|
||||
std::make_shared<PyCreateOrderRequest>(std::move(py_req)));
|
||||
}
|
||||
|
||||
void update_order(const CancelReplaceOrderMsg &m) {
|
||||
LOG_MODIFY_ORDER(Info, m);
|
||||
PyCancelReplaceOrderRequest py_req;
|
||||
to_py(py_req, m);
|
||||
bridge.on_order_msg(
|
||||
std::make_shared<PyCancelReplaceOrderRequest>(std::move(py_req)));
|
||||
}
|
||||
|
||||
void cancel_order(const CancelOrderMsg &m) {
|
||||
LOG_CANCEL_ORDER(Info, m);
|
||||
PyCancelOrderRequest req;
|
||||
to_py(req, m);
|
||||
bridge.on_order_msg(
|
||||
std::make_shared<PyCancelOrderRequest>(std::move(req)));
|
||||
}
|
||||
|
||||
void on_securities(const std::vector<api::Security const *> &secs) {
|
||||
std::vector<std::shared_ptr<bts::md::py_interop::PySecurity>> py_secs;
|
||||
for (const auto *sec : secs) {
|
||||
bts::md::py_interop::PySecurity py_sec;
|
||||
to_py(py_sec, *sec);
|
||||
py_secs.push_back(std::make_shared<bts::md::py_interop::PySecurity>(
|
||||
std::move(py_sec)));
|
||||
}
|
||||
bridge.on_securities(std::move(py_secs));
|
||||
}
|
||||
|
||||
void get_position_info() {
|
||||
sm.invoke_if_active([&](auto) {
|
||||
//
|
||||
});
|
||||
}
|
||||
|
||||
void dispatch_is_ready() {
|
||||
BTS_LOCK_GUARD(sm.mutex());
|
||||
if (is_ready_dirty) {
|
||||
is_ready_dirty = false;
|
||||
callbacks.gw_status_change(is_ready);
|
||||
}
|
||||
}
|
||||
|
||||
void from_py(
|
||||
const python_execution::PyExecutionReport &py_er,
|
||||
ExecutionReport &er) const {
|
||||
*er.order_key = py_er.key();
|
||||
bts_assert(venue_parse_smart(py_er.venue(), &er.exchange));
|
||||
SecurityType sec_type;
|
||||
bts_assert(security_type_parse_smart(py_er.security_type(), &sec_type));
|
||||
er.transact_time =
|
||||
ExchangeTimeStamp64{static_cast<uint64_t>(py_er.transact_time())};
|
||||
uint64_t exch_oid;
|
||||
if (parse_uint(py_er.exchange_order_id(), exch_oid)) {
|
||||
er.exchange_order_id.emplace<ExchangeOrderID64>().value() = exch_oid;
|
||||
} else {
|
||||
er.exchange_order_id.emplace<ExchangeOrderID>().value() =
|
||||
py_er.exchange_order_id();
|
||||
}
|
||||
er.security = sec_oracle.get_security(
|
||||
er.exchange, sec_type, py_er.exchange_security_id());
|
||||
er.leg_security = nullptr;
|
||||
er.side = py_er.is_buy() ? api::SIDE_BUY : api::SIDE_SELL;
|
||||
time_in_force_parse_smart(py_er.time_in_force(), &er.time_in_force);
|
||||
native_order_type_parse_smart(py_er.order_type(), &er.order_type);
|
||||
exec_type_parse_smart(py_er.exec_type(), &er.exec_type);
|
||||
native_order_status_parse_smart(py_er.order_status(), &er.order_status);
|
||||
er.manual_order_indicator = bts::api::orderman::MANUAL_ORDER_INDICATOR_NO;
|
||||
er.price.value() = py_er.price();
|
||||
er.quantity = py_er.quantity();
|
||||
er.filled_quantity = py_er.filled_quantity();
|
||||
er.last_shares = py_er.last_filled_quantity();
|
||||
er.last_price.value() = py_er.last_filled_price();
|
||||
er.avg_price.value() = py_er.avg_filled_price();
|
||||
er.stop_price.value() = std::numeric_limits<double>::quiet_NaN();
|
||||
const string &tx_id = py_er.transact_id();
|
||||
bts_assert(tx_id.size() < er.exchange_fill_id.value().max_size());
|
||||
bts_assert(!py_er.transact_id().empty());
|
||||
er.exchange_fill_id->copy_from(py_er.transact_id());
|
||||
}
|
||||
|
||||
void to_py(
|
||||
bts::md::py_interop::PySecurity &py_sec, const bts::api::Security &sec) {
|
||||
py_sec.set_id(sec.id());
|
||||
py_sec.set_asset(sec.symbol());
|
||||
py_sec.set_venue(bts::api::venue_name_pretty(sec.exchange()));
|
||||
py_sec.set_currency(sec.currency());
|
||||
// Exchange ID and name
|
||||
std::string name = bts::securities::descriptor(sec);
|
||||
py_sec.set_exchange_id(name);
|
||||
py_sec.set_name(std::move(name));
|
||||
// Price ticks
|
||||
const bts::api::ContractSpecification &spec = sec.contract_spec();
|
||||
md::py_interop::PyContractSpecification py_spec;
|
||||
md::py_interop::PyContractPriceInfo &py_price_info =
|
||||
py_spec.mutable_price_infos().emplace_back();
|
||||
float tick_unit;
|
||||
float tick_base;
|
||||
if (!domain::get_tick_factors(spec, tick_unit, tick_base)) {
|
||||
WARN_FN(
|
||||
"on_securities", "Failed to get price tick from {} {}. spec: {}. ",
|
||||
sec.exchange(), name, utils::pb::to_string(sec.contract_spec()));
|
||||
tick_unit = 0.01f;
|
||||
tick_base = 0.01f;
|
||||
}
|
||||
py_price_info.set_tick_unit(tick_unit);
|
||||
py_price_info.set_tick_base(tick_base);
|
||||
// Quantity ticks
|
||||
md::py_interop::PyContractQuantityInfo &py_qty_info =
|
||||
py_spec.mutable_quantity_infos().emplace_back();
|
||||
if (spec.quantity_infos().empty()) {
|
||||
tick_unit = 1.0f;
|
||||
tick_base = 1.0f;
|
||||
WARN_FN(
|
||||
"on_securities", "Failed to get qty tick from {} {}. spec: {}. ",
|
||||
sec.exchange(), name, utils::pb::to_string(sec.contract_spec()));
|
||||
} else {
|
||||
tick_unit = spec.quantity_infos(0).tick_unit();
|
||||
tick_base = spec.quantity_infos(0).tick_base();
|
||||
}
|
||||
py_qty_info.set_tick_unit(tick_unit);
|
||||
py_qty_info.set_tick_base(tick_base);
|
||||
py_sec.set_contract_spec(std::move(py_spec));
|
||||
// Spot/Future/Option
|
||||
switch (sec.Contract_case()) {
|
||||
case bts::api::Security::kSpot: {
|
||||
py_sec.set_spot(bts::md::py_interop::PySpot{});
|
||||
break;
|
||||
}
|
||||
case bts::api::Security::kFuture: {
|
||||
const bts::api::FutureContract &fut = sec.future();
|
||||
bts::md::py_interop::PyFuture py_fut;
|
||||
py_fut.set_expiration(fmt::format(
|
||||
"{}-{:02}-{:02}", fut.expiration_year(), fut.expiration_month(),
|
||||
fut.expiration_day()));
|
||||
py_sec.set_future(std::move(py_fut));
|
||||
break;
|
||||
}
|
||||
case bts::api::Security::kOption: {
|
||||
const bts::api::OptionContract &opt = sec.option();
|
||||
bts::md::py_interop::PyOption py_opt;
|
||||
py_opt.set_expiration(fmt::format(
|
||||
"{}-{:02}-{:02}", opt.expiration_year(), opt.expiration_month(),
|
||||
opt.expiration_day()));
|
||||
py_opt.set_strike(opt.strike());
|
||||
py_opt.set_is_call(opt.call_put() == bts::api::PUTORCALL_CALL);
|
||||
py_sec.set_option(std::move(py_opt));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
bts_assert_msg(false, "Only spots, futures, and options supported");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void to_py(PyCreateOrderRequest &py_req, const CreateOrderMsg &msg) {
|
||||
py_req.set_key(msg.key.value());
|
||||
py_req.set_exchange_security_id(msg.security_name());
|
||||
py_req.set_venue(bts::api::venue_name_pretty(msg.venue()));
|
||||
py_req.set_is_buy(msg.side == bts::api::SIDE_BUY);
|
||||
py_req.set_price(msg.price.value());
|
||||
py_req.set_quantity(msg.quantity.full_value_float());
|
||||
py_req.set_order_type(
|
||||
bts::api::orderman::native_order_type_name_pretty(msg.order_type));
|
||||
py_req.set_time_in_force(
|
||||
bts::api::orderman::time_in_force_name_pretty(msg.time_in_force));
|
||||
}
|
||||
|
||||
void to_py(
|
||||
PyCancelReplaceOrderRequest &py_req,
|
||||
const bts::orderman::CancelReplaceOrderMsg &msg) {
|
||||
auto &cf = msg.create_fields;
|
||||
py_req.set_key(cf.key.value());
|
||||
py_req.set_exchange_order_id(
|
||||
std::to_string(msg.exchange_order_id.value()));
|
||||
py_req.set_exchange_security_id(cf.security_name());
|
||||
py_req.set_venue(bts::api::venue_name_pretty(cf.venue()));
|
||||
py_req.set_is_buy(cf.side == bts::api::SIDE_BUY);
|
||||
py_req.set_price(cf.price.value());
|
||||
py_req.set_quantity(cf.quantity.full_value_float());
|
||||
}
|
||||
|
||||
void to_py(PyCancelOrderRequest &py_req, const CancelOrderMsg &msg) {
|
||||
py_req.set_key(msg.key.value());
|
||||
py_req.set_exchange_order_id(
|
||||
std::to_string(msg.exchange_order_id.value()));
|
||||
py_req.set_exchange_security_id(msg.security_name());
|
||||
py_req.set_venue(bts::api::venue_name_pretty(msg.venue()));
|
||||
py_req.set_is_buy(msg.side == bts::api::SIDE_BUY);
|
||||
}
|
||||
|
||||
void dispatch_order_updates() {
|
||||
BTS_LOCK_GUARD(sm.mutex());
|
||||
std::shared_ptr<const python_execution::PyExecutionReport> py_er_p;
|
||||
utils::Stopwatch sw;
|
||||
uint32_t n_dequed = 0;
|
||||
uint32_t n_invalid = 0;
|
||||
sw.start();
|
||||
while (bridge.pop_exe_report(py_er_p)) {
|
||||
n_dequed++;
|
||||
const python_execution::PyExecutionReport &py_er = *py_er_p;
|
||||
shared_ptr<const ExecutionReport> er_p =
|
||||
orders.update(OrderKey{py_er.key()}, [&](auto &er_p, bool) {
|
||||
from_py(py_er, *er_p);
|
||||
});
|
||||
const ExecutionReport &er = *er_p;
|
||||
string error;
|
||||
if (!validate(er, error)) {
|
||||
LOG_EXECUTION_REPORT(Error, er_p, "INVALID_STATE", error);
|
||||
n_invalid++;
|
||||
continue;
|
||||
}
|
||||
LOG_EXECUTION_REPORT(Info, er_p);
|
||||
switch (er.order_status) {
|
||||
case api::orderman::NATIVE_ORDER_STATUS_FILLED:
|
||||
case api::orderman::NATIVE_ORDER_STATUS_CANCELED:
|
||||
case api::orderman::NATIVE_ORDER_STATUS_REJECTED: {
|
||||
orders.erase(er.order_key);
|
||||
INFO_FN(
|
||||
"dispatch_order_updates",
|
||||
"Order reached terminal state {} (id={})", er.order_status,
|
||||
er.exchange_order_id);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
INFO_FN(
|
||||
"dispatch_order_updates",
|
||||
"Firing exe report callback. {} {} {} @ {} {} (filled {}/{}, "
|
||||
"id={}). {} active orders",
|
||||
er.side, er.security_name(), er.quantity, er.price,
|
||||
er.order_status, er.filled_quantity, er.quantity, er.order_key,
|
||||
orders.size());
|
||||
callbacks.exe_report(er_p, {});
|
||||
}
|
||||
sw.stop();
|
||||
if (n_dequed > 0) {
|
||||
INFO(
|
||||
R"([py_exe_gw_disp_ord_stats.json]{{ "n_active": {}, "n_dequed": {}, "n_invalid": {}, "dur": {}, "ts": {} }})",
|
||||
orders.size(), n_dequed, n_invalid, sw.total_elapsed().nanos(),
|
||||
TimeStamp::now_ns());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
PyExecutionGatewayAdapter::PyExecutionGatewayAdapter(
|
||||
SecurityOracle sec_oracle,
|
||||
PyExecutionGatewayConfig session_cfg,
|
||||
bts::config::JetFIX::Connection jetfix_connection,
|
||||
shared_ptr<PluginManager> plugin_man)
|
||||
: me(std::make_unique<PImpl>(
|
||||
std::move(sec_oracle),
|
||||
std::move(session_cfg),
|
||||
std::move(jetfix_connection),
|
||||
std::move(plugin_man))) {
|
||||
bts_assert(
|
||||
me->config.account.size() <=
|
||||
ExecutionReport{}.account_name.value().max_size());
|
||||
}
|
||||
|
||||
PyExecutionGatewayAdapter::~PyExecutionGatewayAdapter() = default;
|
||||
|
||||
void PyExecutionGatewayAdapter::start() { me->start(); }
|
||||
|
||||
void PyExecutionGatewayAdapter::on_securities(
|
||||
const bts::mercury::Update<NewSecuritiesEvent> & /*update*/) {
|
||||
INFO_FN(
|
||||
"on_securities", "t_id: {} t_name: {}", bts::os::get_thread_id(),
|
||||
bts::os::get_thread_name());
|
||||
std::vector<api::Security const *> secs =
|
||||
me->sec_oracle.get_securities([&](const bts::api::Security &sec) {
|
||||
return sec.exchange() == me->jetfix_connection->venue();
|
||||
});
|
||||
me->on_securities(secs);
|
||||
}
|
||||
|
||||
void PyExecutionGatewayAdapter::on_admin_data(
|
||||
const bts::mercury::Update<NewAdminDataEvent> &update) {
|
||||
INFO_FN(
|
||||
"on_admin_data", "t_id: {} t_name: {}", bts::os::get_thread_id(),
|
||||
bts::os::get_thread_name());
|
||||
if (update.events == nullptr) {
|
||||
return;
|
||||
}
|
||||
std::lock_guard<std::recursive_mutex> lock{me->sm.mutex()};
|
||||
const auto *account = bts::admin::AdminServiceUtils::find_account(
|
||||
*update.events->back().admin_data, me->config.account);
|
||||
if (account == nullptr) {
|
||||
WARN_FN(
|
||||
"on_admin_data",
|
||||
"Gateway account not found. Cannot execute orders. name: '{}'",
|
||||
me->config.account);
|
||||
return;
|
||||
}
|
||||
if (me->account.name() == account->name() &&
|
||||
me->account.id() == account->id()) {
|
||||
return;
|
||||
}
|
||||
me->account.CopyFrom(*account);
|
||||
INFO_FN("on_admin_data", "Resolved gateway account '{}'", account->name());
|
||||
}
|
||||
|
||||
void PyExecutionGatewayAdapter::send_msgs(
|
||||
vector<OrderMsg> const &msg_vec, bts::trace::CorID) {
|
||||
INFO_FN("send_msgs", "count: {}", msg_vec.size());
|
||||
for (const auto &msg : msg_vec) {
|
||||
std::visit(
|
||||
bts::overloaded{
|
||||
[&](const CreateOrderMsg &m) { me->create_order(m); },
|
||||
[&](const CancelReplaceOrderMsg &m) { me->update_order(m); },
|
||||
[&](const CancelOrderMsg &m) { me->cancel_order(m); },
|
||||
[&](const RFQMsg &) {
|
||||
bts_assert_msg(
|
||||
false, "PyExecutionGatewayAdapter RFQ not implemented");
|
||||
},
|
||||
},
|
||||
msg);
|
||||
}
|
||||
}
|
||||
|
||||
void PyExecutionGatewayAdapter::send_fix(
|
||||
const bts::jetfix::OutFIXPtr &, bts::trace::CorID) {
|
||||
bts_assert_msg(false, "ExecutionGateway does not support fix");
|
||||
}
|
||||
|
||||
void PyExecutionGatewayAdapter::dispatch_incoming_fix_messages() {
|
||||
me->dispatch_is_ready();
|
||||
me->dispatch_order_updates();
|
||||
}
|
||||
|
||||
// Callbacks
|
||||
bts::orderman::OnExeReport &PyExecutionGatewayAdapter::on_exe_report() {
|
||||
return me->callbacks.exe_report;
|
||||
}
|
||||
bts::orderman::OnGwStatusChange &
|
||||
PyExecutionGatewayAdapter::on_gw_status_change() {
|
||||
return me->callbacks.gw_status_change;
|
||||
}
|
||||
bts::orderman::OnCancelReject &PyExecutionGatewayAdapter::on_cancel_reject() {
|
||||
return me->callbacks.cancel_reject;
|
||||
}
|
||||
bts::orderman::OnExchangePositionState &
|
||||
PyExecutionGatewayAdapter::on_exchange_position_state() {
|
||||
return me->callbacks.exchange_position_state;
|
||||
}
|
||||
bts::orderman::OnExchangeOpenOrdersState &
|
||||
PyExecutionGatewayAdapter::on_exchange_open_orders() {
|
||||
return me->callbacks.exchange_open_orders;
|
||||
}
|
||||
|
||||
} // namespace bts::orderman
|
||||
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include "spark/config/protos/jetfix.pb.h"
|
||||
#include "spark/md/SecurityOracle.h"
|
||||
#include "spark/orderman/gateways/IExecutionGateway.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace python_interop {
|
||||
class PluginManager;
|
||||
}
|
||||
|
||||
namespace bts::orderman {
|
||||
|
||||
struct PyExecutionGatewayConfig;
|
||||
|
||||
class PyExecutionGatewayAdapter : public bts::orderman::IExecutionGateway {
|
||||
public:
|
||||
explicit PyExecutionGatewayAdapter(
|
||||
bts::md::SecurityOracle sec_oracle,
|
||||
PyExecutionGatewayConfig session_cfg,
|
||||
bts::config::JetFIX::Connection jetfix_connection,
|
||||
std::shared_ptr<python_interop::PluginManager> plugin_man);
|
||||
~PyExecutionGatewayAdapter() override;
|
||||
|
||||
void start() override;
|
||||
void dispatch_incoming_fix_messages() override;
|
||||
void on_securities(
|
||||
const bts::mercury::Update<bts::orderman::NewSecuritiesEvent> &)
|
||||
override;
|
||||
void on_admin_data(
|
||||
const bts::mercury::Update<bts::orderman::NewAdminDataEvent> &) override;
|
||||
void send_fix(const bts::jetfix::OutFIXPtr &, bts::trace::CorID) override;
|
||||
void send_msgs(
|
||||
std::vector<bts::orderman::OrderMsg> const &,
|
||||
bts::trace::CorID) override;
|
||||
|
||||
bts::orderman::OnExeReport &on_exe_report() override;
|
||||
bts::orderman::OnGwStatusChange &on_gw_status_change() override;
|
||||
bts::orderman::OnCancelReject &on_cancel_reject() override;
|
||||
bts::orderman::OnExchangePositionState &
|
||||
on_exchange_position_state() override;
|
||||
bts::orderman::OnExchangeOpenOrdersState &on_exchange_open_orders() override;
|
||||
|
||||
private:
|
||||
struct PImpl;
|
||||
std::unique_ptr<PImpl> me;
|
||||
};
|
||||
|
||||
} // namespace bts::orderman
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include "spark/api/domain/Venue.pb.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace bts::orderman {
|
||||
|
||||
struct PyExecutionGatewayConfig {
|
||||
std::string gateway_type;
|
||||
std::string account;
|
||||
std::vector<std::string> module_path;
|
||||
api::domain::Venue venue;
|
||||
};
|
||||
|
||||
inline void to_json(nlohmann::json &j, const PyExecutionGatewayConfig &cfg) {
|
||||
j["gateway_type"] = cfg.gateway_type;
|
||||
j["account"] = cfg.account;
|
||||
j["module_path"] = cfg.module_path;
|
||||
j["venue"] = cfg.venue;
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json &j, PyExecutionGatewayConfig &cfg) {
|
||||
auto it = j.find("gateway_type");
|
||||
if (it != j.end()) {
|
||||
it->get_to(cfg.gateway_type);
|
||||
}
|
||||
it = j.find("account");
|
||||
if (it != j.end()) {
|
||||
it->get_to(cfg.account);
|
||||
}
|
||||
it = j.find("module_path");
|
||||
if (it != j.end()) {
|
||||
it->get_to(cfg.module_path);
|
||||
}
|
||||
it = j.find("venue");
|
||||
if (it != j.end()) {
|
||||
it->get_to(cfg.venue);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace bts::orderman
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "MarketDataGateway.h"
|
||||
#include "python_interop/plugin_manager.h"
|
||||
#include "spark/api/domain/Venue.h"
|
||||
#include "spark/api/md/MarketDataConfig.pb.h"
|
||||
#include "spark/api/md/MarketDataConfigUtils.h"
|
||||
@@ -67,7 +68,10 @@ struct MarketDataGateway::PImpl {
|
||||
vector<unique_ptr<MdSink>> sinks;
|
||||
vector<shared_ptr<IMarketDataSource>> sources;
|
||||
|
||||
PImpl(MarketDataConfig config, shared_ptr<IDGenerator> sec_id_store)
|
||||
PImpl(
|
||||
MarketDataConfig config,
|
||||
shared_ptr<IDGenerator> sec_id_store,
|
||||
shared_ptr<python_interop::PluginManager> py_plugin_man)
|
||||
: config(std::move(config)), sec_id_store(std::move(sec_id_store)) {
|
||||
providers.push_back(std::make_unique<CCApiMarketDataProvider>(
|
||||
this->config, this->sec_id_store));
|
||||
@@ -80,7 +84,7 @@ struct MarketDataGateway::PImpl {
|
||||
providers.push_back(std::make_unique<TaifexMarketDataProvider>(
|
||||
this->config, this->sec_id_store));
|
||||
providers.push_back(std::make_unique<PyMarketDataProvider>(
|
||||
this->config, this->sec_id_store));
|
||||
this->config, this->sec_id_store, std::move(py_plugin_man)));
|
||||
providers.push_back(std::make_unique<ReplayMarketDataProvider>(
|
||||
this->config, this->sec_id_store));
|
||||
}
|
||||
@@ -126,9 +130,13 @@ struct MarketDataGateway::PImpl {
|
||||
};
|
||||
|
||||
MarketDataGateway::MarketDataGateway(
|
||||
MarketDataConfig config, shared_ptr<IDGenerator> sec_id_store)
|
||||
MarketDataConfig config,
|
||||
shared_ptr<IDGenerator> sec_id_store,
|
||||
std::shared_ptr<python_interop::PluginManager> py_plugin_man)
|
||||
: me_(std::make_unique<MarketDataGateway::PImpl>(
|
||||
std::move(config), std::move(sec_id_store))) {}
|
||||
std::move(config),
|
||||
std::move(sec_id_store),
|
||||
std::move(py_plugin_man))) {}
|
||||
|
||||
MarketDataGateway::~MarketDataGateway() = default;
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@ namespace bts::persistence {
|
||||
class IDGenerator;
|
||||
}
|
||||
|
||||
namespace python_interop {
|
||||
class PluginManager;
|
||||
}
|
||||
|
||||
namespace bts::md {
|
||||
|
||||
/**
|
||||
@@ -20,7 +24,8 @@ class MarketDataGateway {
|
||||
public:
|
||||
explicit MarketDataGateway(
|
||||
api::md::MarketDataConfig config,
|
||||
std::shared_ptr<persistence::IDGenerator> sec_id_store);
|
||||
std::shared_ptr<persistence::IDGenerator> sec_id_store,
|
||||
std::shared_ptr<python_interop::PluginManager> py_plugin_man);
|
||||
~MarketDataGateway();
|
||||
|
||||
void start();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "PyMarketDataProvider.h"
|
||||
#include "python_interop/plugin_manager.h"
|
||||
#include "spark/api/md/MarketDataConfig.pb.h"
|
||||
#include "spark/md/IMarketDataSource.h"
|
||||
#include "spark/persistence/IDGenerator.h"
|
||||
@@ -19,16 +20,23 @@ using Path = boost::filesystem::path;
|
||||
struct PyMarketDataProvider::PImpl {
|
||||
MarketDataConfig config;
|
||||
shared_ptr<IDGenerator> sec_id_store;
|
||||
shared_ptr<python_interop::PluginManager> py_worker;
|
||||
vector<shared_ptr<PyMarketDataSubscription>> py_md_subs;
|
||||
|
||||
PImpl(MarketDataConfig config, shared_ptr<IDGenerator> sec_id_store)
|
||||
: config(std::move(config)), sec_id_store(std::move(sec_id_store)) {}
|
||||
PImpl(
|
||||
MarketDataConfig config,
|
||||
shared_ptr<IDGenerator> sec_id_store,
|
||||
shared_ptr<python_interop::PluginManager> py_worker)
|
||||
: config(std::move(config)), sec_id_store(std::move(sec_id_store)),
|
||||
py_worker(std::move(py_worker)) {}
|
||||
};
|
||||
|
||||
PyMarketDataProvider::PyMarketDataProvider(
|
||||
MarketDataConfig config, shared_ptr<IDGenerator> sec_id_store)
|
||||
: me_(std::make_unique<PImpl>(std::move(config), std::move(sec_id_store))) {
|
||||
}
|
||||
MarketDataConfig config,
|
||||
shared_ptr<IDGenerator> sec_id_store,
|
||||
shared_ptr<python_interop::PluginManager> py_worker)
|
||||
: me_(std::make_unique<PImpl>(
|
||||
std::move(config), std::move(sec_id_store), std::move(py_worker))) {}
|
||||
|
||||
PyMarketDataProvider::~PyMarketDataProvider() = default;
|
||||
|
||||
@@ -38,7 +46,7 @@ PyMarketDataProvider::get_sources() {
|
||||
for (const auto &py_plugin : me_->config.python_md_providers()) {
|
||||
try {
|
||||
auto source = std::make_shared<PyMarketDataSubscription>(
|
||||
py_plugin, me_->sec_id_store.get());
|
||||
py_plugin, me_->sec_id_store.get(), me_->py_worker);
|
||||
me_->py_md_subs.push_back(source);
|
||||
rv.emplace_back(py_plugin.venue(), std::move(source));
|
||||
} catch (const std::exception &ex) {
|
||||
|
||||
@@ -7,13 +7,18 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace python_interop {
|
||||
class PluginManager;
|
||||
}
|
||||
|
||||
namespace bts::md {
|
||||
|
||||
class PyMarketDataProvider : public IMarketDataProvider {
|
||||
public:
|
||||
explicit PyMarketDataProvider(
|
||||
api::md::MarketDataConfig config,
|
||||
std::shared_ptr<persistence::IDGenerator> sec_id_store);
|
||||
std::shared_ptr<persistence::IDGenerator> sec_id_store,
|
||||
std::shared_ptr<python_interop::PluginManager> py_worker);
|
||||
~PyMarketDataProvider();
|
||||
|
||||
std::vector<
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
#include "PyMarketDataSubscription.h"
|
||||
#include "python_md/md_sink.h"
|
||||
#include "python_md/py_book.h"
|
||||
#include "python_md/py_contract_specification.h"
|
||||
#include "python_md/py_md_executor.h"
|
||||
#include "python_md/py_security.h"
|
||||
#include "python_md/py_security_list.h"
|
||||
#include "python_md/py_trade.h"
|
||||
#include "python_interop/md/md_bridge_impl.h"
|
||||
#include "python_interop/md/py_book.h"
|
||||
#include "python_interop/md/py_trade.h"
|
||||
#include "python_interop/plugin_manager.h"
|
||||
#include "python_interop/py_contract_specification.h"
|
||||
#include "python_interop/py_security.h"
|
||||
#include "python_interop/python_object.h"
|
||||
#include "spark/api/domain/CallPut.pb.h"
|
||||
#include "spark/api/domain/SecurityType.h"
|
||||
#include "spark/api/domain/Venue.h"
|
||||
#include "spark/api/md/PyMarketDataProviderConfig.pb.h"
|
||||
#include "spark/core/TimeSpan.h"
|
||||
#include "spark/core/TimeStamp.h"
|
||||
#include "spark/domain/AggressorSide.h"
|
||||
#include "spark/domain/FutureSecurity.h"
|
||||
@@ -17,13 +18,17 @@
|
||||
#include "spark/domain/SecurityID.h"
|
||||
#include "spark/domain/SpotSecurity.h"
|
||||
#include "spark/domain/TradeRecord.h"
|
||||
#include "spark/domain_model/SecurityType.h"
|
||||
#include "spark/io/json_formatter.h"
|
||||
#include "spark/logging/logging.h"
|
||||
#include "spark/logging/trace_log.h"
|
||||
#include "spark/md/IMarketDataListener.h"
|
||||
#include "spark/os/os.h"
|
||||
#include "spark/persistence/IDGenerator.h"
|
||||
#include "spark/utils/bts_assert.h"
|
||||
#include "spark/utils/stopwatch.h"
|
||||
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <pybind11/embed.h>
|
||||
@@ -33,12 +38,44 @@
|
||||
|
||||
namespace bts::md {
|
||||
|
||||
#define AGGRESSOR_SIDE(x) bts::domain::AGGRESSOR_SIDE_##x
|
||||
#define _LOG(level, ...) LOG(md, level, __VA_ARGS__)
|
||||
#define INFO(...) _LOG(bts::Info, __VA_ARGS__)
|
||||
#define ERROR(...) _LOG(bts::Error, __VA_ARGS__)
|
||||
|
||||
#define _LOG_TRACE(level, name) \
|
||||
LOG_TRACE(md, level, std::string("py_md_sub"), std::string(name))
|
||||
#define INFO_TRACE(name) _LOG_TRACE(bts::Info, name)
|
||||
#define WARN_TRACE(name) _LOG_TRACE(bts::Warn, name)
|
||||
#define ERROR_TRACE(name) _LOG_TRACE(bts::Error, name)
|
||||
|
||||
#define INFO_TRACE_PARAMS(name, params) \
|
||||
LOG_TRACE_PARAMS( \
|
||||
md, bts::Info, std::string("py_md_sub"), std::string(name), params)
|
||||
|
||||
#define LOG_DISPATCH_STATS(level, typ, n, dur) \
|
||||
_LOG( \
|
||||
level, \
|
||||
R"([py_md_dispatch.json]{{ "typ": ")" typ \
|
||||
R"(", "n_recs": {}, "dur": "{:.2f}ms", "ts": {} }})", \
|
||||
n, dur.total_millis(), bts::core::TimeStamp::now_ns())
|
||||
|
||||
#define LOG_SECURITY(level, py_sec) \
|
||||
LOG(md, level, R"([py_md_sec.json]{{ "sec": {}, "ts": {}}})", \
|
||||
json_formatter(py_sec), bts::core::TimeStamp::now_ns())
|
||||
|
||||
#define LOG_BOOK(level, py_book) \
|
||||
LOG(md, level, R"([py_md_book.json]{{ "bk": {}, "ts": {}}})", \
|
||||
json_formatter(py_book), bts::core::TimeStamp::now_ns())
|
||||
|
||||
#define LOG_TRADE(level, py_trade) \
|
||||
LOG(md, level, R"([py_md_trade.json]{{ "trd": {}, "ts": {}}})", \
|
||||
json_formatter(py_trade), bts::core::TimeStamp::now_ns())
|
||||
|
||||
using bts::api::domain::CallPut;
|
||||
using bts::api::domain::SecurityType;
|
||||
using bts::api::domain::Venue;
|
||||
using bts::api::md::PyMarketDataProviderConfig;
|
||||
using bts::core::TimeSpan;
|
||||
using bts::core::TimeStamp;
|
||||
using bts::domain::AggressorSide;
|
||||
using bts::domain::Book;
|
||||
@@ -51,17 +88,15 @@ using bts::domain::SpotSecurity;
|
||||
using bts::domain::TradeRecord;
|
||||
using bts::io::json_formatter;
|
||||
using bts::md::py_interop::PyBook;
|
||||
using bts::md::py_interop::PyBookList;
|
||||
using bts::md::py_interop::PyBookSide;
|
||||
using bts::md::py_interop::PyMdExecutor;
|
||||
using bts::md::py_interop::PySecurity;
|
||||
using bts::md::py_interop::PySecurityList;
|
||||
using bts::md::py_interop::PyTrade;
|
||||
using bts::md::py_interop::PyTradeList;
|
||||
using bts::persistence::IDGenerator;
|
||||
using std::optional;
|
||||
using std::shared_ptr;
|
||||
using std::size_t;
|
||||
using std::string;
|
||||
using std::thread;
|
||||
using std::vector;
|
||||
|
||||
using Path = boost::filesystem::path;
|
||||
@@ -72,28 +107,172 @@ struct TradeTuple {
|
||||
Venue venue;
|
||||
};
|
||||
|
||||
struct PyMarketDataSubscription::PImpl : private py_interop::MdSink {
|
||||
PImpl(PyMarketDataProviderConfig config, IDGenerator *sec_id_store)
|
||||
: config(std::move(config)), py_md_exec_(
|
||||
this->config.provider_type(),
|
||||
{this->config.module_path().begin(),
|
||||
this->config.module_path().end()},
|
||||
this),
|
||||
struct PyMarketDataSubscription::PImpl {
|
||||
PImpl(
|
||||
PyMarketDataProviderConfig config,
|
||||
IDGenerator *sec_id_store,
|
||||
shared_ptr<python_interop::PluginManager> py_plugin_man)
|
||||
: config(std::move(config)),
|
||||
name(fmt::format("{}_md_pro", string{this->config.provider_type()})),
|
||||
bridge_ptr(std::make_shared<py_interop::MdBridgeImpl>()),
|
||||
bridge(*bridge_ptr), py_plugin_man(std::move(py_plugin_man)),
|
||||
sec_id_store_(sec_id_store) {}
|
||||
|
||||
size_t dispatch_securities() {
|
||||
// `securities_` and `security_ptrs_` should already be empty, but no harm in clearing them
|
||||
// again. We keep them as member variables to avoid (re)allocating memory in each callback.
|
||||
securities_.clear();
|
||||
security_ptrs_.clear();
|
||||
std::shared_ptr<const PySecurity> py_sec_p;
|
||||
while (bridge.pop_security(py_sec_p)) {
|
||||
const auto &py_sec = *py_sec_p;
|
||||
LOG_SECURITY(Info, py_sec);
|
||||
auto sec = to_bts_security_(py_sec);
|
||||
if (!sec) {
|
||||
LOG(md, Error,
|
||||
"Failed to convert security {} to bts::domain::Security",
|
||||
py_sec.name());
|
||||
LOG(md, Error,
|
||||
R"([py_md_sec_parse_error.json]{{ "sec": {}, "ts": {}}})",
|
||||
py_sec);
|
||||
continue;
|
||||
}
|
||||
securities_.push_back(std::move(*sec));
|
||||
}
|
||||
const size_t n = securities_.size();
|
||||
if (!securities_.empty()) {
|
||||
// Populate vectors of security ptrs per each venue.
|
||||
for (auto &sec : securities_) {
|
||||
security_ptrs_.push_back(&sec);
|
||||
}
|
||||
// Pass securities to the sinks (per venue).
|
||||
listener_->on_securities(security_ptrs_);
|
||||
securities_.clear();
|
||||
security_ptrs_.clear();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
size_t dispatch_books() {
|
||||
size_t n = 0;
|
||||
std::shared_ptr<const PyBook> py_book_p;
|
||||
while (bridge.pop_book(py_book_p)) {
|
||||
const auto &py_book = *py_book_p;
|
||||
LOG_BOOK(Diag, py_book);
|
||||
bts_assert(book_.count_populated() == 0);
|
||||
to_book_(py_book, book_);
|
||||
listener_->on_book(book_);
|
||||
book_.clear();
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
size_t dispatch_trades() {
|
||||
// `trades_` and `trade_ptrs_` should already be empty, but no harm in clearing them again.
|
||||
// We keep them as member variables to avoid (re)allocating memory in each callback.
|
||||
trades_.clear();
|
||||
trade_ptrs_.clear();
|
||||
std::shared_ptr<const PyTrade> py_trade_p;
|
||||
while (bridge.pop_trade(py_trade_p)) {
|
||||
const auto &py_trade = *py_trade_p;
|
||||
LOG_TRADE(Diag, py_trade);
|
||||
to_bts_trade_(py_trade, trades_.emplace_back());
|
||||
}
|
||||
const size_t n = trades_.size();
|
||||
for (const auto &t : trades_) {
|
||||
trade_ptrs_.push_back(&t);
|
||||
}
|
||||
listener_->on_trades(trade_ptrs_);
|
||||
trades_.clear();
|
||||
trade_ptrs_.clear();
|
||||
return n;
|
||||
}
|
||||
|
||||
void start(shared_ptr<IMarketDataListener> listener) {
|
||||
bts_assert(listener != nullptr);
|
||||
this->listener_ = std::move(listener);
|
||||
LOG(md, Info, "PyMarketDataSubscription::start t_id:{} t_name:{}",
|
||||
os::get_thread_id(), os::get_thread_name());
|
||||
py_md_exec_.start();
|
||||
py_keep_going = true;
|
||||
py_thread.emplace([&]() {
|
||||
INFO_TRACE_PARAMS(
|
||||
"run_py", json_formatter(std::map<string, string>{
|
||||
{"name", name}, {"ev", "STARTING_PY_MD_PRO"}}));
|
||||
utils::Stopwatch sw = utils::Stopwatch::start_new();
|
||||
std::future<std::shared_ptr<
|
||||
python_interop::PythonObjectT<bts::md::py_interop::MdProvider>>>
|
||||
md_pro_fut = py_plugin_man->start_md_provider(
|
||||
name, config.provider_type(), bridge_ptr);
|
||||
md_pro_fut.wait();
|
||||
INFO_TRACE_PARAMS(
|
||||
"run_py",
|
||||
json_formatter(std::map<string, string>{
|
||||
{"name", name},
|
||||
{"ev", "STARTED_PY_MD_PRO"},
|
||||
{"dur",
|
||||
fmt::format("{:.3f}ms", sw.elapsed().total_millis())}}));
|
||||
});
|
||||
cpp_keep_going = true;
|
||||
cpp_thread.emplace([&]() {
|
||||
static const TimeSpan sec_warn_thresh = TimeSpan::from_millis(100);
|
||||
static const TimeSpan bk_warn_thresh = TimeSpan::from_millis(30);
|
||||
static const TimeSpan trd_warn_thresh = TimeSpan::from_millis(30);
|
||||
utils::Stopwatch sw;
|
||||
TimeSpan dur;
|
||||
size_t n;
|
||||
while (cpp_keep_going) {
|
||||
// Dispatch securities
|
||||
sw.restart();
|
||||
n = dispatch_securities();
|
||||
dur = sw.elapsed();
|
||||
if (n > 0) {
|
||||
LOG_DISPATCH_STATS(
|
||||
dur > sec_warn_thresh ? Warn : Info, "sec", n, dur);
|
||||
}
|
||||
// Dispatch books
|
||||
sw.restart();
|
||||
n = dispatch_books();
|
||||
dur = sw.elapsed();
|
||||
if (n > 0) {
|
||||
LOG_DISPATCH_STATS(
|
||||
dur > bk_warn_thresh ? Warn : Diag, "bk", n, dur);
|
||||
}
|
||||
// Dispatch trades
|
||||
sw.restart();
|
||||
n = dispatch_trades();
|
||||
dur = sw.elapsed();
|
||||
if (n > 0) {
|
||||
LOG_DISPATCH_STATS(
|
||||
dur > trd_warn_thresh ? Warn : Diag, "trd", n, dur);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void stop() { py_md_exec_.stop(); }
|
||||
void stop() {
|
||||
py_keep_going = false;
|
||||
if (py_thread) {
|
||||
py_thread->join();
|
||||
py_thread.reset();
|
||||
}
|
||||
cpp_keep_going = false;
|
||||
if (cpp_thread) {
|
||||
cpp_thread->join();
|
||||
cpp_thread.reset();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
PyMarketDataProviderConfig config;
|
||||
PyMdExecutor py_md_exec_;
|
||||
std::string name;
|
||||
std::shared_ptr<py_interop::MdBridgeImpl> bridge_ptr;
|
||||
py_interop::MdBridgeImpl &bridge;
|
||||
optional<thread> py_thread;
|
||||
std::shared_ptr<python_interop::PluginManager> py_plugin_man;
|
||||
volatile bool py_keep_going = false;
|
||||
optional<thread> cpp_thread;
|
||||
volatile bool cpp_keep_going = false;
|
||||
shared_ptr<IMarketDataListener> listener_;
|
||||
|
||||
shared_ptr<IDGenerator> sec_id_store_;
|
||||
@@ -106,65 +285,6 @@ struct PyMarketDataSubscription::PImpl : private py_interop::MdSink {
|
||||
vector<TradeRecord> trades_;
|
||||
vector<const TradeRecord *> trade_ptrs_;
|
||||
|
||||
void on_securities(const PySecurityList &py_securities) override {
|
||||
LOG(md, Info, R"([py_md_secs.json]{{ "secs": {}, "ts": {}}})",
|
||||
json_formatter(py_securities.items()), TimeStamp::now_ns());
|
||||
// `securities_` and `security_ptrs_` should already be empty, but no harm in clearing them
|
||||
// again. We keep them as member variables to avoid (re)allocating memory in each callback.
|
||||
securities_.clear();
|
||||
security_ptrs_.clear();
|
||||
for (const auto &py_sec : py_securities.items()) {
|
||||
auto sec = to_bts_security_(py_sec);
|
||||
if (!sec) {
|
||||
LOG(md, Error,
|
||||
"Failed to convert security {} to bts::domain::Security",
|
||||
py_sec.name());
|
||||
LOG(md, Error, R"([py_md_sec_parse_error.json]{{ "sec": {}, "ts": {}}})", py_sec);
|
||||
continue;
|
||||
}
|
||||
securities_.push_back(std::move(*sec));
|
||||
}
|
||||
// Populate vectors of security ptrs per each venue.
|
||||
for (auto &sec : securities_) {
|
||||
security_ptrs_.push_back(&sec);
|
||||
}
|
||||
// Pass securities to the sinks (per venue).
|
||||
listener_->on_securities(security_ptrs_);
|
||||
securities_.clear();
|
||||
security_ptrs_.clear();
|
||||
}
|
||||
|
||||
void on_books(const PyBookList &py_books) override {
|
||||
LOG(md, Info, R"([py_md_books.json]{{ "bks": {}, "ts": {}}})",
|
||||
json_formatter(py_books.items()), TimeStamp::now_ns());
|
||||
for (auto &py_book : py_books.items()) {
|
||||
bts_assert(book_.count_populated() == 0);
|
||||
to_book_(py_book, book_);
|
||||
listener_->on_book(book_);
|
||||
book_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void on_trades(const PyTradeList &py_trades) override {
|
||||
LOG(md, Info, R"([py_md_trades.json]{{ "trds": {}, "ts": {}}})",
|
||||
json_formatter(py_trades.items()), TimeStamp::now_ns());
|
||||
// `trades_` and `trade_ptrs_` should already be empty, but no harm in clearing them again.
|
||||
// We keep them as member variables to avoid (re)allocating memory in each callback.
|
||||
trades_.clear();
|
||||
trade_ptrs_.clear();
|
||||
for (const auto &py_trade : py_trades.items()) {
|
||||
LOG(md, Info, "PyMarketDataSubscription received trade {}",
|
||||
py_trade.to_string());
|
||||
to_bts_trade_(py_trade, trades_.emplace_back());
|
||||
}
|
||||
for (const auto &t : trades_) {
|
||||
trade_ptrs_.push_back(&t);
|
||||
}
|
||||
listener_->on_trades(trade_ptrs_);
|
||||
trades_.clear();
|
||||
trade_ptrs_.clear();
|
||||
}
|
||||
|
||||
SecurityID get_sec_id_(
|
||||
Venue venue, SecurityType sec_type, const string &sec_name) {
|
||||
auto sec_id = sec_id_store_->get_id(venue, sec_type, sec_name, true);
|
||||
@@ -198,7 +318,7 @@ struct PyMarketDataSubscription::PImpl : private py_interop::MdSink {
|
||||
if (spec.price_infos().size() > 0) {
|
||||
bts_assert(spec.price_infos().size() == 1);
|
||||
const py_interop::PyContractPriceInfo &price_info =
|
||||
spec.price_infos().items()[0];
|
||||
spec.price_infos()[0];
|
||||
bts_assert(price_info.tick_unit() > 0 && price_info.tick_base() > 0);
|
||||
sec_comm.min_price_increment =
|
||||
price_info.tick_unit() / price_info.tick_base();
|
||||
@@ -209,13 +329,12 @@ struct PyMarketDataSubscription::PImpl : private py_interop::MdSink {
|
||||
if (spec.quantity_infos().size() > 0) {
|
||||
bts_assert(spec.quantity_infos().size() == 1);
|
||||
const py_interop::PyContractQuantityInfo &qty_info =
|
||||
spec.quantity_infos().items()[0];
|
||||
spec.quantity_infos()[0];
|
||||
bts_assert(qty_info.tick_unit() > 0 && qty_info.tick_base() > 0);
|
||||
sec_comm.min_quantity_increment =
|
||||
qty_info.tick_unit() / qty_info.tick_base();
|
||||
sec_comm.min_quantity = std::max(
|
||||
qty_info.min_value(),
|
||||
static_cast<float>(sec_comm.min_quantity_increment));
|
||||
sec_comm.min_quantity =
|
||||
std::max(qty_info.min_value(), sec_comm.min_quantity_increment);
|
||||
}
|
||||
sec_comm.desc = py_sec.name();
|
||||
if (py_sec.is_spot()) {
|
||||
@@ -284,9 +403,11 @@ struct PyMarketDataSubscription::PImpl : private py_interop::MdSink {
|
||||
};
|
||||
|
||||
PyMarketDataSubscription::PyMarketDataSubscription(
|
||||
PyMarketDataProviderConfig config, IDGenerator *sec_id_store)
|
||||
PyMarketDataProviderConfig config,
|
||||
IDGenerator *sec_id_store,
|
||||
std::shared_ptr<python_interop::PluginManager> py_plugin_man)
|
||||
: me(std::make_unique<PyMarketDataSubscription::PImpl>(
|
||||
std::move(config), sec_id_store)) {}
|
||||
std::move(config), sec_id_store, std::move(py_plugin_man))) {}
|
||||
|
||||
PyMarketDataSubscription::~PyMarketDataSubscription() { me->stop(); }
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "python_interop/plugin_manager.h"
|
||||
#include "spark/md/IMarketDataSource.h"
|
||||
|
||||
#include <memory>
|
||||
@@ -18,7 +19,8 @@ class PyMarketDataSubscription : public IMarketDataSource {
|
||||
public:
|
||||
explicit PyMarketDataSubscription(
|
||||
api::md::PyMarketDataProviderConfig config,
|
||||
persistence::IDGenerator *sec_id_store);
|
||||
persistence::IDGenerator *sec_id_store,
|
||||
std::shared_ptr<python_interop::PluginManager> py_worker);
|
||||
~PyMarketDataSubscription();
|
||||
|
||||
void start(std::shared_ptr<IMarketDataListener> listener) override;
|
||||
|
||||
@@ -64,7 +64,7 @@ struct OnixsGlbxSubscription::PImpl {
|
||||
os::get_thread_id(), os::get_thread_name(), config.DebugString(),
|
||||
FMT_JOIN(symbols, ", "));
|
||||
vector<shared_ptr<IMDPlatform>> platforms;
|
||||
auto main = shared_ptr<MDP3Main>();
|
||||
auto main = std::make_shared<MDP3Main>();
|
||||
main->start({symbols.begin(), symbols.end()}, nullptr, nullptr);
|
||||
platforms.emplace_back(std::move(main));
|
||||
dispatcher = &utils::Dispatcher::current();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "PositionServicePNLHelper.h"
|
||||
#include "spark/config/config.h"
|
||||
#include "spark/config/protos/toggles.pb.h"
|
||||
#include "spark/core/TimeStamp.h"
|
||||
#include "spark/domain_model/Security.h"
|
||||
#include "spark/logging/logging.h"
|
||||
#include "spark/md/MDClient.h"
|
||||
@@ -125,9 +126,9 @@ class HandleIndex {
|
||||
|
||||
struct PositionServicePNLHelper::PImpl {
|
||||
HandleIndex handle_index;
|
||||
std::map<SecurityKey, uint16_t> missing_data_log_count;
|
||||
std::map<SecurityKey, core::TimeStamp> missing_data_log_timestamps;
|
||||
PImpl(bts::md::MDClient &md_client)
|
||||
: handle_index(md_client), missing_data_log_count() {}
|
||||
: handle_index(md_client), missing_data_log_timestamps() {}
|
||||
};
|
||||
|
||||
void PositionServicePNLHelper::compute_pnl(
|
||||
@@ -244,14 +245,15 @@ void PositionServicePNLHelper::_log_missing_data(const PNLUnit &u) {
|
||||
os::is_nan(u.last_price()) &&
|
||||
os::is_nan(u.settlement_price());
|
||||
if (missing_cpp || missing_md) {
|
||||
const auto it = me->missing_data_log_count.find(sec_key);
|
||||
uint16_t count = it == me->missing_data_log_count.end() ? 0 : it->second;
|
||||
if (count <= 50)
|
||||
const auto it = me->missing_data_log_timestamps.find(sec_key);
|
||||
const auto ts = it == me->missing_data_log_timestamps.end() ? core::TimeStamp::min() : it->second;
|
||||
const auto now = core::TimeStamp::now();
|
||||
if (now - ts > core::TimeSpan::from_seconds(10)) {
|
||||
LOG(md, Warn, "Missing pnl data sec:{} cpp:{} md:{}", sec_key,
|
||||
missing_cpp, missing_md);
|
||||
me->missing_data_log_count[sec_key] = count + 1;
|
||||
} else
|
||||
me->missing_data_log_count[sec_key] = 0;
|
||||
me->missing_data_log_timestamps[sec_key] = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
PositionServicePNLHelper::PositionServicePNLHelper(md::MDClient &md_client)
|
||||
: me(new PImpl(md_client)) {}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "binance_execution/cross_margin/rest_api_client.h"
|
||||
#include "binance_execution/cross_margin/side_effect_type.h"
|
||||
#include "binance_execution/cross_margin/trade_data_stream/trade_data_subscription.h"
|
||||
#include "binance_execution/execution_report_store.h"
|
||||
#include "binance_execution/gateway_config.h"
|
||||
#include "binance_execution/listen_key_requestor.h"
|
||||
#include "binance_execution/order_key_map.h"
|
||||
@@ -28,6 +27,7 @@
|
||||
#include "spark/orderman/events/NewSecuritiesEvent.h"
|
||||
#include "spark/orderman/orderman_utils/ExchangePosition.h"
|
||||
#include "spark/orderman/orderman_utils/ExecutionReport.h"
|
||||
#include "spark/orderman/orderman_utils/execution_report_store.h"
|
||||
#include "spark/orderman/orderman_utils/orderman_common.h"
|
||||
#include "spark/utils/overloaded.h"
|
||||
|
||||
@@ -60,6 +60,7 @@ using bts::orderman::CancelOrderMsg;
|
||||
using bts::orderman::CancelReplaceOrderMsg;
|
||||
using bts::orderman::CreateOrderMsg;
|
||||
using bts::orderman::ExecutionReport;
|
||||
using bts::orderman::ExecutionReportStore;
|
||||
using bts::orderman::NewAdminDataEvent;
|
||||
using bts::orderman::NewSecuritiesEvent;
|
||||
using bts::orderman::OrderMsg;
|
||||
@@ -373,7 +374,7 @@ CrossMarginGateway::CrossMarginGateway(
|
||||
std::move(jetfix_connection))) {
|
||||
bts_assert(
|
||||
me_->config.account.size() <=
|
||||
bts::orderman::ExecutionReport{}.account_name.value().size());
|
||||
bts::orderman::ExecutionReport{}.account_name.value().max_size());
|
||||
}
|
||||
|
||||
CrossMarginGateway::~CrossMarginGateway() = default;
|
||||
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
#include "cancel_order_request.h"
|
||||
#include "binance_execution/error_list.h"
|
||||
#include "binance_execution/order_key_map.h"
|
||||
#include "binance_execution/type_converters.h"
|
||||
#include "binance_execution/type_validators.h"
|
||||
#include "spark/io/JsonReader.h"
|
||||
#include "spark/orderman/CancelOrderMsg.h"
|
||||
#include "spark/utils/checked_arithmetic.h"
|
||||
#include "spark/utils/error_list.h"
|
||||
|
||||
namespace binance_execution::cross_margin {
|
||||
|
||||
@@ -22,7 +22,7 @@ bool CancelOrderRequest::init(
|
||||
// The exchange accepts either orderId, or origClientOrderId (only one
|
||||
// need be populated).
|
||||
if (orderId == 0 && origClientOrderId.empty()) {
|
||||
ErrorList::append(
|
||||
bts::utils::ErrorList::append(
|
||||
error, "Either orderId or origClientOrderId must be defined");
|
||||
} else {
|
||||
// Can't reuse the same order id (only increment order version
|
||||
|
||||
+3
-2
@@ -5,6 +5,7 @@
|
||||
#include "spark/io/JsonReader.h"
|
||||
#include "spark/orderman/orderman_utils/ExecutionReport.h"
|
||||
#include "spark/utils/checked_arithmetic.h"
|
||||
#include "spark/utils/error_list.h"
|
||||
#include "spark/utils/string_utils.h"
|
||||
|
||||
#include <string>
|
||||
@@ -123,7 +124,7 @@ bool OrderUpdate::to_execution_report(
|
||||
client_order_id.begin(), client_order_id.begin() + version_sep,
|
||||
er.order_key.value());
|
||||
} else {
|
||||
ErrorList::append(
|
||||
bts::utils::ErrorList::append(
|
||||
error,
|
||||
fmt::format(
|
||||
"failed to parse client order id '{}'", client_order_id));
|
||||
@@ -149,7 +150,7 @@ bool OrderUpdate::to_execution_report(
|
||||
filled_quantity > 0 ? quote_asset_transacted_quantity / filled_quantity
|
||||
: 0.0;
|
||||
er.stop_price.value() = stop_price;
|
||||
copy_to_array<40>(std::to_string(trade_id), er.exchange_fill_id.value());
|
||||
er.exchange_fill_id->copy_from(std::to_string(trade_id));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
#include "cancel_order_request.h"
|
||||
#include "binance_execution/error_list.h"
|
||||
#include "binance_execution/type_converters.h"
|
||||
#include "binance_execution/type_validators.h"
|
||||
#include "binance_execution/websocket_api_request.h"
|
||||
#include "spark/core/TimeStamp.h"
|
||||
#include "spark/orderman/CancelOrderMsg.h"
|
||||
#include "spark/utils/error_list.h"
|
||||
|
||||
namespace binance_execution::spots {
|
||||
|
||||
using bts::utils::ErrorList;
|
||||
|
||||
bool CancelOrderRequest::init(
|
||||
const bts::orderman::CancelOrderMsg &m,
|
||||
const std::string &id,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "modify_order_request.h"
|
||||
#include "binance_execution/enums/new_order_response_type.h"
|
||||
#include "binance_execution/enums/time_in_force.h"
|
||||
#include "binance_execution/error_list.h"
|
||||
#include "binance_execution/price.h"
|
||||
#include "binance_execution/quantity.h"
|
||||
#include "binance_execution/type_converters.h"
|
||||
@@ -9,9 +8,12 @@
|
||||
#include "binance_execution/websocket_api_request.h"
|
||||
#include "spark/io/JsonReader.h"
|
||||
#include "spark/orderman/CancelReplaceOrderMsg.h"
|
||||
#include "spark/utils/error_list.h"
|
||||
|
||||
namespace binance_execution::spots {
|
||||
|
||||
using bts::utils::ErrorList;
|
||||
|
||||
void ModifyOrderRequest::to_signature_params(std::string &payload) const {
|
||||
payload.clear();
|
||||
payload.append("apiKey=");
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "spots_gateway.h"
|
||||
#include "binance_execution/enums/api_key_type.h"
|
||||
#include "binance_execution/execution_report_store.h"
|
||||
#include "binance_execution/gateway_config.h"
|
||||
#include "binance_execution/listen_key_requestor.h"
|
||||
#include "binance_execution/order_key_map.h"
|
||||
@@ -42,6 +41,7 @@
|
||||
#include "spark/orderman/log_messages/modify_order_log.h"
|
||||
#include "spark/orderman/orderman_utils/ExchangePosition.h"
|
||||
#include "spark/orderman/orderman_utils/ExecutionReport.h"
|
||||
#include "spark/orderman/orderman_utils/execution_report_store.h"
|
||||
#include "spark/orderman/orderman_utils/orderman_common.h"
|
||||
#include "spark/utils/Delegate.h"
|
||||
#include "spark/utils/Uuid.h"
|
||||
@@ -149,7 +149,7 @@ struct SpotsGateway::PImpl {
|
||||
ExecutionGatewayCallbacks callbacks;
|
||||
bts::orderman::ExchangePosition pos;
|
||||
bts::api::admin::BTSAccount account;
|
||||
ExecutionReportStore orders;
|
||||
bts::orderman::ExecutionReportStore orders;
|
||||
|
||||
tuple<string, string, Delegate<void(const json &)> &> build_request(
|
||||
const string &method) {
|
||||
@@ -612,7 +612,7 @@ SpotsGateway::SpotsGateway(
|
||||
std::move(jetfix_connection))) {
|
||||
bts_assert(
|
||||
me_->config.account.size() <=
|
||||
bts::orderman::ExecutionReport{}.account_name.value().size());
|
||||
bts::orderman::ExecutionReport{}.account_name.value().max_size());
|
||||
}
|
||||
|
||||
SpotsGateway::~SpotsGateway() = default;
|
||||
|
||||
@@ -134,7 +134,7 @@ bool OrderUpdate::to_execution_report(
|
||||
filled_quantity > 0 ? quote_asset_transacted_quantity / filled_quantity
|
||||
: 0.0;
|
||||
er.stop_price.value() = stop_price;
|
||||
copy_to_array<40>(std::to_string(trade_id), er.exchange_fill_id.value());
|
||||
er.exchange_fill_id->copy_from(std::to_string(trade_id));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ bool OrderUpdate::to_execution_report(
|
||||
filled_quantity > 0 ? quote_asset_transacted_quantity / filled_quantity
|
||||
: 0.0;
|
||||
er.stop_price.value() = stop_price;
|
||||
copy_to_array<40>(std::to_string(trade_id), er.exchange_fill_id.value());
|
||||
er.exchange_fill_id->copy_from(std::to_string(trade_id));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
#include "binance_execution/enums/order_type.h"
|
||||
#include "binance_execution/enums/side.h"
|
||||
#include "binance_execution/enums/time_in_force.h"
|
||||
#include "binance_execution/error_list.h"
|
||||
#include "spark/api/services/OrderService.pb.h"
|
||||
#include "spark/core/TimeStamp.h"
|
||||
#include "spark/domain/Side.h"
|
||||
#include "spark/utils/bts_assert.h"
|
||||
#include "spark/utils/error_list.h"
|
||||
|
||||
namespace binance_execution {
|
||||
|
||||
@@ -145,7 +145,7 @@ inline bool convert_side(bts::api::Side side, Side &res, std::string &error) {
|
||||
res = Side::SELL;
|
||||
break;
|
||||
default:
|
||||
ErrorList::append(error, "invalid side");
|
||||
bts::utils::ErrorList::append(error, "invalid side");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -230,7 +230,7 @@ inline bool convert_order_type(
|
||||
result = OrderType::NONE;
|
||||
break;
|
||||
default:
|
||||
ErrorList::append(
|
||||
bts::utils::ErrorList::append(
|
||||
error, fmt::format(
|
||||
"NativeOrderType '{}' not implemented at Binance",
|
||||
bts::api::orderman::NativeOrderType_Name(native_type)));
|
||||
@@ -280,10 +280,10 @@ inline bool convert_time_in_force(
|
||||
result = TimeInForce::NONE;
|
||||
break;
|
||||
}
|
||||
ErrorList::append(error, "TimeInForce is undefined");
|
||||
bts::utils::ErrorList::append(error, "TimeInForce is undefined");
|
||||
return false;
|
||||
default:
|
||||
ErrorList::append(error, "Unknown BinanceTimeInForce");
|
||||
bts::utils::ErrorList::append(error, "Unknown BinanceTimeInForce");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#include "type_validators.h"
|
||||
#include "spark/utils/error_list.h"
|
||||
|
||||
namespace binance_execution {
|
||||
|
||||
using bts::utils::ErrorList;
|
||||
|
||||
bool validate(
|
||||
const bts::api::Security *sec, bts::api::Venue venue, std::string &error) {
|
||||
if (sec == nullptr) {
|
||||
|
||||
Loaded 100 of 248 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user