Compare commits

..
Author SHA1 Message Date
John Colvin 6997f66505 refactor: Use generic collections in PrimitiveManager
The class that instantiates ViewStreams via reflection
2024-11-02 16:48:42 +00:00
John Colvin d78442820d Fix typo in NLStream's namespace 2024-11-02 02:57:19 +00:00
John Colvin 7f238a7b24 Fix LiteralStream syntax in md publisher appsettings.json 2024-10-30 20:42:14 +00:00
John Colvin e113162925 fix build: Upgrade Newtonsoft.Json 13.0.2 -> 13.0.3 in spark_websocket
The DataStreamSuite.sln build was broken because other projects in the sln were
already using the newer version (can't mix and match).
2024-10-30 20:13:40 +00:00
John Colvin 1ca7f65a78 vscode: Set ASPNETCORE_ENVIRONMENT=Development in api server debug cfg 2024-10-30 19:37:48 +00:00
John Colvin 2148348e82 build: Change publish_spark.py default verbosity to 2
This equates to STATUS and normal in cmake and dotnet, respectively
2024-10-30 19:37:48 +00:00
John Colvin 53ddaaf593 Add c# Variant class, modeled after std::variant in c++ 2024-10-30 19:37:48 +00:00
John Colvin 0430b87e35 Add generic ViewStream, BlueRow types/interfaces 2024-10-30 19:37:48 +00:00
John Colvin 4c93d20ce6 Add scripts to deploy apps via docker compose 2024-10-30 19:35:06 +00:00
John Colvin 2840ac284d Clean up build scripts 2024-10-30 19:35:06 +00:00
John Colvin 3e355d4afb fix: Add null check 2024-10-30 19:35:06 +00:00
John Colvin 922844e7ab vscode: Add debug cfg for api server on linux 2024-10-30 19:35:06 +00:00
John Colvin 1d3186e192 fix: Update source paths for api server docker 2024-10-30 19:35:06 +00:00
John Colvin 287e784d5e feat: Add stream subscriptions to websocket api 2024-10-30 19:35:06 +00:00
John Colvin d29360f903 fix build: Update proto path in csproj file 2024-10-30 19:35:06 +00:00
John Colvin 3681c1db4f fix build: Install actual dir instead of symlink 2024-10-30 19:35:06 +00:00
John Colvin bef4325666 fix build (c#): Update source file paths, namespaces, etc.
The c# builds have been broken ever since we reorganized the c++ projects with
stuttering dirs.
2024-10-30 19:35:06 +00:00
John Colvin 32fd0f6f9b fix: Load logging config at runtime
Don't use hardcoded default
2024-10-30 19:35:06 +00:00
John Colvin f1cb37277a refactor: Replace BoolName w/ FormattedBool 2024-10-30 19:35:06 +00:00
John Colvin 4fa76a35e3 Init hb timestamp to avoid false drops
Mainly when debugging.
2024-10-30 19:35:05 +00:00
John Colvin aded3a9e86 fix: Run heartbeat timer in MarketDataGateway
This is why we were getting those heartbeat asserts. Previously,
only ExchangeSimApplication would start this timer. Since
MarketDataGateway doesn't share its MDProducer with
ExchangeSimApplication, it must start one itself.
2024-10-30 19:35:05 +00:00
John Colvin 257b7c3f0f Make utils::TimeStamp formattable for logs, etc. 2024-10-30 19:35:05 +00:00
John Colvin 442f827af3 fix build: Link Onixs dep libACE with --no-as-needed
libACE is an Onixs dep that isn't called directly by our code. For some
reason, gcc builds fail to resolve this dep at runtime unless this flag
is used. This isn't a problem with clang builds.
2024-10-30 19:35:05 +00:00
John Colvin 8e0e6a62bc build: Replace global include/link dirs w/ target-specific calls
In other words, migrate "include_directories" and "link_directories" calls in the
root CMakeLists.txt to "target_include_directories" and "target_link_directories"
calls in InitLibrary.cmake
2024-10-30 19:35:05 +00:00
John Colvin f1ddd8ef6a fix build: Remove unused refs
Sparkd release builds were failing with warnings-as-errors about unused refs
2024-10-30 19:35:05 +00:00
John Colvin 66a1d19dd4 Fix eslint config and use modern flat style
https://eslint.org/docs/latest/use/configure/configuration-files#configuration-file
2024-10-30 19:35:05 +00:00
John Colvin 2ddf14d2b5 cleanup: Move global var into function scope in vite cfg 2024-10-30 19:35:05 +00:00
John Colvin 51aa380c3f Update node pkgs in datastream webapp 2024-10-30 19:35:05 +00:00
John Colvin b8d8dd5b49 cleanup (build): Remove unused cmake cache var 2024-10-30 19:35:05 +00:00
John Colvin d718a84f6c Put cme config url in GlbxMarketDataConfig proto
Instead of using magic strings
2024-10-30 19:35:05 +00:00
John Colvin 13552e29fe refactor: Change raw IDGenerator ptr to shared_ptr 2024-10-30 19:35:05 +00:00
John Colvin 0ac95710b1 refactor: Return row id in IStore::retrieve_one
This is a prerequisite to synchronizing security ids stored via IDGenerator
(the "new" way to store ids, added when we implemented Deribit), and
SecurityDB (the original way from spark days). The problem was that these
2 implementations were using separate databases. We need to unify the
interfaces between these storage services to avoid breaking existing code.
2024-10-30 19:35:05 +00:00
John Colvin f23c7af88c Add database dir and file fns to SparkDataDirectory 2024-10-30 19:35:05 +00:00
John Colvin 345b76257b Interpret db strng param as name or path in SqliteStore ctr 2024-10-30 19:35:05 +00:00
John Colvin 4996c27f01 refactor: Normalize sec key indexing for old md sources
Now both old and new (domain) md sources index securities by
the same string format.
2024-10-30 19:35:05 +00:00
John Colvin 9a9bcef348 Add ostream operator for Venue 2024-10-30 19:35:05 +00:00
John Colvin 8502b113fe Add ostream operator for SecurityType 2024-10-30 19:35:05 +00:00
John Colvin 979b2ccb6f refactor: Normalize sec key indexing for new (domain) md sources
This is part of a larger effort to unify the way we index securities in order
to generate unique ids. Older md sources (e.g., MDP3Listener) use
SecurityIDRecorder to generate ids, which uses a different database and
different key-string formatting than the IDGenerator used by the newer md
sources (e.g., GeneralMarketDataListener and various ccapi-based sources).
2024-10-30 19:35:05 +00:00
John Colvin 6e12c5b895 refactor: Replace ConfigDownloader w/ lib_http fns
Essentially all it did was fetch config files via http GET. I ported some http
fns over from the cme-websocket-connector repo, which uses curlpp instead of
curl, and also supports async requests (coroutines) via the 'concurrencpp'

https://github.com/David-Haim/concurrencpp
2024-10-30 19:35:06 +00:00
John Colvin 2624a2c00d cleanup: Get rid of another Security type aliases 2024-10-30 19:35:06 +00:00
John Colvin fb01953296 cleanup: Remove unused code in Security.h/cpp 2024-10-30 19:35:06 +00:00
John Colvin 72a3358f44 Fix typo in 'Quantity' FMT_IMPL
I didn't realize the "value()" function rounded out the decimal,
so I replaced it with a call to "full_value()".
2024-10-30 19:35:06 +00:00
John Colvin 2a505e8ce5 diag: Log when sqlite db is created 2024-10-30 19:35:06 +00:00
John Colvin a4d4a938b0 diag: Log when an MDProducer is created 2024-10-30 19:35:06 +00:00
John Colvin c2922d5e02 diag: Add logs around synthetic security discovery in MDConsumer 2024-10-30 19:35:06 +00:00
John Colvin 8e109c6450 refactor: Break shared mem md code up into smaller files
For easier reading/navigation
2024-10-30 19:35:06 +00:00
John Colvin 85b1909543 refactor: Break md relay/query server code up into smaller files
To make code navigation easier. This was a difficulty which I recently
confronted, while investigating a bug that prevented synthetics from
showing up the ui.
2024-10-30 19:35:06 +00:00
John Colvin ba4e20552e refactor: Get rid of 'TimeStamp' typedef in MDStructure.h
Before this commit, we had 4 different TimeStamp types/identifiers, which is
confusing enough as it is:

* bts::core::TimeStamp
  - in system/time_stamp.h
* bts::utils::TimeStamp
  - in spark/utils/TimeStamp.h
* bts::TimeStamp
  - in system/bts_chrono.h
  - alias for std::chrono::system_clock::time_point
* bts::md::TimeStamp
  - in spark/md/Shm/MDStructure.h
  - alias for bts::utils::TimeStamp
  - removed in this commit

Additional aliases for these TimeStamps only adds to the confusion (esp
when the alias comes from a nested include).
2024-10-30 19:35:06 +00:00
John Colvin b1f1987922 Make security 'formattable'
So it can be used as a param in LOG statements
2024-10-30 19:35:06 +00:00
John Colvin b13725bc09 refactor: Change refs/raw ptrs to smart ptrs in md gateway 2024-10-30 19:35:06 +00:00
John Colvin 85dbee2f00 Fix typo in market_data.conf 2024-10-30 19:35:06 +00:00
John Colvin 14232f5644 Add BTC and ETH to VENUE_SIM in symbols.conf 2024-10-30 19:35:06 +00:00
John Colvin 89d49cd721 fix: Resolve main logging dir in new SparkDataDirectory class 2024-10-30 19:35:05 +00:00
John Colvin 590bc1749a refactor: Replace if/else with switch cases 2024-10-30 19:35:05 +00:00
John Colvin 339767a51f fix: Resolve onixs log & data dirs relative to app data root
When our logging code moved to lib_system, it lost access to the config
system. As a result, the "log_path" function, which was used by ICEMain
and MDP3Main, broke. I added a new class, SparkDataDirectory, to be the
central/common authority on paths to local storage.
2024-10-30 19:35:05 +00:00
John Colvin 47f2f16cd3 cleanup: Remove over-engineered subcommands
We don't need explicit subcommands to 'run onixs' md (just use config).
2024-10-30 19:35:05 +00:00
John Colvin d1b28a86c8 fix: Ensure IDGenerator instance is created before deref 2024-10-30 19:35:05 +00:00
John Colvin 50e7ad925e fix: Expand undefined vars to empty string
Bugs (fixed):
  * Md shared memory file names have unexpanded variable: '${BTS_SHARED_MEMORY_PREFIX}'
2024-10-30 19:35:05 +00:00
John Colvin 132201d2cf fix: Emplace MDConsumerHandle instead of copy assign 2024-10-30 19:35:05 +00:00
John Colvin 5543725ee2 Generate c++ protos w/ relative paths
We shouldn't be generating protos defined outside of the current CMakeLists.txt dir.
2024-10-30 19:35:05 +00:00
John Colvin 9c535616df fix build: Make venue conversion constexpr 2024-10-30 19:35:05 +00:00
John Colvin 1dd2db41f2 build: Use new/robust init_library fn in lib_domain 2024-10-30 19:35:05 +00:00
John Colvin a451c3e880 refactor: Move config services to lib_system
The proto files are still in lib_spark, w/ the exception of
system_config.proto, since that is a dep of ConfigurationState.
2024-10-30 19:35:05 +00:00
John Colvin 5485cc9c4f refactor: Move logging into lib_system
This is a fundamental part of our application suite, so system seems
like a sensible home.

Also moved a few logging deps (to lib_system):
* bts_chrono.h
* bts_no_copy.h
* defer.h
* dispatcher.h/cpp
* file_utils.h/cpp
* type_name.h
2024-10-30 19:35:05 +00:00
John Colvin 5e7e20df5a build: Move http code into its own lib 2024-10-30 19:35:05 +00:00
John Colvin 28f5181585 refactor: Move thread_manager to lib_system 2024-10-30 19:35:05 +00:00
John Colvin 5e7a478bf5 build (linux): Migrate catch2 dep to vcpkg (from externals) 2024-10-30 19:35:05 +00:00
John Colvin 0abc386d71 build (linux): Migrate fmt dep to vcpkg (from externals)
Now both linux and windows use vcpkg's fmt
2024-10-30 19:35:05 +00:00
John Colvin 53b040342a cleanup: Remove unnecessary includes 2024-10-30 19:31:41 +00:00
John Colvin e85f669534 refactor: Move threads.proto to lib_system 2024-10-30 19:31:41 +00:00
John Colvin 935d414b7f refactor: Move warn_ctrl.h into lib_system
This is another logging dep
2024-10-30 19:31:41 +00:00
John Colvin 89f4fe88f9 build: Link boost_filesystem in lib_system 2024-10-30 19:31:41 +00:00
John Colvin 161e0a8c3b refactor: Move os.h/cpp and deps to lib_system
This is a prerequisite for the next objective: move logging into its own lib.
As logging is a core dependency for so much of our system, this will enable
us to move more complex services (like md providers) into smaller libs.
2024-10-30 19:31:41 +00:00
John Colvin f5a6dc7a88 cleanup: Remove unused code 2024-10-30 19:31:41 +00:00
John Colvin 298a2f0617 cleanup: Remove more unnecessary includes 2024-10-30 19:31:41 +00:00
John Colvin c6fc1a0f89 refactor: Move SecurityMarketDataStatistics to lib_domain 2024-10-30 19:31:41 +00:00
John Colvin 6cd7baf903 cleanup: Remove unnecessary code 2024-10-30 19:31:41 +00:00
John Colvin 7d7621d832 cleanup: Remove empty header FTXSecurity.h 2024-10-30 19:31:41 +00:00
John Colvin ef8e4c4998 cleanup: Remove temporary redirect headers
These were used to make code migration smoother (to the new lib_system and lib_domain)

* spark/algorithm/TryCatch.h -> system/algorithm/try_catch.h
* spark/core/BoolName.h -> system/bool_name.h
* spark/core/EnumMacros.h -> system/enum_macros.h
* spark/core/FMT.h -> system/FMT.h
* spark/core/FMT_IMPL.h -> system/FMT_IMPL.h
* spark/core/TimeSpan.h -> system/time_span.h
* spark/core/TimeStamp.h -> system/time_stamp.h
* spark/domain/Price.h -> domain/price.h
* spark/io/JsonReader.h -> system/json/json_reader.h
* spark/io/JsonDumper.h -> system/json/json_dumper.h
2024-10-30 19:31:41 +00:00
John Colvin ad2e631bbc refactor: Move RFQ to lib_domain 2024-10-30 19:31:41 +00:00
John Colvin dccc9f0099 refactor: Move AggressorSide proto to lib_domain
Get rid of non-proto AggressorSide enum while we're at it (consolidate to 1 type)
2024-10-30 19:31:41 +00:00
John Colvin bab5eb4c70 cleanup: Remove more unused code 2024-10-30 19:31:41 +00:00
John Colvin e0608c3791 refactor: Move a few Order types to lib_domain 2024-10-30 19:31:41 +00:00
John Colvin e69c4782b5 refactor: Move TimeInForce to lib_domain 2024-10-30 19:31:41 +00:00
John Colvin 7ab045b120 cleanup: Remove unused code 2024-10-30 19:31:41 +00:00
John Colvin 9bd4bc421d refactor: Move TradeRecord into lib_domain 2024-10-30 19:31:41 +00:00
John Colvin 8b90debdd0 refactor: Move TradingStatus enum to lib_domain 2024-10-30 19:31:41 +00:00
John Colvin dc2e8b114a build: Update webpack-cli in admin webapp 2024-10-30 19:31:41 +00:00
John Colvin e064ab0433 build: Configure admin webapp build entirely in CMakeLists
Get rid of build_admin_web_app.py
2024-10-30 19:31:41 +00:00
John Colvin 42a9edbee3 refactor: Move domain types into new lib_domain
Also rename files in snake_case (standard naming convention for protos)
https://protobuf.dev/programming-guides/style/
2024-10-30 19:31:41 +00:00
John Colvin 92ea362d1e refactor: Move collections into lib_system 2024-10-30 19:31:41 +00:00
John Colvin 750228355a refactor: Move a handful of types/utils to lib_system 2024-10-30 19:31:41 +00:00
John Colvin 930c5e1187 build: Add preset to build with 6 jobs (-j 6)
Otherwise the devcontainer may hog too much cpu and cause the container to freeze
2024-10-30 19:31:41 +00:00
John Colvin b286f81067 cleanup: Remove unused includes
Mostly collection macro headers
2024-10-30 19:31:41 +00:00
John Colvin 36795bd851 cleanup (build): Move protobuf service generation cmake into lib_spark
Because lib_spark owns all the services
2024-10-30 19:31:41 +00:00
John Colvin 857702fae7 refactor: Rename protobuf out dir "include" -> "protobufs"
This is a more accurate and precise name. The dir contains c++ code generated
from .proto files - both sources (.cc) and headers (.pb.h).
2024-10-30 19:31:41 +00:00
John Colvin 2702d0d32e refactor: Move ValueWrapper into lib_system
So other projects can use it
2024-10-30 19:31:40 +00:00
John Colvin 10e78b5e20 Include bts_assert.h in bts_fatal.h
We shouldn't have to include bts_assert.h when we're already including bts_fatal.h
2024-10-30 19:31:40 +00:00
John Colvin 74027fc9cc refactor: Split MarketDataGateway up into providers/sources
MarketDataGateway still exists, but is greatly simplified. It creates
several `IMarketDataProvider`s, each of which generates a list of
`IMarketDataSource`s. The market data sources provide market data to an
`IMarketDataListener`.

* EmumbaMarketDataProvider
* CCApiMarketDataProvider
* PyMarketDataProvider
* OnixsMarketDataProvider
* ReplayMarketDataProvider

As a side effect, individual py market data sources are now explicitly
limited to a single venue (it probably should have been this way all
along, since the shared memory writers do not expect updates to involve
multiple venues).
2024-10-30 19:31:40 +00:00
John Colvin af5af01b39 cleanup: Remove superfluous code 2024-10-30 19:31:40 +00:00
John Colvin ca3fe805c7 improvement: Set SHARED_MEMORY_PREFIX to an environment variable
So we can effectively define it at runtime. We don't want to have to
produce different builds just to change the shared mem prefix.
2024-10-30 19:31:40 +00:00
John Colvin 40bf2d8b43 fix: Always prepend proc name to shared mem prefix
Our shared memory model isn't really designed to be shared across processes.
Maybe some day it will.
2024-10-30 19:31:40 +00:00
John Colvin 98fb021f3f cleanup: Remove redundant macro def 2024-10-30 19:31:40 +00:00
John Colvin b34a3421bb Skip md recording thread loop if no recorders configured to run 2024-10-30 19:31:40 +00:00
John Colvin 9dc61daefa Handle signal interrupts explicitly in sparkd
Otherwise the python interpretter, which is instantiated when we load
python md plugins, prevents the application from exiting.
2024-10-30 19:31:40 +00:00
John Colvin 32fe2533c7 feat: Add support for python md plugins 2024-10-30 19:31:40 +00:00
John Colvin 9a49767b75 fix build: Ensure py scripts use unix line endings
I'm not sure why, but my sparkd build (linux) suddenly began failing to
generate cpp headers for protos. The error message indicated it was due
to an unexpected carriage return (\r) in plugin.py:

/usr/bin/env: ‘python\r’: No such file or directory
2024-10-30 19:31:40 +00:00
John Colvin b020939c99 refactor: Add lib_system proj for common utils
Migrates the following:
* Delegate.h
* bts_array_size.h
* string_utils.h

Note: File names in the new lib are snake_case (this seems to be the
standard naming convention for c++).
2024-10-30 19:31:40 +00:00
John Colvin c660c52c3d cleanup: Trivial whitespace cleanup in sparkd CMakeLists 2024-10-30 19:31:40 +00:00
John Colvin 37322fccb1 build: Add cmake fns to symlink and install local libs
The code needed to do this (inline) is ugly and verbose
2024-10-30 19:31:40 +00:00
John Colvin 40ae9ff7a0 cleanup: Use conventional UPPER_CASE var names in sparkd CMakeLists 2024-10-30 19:31:40 +00:00
John Colvin 60bcbe67e4 cleanup: Trivial whitespace cleanup in Protobuf.cmake 2024-10-30 19:31:40 +00:00
John Colvin b983f80bfa build: Clean up data_files cmake proj (support --target clean) 2024-10-30 19:31:40 +00:00
John Colvin c58a2f90e0 refactor: Organize libspark proj w/ stuttering include
This new structure provides several benefits:

* Allows us to install multiple CMake projects in the same destination
  dir (without include path conflicts).
  - This makes shipping the trading algo sdk a trivial excercise. Simply
    copy the lib and include dirs from the install dest. That's it!
* Puts us in a position to break up the monolithic libspark into smaller
  modules, which are easier to manage.
2024-10-30 19:31:40 +00:00
John Colvin 2515030501 vscode: Add debug cfg to skip sparkd build 2024-10-30 19:31:40 +00:00
John Colvin 7146aca538 fix build: Add ShmSegmentName.cpp to windows client sources 2024-10-30 19:31:40 +00:00
John Colvin e0f01b6810 Make a few timestamp fns const
They should have been const in the first place
2024-10-30 19:31:40 +00:00
John Colvin 92e3094259 Add config profiles to libspark
This is part of a larger effort to unify the libspark build, and eliminate
compile time differences between the builds used by our various services
2024-10-30 19:31:40 +00:00
John Colvin a9a5b563a0 Add api server to docker compose 2024-10-30 19:31:40 +00:00
John Colvin 84183d2741 Rename pingtest "sparkd-ping-test", make it depend on sparkd
Add it to a "ping-test" profile, as we may add similar tests for other services
2024-10-30 19:31:40 +00:00
John Colvin acb44c836d cleanup: Remove unnecessary env vars from webapp docker compose 2024-10-30 19:31:40 +00:00
John Colvin cb5c7d2012 Change api server ports so they don't conflict w/ ds webapp 2024-10-30 19:31:40 +00:00
John Colvin fefb848281 vscode: Fix sparkd debug config 2024-10-30 19:31:40 +00:00
John Colvin 3af14cecd7 build: Resolve app data dir and shared mem prefix at runtime
We really can't define them at compile time anymore as we build multiple
exes that depend on libspark in the same dir (e.g., spark api server,
pubsub, ds-publisher, webapp, etc.). That means one libspark.so (or dll)
must serve all exes. We should compile a consistent libspark to mitigate
the risk of running an exe with the wrong version (of libspark). For
example, running pubsub with a libspark configured for spark api server,
leftover from an earlier build.
2024-10-30 19:31:40 +00:00
John Colvin 5df30e8857 Don't log "initializing spark config" if it has already been initialized 2024-10-30 19:31:40 +00:00
John Colvin d813a6a2c6 vscode: Add debug cfg for spark api server 2024-10-30 19:31:40 +00:00
John Colvin b3e28d5f7f Use dependency injection in spark api server 2024-10-30 19:31:40 +00:00
John Colvin 5c11b83df5 Add SparkLoggerAdapter for Msft ILogger 2024-10-30 19:31:40 +00:00
John Colvin 5c5a0ca09a Display exception stack trace w/ 1 call per line in ds webapp 2024-10-30 19:31:40 +00:00
John Colvin 114873eb35 Remove unused ErrorView from ds webapp 2024-10-30 19:31:40 +00:00
John Colvin a9799b2a00 Propagate ViewStream errors to webapp frontend 2024-10-30 19:31:40 +00:00
John Colvin d5aeaa5e0f Add ErrorChanged event to propagate DataStream exceptions 2024-10-30 19:31:40 +00:00
John Colvin 8a8c491fb1 Set columns to empty array rather than null in ds webapp
If no data has been received. Since the 'columns' property was marked
'required', this eliminates a warning that was being logged to console.
2024-10-30 19:31:39 +00:00
John Colvin 450a6c62b6 Improve alignment/spacing in ds webapp 2024-10-30 19:31:39 +00:00
John Colvin a3887e6246 Configure datastream deployment with docker-compose
To deploy to aws, connect to our aws vpn then run:
tools/deploy-docker-compose.sh

The deploy script does the following:
* builds sparkd
* builds datastream apps (pubsub, md-publisher, and webapp)
* uploads the build to aws
* bounces all services (sparkd, pubsub, md-publisher, and webapp)

Note: the name 'md-publisher' refers to DataStreamPublisher w/ MarketData
configuration
2024-10-30 19:31:39 +00:00
John Colvin 982ddd24d4 Remove "BTS Cloud Streams" title from webapp
Space is precious when viewing on phone (tbh, it didn't look that good
anyways).
2024-10-30 19:31:39 +00:00
John Colvin 20e959fea3 fix: Ensure md-publisher waits for pubsub and sparkd
Bugs (fixed):
  * md-publisher silently fails if it tries to connect before those services are "ready".

The Docker compose "depends_on" property ensures sparkd and pubsub
containers are up, but doesn't prevent startup race bugs (e.g., if
md-publisher and pubsub start around the same time, and md-publisher
tries to connect before pubsub has started accepting connections). To
address these race conditions, we use a nifty tool called "dockerize"

References:
  * https://github.com/jwilder/dockerize
2024-10-30 19:31:39 +00:00
John Colvin a108823ea5 Replace ErrorView component w/ robust QueryStatusView 2024-10-30 19:31:39 +00:00
John Colvin 52c74cd800 Fix typos in query button theme 2024-10-30 19:31:39 +00:00
John Colvin a473cbc703 fix: Don't assert on exception in BooleanExpression.evaluate
Bugs (fixed):
  * DataStream WebApp Server sometimes crashes when the user enters a
    bad query.
2024-10-30 19:31:39 +00:00
John Colvin 63c648a659 Change VITE_DATASTREAM_WEBSOCKET_URL to localhost 2024-10-30 19:31:39 +00:00
John Colvin 3ffe02254e Assert that local ip address is successfully obtained 2024-10-30 19:31:39 +00:00
John Colvin f5de2b5355 improvement: Resolve hostnames when comparing ip addrs
So we can to configure pubsub routes by hostname or ipv4 (ipv6 hasn't been
tested yet...so we should assume it doesn't work).
2024-10-30 19:31:39 +00:00
John Colvin 979b51fb20 Assert TcpConnection endpoint matches expected 2024-10-30 19:31:39 +00:00
John Colvin 4efb0bfc42 Fix typo in log msg (was missing interpolation '$') 2024-10-30 19:31:39 +00:00
John Colvin f925be32a6 Catch exceptions on AsyncTCPConnector_AsyncWrapper background thread
Since it spawns a new thread on each attempt, uncaught exceptions were previously undetected,
and caused silent failures down the pipe. This should make it easier to track such bugs down.
2024-10-30 19:31:39 +00:00
John Colvin f3b5ff8970 fix: Format PubSubRoute as json in log msgs
It was previously using the default ToString, which simply returns the type name
2024-10-30 19:31:39 +00:00
John Colvin fdea340492 build: Upgrade packages (deps) in DataStreamTests 2024-10-30 19:31:39 +00:00
John Colvin 5ce2ffa5d6 cleanup: Remove unused ViewStreamDatabase config 2024-10-30 19:31:39 +00:00
John Colvin cdae65b098 vscode: Add PubSubServer debug cfg to skip build 2024-10-30 19:31:39 +00:00
John Colvin a1d7607065 Configure apps as a distributed system via Docker compose 2024-10-30 19:31:39 +00:00
John Colvin 233d089c34 Remove reference to spark protobuf Config in ViewStream
We can't read spark protobuf Config until it has been initialized,
and since ViewStream doesn't have the configServer param handy it
isn't able to initialize config if necessary. In that case, it would simply
block until config was initialized by someone else. Such a risk doesn't
seem justified for this particular configuration dependency
(DataStreamConfig.UseUncopiedNewRowsInFire). For now, its hardcoded to false.
2024-10-30 19:31:39 +00:00
John Colvin 759f6dd59a refactor: Run RemoteQueryManager as an IHostedService
This paradigm more accurately represents the RemoteQueryManager as a
Service that can be started/stopped.
2024-10-30 19:31:39 +00:00
John Colvin 80622ddaba Remove inappropriate "Unable to dispose subscription" log msg 2024-10-30 19:31:39 +00:00
John Colvin 434d4b6ad3 fix: Ensure spark config init and login are only performed once
As a static function that could be called from anywhere in the code
(directly or indirectly), 'Configuration.Initialize' was vulnerable to
race bugs. The "Configuration.get<T>()" method, whose calls are
ubiquitous in our code, would initialize config if it hadn't been done
so already. Without a reference to the configServer param, this param
would effectively be ignored/overridden with null.

It's important that login only be performed once, for obvious reasons
(our exes that connect to sparkd are generally designed to process data
for a single user). Furthermore, not every routine that requires a
successful login has a reference to the credentials needed to do it. To
address this, we now pass the ISparkdLoginService singleton to these
routines so they can wait until the singular login task has completed
before they begin.
2024-10-30 19:31:39 +00:00
John Colvin a90e8807ad Move misplaced log abt initializing query text to appropriate place 2024-10-30 19:31:39 +00:00
John Colvin 6dc25bab01 Assert ViewStreamDatabase.instance is only created once
The instance must be initialized via ViewStreamDatabase.SetGlobalInstance
2024-10-30 19:31:39 +00:00
John Colvin 00042d2471 cleanup: Remove unused code from ViewStreamDatabase 2024-10-30 19:31:39 +00:00
John Colvin de9d47cd00 Include subscription id in ds websocket updates 2024-10-30 19:31:39 +00:00
John Colvin 461f690463 fix: Define BoooleanConstant before StringConstant
Otherwise the former masks the latter and prevents it from matching
2024-10-30 19:31:39 +00:00
John Colvin a425d3e6ba Log event data in websocket open, close, error callbacks 2024-10-30 19:31:39 +00:00
John Colvin fe81b3107f refactor: Bring back Material DataGrid
Fix the column resize bugs that made it previously unusable
2024-10-30 19:31:39 +00:00
John Colvin d5d12c56c9 refactor: Improve react webapp state management
* Add queryTextResetTime as part of the CannedQuery state
  (the QueryControl compares the canned query click time with the time of the
  last user input, and applies the text from the most recent change)

* Replace 'isRunning' (bool) with a 'queryState' (string) to reflect the subscription status
  value received from the server. Less indirection this way.

* Replace inappropriate useMemo call w/ useEffect
2024-10-30 19:31:39 +00:00
John Colvin 8979d5def8 cleanup: Get rid of magic const (subscriptionId) in webapp App.jsx 2024-10-30 19:31:39 +00:00
John Colvin 922cf3170b build: Copy configuration profiles to publish dir 2024-10-30 19:31:39 +00:00
John Colvin 5da807b5f0 cleanup: Remove unnecessary deps from csprojs 2024-10-30 19:31:39 +00:00
John Colvin c3507ab77b build: Change publish_data_stream.py to publish the sln
We were publishing individual projects as a workaround for the
dependency issue (bug) that caused some projects to incorrectly target
net8.0. The fact that publishing individual projects "just worked" was
probably dumb luck. Now that the underlying issue has been addressed, we
can resume publishing the sln as a whole.
2024-10-30 19:31:39 +00:00
John Colvin 521346a5c5 dev: Install docker in devcontainer
So we can start various services like sparkd, pubsub, ds publishers, ds
webapp, etc. from inside the devcontainer.
2024-10-30 19:31:39 +00:00
John Colvin a94d1f6f67 dev: Remove dotnet8 from devcontainer
We'll upgrade to dotnet8 eventually, but now isn't the time (too much
more important work to be done).
2024-10-30 19:31:39 +00:00
John Colvin c69adca7ba dev: Run devcontainer on host network
So we don't need to worry about publishing the right ports (and don't
need to rebuild the container everytime the list of ports changes).
2024-10-30 19:31:39 +00:00
John Colvin 212bd432af fix: Downgrade Microsoft.Extensions.Hosting to 7.0.1
A handful of csprojs were referencing version 8.0.0. Since that version
targets net8.0, I have no idea why it didn't cause the build to fail.
Instead, this framework mismatch led to a mysterious runtime crash with
the following error, despite the dll in question being present in the
build dir:

Unhandled exception. System.IO.FileNotFoundException: Could not load file or assembly 'System.Diagnostics.DiagnosticSource, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'.
The system cannot find the file specified.

It is worth noting that this crash would only happen for exes published
as part of the DataStreamSuite.sln with the "--self-contained" flag. For
example, the Bts.DataStreamSuite.Server exe would always fail if
published via:

dotnet publish --configuration Release --sc --framework net7.0 --runtime linux-x64 <DataStreamSuite.sln>
2024-10-30 19:31:39 +00:00
John Colvin 4361ac6519 Add HttpsRedirection middleware first in the webapp pipeline
Middleware handlers are invoked in the order in which they are added. Any middleware
that is added before HttpsRedirection may can prevent the request from being redirected.
As of now, only development-specific middleware is added ahead of HttpsRedircetion.
2024-10-30 19:31:09 +00:00
John Colvin 93dc4c9c1f build: Copy configuration profiles to build dir
So they are readily available to publish/deploy scripts.
2024-10-30 19:31:09 +00:00
John Colvin ebc39f203d Fix benign typo in Scanner/Parser msbuild targets
The output path referenced a non-existant msbuild metadata prop "RelativeOutputDir"
(the actual prop is called "RelativeDir"). Also, the OutputFile path is relative to the
project dir (the 'MSBuildProjectDir' prop), not the file being included.
2024-10-30 19:31:09 +00:00
John Colvin ec88ffdbf3 fix: Catch exception when creating TcpClient 2024-10-30 19:31:09 +00:00
John Colvin 26fecc73c6 fix: Catch WebSocketException in DataStreamSubscription 2024-10-30 19:31:09 +00:00
John Colvin 05715eb489 fix: Add null check to NonIncGroupByStream 2024-10-30 19:31:09 +00:00
John Colvin 70019c59e5 fix: Add null check to TableAccumulator2 2024-10-30 19:31:09 +00:00
John Colvin 66f9e9aa4c fix: Add self-signed certificate to Dockerfile for ds webapp 2024-10-30 19:31:09 +00:00
John Colvin 6caa98a6b3 fix: Make SystemSubscriptionMapPublisher threadsafe
The PubSubState.onTagMapChanged event may not be invoked on
the same thread as doWork.
2024-10-30 19:31:09 +00:00
John Colvin 40930628fb fix: Add null check in TimeBasedFilter.cs 2024-10-30 19:31:09 +00:00
John Colvin d439e3e695 Reduce task wait/timeout code in AsyncTCPConnector 2024-10-30 19:31:09 +00:00
John Colvin aae50cd5f8 Add a few canned queries to AwsProd.json for ds webapp 2024-10-30 19:31:09 +00:00
John Colvin 587e340071 fix: Copy canned queries to clipboard on click
Instead of trying to update the text directly, which causes the front end
to overwrite the user's changes everytime the data updates from then on.
2024-10-30 19:31:09 +00:00
John Colvin 156ec8f3a8 fix: Convert invalid doubles to nan in ds webapp serializer 2024-10-30 19:31:09 +00:00
John Colvin 7ca96f128b Increase datagrid maxheight in ds webapp to 1000px 2024-10-30 19:31:09 +00:00
John Colvin 9aec630100 Improve error display in ds webapp 2024-10-30 19:31:09 +00:00
John Colvin 831cf840a7 fix: Dispose existing sub on subscribe req in ds webapp
Even if new sub has parse error. This fixes a bug where the previous
subscription would continue firing and updating the frontend, when it
should just display the error info to the user and cease updates.
2024-10-30 19:31:09 +00:00
John Colvin 8940325cfd fix: Get rid of "UseSpa" calls in webapp Production environment
This was leftover from a misguided attempt to fix a bug that turned out
to be due to an omitted environment var:
ASPNETCORE_HOSTINGSTARTUPASSEMBLIES=Microsoft.AspNetCore.SpaProxy
2024-10-30 19:31:09 +00:00
John Colvin aaf75acd89 Define webapp specific env vars in generic datastream app deploy script
For now, this makes things easier, as I don't need to write a special script just for the webapp
2024-10-30 19:31:09 +00:00
John Colvin 13200de777 Add config profile and Dockerfile args to deploy script 2024-10-30 19:31:09 +00:00
John Colvin 26f8a2a4ed Copy DataStream WebApp 'dist' to 'wwwroot' in deploy script 2024-10-30 19:31:09 +00:00
John Colvin 5b77e4a36d Tweak DataGrid style in DataStream WebApp 2024-10-30 19:31:09 +00:00
John Colvin 13668374b7 Add AwsProd config for DataStream WebApp 2024-10-30 19:31:09 +00:00
John Colvin 405c6889c4 fix: Add VITE_ prefix to imported env vars
For the DataStream WebApp client
2024-10-30 19:31:09 +00:00
John Colvin b2a15cc33b Fix urls for DataStreamPublisher AwsProd.json 2024-10-30 19:31:09 +00:00
John Colvin e31d1ad69b Add benzinga news streams to AwsProd.json
This is a config profile for DataStreamPublisher, designed to be deployed
to aws (on public internet).
2024-10-30 19:31:09 +00:00
John Colvin c88d1b26f0 fix build warnings: Make props nullable strings in SharedStreamConfig 2024-10-30 19:31:09 +00:00
John Colvin b1446993b4 whitespace: Format PubSubServer.json (indentation issues) 2024-10-30 19:31:09 +00:00
John Colvin db0d415b5a Add a few tasks to run DataStream WebApp 2024-10-30 19:31:09 +00:00
John Colvin 3f1ad91beb fix: Import websocket url from environment var 2024-10-30 19:31:09 +00:00
John Colvin 639d17a625 Add scripts to deploy data stream apps 2024-10-30 19:31:09 +00:00
John Colvin 072b8b2b02 Add SpaServer to Production environment
For Bts.DataStreamWebApp.Server
2024-10-30 19:31:09 +00:00
John Colvin 658ffcad27 Change DirectoryBrowser requestPath to "/browse"
So we can view a psuedo file explorer for the webapp by requesting the "/browse" path
(only activated in Development environment).
2024-10-30 19:31:09 +00:00
John Colvin 2b0f6e52b2 Publish Bts.DataStreamWebApp.Server along with PubSub & DataStream 2024-10-30 19:31:09 +00:00
John Colvin d721bba4db cleanup: Remove unused code in Bts.DataStreamWebApp.Client 2024-10-30 19:31:08 +00:00
John Colvin c387eb81c9 Capture OperationCanceledException (instead of TaskCanceledException)
The former is the base class for the latter, so this will widen the umbrella to catch a few corner cases
that would otherwise been handled by the generic "Exception" catch block. One corner case I know
of is when an op is canceled via "CancellationToken.ThrowIfCancellationRequested". In that case,
an OperationCanceledException is thrown (not a TaskCanceledException).

https://stackoverflow.com/questions/34359129/operationcanceledexception-vs-taskcanceledexception-when-task-is-canceled
2024-10-30 19:31:08 +00:00
John Colvin 707ebcfbc4 Hardcode webSocketUrl to aws server in DataStreamWebApp client 2024-10-30 19:31:08 +00:00
John Colvin 1f4f3b032e Assert that System publishers 'doWork' is always called on same thread 2024-10-30 19:31:08 +00:00
John Colvin f6e54254a9 fix: Make InternalPublisherQueue thread safe 2024-10-30 19:31:08 +00:00
John Colvin b5adcb1a84 Improve error reporting in DataStreamWebSocketServer 2024-10-30 19:31:08 +00:00
John Colvin 179b034bc7 data stream web app client check pt 2024-10-30 19:31:08 +00:00
John Colvin 2cf8a3963d fix build: Fix Spark build after making config init async 2024-10-30 19:31:08 +00:00
John Colvin 49e4e05a3c refactor: Tweak SparkCoreServices to synchronize Configuration.init globally 2024-10-30 19:31:08 +00:00
John Colvin ae9311a2a7 refactor: Change Configuration.initialize to be async 2024-10-30 19:31:08 +00:00
John Colvin 21c3d57324 vscode: Add 'isSparkApiEnabled' input to launch.json 2024-10-30 19:31:08 +00:00
John Colvin 9352f529de SparkApiService check pt 2024-10-30 19:31:08 +00:00
John Colvin 0b76ac4763 datastreamwebapp server checkpt 2024-10-30 19:31:08 +00:00
John Colvin b887801cf5 datastream webapp client check pt 2024-10-30 19:31:08 +00:00
John Colvin c8ffa1489e Remove unused PubSubConfig props 2024-10-30 19:31:08 +00:00
John Colvin f72ea96d20 Fix dsqa config json 2024-10-30 19:31:08 +00:00
John Colvin 7025ffbc4d fix: Always call 'handle_internal_pubs' in PubSubMessageReactor main loop 2024-10-30 19:31:08 +00:00
John Colvin bf989e1f70 improvement: Accept CancellationToken, catch exceptions when running ds websocket server 2024-10-30 19:31:08 +00:00
John Colvin 933db6ea96 Fix mismatched types in PubSubServerTagStatsStream schema 2024-10-30 19:31:08 +00:00
John Colvin 0e92e7f30c Add DataStreamPublisher service modules designed for Dependency Injection
* Change DataStreamPublisher json fields to match cs class property names verbatim
* Rename Publishers -> PublisherNodes in DataStreamPublisherConfig
2024-10-30 19:31:08 +00:00
John Colvin 25c42e388f refactor: Decouple ViewPublisher from IPubTableBroker impl 2024-10-30 19:31:08 +00:00
John Colvin 3187722159 Add PubSub service modules designed for Dependency Injection
* Get rid of JsonPropertyName attributes in PubSubConfig
  - The Dependency Injection framework (based around dotnet ApplicationBuilder)
    does not support this attribute, and maps property names verbatim to json fields.

* Change PubSubServer json fields to match cs class property names verbatim
2024-10-30 19:31:08 +00:00
John Colvin 352cb97818 refactor: Decouple RemoteQueryManager from IManagedSubscriber, IPubTableBroker impl 2024-10-30 19:30:30 +00:00
John Colvin 0215b7990e refactor: Decouple ManagedSubscriber from ISubscriberCore impl 2024-10-30 19:30:30 +00:00
John Colvin 1644b5b58b refactor: Only support 1 tag type in IPubSubServerTagHandler 2024-10-30 19:30:30 +00:00
John Colvin b2ea6820d7 refactor: Decouple PubTableBroker from IPublisherCore impl
Reference interface rather than the PublisherCore impl.
2024-10-30 19:30:30 +00:00
John Colvin 5c9fc5ccc2 improvement: Accept CancellationToken in JsonRpcRequestObservable 2024-10-30 19:30:30 +00:00
John Colvin 4bd6f149b9 fix: Dispose market data sub in various md streams 2024-10-30 19:30:30 +00:00
John Colvin 293f7f3833 cleanup: Remove unused pubsub tag handler code
* Removed 'IPubSubServerTagHandler.register'
  - All implementations of this function were empty
* Removed 'IPubSubServerTagHandler.supportedTagTypes'
  - This property wasn't referenced anywhere
* Remove IWorker inheritance from IPubSubServerTagHandler
  - No reason these interfaces have to be tied together
2024-10-30 19:30:30 +00:00
John Colvin ad2f9a8624 refactor: Rename CloudStreamManager.cs -> RemoteQueryManager.cs
This matches the file name
2024-10-30 19:30:30 +00:00
John Colvin 99922725e1 Change pub sub schema prefix 'bluto://' -> 'bts-cloud://'
In config json files (not actual code change)
2024-10-30 19:30:30 +00:00
John Colvin 60c3cc0015 fix build: Fix prop names after refactoring PubSubRoute
* Change 'server_address' -> 'ServerAddress'
* Change 'tag_reg_exp' -> 'TagRegExp'
2024-10-30 19:30:30 +00:00
John Colvin 775567968b build: Add scripts to publish data stream apps 2024-10-30 19:30:30 +00:00
John Colvin e1555387ec Fix typo in log msg 2024-10-30 19:30:30 +00:00
John Colvin d044f711a3 Fix build scripts so they propagate the exit code back to the shell
I noticed this was broken because launch.json configurations would still start the debugger
even when the preLaunchTask (build) failed!
2024-10-30 19:30:30 +00:00
John Colvin eba801845c vscode: Add DataStreamPublisher debug cfg to skip build 2024-10-30 19:30:30 +00:00
John Colvin 04f85818c6 build: Use separate cmd to build DataStreamSuite/QueryAnalyzer.sln
The DataStreamSuite.sln builds the new webapp now, which will
(eventually) replace DSQA. We'll keep it around for now, but with a
clean separation from the main build.
2024-10-30 19:30:29 +00:00
John Colvin 545dcdf9ff feat: Add DataStream WebApp
Included in DataStreamSuite.sln, but not DataStreamQueryAnalyzer.sln
2024-10-30 19:30:29 +00:00
John Colvin 8c2d643e0c Add spark service modules designed for Dependency Injection 2024-10-30 19:30:29 +00:00
John Colvin ae70f731b7 build: Copy libspark to all dotnet dirs in single cmake target
This is cleaner than the previous impl, which used separate build
targets to copy libspark to "net7.0", and "net7.0-windows" (dirs).
2024-10-30 19:30:29 +00:00
John Colvin 828b06d4e2 fix: Parse ServerAddress as normal string
The "JsonValueOrArrayConverter<T>" doesn't work the way I thought it
would. When it is associated with a property via the JsonConverter
attribute, dotnet json parsing code throws an exception if "T" isn't
assignable to the property type.
2024-10-30 19:30:29 +00:00
John Colvin 6fa8de176c fix build (api server): Change namespace to bts.websocket_core 2024-10-30 19:30:29 +00:00
John Colvin fec918aefd Remove docker submodules again 2024-10-30 19:30:29 +00:00
John Colvin aa57b3bb8c Change externals url from ssh to https style
So we don't need ssh keys to clone it (the builtin credential manager
makes https authentication safe/easy)
2024-10-30 19:30:29 +00:00
John Colvin ab206ccf87 cleanup: Fix benign typos in build scripts
Change BUILD_SPARK_PY var name to BUILD_PY (because these scripts to actually build 'spark')
2024-10-30 19:30:29 +00:00
John Colvin 06ad7c51be fix build (warn): Only use Registry on windows 2024-10-30 19:30:29 +00:00
John Colvin 60a52b3922 cleanup: Fix numerous build warnings in DataStreamSuite
Nearly all warnings were due to nullability issues, since the concept of
nullable reference types did not exist when the DataStream code was
originally written.

* PubSubClientMessage
  - Normalize indentation/whitespace
  - Fix some typos
  - Address nullability issues to fix build warnings
  - Modernize code

* Compression related files
  - ZipEntry
  - ZipFile
  - ZipInputStream
  - TarHeader

* RemoteMessage
  - make 'message' nullable to resolve warnings elsewhere (in calling code)

* PubSubSingleTagStateEvent.cs
* PubSubMessageReactor
* PubSubState
* SystemHeartbeatPublisher
* SystemStatsPublisher
* SingleTagData
* ManagedTCPMessageHub
* PubsubDeserializationController
* PublisherCore
* InflaterHuffmanTree
* AsyncTCPConnection_APM
* ThreadSafeQueue
* ManagedSubscriber
* SubscriberCore
* PubSubServerManagedTCPConnection
* AsyncTCPConnection_APM
* SuperMapStream
* MapStream
* TradeBarsStream
2024-10-30 19:30:29 +00:00
John Colvin 5426ba0aa7 cleanup: Disable nullability warnings
* BZip2InputStream
* TarInputStream
* Inflater
* InflaterDynHeader
* BZip2OutputStream
* DeflaterHuffman
* ParallelJoinTest
2024-10-30 19:24:18 +00:00
John Colvin b1fdee5a22 cleanup: Modernize PubSubRoute code, json parsing 2024-10-30 19:24:18 +00:00
John Colvin 59461aadff cleanup: Remove unused DateTime grammar from exp parser 2024-10-30 19:24:18 +00:00
John Colvin 2d3a9a77ae Add JsonRpcRequestObservable, AsyncEnumerable 2024-10-30 19:24:18 +00:00
John Colvin 868b43ef8b Add json facilities for DataStream websocket api 2024-10-30 19:24:18 +00:00
John Colvin fc7043f6e1 refactor: Add handy WebsocketExtensions
* IsSocketStateOpen
* IsClosedProperly
2024-10-30 19:24:18 +00:00
John Colvin 7bec823545 feat: Add JsonRpc types for websocket paradigm 2024-10-30 19:24:18 +00:00
John Colvin 57c7868a24 cleanup: Remove empty file (clearly added by accident) 2024-10-30 19:24:18 +00:00
John Colvin 0b0ff8f502 fix: Fix improper 'OfType' linq cast
Previously, animations were expressed through polymorphism (derived from the
base class CellAnimation), but the new impl uses composition. This 'OfType' call
was leftover from the polymorphism implementation, an attempt to linq cast
elements of base class (CellAnimation) to BackgroundAnimation.
2024-10-30 19:24:18 +00:00
John Colvin c9a8325713 editorconfig: Use standard c# capitalization rules
* Use 'PascalCase' for public members (instead of 'snake_case')
* Use 'camelCase' for private members (instead of '_snake_case')
2024-10-30 19:24:18 +00:00
John Colvin d7e4c68c70 refactor: Add WebsocketClient class
Basically an ISubject (I/O) wrapper for IWebsocketConnectionGateway
2024-10-30 19:24:18 +00:00
John Colvin 70f782bc79 refactor: Change WebsocketServerConfig.port to uint32
It should always be a number between 1-65535
2024-10-30 19:24:18 +00:00
John Colvin 7e142860a3 refactor: Rename WebsocketServerConfig proto pkg 'bts.websocket_core'
This matches the namespace used in spark_websocket csproj. Eventually,
I'd like to use conventional capitalization (TitleCase namespaces/types,
etc.), but let's shelf that until we have enough time/energy to do a
more comprehensive refactoring.
2024-10-30 19:24:18 +00:00
John Colvin 1956e3a804 refactor: Remove nullability constraint in GetValueOrDefault 2024-10-30 19:24:18 +00:00
John Colvin 65b50eda3a refactor: Use events to avoid circular deps for ws sync/gateway 2024-10-30 19:24:18 +00:00
309 changed files with 732 additions and 7811 deletions

No files matched your search

+1 -7
View File
@@ -25,12 +25,6 @@
"commands": [
"fake"
]
},
"dotnet-t4": {
"version": "3.0.0",
"commands": [
"t4"
]
}
}
}
}
-81
View File
@@ -1,81 +0,0 @@
# This file defines the global default configuration for docker deployments.
# It is tracked in source control. For temporary modifications, please create
# a separate file called ".env.local" (it's in .gitignore, so won't be tracked).
# If a variable is defined in both files, the value in .env.local will be used.
#
# About the deployment procedure:
# We use Docker Compose to build images and launch containers for various services
# that make up our software suite:
# - sparkd
# - pubsub
# - md-publisher
# - webapp (DSQA on the web)
# - api-server (websocket based)
# From inside a devcontainer, you can launch all services in separate containers
# on your machine by running:
#
# tools/deploy.sh
#
# This script builds release versions for each service, packaged (generally) in
# self-contained directories. Next, it runs a few "docker compose" commands to
# build docker images loaded with the self-contained binaries from the previous
# step, and start containers with those images to run each service. To view the
# commands that deploy.sh runs internally (without actually running them), run:
#
# tools/deploy.sh --dry-run
#
# For additional information/help, run:
#
# tools/deploy.sh --help
#
### Sparkd ###
# The internal image for sparkd to use. For more info, view:
# src/app_sparkd/Dockerfile
SPARKD__IMAGE=sparkd
# The 'spark.conf' for this service to use
SPARKD__CONF_FILE=src/app_sparkd/spark.conf
# To run replay md, put recorded md files in SPARKD__MD_RECORDING_DIR, and use:
# SPARKD__IMAGE=sparkd_md_replay
# SPARKD__CONF_FILE=src/app_sparkd/spark.local.conf
# (configure which md to replay in the new SPARKD__CONF_FILE)
SPARKD__MD_RECORDING_DIR=.md_recording
SPARKD__SOURCE_DIR=install/sparkd-release-gcc
SPARKD__CONFIG_PORT=8000
SPARKD__QUICKBUS_PORT=8001
SPARKD__SIM_FIX_PORT=8002
### Pubsub ###
# The 'spark.conf' for this service to use
PUBSUB__CONF_FILE=src/DataStreamSuite/PubSubServer/spark.conf
PUBSUB__CS_SOURCE_DIR=build/Release/bin/net7.0/linux-x64/publish
PUBSUB__CPP_SOURCE_DIR=install/data-stream-suite-linux
# The appsettings to load at runtime. Expects a file named:
# src/DataStreamSuite/PubSub/appsettings.${PUBSUB__DOTNET_ENVIRONMENT}.json
PUBSUB__DOTNET_ENVIRONMENT=Staging
### Md Publisher ###
# The 'spark.conf' for this service to use
MD_PUBLISHER__CONF_FILE=src/DataStreamSuite/DataStreamPublisher/spark.conf
MD_PUBLISHER__SOURCE_DIR=build/Release/bin/net7.0/linux-x64/publish
# Determines which appsettings to load at runtime. Expects a file named:
# src/DataStreamSuite/DataStreamPublisher/appsettings.${MD_PUBLISHER__DOTNET_ENVIRONMENT}.json
MD_PUBLISHER__DOTNET_ENVIRONMENT=MarketData
### Webapp (DSQA on the web) ###
# The 'spark.conf' for this service to use
WEBAPP__CONF_FILE=src/client/spark_api_server/spark.conf
WEBAPP__SOURCE_DIR=build/Release/bin/net7.0/linux-x64/publish
WEBAPP__WEB_ROOT=build/Bts.DataStreamWebApp.Client/production/dist
# Determines which appsettings to load at runtime. Expects a file named:
# src/DataStreamSuite/Bts.DataStreamWebApp.Server/appsettings.${WEBAPP__ASPNETCORE_ENVIRONMENT}.json
WEBAPP__ASPNETCORE_ENVIRONMENT=Staging
### Api Server ###
# The 'spark.conf' for this service to use
API_SERVER__CONF_FILE=src/client/spark_api_server/spark.conf
API_SERVER__CS_SOURCE_DIR=build/Release/bin/net7.0/linux-x64/publish
API_SERVER__CPP_SOURCE_DIR=install/spark-api-server-linux
# Determines which appsettings to load at runtime. Expects a file named:
# src/client/spark_api_server/appsettings.${API_SERVER__ASPNETCORE_ENVIRONMENT}.json
API_SERVER__ASPNETCORE_ENVIRONMENT=Staging
-3
View File
@@ -59,6 +59,3 @@ trace_data/
.devcontainer/
*.Generated.cs
*.lst
src/app_sparkd/spark.local.conf
.env.local
.md_recording
+1 -1
View File
@@ -62,7 +62,7 @@
"name": "Bts.DataStreamWebApp.Server",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "Build DataStream (Debug)",
"preLaunchTask": "Build DataStream WebApp (Debug)",
"program": "${workspaceFolder}/build/Debug/bin/net7.0/Bts.DataStreamWebApp.Server.dll",
"cwd": "${workspaceFolder}/build/Debug/bin/net7.0",
"env": {
+1 -1
View File
@@ -112,7 +112,7 @@ function(init_library)
)
# Add tests executable (if there are any)
if (${_name}_TEST_SOURCES AND NOT WIN32)
if (${_name}_TEST_SOURCES)
add_executable(${_name}-tests ${${_name}_TEST_SOURCES})
get_target_property(_lib_type ${_name} TYPE)
+1 -5
View File
@@ -15,11 +15,7 @@ function(protobuf_generate_cpp)
cmake_parse_arguments(_ARGS "${_options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN})
if (NOT DEFINED _ARGS_PROTOC)
if (WIN32)
set(_ARGS_PROTOC "${EXTERNALS_BIN}/protoc.exe")
else()
set(_ARGS_PROTOC "${EXTERNALS_BIN}/protoc")
endif()
set(_ARGS_PROTOC "${EXTERNALS_BIN}/protoc")
endif()
set(${_ARGS_CXX_SOURCES})
set(${_ARGS_CXX_HEADERS})
+1 -1
View File
@@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.5)
cmake_policy(SET CMP0076 NEW)
# add protos to the library to facilitate source_group
target_sources(spark PUBLIC ${Config_Protos} ${NonConfig_Protos} ${Service_Protos})
target_sources(libspark PUBLIC ${Config_Protos} ${NonConfig_Protos} ${Service_Protos})
function(assign_source_group)
foreach(_source IN ITEMS ${ARGN})
+19 -22
View File
@@ -3,55 +3,52 @@ services:
build:
dockerfile: src/app_sparkd/Dockerfile
args:
- IMAGE=${SPARKD__IMAGE}
- SOURCE_DIR=${SPARKD__SOURCE_DIR}
- CONF_FILE=${SPARKD__CONF_FILE}
- MD_RECORDING_DIR=${SPARKD__MD_RECORDING_DIR}
- SOURCE_DIR=install/sparkd-release-gcc
- CONF_FILE=src/app_sparkd/spark.conf
command: [ "./sparkd", "run" ]
ports:
- "8000:8000"
- "8001:8001"
- "8002:8002"
- 8000:8000
- 8001:8001
- 8002:8002
pubsub:
environment:
- DOTNET_ENVIRONMENT=${PUBSUB__DOTNET_ENVIRONMENT}
- DOTNET_ENVIRONMENT=Staging
build:
dockerfile: src/DataStreamSuite/PubSubServer/Dockerfile
args:
- CS_SOURCE_DIR=${PUBSUB__CS_SOURCE_DIR}
- CPP_SOURCE_DIR=${PUBSUB__CPP_SOURCE_DIR}
- CONF_FILE=${PUBSUB__CONF_FILE}
- SOURCE_DIR=build/Release/bin/net7.0/linux-x64/publish
- CONF_FILE=src/DataStreamSuite/PubSubServer/spark.conf
ports:
- "22019:22019"
- 22019:22019
command: [ "./PubSubServer" ]
md-publisher:
build:
dockerfile: src/DataStreamSuite/DataStreamPublisher/Dockerfile
args:
- SOURCE_DIR=${MD_PUBLISHER__SOURCE_DIR}
- CONF_FILE=${MD_PUBLISHER__CONF_FILE}
- SOURCE_DIR=build/Release/bin/net7.0/linux-x64/publish
- CONF_FILE=src/DataStreamSuite/DataStreamPublisher/spark.conf
depends_on:
- pubsub
- sparkd
command: [ "dockerize", "-wait", "tcp://pubsub:22019", "-wait", "tcp://sparkd:8000", "-timeout", "10s", "./DataStreamPublisher" ]
environment:
- DOTNET_ENVIRONMENT=${MD_PUBLISHER__DOTNET_ENVIRONMENT}
- DOTNET_ENVIRONMENT=MarketData
webapp:
ports:
- "8080:8080"
- "8443:8443"
- 8080:8080
- 8443:8443
build:
dockerfile: src/DataStreamSuite/Bts.DataStreamWebApp.Server/Dockerfile
args:
- SOURCE_DIR=${WEBAPP__SOURCE_DIR}
- WEB_ROOT=${WEBAPP__WEB_ROOT}
- CONF_FILE=${WEBAPP__CONF_FILE}
- SOURCE_DIR=build/Release/bin/net7.0/linux-x64/publish
- WEB_ROOT=build/Bts.DataStreamWebApp.Client/production/dist
- CONF_FILE=src/DataStreamSuite/Bts.DataStreamWebApp.Server/spark.conf
command: [ "./Bts.DataStreamWebApp.Server" ]
environment:
- ASPNETCORE_ENVIRONMENT=${WEBAPP__ASPNETCORE_ENVIRONMENT}
- ASPNETCORE_ENVIRONMENT=Staging
api-server:
depends_on:
@@ -63,7 +60,7 @@ services:
dockerfile: src/client/spark_api_server/Dockerfile
command: [ "dockerize", "-wait", "tcp://sparkd:8000", "-timeout", "10s", "./spark_api_server", "run" ]
environment:
- ASPNETCORE_ENVIRONMENT=${API_SERVER__ASPNETCORE_ENVIRONMENT}
- ASPNETCORE_ENVIRONMENT=Staging
sparkd-ping-test:
depends_on:
@@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import { useTheme } from '@mui/material/styles';
import { DataGrid } from '@mui/x-data-grid';
import './DataGrid.css';
import './DataGrid.css'
import { useMemo, useState } from 'react';
export default function DataTable({ columns, rows }) {
@@ -23,7 +23,7 @@ export default function DataTable({ columns, rows }) {
headerName: col.name,
width: columnWidths[col.name],
};
};
}
}, [columnWidths]);
return (
@@ -12,7 +12,7 @@ const QueryStatusView = ({ queryState, details, error }) => {
let header = queryState;
let headerContent = null;
const detailElements = [];
let detailElements = [];
let severity = queryState == "Subscribed" ? "success" : "info";
let cursor = details == null ? "default" : "pointer";
if (error != null) {
@@ -20,14 +20,14 @@ const QueryStatusView = ({ queryState, details, error }) => {
cursor = "pointer";
header = `Error Code: ${error.code}`;
headerContent = error.message;
detailElements.push(<Typography key="error-msg" variant="body1">{error.data.error_message}</Typography>);
detailElements.push(<Typography key="error-details" variant="subtitle1">Stack Trace:</Typography>);
(error.data.stack_trace ?? []).forEach((line, idx) => detailElements.push(
<Typography key={`stacktrace-${idx}`} variant="body2" component="pre" sx={{ whiteSpace: 'pre-wrap' }}>
detailElements.push(<Typography variant="body1">{error.data.error_message}</Typography>);
detailElements.push(<Typography variant="subtitle1">Stack Trace:</Typography>);
(error.data.stack_trace ?? []).forEach(line => detailElements.push(
<Typography variant="body2" component="pre" sx={{ whiteSpace: 'pre-wrap' }}>
{line}
</Typography>));
} else if (details != null) {
detailElements.push(<Typography key="status-msg" variant="body1">{details}</Typography>);
detailElements.push(<Typography variant="body1">{details}</Typography>);
}
return (
<Box sx={{ margin: theme.spacing(2, 0, 0, 0) }}>
@@ -13,7 +13,7 @@ function getGridColDef(col) {
export default function ReactDataGrid({columns, rows}) {
return (
<DataGrid className={"data-grid"} rows={rows} columns={columns.map(getGridColDef)} />
);
)
}
ReactDataGrid.propTypes = {
@@ -3,7 +3,7 @@ import Button from '@mui/material/Button';
import { useTheme } from '@mui/material/styles';
import PropTypes from 'prop-types';
import Tooltip from '@mui/material/Tooltip';
import ChevronLeftRounded from "@mui/icons-material/ChevronLeftRounded";
import ChevronLeftRounded from "@mui/icons-material/ChevronLeftRounded"
import ChevronRightRounded from '@mui/icons-material/ChevronRightRounded';
import FileCopyIcon from '@mui/icons-material/FileCopy';
import './SideBar.css';
@@ -51,7 +51,7 @@ const Sidebar = ({ onSelection, onOpened, onClosed }) => {
} catch (error) {
console.error('Error fetching WebSocket config:', error);
}
};
}
fetchCannedQueries();
}, []);
@@ -50,25 +50,10 @@ export default defineConfig(({ $$command, mode, $$isSsrBuild, $$isPreview }) =>
server: {
proxy: {
'^/api': {
target,
secure: false,
configure: (proxy) => {
proxy.on('error', (err) => {
console.log('proxy error', err);
});
proxy.on('proxyReq', (proxyReq, req) => {
console.log(`Forwarding ${proxyReq.method} ${proxyReq.protocol}//${proxyReq.host}${proxyReq.path} to ${req.socket.remoteAddress}${req.url}`);
});
proxy.on('proxyRes', (proxyRes, req, res) => {
console.log(`Forwarding response ${req.socket.localAddress} to ${res.socket.remoteAddress}. Status=${proxyRes.statusCode}`);
});
},
},
'^/ws': {
target,
secure: false
},
'^/browse': {
'^/ws': {
target,
secure: false
}
@@ -1,21 +1,14 @@
namespace Bts.DataStreamWebApp.Server.Controllers;
using Bts.DataStream;
using Microsoft.AspNetCore.Mvc;
[Route("/ws")]
public class DataStreamSubscriptionController : ControllerBase {
readonly ILogger logger;
readonly IDataStreamFactory streamFactory;
public DataStreamSubscriptionController(ILogger<DataStreamSubscriptionController> logger, IEnumerable<IDataStreamFactory> streamFactories) {
public DataStreamSubscriptionController(ILogger<DataStreamSubscriptionController> logger) {
this.logger = logger;
logger.LogInformation("Creating {ControllerType}", GetType().Name);
var streamFactoryList = streamFactories.ToList();
streamFactory = streamFactoryList.Count == 1
? streamFactoryList[0]
: new CompositeDataStreamFactory(streamFactoryList);
}
[Route("/ws")]
@@ -28,7 +21,7 @@ public class DataStreamSubscriptionController : ControllerBase {
try {
using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
logger.LogInformation("Websocket connection established.");
using var server = new DataStreamWebSocketServer(webSocket, streamFactory, logger);
using var server = new DataStreamWebSocketServer(webSocket, logger);
await server.Run(cancellationToken);
} catch (Exception ex) {
logger.LogError("DataStreamSubscriptionController.Run threw exception: {Exception}", ex);
@@ -2,7 +2,6 @@ namespace Bts.DataStreamWebApp.Server.Controllers;
using bts.websocket_core.json_rpc;
using Bts.DataStream;
using Bts.DataStreamWebApp.Server.Messages;
using System.Net.WebSockets;
@@ -14,11 +13,9 @@ public class DataStreamWebSocketServer : IDisposable, IAsyncDisposable {
readonly ILogger logger;
readonly WebSocket webSocket;
readonly Dictionary<string, DataStreamSubscription> subscriptions = new();
readonly IDataStreamFactory streamFactory;
public DataStreamWebSocketServer(WebSocket webSocket, IDataStreamFactory streamFactory, ILogger logger) {
public DataStreamWebSocketServer(WebSocket webSocket, ILogger logger) {
this.webSocket = webSocket;
this.streamFactory = streamFactory;
this.logger = logger;
}
@@ -70,7 +67,7 @@ public class DataStreamWebSocketServer : IDisposable, IAsyncDisposable {
}
var subId = subReq.SubscriptionId ?? throw new InvalidOperationException("SubscriptionId is null (parse false)");
disposeSubscription(subId);
if (!subReq.ParseQuery(streamFactory, out var vs, out parseErr, out errCode)) {
if (!subReq.ParseQuery(out var vs, out parseErr, out errCode)) {
logger.LogError("Invalid 'subscribe' request: {Reason}", parseErr);
var rpcError = JsonRpcResponse.CreateError<SubscriptionError>(
req.Id, new() { Code = (long)errCode, Message = "Failed to parse subscription request", Data = parseErr });
@@ -4,7 +4,6 @@ using BCG;
using bts.websocket_core.json_rpc;
using Bts.DataStream;
using Bts.DataStreamExpressions;
using System.Diagnostics.CodeAnalysis;
@@ -38,12 +37,7 @@ public class SubscribeRequest {
return true;
}
internal bool ParseQuery(
IDataStreamFactory streamFactory,
[NotNullWhen(true)] out ViewStream? stream,
[NotNullWhen(false)] out SubscriptionError? error,
out ErrorCode errorCode
) {
internal bool ParseQuery([NotNullWhen(true)] out ViewStream? stream, [NotNullWhen(false)] out SubscriptionError? error, out ErrorCode errorCode) {
stream = null;
errorCode = ErrorCode.None;
error = null;
@@ -60,7 +54,7 @@ public class SubscribeRequest {
}
Exception? exception = null;
try {
stream = exp.getView(new Dictionary<string, StatementExpression>(0), streamFactory);
stream = exp.getView(new Dictionary<string, StatementExpression>(0));
} catch (Exception ex) {
exception = ex;
}
@@ -1,6 +1,5 @@
using Bts.DataStreamCore.Services;
using Bts.PubSub.Services;
using Bts.SharedStreams.Services;
using Spark.Api.Services;
using Spark.Core.Services;
@@ -79,9 +78,5 @@ static void ConfigureServices(IServiceCollection services, IConfiguration config
.AddSparkApi(config.GetSection("SparkApi"))
.AddMasterWorker(config.GetSection("MasterWorker"))
.AddViewStreamDatabase()
.AddPrimitiveDataStreamFactory(config.GetSection("PrimitiveStreams"))
.AddOhlcvStreams(config.GetSection("StreamDatabase"))
.AddNaturalLanguageStreams(config.GetSection("NaturalLanguageStream"))
.AddCompositeDataStreamFactory()
.AddPubSubClient(config.GetSection("PubSub"));
}
@@ -1,45 +0,0 @@
{
"NaturalLanguagePrompt": {
"prompts": [
{
"category": "Impact of financial events on the market",
"description": "Show how trading metrics (e.g., price movement, volume) are affected by events related to finance (rate announcements, earnings reports, etc.).",
"prompt": "Show me the price movement and volume of Crude Oil futures during the 5-minute window following the last 5 OPEC meetings",
"examples": [
{
"natural_language": "Show me the price movement and volume of Crude Oil futures during the 5-minute window following the last 5 OPEC meetings",
"dsl": [
"time_idx=EventTimeIndex.OpecMeetings(which: \"last 5\", duration: \"+5min\")",
"contract_idx=ActiveFutureContracts(predicate: \"product==CL\")",
"Bars(time_index: time_idx, contract_index: contract_idx, fields: [\"price_movement\", \"volume\"])"
]
},
{
"natural_language": "How do OPEC meetings affect the price of Crude futures? Examine the 10 minutes after each meeting this year.",
"dsl": [
"time_idx=EventTimeIndex.OpecMeetings(duration: \"+10min\", predicate: \"timestamp.year==2024\")",
"contract_idx=ActiveFutureContracts(predicate: \"product==CL\")",
"Bars(time_index: time_idx, contract_index: contract_idx, fields: [\"price_movement\", \"volume\"])"
]
},
{
"natural_language": "Show me how the SP 500 price moves after FOMC rate announcements that day",
"dsl": [
"time_idx=EventTimeIndex.FomcRateAnnouncements(which: \"timestamp.year==2024\", duration: \"+10min\")",
"contract_idx=ActiveFutureContracts(predicate: \"product==ES\")",
"Bars(time_index: time_idx, contract_index: contract_idx, fields: [\"price_movement\", \"volume\"])"
]
},
{
"natural_language": "Analyze the impact of the FED's interest rate hikes in 2022 on the 10-year Treasury yield, focusing on the 2-week period following each hike.",
"dsl": [
"time_idx=FedInterestRateAnnouncements(predicate: \"timestamp.year==2022 && change_pct>0\", duration: \"+2 weeks\")",
"contract_idx=ActiveFutureContracts(predicate: \"product==ZN\")",
"Bars(time_index: time_idx, contract_index: contract_idx)"
]
}
]
}
]
}
}
@@ -11,14 +11,8 @@
"Http": {
"Url": "http://0.0.0.0:8080"
},
"HttpIpv6": {
"Url": "http://[::1]:8080"
},
"Https": {
"Url": "https://0.0.0.0:8443"
},
"HttpsIpv6": {
"Url": "https://[::1]:8443"
}
}
},
@@ -28,358 +22,6 @@
"ViewStreamDatabase": {
"Interval": "00:00:00.1"
},
"PrimitiveStream": {
"Assemblies": [
"DataStream.dll",
"SharedStreams.dll"
],
"ExcludedTypes": [
"SettlementPriceStream",
"OHLCVStream"
]
},
"StreamDatabase": {
"Path": "/home/bts/.local/share/bts/silver_pine/store.db",
"Query": "select * from metadata"
},
"NaturalLanguageStream": {
"prompts": [
{
"category": "Settlement",
"MulitLineDescription": [
"This is a few shot learning example for an NL to DSL. The DSL function in question is Settlement (symbol_or_contract, datetime1). ",
"The first argument is a future (like CL, for crude oil) or a specific future contract like crude oil Dec 24 future (CLZ4). ",
"The second argument is the date at which the settlement is to be fetched. There is an overloaded function, Settlement ",
"(symbol_or_contract, begin_dt, end_dt) which gets the settlements for all dates from begin_dt to end_dt. This will also have ",
"datetimes expressed as Today, Yesterday, Last Friday, First Friday of the month, Last Thursday of August etc. You will need to know ",
"what Today is, since there will be referecnes to last week, yesterday etc. These examples are assuming Today is 10/11/2024, however ",
"you should use the Today's real date"
],
"prompt": "What was the settlement for SP 500 Future on June 8th?",
"examples": [
{
"natural_language": "Find Settlement price of the SP500 future on 04/22/2023.",
"dsl": "Settlement(\"ES\", 4/22/2023)"
},
{
"natural_language": "Find Settlement price of the Silver Dec 24 Future on 04/22/2023.",
"dsl": "Settlement(\"SIZ4\", 4/22/2023)"
},
{
"natural_language": "Find Yesterday's Settlement of the Crude Oil Future.",
"dsl": "Settlement(\"CL\", 10/10/2024)"
},
{
"natural_language": "Find today's settlement of the 10 year treasury future.",
"dsl": "Settlement(\"ZN\", 10/11/2024)"
},
{
"natural_language": "Find the Settlement of Natural Gas Dec 24 future on the first Friday of Sept.",
"dsl": "Settlement(\"NGZ4\", 9/6/2024)"
},
{
"natural_language": "Find the Settlement of WTI Jan 25 future in the first Week of Sept.",
"dsl": "Settlement(\"CLF5\", 9/2/2024, 9/6/2024)"
},
{
"natural_language": "Find the Settlement price for the Sp 500 Dec 24 Mar 25 spread for the first two weeks of September.",
"dsl": "Settlement(\"ESZ4-ESH5\", 9/14/2024, 9/1/2024)"
},
{
"natural_language": "Find the Settlement of Natural Gas future on the first Friday of Sept.",
"dsl": "Settlement(\"NG\", 9/6/2024)"
}
]
},
{
"category": "BBO",
"MulitLineDescription": [
"This is a few shot learning example for an NL to DSL. The DSL function in question is Bbo(args...). ",
"It accepts the following parameters: ",
"symbol (a product code such as ZN for 10 year T-Note futures), ",
"contract (a specific contract, such as ZNZ4 for the 10 Year T-Note future Dec 2024 exp), ",
"venue (name of the exchange, such as CME, NASDAQ, NYSE, NYMEX, DERIBIT etc.), ",
"interval (sample frequency of the data), ",
"begin and end (inclusive beginning and end of the time window to query, respectively), ",
"Arguments are specified by name, for example: Bbo(symbol: \"ZN\", venue: \"CME\", interval: \"PT1M\", begin: \"2024-10-10\", end: \"2024-10-11\"). ",
"Depending on the wording of the request, some arguments may be omitted. ",
"The begin, end, and interval args should be expressed in iso 8601 format. ",
"Interpret dates according to the trading schedule rather than calendar date whenever possible. ",
"In some prompts, the time window will be expressed in relative terms (e.g., \"last Monday\", or \"yesterday\") ",
"which must be translated to exact dates. To correctly interpret the dsl in the examples, assume the current time is 2024-10-11T12:00-05:00."
],
"prompt": "Show me bbo for SP 500 futures on Sep 23rd of this year.",
"examples": [
{
"natural_language": "Get SP500 future bbo from Wednesday.",
"dsl": "Bbo(symbol: \"ES\", venue: \"CME\", begin: \"2024-10-08T17:00:00-06:00\", end: \"2024-10-09T16:00:00-06:00\")"
},
{
"natural_language": "Get bbo for 10 year T-Notes in the first minute after the market opened yesterday.",
"dsl": "Bbo(symbol: \"ES\", venue: \"CME\", begin: \"2024-10-10T17:00:00-06:00\", end: \"2024-10-10T17:01:00-06:00\")"
},
{
"natural_language": "Retrieve NVIDIA L1 books from Oct 3",
"dsl": "Bbo(symbol: \"NVDA\", venue: \"NASDAQ\", begin: \"2024-10-02TT18:00:00-05:00\", end: \"2024-10-03T17:00:00-05:00\")"
},
{
"natural_language": "Show me L1 data for Crude oil futures this week",
"dsl": "Bbo(symbol: \"CL\", venue: \"CME\", begin: \"2024-10-06:T17:00:00-06:00\")"
},
{
"natural_language": "Fetch hourly bbo for Gold futures this week",
"dsl": "Bbo(symbol: \"GC\", venue: \"CME\", begin: \"2024-10-06:T17:00:00-06:00\", interval: \"PT1H\")"
},
{
"natural_language": "What was the minute by minute bbo for Tesla yesterday?",
"dsl": "Bbo(symbol: \"TSLA\", venue: \"NASDAQ\", begin: \"2024-10-09T18:00:00-05:00\", end: \"2024-10-10T17:00:00-05:00\", interval: \"PT1M\")"
}
]
},
{
"category": "Order flow imbalance",
"MulitLineDescription": [
"This is a few shot learning example for an NL to DSL. The DSL function in question is OrderImbalance(args...). ",
"It accepts the following parameters: ",
"symbol (a product code such as ZN for 10 year T-Note futures), ",
"contract (a specific contract, such as ZNZ4 for the 10 Year T-Note future Dec 2024 exp), ",
"venue (name of the exchange, such as CME, NASDAQ, NYSE, NYMEX, DERIBIT etc.), ",
"interval (sample frequency of the data), ",
"levels (the number of price levels to examine)",
"begin and end (inclusive beginning and end of the time window to query, respectively), ",
"Arguments are specified by name, for example: OrderImbalance(symbol: \"ZN\", venue: \"CME\", interval: \"PT1M\", begin: \"2024-10-10\", end: \"2024-10-11\"). ",
"Depending on the wording of the request, some arguments may be omitted. ",
"The begin, end, and interval args should be expressed in iso 8601 format. ",
"If the prompt doesn't contain an explicit interval, please choose a reasonable value depending on the duration between begin and end.",
"Interpret dates according to the trading schedule rather than calendar date whenever possible. ",
"In some prompts, the time window will be expressed in relative terms (e.g., \"last Monday\", or \"yesterday\") ",
"which must be translated to exact dates. To correctly interpret the dsl in the examples, assume the current time is 2024-10-11T12:00-05:00."
],
"examples": [
{
"natural_language": "Get SP500 order imbalance from Wednesday.",
"dsl": "OrderImbalance(symbol: \"ES\", venue: \"CME\", begin: \"2024-10-08T17:00:00-06:00\", end: \"2024-10-09T16:00:00-06:00\", \"PT1M\")"
},
{
"natural_language": "Get SP500 order imbalance from Wednesday for the top 5 price levels.",
"dsl": "OrderImbalance(symbol: \"ES\", venue: \"CME\", begin: \"2024-10-08T17:00:00-06:00\", end: \"2024-10-09T16:00:00-06:00\", levels: 5, interval: \"PT1M\")"
},
{
"natural_language": "Get orderflow imbalance for 10 year T-Notes in the first minute after the market opened yesterday.",
"dsl": "OrderImbalance(symbol: \"ES\", venue: \"CME\", begin: \"2024-10-10T17:00:00-06:00\", end: \"2024-10-10T17:01:00-06:00\", interval: \"PT1S\")"
},
{
"natural_language": "Retrieve NVIDIA order flow from Oct 3",
"dsl": "OrderImbalance(symbol: \"NVDA\", venue: \"NASDAQ\", begin: \"2024-10-02TT18:00:00-05:00\", end: \"2024-10-03T17:00:00-05:00\", interval: \"PT1S\")"
},
{
"natural_language": "Show me order imbalance for Crude oil futures this week",
"dsl": "OrderImbalance(symbol: \"CL\", venue: \"CME\", begin: \"2024-10-06:T17:00:00-06:00\", interval: \"PT1H\")"
},
{
"natural_language": "Fetch hourly orderflow for Gold futures this week",
"dsl": "OrderImbalance(symbol: \"GC\", venue: \"CME\", begin: \"2024-10-06:T17:00:00-06:00\", interval: \"PT1H\")"
},
{
"natural_language": "What was the minute by minute orderflow for Tesla yesterday?",
"dsl": "OrderImbalance(symbol: \"TSLA\", venue: \"NASDAQ\", begin: \"2024-10-09T18:00:00-05:00\", end: \"2024-10-10T17:00:00-05:00\", interval: \"PT1M\")"
}
]
},
{
"category": "VWAP",
"description": "This is a few shot learning example for an NL to DSL. The DSL function in question is VWAP (symbol_or_contract, datetime1, datetime2). The first argument is a future (like CL, for crude oil, or ES for SP 500) or a specific future contract like crude oil Dec 24 future (CLZ4) or SP 500 Dec 2024 (ESZ4). The second argument is where the VWAP has to be calculated (also the beginning of the time window), and the third argument is datetime specifying the end of the time window. So, for example, a 20-day VWAP on 9/30/2023, the first argument will be 9/30/2023, and the third argument will be 9/10/2023 (20 days prior). This will also have datetimes expressed as Today, Yesterday, Last Friday, Last Sunday, etc. For the purpose of this few-shot prompts, let's assume today is 10/11/2024.",
"prompt": "What was the VWAP for SP 500 Future over the past 30 days?",
"examples": [
{
"natural_language": "Find the 20 day VWAP of the SP future on 04/22/2023.",
"dsl": "VWAP(\"ES\", 4/22/2023, 4/02/2023)"
},
{
"natural_language": "Find Yesterday's 10 day VWAP of the Crude Oil Future.",
"dsl": "VWAP(\"CL\", 10/10/2024, 10/1/2024)"
},
{
"natural_language": "Find today's 30-day VWAP of the 10 year treasury future.",
"dsl": "VWAP(\"ZN\", 10/11/2024, 9/11/2024)"
},
{
"natural_language": "Find the 10 hour VWAP of the Natural Gas future from yesterday at 5:00 PM.",
"dsl": "VWAP(\"NG\", 9/27/2024 17:00:00, 9/27/2024 7:00:00)"
}
]
},
{
"category": "Price Diff",
"description": "This is a few shot learning example for an NL to DSL. The DSL function in question is OHLCVPriceDiff (symbol_or_contract, datetime1, datetime2). The first argument is a future (like CL, for crude oil) or a specific future contract like crude oil Dec 24 future (CLZ4). The second and third arguments are dates, that the function finds the difference of prices at. Assume today is 10/15/2024",
"prompt": "What is the difference in SP 500 Future from July 10th to July 14th?",
"examples": [
{
"natural_language": "What is the difference in ZN price from July 10th to July 14th?",
"dsl": "OHLCVPriceDiff(\"ZN\", 7/14/2024, 7/10/2024)"
},
{
"natural_language": "What is the price difference in SP Future price from Sept 10th and Sept 5th?",
"dsl": "OHLCVPriceDiff(\"ES\", 9/10/2024, 9/5/2024)"
},
{
"natural_language": "What is the price difference in Natural Gas Dec Future price from Aug 30th and Aug 20th?",
"dsl": "OHLCVPriceDiff(\"NGZ4\", 8/30/2024, 8/20/2024)"
}
]
},
{
"category": "Absolute Volume",
"description": "this is a few shot learning example for an NL to DSL. The DSL function in question is AbsVolume(symbol, ContractType, datetime1, datetime2), which computes the highest volume in the date range, specified by datetime1, and datetime2. The first argument is a future symbol (like CL for crude oil, ES for SP 500 etc). The second argument is the ContractType, which is \"Future\" for futures and \"FutureSpread\" for future spreads. the 3rd argument is the begin date and the 4th argument is the end date, between which the max volume is computed. If no future is specified use \"*\" from the first arugment. For this prompt, assume today is 10/11/2024 ",
"prompt": "Which SP 500 Future traded the most between 7/14/2024 and 7/10/2024",
"examples": [
{
"natural_language": "Which SP Future traded the most between 7/14/2024 and 7/10/2024",
"dsl": "AbsVolume(\"ES\", \"Future\", 7/14/2024, 7/10/2024)"
},
{
"natural_language": "Which Gold Spread traded the most between Today and Sept 20th",
"dsl": "AbsVolume(\"GC\", \"FutureSpread\", 10/1/2024, 9/20/2024)"
},
{
"natural_language": "Which 10 year Spread traded the highest volume last week?",
"dsl": "AbsVolume(\"ZN\", \"FutureSpread\", 9/27/2024, 9/23/2024)"
},
{
"natural_language": "Which Corn Spread traded the most today?",
"dsl": "AbsVolume(\"ZC\", \"FutureSpread\", 10/1/2024, 10/1/2024)"
},
{
"natural_language": "Which Gold Future traded the most today?",
"dsl": "AbsVolume(\"GC\", \"Future\", 9/30/2024, 9/30/2024)"
}
]
},
{
"category": "Roll",
"description": "This is a few shot learning example for an NL to DSL. The DSL function in question is FuturesRoll(symbol) which computes the rollover date for a future, ie when the highest volume swtches from one expiration to the next one. ",
"prompt": "When does the roll typically occur In SP 500?",
"examples": [
{
"natural_language": "When does the roll typically occur In CL?",
"dsl": "FuturesRoll(\"CL\")"
},
{
"natural_language": "When does the roll typically occur for 10 year treasury?",
"dsl": "FuturesRoll(\"ZN\")"
},
{
"natural_language": "When does the roll usually occur for Gold Future",
"dsl": "FuturesRoll(\"GC\")"
},
{
"natural_language": "At what date does the future rollover happens for SP500?",
"dsl": "FuturesRoll(\"ES\")"
},
{
"natural_language": "When does the future rollover happens for the Corn Future?",
"dsl": "FuturesRoll(\"ZC\")"
}
]
},
{
"category": "Daily Move",
"description": "This is a few shot learning example for an NL to DSL. ",
"prompt": "How often did Sp 500 Future settle + or - 10% over the previous day?",
"examples": [
{
"natural_language": "List all the days when the SP 500 closed + or -5% from the previous day's close",
"dsl": "ColDiff(filteredOHLCV(\"ES\"),\"ts_event\", -1, \"close\", \"close\", \"close_diff\", 5)"
},
{
"natural_language": "List all the days when the Crude Oil closed + or -2.5% from the previous day's close",
"dsl": "ColDiff(filteredOHLCV(\"CL\"),\"ts_event\", -1, \"close\", \"close\", \"close_diff\", 2.5)"
},
{
"natural_language": "Show me all the days when the Ten year Treaury Future opened + or -1% from the previous day's open",
"dsl": "ColDiff(filteredOHLCV(\"ZN\"),\"ts_event\", -1, \"open\", \"open\", \"open_diff\", 1.0)"
},
{
"natural_language": "List all the trading days when the Silver Future opened + or -1% from the previous day's close",
"dsl": "ColDiff(filteredOHLCV(\"SI\"),\"ts_event\", -1, \"open\", \"open\", \"open_diff\", 1.0)"
}
]
},
{
"category": "Average Volume",
"description": "This is a few shot learning example for an NL to DSL. For the purpose of this few shot prompts, lets assume today is 10/3/2024 ",
"prompt": "When does the most open interest typically shift from one contract to another in SP 500 Future?",
"examples": [
{
"natural_language": "When does the most open interest typically shift from one contract to another in e-minis? ",
"dsl": "FuturesRoll(\"ES\")"
},
{
"natural_language": "Usually when does the open interest move from one contract to another in WTI?",
"dsl": "FuturesRoll(\"CL\")"
}
]
},
{
"category": "Events",
"description": "This is a few shot learning example for an NL to DSL. In the NL prompt the user is asking to show the price of a future (treasury future, or SP 500 future etc) around an event type. The event type could be \"FOMC Announcement\" or \"Storage numbers\" etc. The DSL is as follows: EventsPriceAction(future_name, event_type, pre_event_window, post_event_window, number_of_events) first arg is the name of the future, (ex ES or ZN etc), the second argument is the event type, for example, \"FOMC announcement\", the 3rd argument is a time window for how much pre-event data to include in the end result, the 4th argument is how much post-event data to include in the end result. Both are strings of the form \"5d\" for 5 days, \"5h\" for 5 hours and \"120m\" for 120 minutes and so on. Usually, only one of the event periods is provided. Set the other to be half of what the user provided. The last argument is the number of events to consider. So if it’s 7, that means the most recent 7 events. If the user does not specify that, pass a large number.",
"prompt": "What did the volume and price of SP500 Future do following the release of the last 10 FOMC rates?",
"examples": [
{
"natural_language": "How did SP 500 Future do in the 5 days following the FOMC announcements?",
"dsl": "EventsPriceAction(\"ES\", \"FOMC Announcement\", \"2d\", \"5d\", 10)"
},
{
"natural_language": "How did the price of Treasury Future evolve in the 2 days following the Job Reports?",
"dsl": "EventsPriceAction(\"ZN\", \"Job Reports\", \"1d\", \"2d\", 10)"
},
{
"natural_language": "Show me the price movement and volume of Crude Oil futures during the 5-minute window following the last 5 OPEC meetings.",
"dsl": "EventsPriceAction(\"CL\", \"OPEC Meeting\", \"2m\", \"5m\", 5)"
},
{
"natural_language": "Show me the price movement and volume of SP 500 futures preceding the 10-minute window of the last 5 OPEC meetings.",
"dsl": "EventsPriceAction(\"CL\", \"OPEC Meeting\", \"10m\", \"5m\", 5)"
}
]
},
{
"category": "Indicator, Standard Deviation",
"MulitLineDescription": [
"This is a few shot learning example for an NL to DSL. In the NL prompt the user is asking to show all the days when ",
"the price of a future (treasury future, or SP 500 future etc) moved more than m times the N days standard deviation, ",
"where both m and N are provided. For example, the DSL for \"Using a 90d standard deviation, give me the dates and ",
"amounts of the last 4+ standard deviation moves in WTI futures\" would be: ",
"r=standardDeviation(\"ES\", \"90d\")\n",
"r=math(r, \"abs_daily_change = Abs(close - open) comp = std_dev * 4\")\n",
"r=filter(r,\"abs_daily_change >= comp\"). ",
"The first line, creates a stream for StandardDeviation, based on the number of days, for example 90d standard deviation, ",
"and assigned it to r, the second line takes r from the first line and passes it to a math stream that creates 2 new columns, ",
"abs_daily_change and comp. comp is the factor (4, from 4+ in the prompt) times standard deviation. The third line filters ",
"(like a sql where) of rows where abs_daily_change >= comp"
],
"prompt": "Using a 90d standard deviation, give me the dates and amounts of the last 1+ standard deviation moves in SP futures ?",
"examples": [
{
"natural_language": "Using a 90d standard deviation, give me the dates and amounts of the last 1+ standard deviation moves in WTI futures ?",
"dsl": "r=standardDeviation(\"CL\", \"90d\")\nr=math(r, \"abs_daily_change = Abs(close - open) comp = std_dev * 1.0\")\nr=filter(r,\"abs_daily_change >= comp\")"
},
{
"natural_language": "Show me the times the SP Futures moved twice the 60 day standard deviation",
"dsl": "r=standardDeviation(\"ES\", \"60d\")\nr=math(r, \"abs_daily_change = Abs(close - open) comp = std_dev * 2.0\")\nr=filter(r,\"abs_daily_change >= comp\")"
},
{
"natural_language": "List all the days the Gold Futures moved 1.5 times the 45 day standard deviation",
"dsl": "r=standardDeviation(\"GC\", \"45d\")\nr=math(r, \"abs_daily_change = Abs(close - open) comp = std_dev * 1.5\")\nr=filter(r,\"abs_daily_change >= comp\")"
},
{
"natural_language": "List all the days the 10 year treasury future moved more than twice the 1 month standard deviation",
"dsl": "r=standardDeviation(\"ZN\", \"30d\")\nr=math(r, \"abs_daily_change = Abs(close - open) comp = std_dev * 2.0\")\nr=filter(r,\"abs_daily_change >= comp\")"
}
]
}
]
},
"SparkApi": {
"IsEnabled": false,
"Credentials": {
@@ -435,7 +77,7 @@
{
"TagType": ".*",
"Assembly": "DataStreamCore",
"Class": "bts.PubsubServerGenericDataHandler"
"Class": "bts.PubsubServerGenericDataHandler"
}
]
}
@@ -2,26 +2,17 @@ namespace Bts.DataStream;
using BCG;
using bts.api.core;
using Bts.DataStreamCore;
using Microsoft.Extensions.Logging;
using Spark.Core.Disposables;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
public static class DataStreamExtensions {
public static ViewStream AsViewStream(this IObservable<DataPacket> source) =>
source as ViewStream ?? new ObservableViewStream(source);
public static ViewStream AsViewStream<T>(this IObservable<IDataPacket<T>> source, string? name = null, ILogger? logger = null) where T : IBlueRow =>
source as ViewStream ?? new ObservableViewStream(source.Select(dp => new DataPacket(dp.newRows, dp.addedRows, dp.deletedRows)), name, logger);
public static ViewStream AsViewStream<T>(this IObservable<IDataPacket<T>> source) where T : IBlueRow =>
source as ViewStream ?? new ObservableViewStream(source.Select(dp => new DataPacket(dp.newRows, dp.addedRows, dp.deletedRows)));
public static IObservable<DataPacket> AsObservable(this ViewStream source) {
return source as IObservable<DataPacket> ?? Observable.Create<DataPacket>(Subscribe);
@@ -35,100 +26,4 @@ public static class DataStreamExtensions {
$"Expected DataPacket, got {newValue?.GetType()}", nameof(newValue)));
}
}
public static IObservable<IDataPacket<T>> SelectDataPacket<T>(this IObservable<IDataPacket> source, Func<IBlueRow, T> selector) where T : IBlueRow =>
source.Select(dp => {
dp.get_new_added_deleted(out var newRows, out var addedRows, out var deletedRows);
return new DataPacket<T>(CastRows(newRows), CastRows(addedRows), CastRows(deletedRows));
IBlueRowCollection<T> CastRows(ICollection rows) =>
new BlueTableSelectorWrapper<T>(
rows as IBlueRowCollection ?? new BlueRowCollection(rows, dp.Schema), selector);
});
public static IObservable<TableUpdate<IBlueRow, int>> AsTables(this IObservable<IDataPacket> source) =>
source.AsTables<IBlueRow, int>(r => r, r => r.Id());
public static IObservable<TableUpdate<T, int>> AsTables<T>(this IObservable<IDataPacket> source, Func<IBlueRow, T> valueSelector) where T : IBlueRow =>
source.AsTables<T, int>(valueSelector, r => r.Id());
public static IObservable<TableUpdate<T, int>> AsTables<T>(
this IObservable<IDataPacket> source, Func<IBlueRow, T> valueSelector, Func<T, int> keySelector) =>
source.AsTables<T, int>(valueSelector, keySelector);
public static IObservable<TableUpdate<T, TKey>> AsTables<T, TKey>(
this IObservable<IDataPacket> source, Func<IBlueRow, T> valueSelector, Func<T, TKey> keySelector, Func<IBlueRow, TKey>? rowKeySelector = null) where TKey : IEquatable<TKey> {
rowKeySelector ??= r => keySelector(valueSelector(r));
return source.Scan(TableUpdate<T, TKey>.mk_empty(keySelector), (res, dp) => {
var bd = res.to_builder();
dp.get_new_added_deleted(out var newRows, out var addedRows, out var deletedRows);
foreach (IBlueRow r in addedRows) {
bd.add_or_update(valueSelector(r));
}
foreach (IBlueRow r in deletedRows) {
bd.remove(rowKeySelector(r));
}
return bd.to_table();
});
}
public static IObservable<IDataPacket<T>> AsDataPackets<T>(this IObservable<TableUpdate<T, int>> source, IBlueTableSchema schema) where T : IBlueRow =>
source.Scan(
(Table: new BlueTableImmutable<T>(schema), Dp: (IDataPacket<T>)DataPacket<T>.Empty(schema)),
(res, table) => {
var bd = res.Table.ToDataPacketBuilder();
foreach (var r in table.removed) {
bd.Remove(r.Id());
}
foreach (var r in table.added) {
bd.AddOrUpdate(r);
}
var resDp = bd.ToDataPacketT(out var newRows, out var addedRows, out var deletedRows);
return (newRows, resDp);
})
.Where(t => t.Dp.addedRows.Any() || t.Dp.deletedRows.Any())
.Select(t => t.Dp);
public static ViewStream AsViewStream<T>(this IObservable<TableUpdate<T, int>> source, IBlueTableSchema schema) where T : IBlueRow =>
source.AsDataPackets(schema).AsViewStream();
public static int ResolveIndices(this IBlueTableSchema schema, string name) {
var indices = ResolveIndices(schema, new[] { name });
return indices[0];
}
public static (int, int) ResolveIndices(this IBlueTableSchema schema, string name1, string name2) {
var indices = ResolveIndices(schema, new[] { name1, name2 });
return (indices[0], indices[1]);
}
public static (int, int, int) ResolveIndices(this IBlueTableSchema schema, string name1, string name2, string name3) {
var indices = ResolveIndices(schema, new[] { name1, name2, name3 });
return (indices[0], indices[1], indices[2]);
}
public static (int, int, int, int) ResolveIndices(this IBlueTableSchema schema, string name1, string name2, string name3, string name4) {
var indices = ResolveIndices(schema, new[] { name1, name2, name3, name4 });
return (indices[0], indices[1], indices[2], indices[3]);
}
public static int[] ResolveIndices(this IBlueTableSchema schema, string[] columnNames) {
var columns = schema.columnInfo;
var indices = new int[columnNames.Length];
int nResolved = 0;
for (int i = 0; i < columnNames.Length; i++) {
int origNResolved = nResolved;
for (int j = 0; j < columns.Length; j++) {
if (columns[j].colName == columnNames[i]) {
indices[i] = j;
nResolved++;
break;
}
}
if (nResolved == origNResolved) {
throw new ArgumentException($"No column named '{columnNames[i]}'", nameof(schema));
}
}
return indices;
}
}
@@ -3,25 +3,24 @@ namespace bts.SilverPine {
using BCG;
using System.Collections.Generic;
using Bts.DataStreamExpressions;
using Bts.DataStream;
// All this does is take in a dsl expression, parse it and fire the data that come out of running it.
// So for example, if an expresison is passed
[DataStreamUserRespresentation("dsl")]
public class DSLStream : ViewStream {
private readonly string _dsl;
public DSLStream(string dsl, IDataStreamFactory streamFactory) {
public DSLStream(string dsl) {
Log.Gen.info($"DSLStream.cons dsl: {dsl}");
_dsl = dsl;
var rs = build_stream(streamFactory, dsl);
rs = rs ?? new LiteralStream("error", $"msg:invalid dsl");
ViewStream rs = build_stream(dsl);
rs = rs ?? new LiteralStream("error", $"msg:invalid dsl \"{dsl}\"");
mySetRealSource(rs);
}
static ViewStream? build_stream(IDataStreamFactory streamFactory, string stream_expression) {
static ViewStream? build_stream(string stream_expression) {
try {
var exp = DataStreamExpressionsInterpreter.Evaluate(stream_expression);
var rv = exp.getView(new Dictionary<string, StatementExpression>(0), streamFactory);
var rv = exp.getView(new Dictionary<string, StatementExpression>(0));
return rv;
} catch (Exception ex) {
@@ -30,4 +29,4 @@ namespace bts.SilverPine {
}
}
}
}
}
@@ -4,21 +4,14 @@ using BCG;
using bts;
using Microsoft.Extensions.Logging;
using System;
public class ObservableViewStream : ViewStream, IObservable<DataPacket>, IDisposable {
readonly IObservable<DataPacket> source;
readonly string? name;
readonly ILogger? logger;
IDisposable? subscription;
public ObservableViewStream(IObservable<DataPacket> source, string? name = null, ILogger? logger = null) {
public ObservableViewStream(IObservable<DataPacket> source) =>
this.source = source;
this.name = name;
this.logger = logger;
}
public void Dispose() {
GC.SuppressFinalize(this);
@@ -30,13 +23,7 @@ public class ObservableViewStream : ViewStream, IObservable<DataPacket>, IDispos
protected override void myOnFirstSubscriber() {
Log.isnull(subscription);
subscription = source.Subscribe(myFireEvent, OnError, OnCompleted);
void OnError(Exception exception) =>
logger?.LogError("Error in ObservableViewStream. name: {Name}, error: {Exception}", name, exception);
void OnCompleted() =>
logger?.LogInformation("ObservableViewStream completed. name: {Name}", name);
subscription = source.Subscribe(myFireEvent);
}
protected override void myOnLastSubscriber() {
@@ -34,12 +34,12 @@ namespace BCG {
view_publisher = view_publisher_;
}
}
static ViewStream? build_stream(IDataStreamFactory streamFactory, string stream_expression)
static ViewStream? build_stream(string stream_expression)
{
try
{
var exp = DataStreamExpressionsInterpreter.Evaluate(stream_expression);
var rv = exp.getView(new Dictionary<string, StatementExpression>(0), streamFactory);
var rv = exp.getView(new Dictionary<string, StatementExpression>(0));
return rv;
} catch (Exception ex) {
@@ -51,17 +51,15 @@ namespace BCG {
_stream_pub_map = new Dictionary<string, StreamPubUnit>();
readonly IPubTableBroker _broker;
readonly IManagedSubscriber _sub;
readonly IDataStreamFactory streamFactory;
readonly object syncRoot = new();
IDisposable? cloudStreamsSub;
IDisposable? remoteRequestsSub;
bool isDisposed;
public RemoteQueryManager(IManagedSubscriber managedSubscriber, IPubTableBroker pubTableBroker, IDataStreamFactory streamFactory) {
public RemoteQueryManager(IManagedSubscriber managedSubscriber, IPubTableBroker pubTableBroker) {
_sub = managedSubscriber;
_broker = pubTableBroker;
this.streamFactory = streamFactory;
}
public Task StartAsync(CancellationToken cancellationToken) {
@@ -104,11 +102,7 @@ namespace BCG {
private void _spawn_remote_stream(string name, string stream_expression, bool persist) {
bts.Log.Gen.info($"CloudStreamManager.assign name: {name} stream_expression: {stream_expression}");
var stream = build_stream(streamFactory, stream_expression);
if (stream is null) {
Log.DataFlow.error($"Failed to spawn remote stream. name: {name}; expression: {stream_expression}");
return;
}
ViewStream stream = build_stream(stream_expression);
_expose_to_ws(name, stream);
if (persist) {
stream = new PersistorStream(stream, name, "C:\\Temp\\", 1000000); // 10MBs
@@ -1,54 +0,0 @@
namespace Bts.DataStream;
using BCG;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SQLite;
using System.Linq;
using System.Threading;
public class SQLiteEnumerable : IAsyncEnumerable<IBlueRowCollection> {
readonly string location;
readonly string statement;
readonly int pageSize;
public SQLiteEnumerable(string location, string statement, int pageSize = 65536) {
this.location = location;
this.statement = statement;
this.pageSize = pageSize;
}
public async IAsyncEnumerator<IBlueRowCollection> GetAsyncEnumerator(CancellationToken cancellationToken = default) {
var dataTable = new DataTable();
await using var conn = new SQLiteConnection($"Data Source={location};Version=3");
await conn.OpenAsync(cancellationToken);
await using var cmd = new SQLiteCommand(statement, conn);
await using var reader = cmd.ExecuteReader();
var schema = await reader.GetColumnSchemaAsync(cancellationToken);
var columns = schema.OrderBy(c => c.ColumnOrdinal ?? int.MaxValue)
.Select((c, i) => new ColumnInfo(c.ColumnName, c.ColumnOrdinal ?? i, c.DataType))
.Append(new ColumnInfo("id", schema.Count, typeof(int)))
.ToArray();
var blueSchema = new BlueTableSchema("anon", columns);
BlueTable? table = null;
int rowId = 1;
while (await reader.ReadAsync(cancellationToken)) {
table ??= new BlueTable(blueSchema);
var rowVals = new object[blueSchema.columnInfo.Length];
if (reader.GetValues(rowVals) != rowVals.Length - 1) {
throw new InvalidOperationException("Sqlite reader value count for single row not equal to column count");
}
rowVals[^1] = rowId++;
table.addRow(new BlueRow(blueSchema, rowVals));
if (table.Count == pageSize) {
yield return table;
table = null;
}
}
if (table is not null) {
yield return table;
}
}
}
@@ -1,73 +0,0 @@
namespace Bts.DataStream;
using BCG;
using bts.api.core;
using Bts.DataStreamCore;
using System;
using System.Collections;
using System.Collections.Generic;
public class TableUpdateDataPacket<T> : IDataPacket<T> where T : IBlueRow {
readonly TableUpdate<T, int> table;
readonly IBlueTableSchema schema;
public TableUpdateDataPacket(TableUpdate<T, int> table, IBlueTableSchema schema) {
this.table = table;
this.schema = schema;
}
public IBlueRowCollection<T> newRows {
get {
var rv = new BlueTableImmutable<T>(schema);
return rv;
}
}
public IBlueRowCollection<T> addedRows => throw new NotImplementedException();
public IBlueRowCollection<T> deletedRows => throw new NotImplementedException();
class MyRowCollection : IBlueRowCollection<T> {
readonly TableUpdate<T, int> table;
readonly IEnumerable<T> items;
Lazy<SortedList<int, T>> itemList;
public MyRowCollection(IEnumerable<T> items, IBlueTableSchema schema) {
this.items = items;
this.schema = schema;
itemList = new(() => {
var rv = new SortedList<int, T>();
foreach (var item in this.items) {
rv.Add(item.Id(), item);
}
return rv;
});
}
public int Count => itemList.Value.Count;
public bool IsSynchronized => false;
public object SyncRoot => this;
public T this[int id] => itemList.Value[id];
public IBlueTableSchema schema { get; }
public bool containsID(int id) =>
itemList.Value.ContainsKey(id);
public void CopyTo(Array array, int index) {
}
public IEnumerator<T> GetEnumerator() {
throw new NotImplementedException();
}
IEnumerator IEnumerable.GetEnumerator() {
throw new NotImplementedException();
}
}
}
@@ -1,127 +0,0 @@
namespace Bts.DataStreamCore;
using BCG;
using bts.utils;
using mbts.utils;
using Spark.Core;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
public class BlueColumnTable : IBlueRowCollection {
readonly Variant[][] columns;
readonly List<int> rowIds;
readonly int idColIndex;
public BlueColumnTable(IBlueTableSchema schema, int capacity = 4) {
ExceptionAssert.Argument.GreaterThanEqualTo(capacity, 0);
rowIds = new(capacity);
columns = new Variant[schema.columnInfo.Length][];
if (columns.Length == 0) {
throw new ArgumentException("Schema contains no columns", nameof(schema));
}
if (!schema.columnInfo.Select(c => c.colName).TryFindIndex("id", out idColIndex)) {
throw new ArgumentException("Schema contains no 'id' column", nameof(schema));
}
this.schema = schema;
for (int i = 0; i < columns.Length; i++) {
columns[schema.columnInfo[i].colNo] = new Variant[capacity];
}
}
public int Count => rowIds.Count;
public IBlueTableSchema schema { get; }
public IBlueRow this[int id] {
get {
var idx = rowIds.BinarySearch(id);
if (idx < 0) {
throw new KeyNotFoundException($"No row with id {id}");
}
return new MyRow(this, idx);
}
}
public void Add(Variant[] values) {
var id = values[idColIndex].VInt32;
var idx = rowIds.BinarySearch(id);
if (idx >= 0) {
throw new ArgumentException($"Row with id {id} already exists.", nameof(values));
}
idx = ~idx;
for (int i = 0; i < columns.Length; i++) {
int len = rowIds.Count;
ArrayHelper.Insert(ref columns[i], idx, values[i], ref len);
}
rowIds.Insert(idx, id);
}
public void AddOrUpdate(Variant[] values) {
var id = values[idColIndex].VInt32;
var idx = rowIds.BinarySearch(id);
if (idx >= 0) {
rowIds[idx] = id;
for (int i = 0; i < columns.Length; i++) {
columns[idx][i] = values[i];
}
return;
}
idx = ~idx;
int len;
for (int i = 0; i < columns.Length; i++) {
len = rowIds.Count;
ArrayHelper.Insert(ref columns[i], idx, values[i], ref len);
}
rowIds.Insert(idx, id);
}
public void Clear() =>
rowIds.Clear();
public bool Remove(int id) {
var idx = rowIds.BinarySearch(id);
if (idx < 0) {
return false;
}
rowIds.RemoveAt(idx);
for (int i = 0; i < columns.Length; i++) {
ArrayHelper.remove_at(ref columns[i], idx);
}
return true;
}
public void Trim() {
for (int i = 0; i < columns.Length; i++) {
ArrayHelper.trim(ref columns[i], rowIds.Count);
}
}
public bool containsID(int id) =>
rowIds.BinarySearch(id) >= 0;
public IEnumerator GetEnumerator() {
var count = rowIds.Count;
for (int i = 0; i < count; i++) {
yield return new MyRow(this, i);
}
}
class MyRow : IBlueRow {
readonly BlueColumnTable table;
readonly int rowIdx;
public MyRow(BlueColumnTable table, int rowIdx) {
this.table = table;
this.rowIdx = rowIdx;
}
public object this[int colNo] => table.columns[colNo][rowIdx].GetValue();
public IBlueTableSchema schema => table.schema;
}
}
@@ -18,8 +18,6 @@ using System.Text.Json.Nodes;
using Newtonsoft.Json;
using BCG;
using System.Runtime.CompilerServices;
using Spark.Core;
using System.Numerics;
namespace bts {
@@ -91,132 +89,6 @@ namespace BCG {
public static DateTime get_datetime(this IBlueRow r, int col_no, DateTime err_value = default(DateTime)) {
return BlueDataUtility.get_row_datetime_value(r, col_no, err_value);
}
public static double? GetFloat64(this IBlueRow r, int colNo) =>
GetFloat64(r[colNo]);
public static double? GetFloat64(this IBlueRow r, string colName) =>
GetFloat64(r[colName]);
public static double? GetFloat64(object obj) {
if (obj is double d) {
return d;
}
if (obj is int i32) {
return i32;
}
if (obj is long i64) {
return i64;
}
if (obj is string s) {
return double.TryParse(s, out d) ? d : null;
}
if (obj is uint u32) {
return u32;
}
if (obj is ulong u64) {
return u64;
}
if (obj is short i16) {
return i16;
}
if (obj is ushort u16) {
return u16;
}
return null;
}
public static int? GetInt32(this IBlueRow r, int colNo) =>
GetInt32(r[colNo]);
public static int? GetInt32(this IBlueRow r, string colName) =>
GetInt32(r[colName]);
public static int? GetInt32(object obj) {
checked {
if (obj is double d) {
return (int)d;
}
if (obj is int i32) {
return i32;
}
if (obj is long i64) {
return (int)i64;
}
if (obj is string s) {
return double.TryParse(s, out d) ? (int)d : null;
}
if (obj is uint u32) {
return (int)u32;
}
if (obj is ulong u64) {
return (int)u64;
}
if (obj is short i16) {
return i16;
}
if (obj is ushort u16) {
return u16;
}
}
return null;
}
public static long? GetInt64(this IBlueRow r, int colNo) =>
GetInt64(r[colNo]);
public static long? GetInt64(this IBlueRow r, string colName) =>
GetInt64(r[colName]);
public static long? GetInt64(object obj) {
checked {
if (obj is double d) {
return (long)d;
}
if (obj is int i32) {
return i32;
}
if (obj is long i64) {
return i64;
}
if (obj is string s) {
return double.TryParse(s, out d) ? (long)d : null;
}
if (obj is uint u32) {
return u32;
}
if (obj is ulong u64) {
return (long)u64;
}
if (obj is short i16) {
return i16;
}
if (obj is ushort u16) {
return u16;
}
}
return null;
}
public static DateTime? GetUnixEpochDateTime(this IBlueRow row, string colName) =>
GetUnixEpochDateTime(row[colName]);
public static DateTime? GetUnixEpochDateTime(this IBlueRow row, int colNo) =>
GetUnixEpochDateTime(row[colNo]);
public static DateTime? GetUnixEpochDateTime(object obj) {
var tsNanos = GetInt64(obj);
return tsNanos.HasValue ? DateTime.UnixEpoch.Add(new TimeSpan(tsNanos.Value / 100)) : null;
}
public static DateTime? GetDateTime(object obj) {
var res = _get_datetime_value(obj, DateTime.MinValue);
if (res == DateTime.MinValue) {
return null;
}
return res;
}
public static T get_enum<T>(this IBlueRow r, string col_name, T err_value = default(T)) where T : struct {
return BlueDataUtility.get_row_enum_value<T>(r, col_name, err_value);
}
@@ -2352,32 +2224,6 @@ namespace BCG {
}
}
public static TimeSpan? get_timespan(this IBlueRow r, int col_no) =>
get_timespan(r[col_no]);
public static TimeSpan? get_timespan(this IBlueRow r, string col_name) =>
get_timespan(r[col_name]);
public static TimeSpan? get_timespan(object obj) {
if (obj is TimeSpan ts) {
return ts;
}
var str = obj as string ?? obj?.ToString();
if (str is null) {
return null;
}
if (TimeSpan.TryParse(str, out ts)) {
return ts;
}
if (str.StartsWith("PT")) {
try {
// Parse ISO 8601 duration
return System.Xml.XmlConvert.ToTimeSpan(str);
} catch {
}
}
return null;
}
#endregion
#region CSV methods
@@ -1,56 +0,0 @@
#nullable enable
namespace Bts.DataStreamCore;
using BCG;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
public class BlueRowCollection : IBlueRowCollection {
readonly ICollection source;
readonly Func<int, IBlueRow> findRow;
readonly Func<int, bool> containsKey;
public BlueRowCollection(ICollection source, IBlueTableSchema schema, Func<int, IBlueRow>? findRow = null, Func<int, bool>? containsKey = null) {
this.source = source;
this.schema = schema;
if (findRow is null || containsKey is null) {
if (source is IBlueRowCollection brc) {
findRow ??= id => brc[id];
containsKey ??= brc.containsID;
} else if (source is IDictionary dict) {
findRow ??= id => dict[id] as IBlueRow ?? throw new KeyNotFoundException();
containsKey ??= id => dict.Contains(id);
} else if (source is IDictionary<int, IBlueRow> genDict) {
findRow ??= id => genDict[id];
containsKey ??= genDict.ContainsKey;
} else if (source is IReadOnlyDictionary<int, IBlueRow> roGenDict) {
findRow ??= id => roGenDict[id];
containsKey = roGenDict.ContainsKey;
} else {
var cachedRows = new Lazy<Dictionary<int, IBlueRow>>(() =>
source.Cast<IBlueRow>().ToDictionary(r => r.Id()));
findRow ??= id => cachedRows.Value[id];
// Don't remove this lambda expression. Intellisense may suggest it, but that would
// instantiate 'cachedRows' immediately and defeat the purpose of using Lazy<T>.
containsKey ??= id => cachedRows.Value.ContainsKey(id);
}
}
this.findRow = findRow;
this.containsKey = containsKey;
}
public IBlueRow this[int id] => findRow(id);
public IBlueTableSchema schema { get; }
public int Count => source.Count;
public bool containsID(int id) =>
containsKey(id);
public IEnumerator GetEnumerator() =>
source.GetEnumerator();
}
@@ -1,64 +0,0 @@
#nullable enable
namespace Bts.DataStreamCore;
using BCG;
using bts.collections;
using bts.utils;
using System;
using System.Collections.Generic;
public class BlueRowColumnMask : IBlueRow {
readonly IBlueRow row;
readonly int[] hiddenColumns;
public BlueRowColumnMask(IBlueRow row, params int[] hiddenColumns) {
this.row = row;
this.hiddenColumns = hiddenColumns;
Array.Sort(hiddenColumns);
}
/// <summary>
/// Provides a way to explicitly skip sorting hiddenColumns. This is done at the callers risk,
/// as the index (int) operator performs a binary search on hiddenColumns.
/// </summary>
/// <param name="row">The underlying row whose columns to hide</param>
/// <param name="sortHiddenColumns">Whether or not to sort <paramref name="hiddenColumns"/> in
/// the constructor. They are sorted by default to ensure correct behavior in the index (int)
/// operator, which performs a binary search on this array.</param>
/// <param name="hiddenColumns">The columns (indices) to hide</param>
public BlueRowColumnMask(IBlueRow row, bool sortHiddenColumns, params int[] hiddenColumns) {
this.row = row;
this.hiddenColumns = hiddenColumns;
if (sortHiddenColumns) {
Array.Sort(hiddenColumns);
}
}
public object? this[string colName] {
get {
for (int i = 0; i < hiddenColumns.Length; i++) {
if (row.schema.columnInfo[i].colName == colName) {
throw new KeyNotFoundException();
}
}
return row[colName];
}
}
public object? this[int index] {
get {
if (index > row.schema.columnInfo.Length - hiddenColumns.Length) {
throw new IndexOutOfRangeException();
}
int pos = hiddenColumns.BinarySearch(index);
if (pos < 0) {
pos = ~pos;
}
return row[index + pos];
}
}
public IBlueTableSchema schema => row.schema;
}
@@ -7,7 +7,7 @@ namespace BCG
/// Used for enumerating an IEnumerable (ex ICollection). Furthermore, this is the
/// default Enueramtor used when enumerating asn IBlueRow
/// </summary>
public class BlueRowEnumerator : IDictionaryEnumerator
public class BlueRowEnumerator : IEnumerator
{
protected int myPos = -1;
protected int myRowSize = -1;
@@ -18,11 +18,6 @@ namespace BCG
myRow = row;
myRowSize = row.schema.columnNames.Length;
}
public DictionaryEntry Entry =>
new(myRow.schema.columnInfo[myPos].colName, myRow[myPos]);
public object Key => myRow.schema.columnInfo[myPos].colName;
public object Value => myRow[myPos];
#region IEnumerator members
public void Reset() {myPos = 0;}
@@ -1,30 +0,0 @@
#nullable enable
namespace BCG;
using System;
using System.Collections;
public class BlueRowKeyCollection : ICollection {
readonly ColumnInfo[] columns;
public BlueRowKeyCollection(ColumnInfo[] columns) =>
this.columns = columns;
public int Count => columns.Length;
public bool IsSynchronized => false;
public object SyncRoot => this;
public void CopyTo(Array array, int index) {
for (int i = 0; i < columns.Length; i++) {
array.SetValue(columns[i].colName, i + index);
}
}
public IEnumerator GetEnumerator() {
for (int i = 0; i < columns.Length; i++) {
yield return columns[i].colName;
}
}
}
@@ -1,44 +0,0 @@
#nullable enable
namespace Bts.DataStreamCore;
using BCG;
using System.Linq;
using System.Reflection;
public static class BlueRowReflectionFactory {
public static IBlueRow Create<T>(T row, string? tableName = null) =>
new BlueRowReflection<T>(row);
class BlueRowReflection<T> : IBlueRow {
public static readonly ColumnInfo[] ColumnInfos;
public static readonly PropertyInfo[] ColumnProps;
readonly T row;
readonly string? tableName;
static BlueRowReflection() {
var colTups = typeof(T).GetProperties(BindingFlags.Instance)
.Select(p => (Prop: p, Attr: p.GetCustomAttribute<BlueTableColumnAttribute>()))
.Where(p => p.Attr is not null)
.ToList();
ColumnInfos = new ColumnInfo[colTups.Count];
ColumnProps = new PropertyInfo[colTups.Count];
for (int i = 0; i < colTups.Count; i++) {
var (prop, attr) = colTups[i];
ColumnInfos[i] = new(attr?.Name ?? prop.Name, i, prop.PropertyType);
ColumnProps[i] = prop;
}
}
public BlueRowReflection(T row, string? tableName = null) {
this.row = row;
this.tableName = tableName;
}
public object? this[int index] => ColumnProps[index].GetValue(row);
public IBlueTableSchema schema =>
new BlueTableSchema(ColumnInfos, tableName ?? typeof(T).Name);
}
}
@@ -1,311 +0,0 @@
#nullable enable
namespace Bts.DataStreamCore;
using BCG;
using mbts.utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
public class BlueRowStore<T1, T2, T3, T4, T5, T6> : IBlueRow, IReadOnlyDictionary<string, object?> {
readonly string? name;
IBlueTableSchema? _schema;
public BlueRowStore() { }
public BlueRowStore(
int id,
(string Name, T1 Value) item1,
(string Name, T2 Value) item2,
(string Name, T3 Value) item3,
(string Name, T4 Value) item4,
(string Name, T5 Value) item5,
(string Name, T6 Value) item6
) {
Id = id;
Item1 = item1;
Item2 = item2;
Item3 = item3;
Item4 = item4;
Item5 = item5;
Item6 = item6;
}
public int Id { get; set; }
public (string Name, T1 Value) Item1 { get; set; }
public (string Name, T2 Value) Item2 { get; set; }
public (string Name, T3 Value) Item3 { get; set; }
public (string Name, T4 Value) Item4 { get; set; }
public (string Name, T5 Value) Item5 { get; set; }
public (string Name, T6 Value) Item6 { get; set; }
public int Count => 5;
public IReadOnlyList<string> Keys => new KeyList(this);
public IReadOnlyList<object?> Values => new ValueList(this);
public IBlueTableSchema schema =>
_schema ??= new BlueTableSchema(name, new ColumnInfo[]{
new("id", 0, typeof(int)),
new(Item1.Name, 1, typeof(T1)),
new(Item2.Name, 2, typeof(T2)),
new(Item3.Name, 3, typeof(T3)),
new(Item4.Name, 4, typeof(T4)),
new(Item5.Name, 5, typeof(T5)),
new(Item6.Name, 6, typeof(T6)),
});
ICollection IDictionary.Keys => new KeyList(this);
ICollection IDictionary.Values => new ValueList(this);
IEnumerable<string> IReadOnlyDictionary<string, object?>.Keys => Keys;
IEnumerable<object?> IReadOnlyDictionary<string, object?>.Values => Values;
bool ICollection.IsSynchronized => false;
object ICollection.SyncRoot => this;
public object? this[string colName] {
get {
if (colName == "id") {
return Id;
}
if (colName == Item1.Name) {
return Item1.Value;
}
if (colName == Item2.Name) {
return Item2.Value;
}
if (colName == Item3.Name) {
return Item3.Value;
}
if (colName == Item4.Name) {
return Item4.Value;
}
if (colName == Item5.Name) {
return Item5.Value;
}
if (colName == Item6.Name) {
return Item6.Value;
}
throw new KeyNotFoundException();
}
set {
if (colName == "id") {
Id = ExceptionAssert.Cast(value).To<int>();
}
if (colName == Item1.Name) {
Item1 = (Item1.Name, ExceptionAssert.Cast(value).To<T1>());
}
if (colName == Item2.Name) {
Item2 = (Item2.Name, ExceptionAssert.Cast(value).To<T2>());
}
if (colName == Item3.Name) {
Item3 = (Item3.Name, ExceptionAssert.Cast(value).To<T3>());
}
if (colName == Item4.Name) {
Item4 = (Item4.Name, ExceptionAssert.Cast(value).To<T4>());
}
if (colName == Item5.Name) {
Item5 = (Item5.Name, ExceptionAssert.Cast(value).To<T5>());
}
if (colName == Item6.Name) {
Item6 = (Item6.Name, ExceptionAssert.Cast(value).To<T6>());
}
throw new KeyNotFoundException();
}
}
public object? this[int index] => index switch {
0 => Id,
1 => Item1.Value,
2 => Item2.Value,
3 => Item3.Value,
4 => Item4.Value,
5 => Item5.Value,
6 => Item6.Value,
_ => throw new IndexOutOfRangeException()
};
object? IDictionary.this[object key] {
get => this[ExceptionAssert.Cast(key).To<string>()];
set => this[ExceptionAssert.Cast(key).To<string>()] = value;
}
public void CopyTo(Array array, int index) {
array.SetValue(Id, index++);
array.SetValue(Item1.Value, index++);
array.SetValue(Item2.Value, index++);
array.SetValue(Item3.Value, index++);
array.SetValue(Item4.Value, index++);
array.SetValue(Item5.Value, index++);
array.SetValue(Item6.Value, index++);
}
public bool Contains(object key) =>
key is string keystr && ContainsKey(keystr);
public bool ContainsKey(string key) =>
Keys.Contains(key);
public void SetId(int id) =>
Id = id;
public void SetValue1(T1 value) =>
Item1 = (Item1.Name, value);
public void SetValue1(string colName, T1 value) =>
Item1 = (colName, value);
public void SetValue2(T2 value) =>
Item2 = (Item2.Name, value);
public void SetValue2(string colName, T2 value) =>
Item2 = (colName, value);
public void SetValue3(T3 value) =>
Item3 = (Item3.Name, value);
public void SetValue3(string colName, T3 value) =>
Item3 = (colName, value);
public void SetValue4(T4 value) =>
Item4 = (Item4.Name, value);
public void SetValue4(string colName, T4 value) =>
Item4 = (colName, value);
public void SetValue5(T5 value) =>
Item5 = (Item5.Name, value);
public void SetValue5(string colName, T5 value) =>
Item5 = (colName, value);
public void SetValue6(T6 value) =>
Item6 = (Item6.Name, value);
public void SetValue6(string colName, T6 value) =>
Item6 = (colName, value);
public bool TryGetValue(string key, [MaybeNullWhen(false)] out object? value) {
if (!Keys.TryFindIndex(key, out int index)) {
value = null;
return false;
}
value = this[index];
return true;
}
void IDictionary.Add(object key, object? value) =>
throw new NotSupportedException();
void IDictionary.Clear() =>
throw new NotSupportedException();
void IDictionary.Remove(object key) =>
throw new NotSupportedException();
IDictionaryEnumerator IDictionary.GetEnumerator() =>
throw new NotImplementedException();
IEnumerator IEnumerable.GetEnumerator() =>
throw new NotImplementedException();
public IEnumerator<KeyValuePair<string, object?>> GetEnumerator() {
throw new NotImplementedException();
}
class KeyList : ICollection, IReadOnlyList<string> {
readonly BlueRowStore<T1, T2, T3, T4, T5, T6> store;
public KeyList(BlueRowStore<T1, T2, T3, T4, T5, T6> store) =>
this.store = store;
public int Count => 5;
bool ICollection.IsSynchronized => false;
object ICollection.SyncRoot => this;
public string this[int index] =>
index switch {
0 => "id",
1 => store.Item1.Name,
2 => store.Item2.Name,
3 => store.Item3.Name,
4 => store.Item4.Name,
5 => store.Item5.Name,
6 => store.Item6.Name,
_ => throw new IndexOutOfRangeException()
};
public void CopyTo(Array array, int index) {
array.SetValue(store.Id, index++);
array.SetValue(store.Item1.Value, index++);
array.SetValue(store.Item2.Value, index++);
array.SetValue(store.Item3.Value, index++);
array.SetValue(store.Item4.Value, index++);
array.SetValue(store.Item5.Value, index++);
array.SetValue(store.Item6.Value, index++);
}
public IEnumerator<string> GetEnumerator() {
yield return "id";
yield return store.Item1.Name;
yield return store.Item2.Name;
yield return store.Item3.Name;
yield return store.Item4.Name;
yield return store.Item5.Name;
yield return store.Item6.Name;
}
IEnumerator IEnumerable.GetEnumerator() =>
GetEnumerator();
}
class ValueList : ICollection, IReadOnlyList<object?> {
readonly BlueRowStore<T1, T2, T3, T4, T5, T6> store;
public ValueList(BlueRowStore<T1, T2, T3, T4, T5, T6> store) =>
this.store = store;
public object? this[int index] =>
index switch {
0 => store.Id,
1 => store.Item1.Value,
2 => store.Item2.Value,
3 => store.Item3.Value,
4 => store.Item4.Value,
5 => store.Item5.Value,
6 => store.Item6.Value,
_ => throw new IndexOutOfRangeException()
};
public int Count => 5;
public bool IsSynchronized => false;
public object SyncRoot => this;
public void CopyTo(Array array, int index) {
array.SetValue(store.Id, index++);
array.SetValue(store.Item1.Value, index++);
array.SetValue(store.Item2.Value, index++);
array.SetValue(store.Item3.Value, index++);
array.SetValue(store.Item4.Value, index++);
array.SetValue(store.Item5.Value, index++);
array.SetValue(store.Item6.Value, index++);
}
public IEnumerator<object?> GetEnumerator() {
yield return store.Id;
yield return store.Item1.Value;
yield return store.Item2.Value;
yield return store.Item3.Value;
yield return store.Item4.Value;
yield return store.Item5.Value;
yield return store.Item6.Value;
}
IEnumerator IEnumerable.GetEnumerator() =>
GetEnumerator();
}
}
@@ -1,32 +0,0 @@
#nullable enable
namespace BCG;
using System;
using System.Collections;
public class BlueRowValueCollection : ICollection {
readonly IBlueRow row;
public BlueRowValueCollection(IBlueRow row) =>
this.row = row;
public int Count => row.schema.columnInfo.Length;
public bool IsSynchronized => false;
public object SyncRoot => this;
public void CopyTo(Array array, int index) {
var length = row.schema.columnInfo.Length;
for (int i = 0; i < length; i++) {
array.SetValue(row[i], i + index);
}
}
public IEnumerator GetEnumerator() {
var length = row.schema.columnInfo.Length;
for (int i = 0; i < length; i++) {
yield return row[i];
}
}
}
@@ -1,17 +0,0 @@
#nullable enable
namespace Bts.DataStreamCore;
using System;
[AttributeUsage(AttributeTargets.Property)]
public class BlueTableColumnAttribute : Attribute {
readonly int index;
public BlueTableColumnAttribute(string? name = null, int index = -1) {
Name = name;
this.index = index;
}
public string? Name { get; }
public int? Index => index >= 0 ? index : null;
}
@@ -196,24 +196,6 @@ public class BlueTableImmutable<T> : IBlueRowCollection<T> where T : IBlueRow {
return new DataPacket<T>(newRows, addedRows, removedRows);
}
public IDataPacket<T> ToDataPacketT(
out BlueTableImmutable<T> newRows,
out BlueTableImmutable<T> addedRows,
out BlueTableImmutable<T> removedRows,
bool reset = true
) {
table.RemoveRange(removed.Keys);
table.AddRange(added);
newRows = new BlueTableImmutable<T>(schema, table.ToImmutable());
addedRows = new BlueTableImmutable<T>(schema, added.ToImmutable());
removedRows = new BlueTableImmutable<T>(schema, removed.ToImmutable());
if (reset) {
added.Clear();
removed.Clear();
}
return new DataPacket<T>(newRows, addedRows, removedRows);
}
public BlueTableImmutable<T> ToImmutable() =>
new(schema, table.ToImmutable());
}
@@ -1,12 +0,0 @@
#nullable enable
namespace Bts.DataStreamCore;
using System;
[AttributeUsage(AttributeTargets.Class)]
public class BlueTableNameAttribute : Attribute {
public BlueTableNameAttribute(string name) =>
Name = name;
public string Name { get; }
}
@@ -1,40 +0,0 @@
#nullable enable
namespace Bts.DataStreamCore;
using BCG;
using System;
using System.Collections;
using System.Collections.Generic;
public class BlueTableSchemaBuilder : IEnumerable<(string, Type)> {
readonly string? tableName;
readonly List<(string Name, Type Type)> colDefs = new();
public BlueTableSchemaBuilder(string? tableName = null) =>
this.tableName = tableName;
public void Add((string Name, Type Type) tup) =>
Add(tup.Name, tup.Type);
public void Add<T>(string Name) =>
colDefs.Add((Name, typeof(T)));
public void Add(string Name, Type Type) =>
colDefs.Add((Name, Type));
public IEnumerator<(string, Type)> GetEnumerator() {
throw new NotImplementedException();
}
public IBlueTableSchema ToSchema(string? tableName = null) {
var colInfos = new ColumnInfo[colDefs.Count];
for (int i = 0; i < colInfos.Length; i++) {
colInfos[i] = new(colDefs[i].Name, i, colDefs[i].Type);
}
return new BlueTableSchema(colInfos, tableName ?? this.tableName);
}
IEnumerator IEnumerable.GetEnumerator() =>
colDefs.GetEnumerator();
}
@@ -1,16 +0,0 @@
#nullable enable
namespace Bts.DataStreamCore;
using BCG;
using System;
public class BlueTableSchemaFactory {
public static IBlueTableSchema Create(string tableName, params (string Name, Type Type)[] columns) {
var colInfos = new ColumnInfo[columns.Length];
for (int i = 0; i < columns.Length; i++) {
colInfos[i] = new(columns[i].Name, i, columns[i].Type);
}
return new BlueTableSchema(colInfos, tableName);
}
}
@@ -1,39 +0,0 @@
#nullable enable
namespace Bts.DataStreamCore;
using BCG;
using System;
using System.Collections.Generic;
public static class BlueTableSelectorWrapper {
public static BlueTableSelectorWrapper<T> Create<T>(
IBlueRowCollection source, Func<IBlueRow, T> selector, IBlueTableSchema? schema = null) where T : IBlueRow =>
new(source, selector, schema);
}
public class BlueTableSelectorWrapper<T> : IBlueRowCollection<T> where T : IBlueRow {
readonly IBlueRowCollection source;
readonly Func<IBlueRow, T> selector;
public BlueTableSelectorWrapper(IBlueRowCollection source, Func<IBlueRow, T> selector, IBlueTableSchema? schema = null) {
this.source = source;
this.selector = selector;
this.schema = schema ?? source.schema;
}
public T this[int id] => selector(source[id]);
public IBlueTableSchema schema { get; }
public int Count => source.Count;
public bool containsID(int id) =>
source.containsID(id);
public IEnumerator<T> GetEnumerator() {
foreach (IBlueRow r in source) {
yield return selector(r);
}
}
}
@@ -1,64 +0,0 @@
namespace Bts.DataStreamCore;
using BCG;
using System;
public struct ColumnIndex {
public readonly string[] ColumnNames;
public readonly int[] ColumnIndices;
public ColumnIndex(params string[] columnNames) {
ColumnNames = columnNames;
ColumnIndices = new int[columnNames.Length];
}
public readonly int this[int nameIndex] => ColumnIndices[nameIndex];
public static int Resolve(ColumnInfo[] columns, string name) {
var indices = Resolve(columns, new[] { name });
return indices[0];
}
public static (int, int) Resolve(ColumnInfo[] columns, string name1, string name2) {
var indices = Resolve(columns, new[] { name1, name2 });
return (indices[0], indices[1]);
}
public static int[] Resolve(ColumnInfo[] columns, params string[] names) {
var indices = new int[names.Length];
int nResolved = 0;
for (int i = 0; i < names.Length; i++) {
int origNResolved = nResolved;
for (int j = 0; j < columns.Length; j++) {
if (columns[j].colName == names[i]) {
indices[i] = j;
nResolved++;
break;
}
}
if (nResolved == origNResolved) {
throw new ArgumentException($"No column named '{names[i]}'", nameof(columns));
}
}
return indices;
}
public int Resolve(ColumnInfo[] columns, bool requireSuccess = true) {
int nResolved = 0;
for (int i = 0; i < ColumnNames.Length; i++) {
int origNResolved = nResolved;
for (int j = 0; j < columns.Length; j++) {
if (columns[j].colName == ColumnNames[i]) {
ColumnIndices[i] = j;
nResolved++;
break;
}
}
if (requireSuccess && nResolved == origNResolved) {
throw new ArgumentException($"No column named '{ColumnNames[i]}'", nameof(columns));
}
}
return nResolved;
}
}
@@ -1,8 +1,6 @@
#nullable enable
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace BCG
{
@@ -25,50 +23,11 @@ namespace BCG
/// half a million type checkings
/// </summary>
object? this[int index] {get;}
object? this[string colName] {
get {
var cols = schema.columnInfo;
for (int i = 0; i < cols.Length; i++) {
if (cols[i].colName == colName) {
return this[i];
}
}
throw new KeyNotFoundException();
}
}
object? IDictionary.this[object key] {
get => key is string s
? this[s]
: throw new ArgumentException($"Key must be of type string (actually {key?.GetType()})", nameof(key));
set => throw new NotImplementedException();
}
ICollection IDictionary.Keys => new BlueRowKeyCollection(schema.columnInfo);
ICollection IDictionary.Values => new BlueRowValueCollection(this);
int ICollection.Count => schema.columnInfo.Length;
object? this[string colName] {get;}
bool IDictionary.IsFixedSize => true;
bool IDictionary.IsReadOnly => true;
object ICollection.SyncRoot => this;
bool ICollection.IsSynchronized => false;
bool IDictionary.Contains(object key) {
if (key is string s) {
return schema.columnNames.Contains(s);
}
return false;
}
void ICollection.CopyTo(Array array, int index) {
var length = schema.columnInfo.Length;
for (int i = 0; i < length; i++) {
array.SetValue(this[i], i + index);
}
}
void IDictionary.Add(object key, object? value) => throw new NotImplementedException();
void IDictionary.Clear() => throw new NotImplementedException();
IDictionaryEnumerator IDictionary.GetEnumerator() => new BlueRowEnumerator(this);
IEnumerator IEnumerable.GetEnumerator() => ((IDictionary)this).GetEnumerator();
void IDictionary.Remove(object key) => throw new NotSupportedException();
}
// public interface IBlueRow<T> : IBlueRow where T : IBlueRow {
@@ -16,23 +16,11 @@ namespace BCG
IBlueRow this[int id]{get;}
IBlueTableSchema schema {get;}
bool containsID (int id);
bool ICollection.IsSynchronized => false;
object ICollection.SyncRoot => this;
void ICollection.CopyTo(Array array, int index) {
foreach (IBlueRow r in this) {
array.SetValue(r, index++);
}
}
}
public interface IBlueRowCollection<T> : IBlueRowCollection, IReadOnlyCollection<T> where T : IBlueRow {
new T this[int id] { get; }
IBlueRow IBlueRowCollection.this[int id] => this[id];
IEnumerator IEnumerable.GetEnumerator() =>
GetEnumerator();
}
}
@@ -3,8 +3,6 @@ using System.Collections;
using System.Data;
using BCG.Utilities.General;
using Bts.DataStreamCore;
namespace BCG
{
[Serializable]
@@ -185,7 +183,6 @@ namespace BCG
return retVal;
}
}
IBlueTableSchema IDataPacket.Schema => schema;
/*
status D total A total D this A This D' total A' Total
@@ -327,12 +324,6 @@ namespace BCG
public IBlueRowCollection<T> newRows { get; }
public IBlueRowCollection<T> addedRows { get; }
public IBlueRowCollection<T> deletedRows { get; }
public static DataPacket<T> Empty(IBlueTableSchema schema) =>
new(
new BlueTableImmutable<T>(schema),
new BlueTableImmutable<T>(schema),
new BlueTableImmutable<T>(schema));
}
public static class DataPacketExtensions {
@@ -129,12 +129,6 @@ public class DataPacketJsonConverter : JsonConverter<DataPacket> {
if (t == typeof(bool)) {
return NullCheckWrapper(v => writer.WriteBooleanValue((bool)v));
}
if (t == typeof(DateTime)) {
return NullCheckWrapper(v => writer.WriteStringValue(((DateTime)v).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.ffffff")));
}
if (t == typeof(TimeSpan)) {
return NullCheckWrapper(v => writer.WriteStringValue(((TimeSpan)v).ToString(@"hh\:mm\:ss\.ffffff")));
}
return NullCheckWrapper(v => writer.WriteStringValue(v?.ToString()));
Action<object?> NullCheckWrapper(Action<object> writeNonNull) =>
@@ -1,29 +0,0 @@
#nullable enable
namespace Bts.DataStream;
using BCG;
using bts.SilverPine;
using System.Collections.Generic;
using System.Linq;
public class CompositeDataStreamFactory : IDataStreamFactory {
readonly List<IDataStreamFactory> streamFactories;
public CompositeDataStreamFactory(IEnumerable<IDataStreamFactory> streamFactories) {
this.streamFactories = streamFactories.ToList();
foreach (var rsf in this.streamFactories.OfType<IRecursiveDataStreamFactory>()) {
rsf.OnCompositeFactory(this);
}
}
public IEnumerable<IDataStreamMetadata> GetSupportedStreams() =>
streamFactories.SelectMany(f => f.GetSupportedStreams());
public ViewStream? Create(string streamId, IEnumerable<(string? Name, int? Index, object? Value)> args) =>
streamFactories.Where(f => f.CanCreate(streamId))
.Select(f => f.Create(streamId, args))
.ExceptNull()
.FirstOrDefault();
}
@@ -1,52 +0,0 @@
#nullable enable
namespace bts.SilverPine;
using BCG;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
public interface IDataStreamMetadata {
public string Name { get; }
public IEnumerable<string> AlternateNames { get; }
}
public class DataStreamMetadata : IDataStreamMetadata {
public string? Name { get; set; }
public List<string>? AlternateNames { get; set; }
string IDataStreamMetadata.Name => Name ?? string.Empty;
IEnumerable<string> IDataStreamMetadata.AlternateNames =>
AlternateNames ?? new(0);
public static DataStreamMetadata FromType(Type type) {
var friendlyName = type.GetCustomAttribute<DataStreamUserRespresentation>()?.friendlyName;
var rv = new DataStreamMetadata() {
Name = friendlyName ?? type.Name,
AlternateNames = new(2) {
Log.notnull(type.FullName)
}
};
if (friendlyName is not null) {
rv.AlternateNames.Add(type.Name);
}
return rv;
}
}
public static class DataStreamMetadataExtensions {
public static bool IsMatch(this IDataStreamMetadata dsm, string name, bool checkAlternateNames = true, bool ignoreCase = true, bool ignoreUnderscores = true) {
var strComp = ignoreCase ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture;
var nameWithoutUnderscores = new Lazy<string>(() => RemoveUnderscores(name));
return IsNameMatch(dsm.Name) || (checkAlternateNames && dsm.AlternateNames.Any(IsNameMatch));
static string RemoveUnderscores(string s) =>
s.Replace("_", "");
bool IsNameMatch(string otherName) =>
string.Equals(name, otherName, strComp)
|| (ignoreUnderscores && string.Equals(nameWithoutUnderscores.Value, RemoveUnderscores(otherName), strComp));
}
}
@@ -1,27 +0,0 @@
#nullable enable
namespace Bts.DataStream;
using BCG;
using bts.SilverPine;
using System.Collections.Generic;
using System.Linq;
public interface IDataStreamFactory {
IEnumerable<IDataStreamMetadata> GetSupportedStreams();
ViewStream? Create(string streamId, IEnumerable<(string? Name, int? Index, object? Value)> args);
bool CanCreate(string streamId) =>
GetSupportedStreams().Any(s => s.IsMatch(streamId));
}
/// <summary>
/// This interface provides a way for IDataStreamFactory's that create ViewStreams
/// that depend specifically on the composite IDataStreamFactory. This was designed
/// specifically for the NLStreamDataFactory.
/// </summary>
public interface IRecursiveDataStreamFactory : IDataStreamFactory {
void OnCompositeFactory(IDataStreamFactory compositeFactory);
}
@@ -9,36 +9,31 @@ namespace BCG {
using System.Reflection;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using bts.collections;
using System.Diagnostics.CodeAnalysis;
using bts.SilverPine;
using Spark.Core.Collections;
using Bts.DataStreamCore;
using Microsoft.Extensions.Options;
public class PrimitiveManager : IDataStreamFactory {
public class PrimitiveManager {
private static PrimitiveManager mySelf;
readonly List<string> myAssemblyNames = new();
readonly Dictionary<string, Type> myTypeLookupTable = new();
public PrimitiveManager(IOptions<PrimitiveManagerConfig> config) : this(config.Value) { }
private PrimitiveManager() {
myLoadAllTypes();
}
public PrimitiveManager(PrimitiveManagerConfig config) {
private void myLoadAllTypes() {
AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve += new ResolveEventHandler(myAssemblyResolver);
loadAssembly(this.GetType().Assembly.FullName);
foreach (string assemblyName in config.Assemblies.empty_if_null()) {
var assemblies = new string[] { "DataStream.dll", "SharedStreams.dll" };
var config_assemblies = Configuration.get<DataStreamConfig>().Assemblies;
bts.Log.Gen.info($"PrimitiveManager.myLoadAllTypes config_assemblies: {config_assemblies}");
foreach (string assemblyName in assemblies/*config_assemblies*/) {
bts.Log.Gen.info($"PrimitiveManager.myLoadAllTypes assemblyName: {assemblyName}");
string resolvedAssemblyPath = assemblyName;
if (!System.IO.Path.IsPathRooted(assemblyName)) {
resolvedAssemblyPath = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, assemblyName));
}
loadAssembly(resolvedAssemblyPath, IncludeType);
loadAssembly(resolvedAssemblyPath);
}
bool IncludeType(Type t) =>
!config.ExcludeTypes.empty_if_null().Any(tname => tname == t.Name);
}
private static Assembly myAssemblyResolver(object sender, ResolveEventArgs args) {
@@ -52,7 +47,7 @@ namespace BCG {
/// Loads all the Stream types associated with the assembly
/// </summary>
/// <param name="assemblyName"></param>
public void loadAssembly(string assemblyName, Func<Type, bool> typeFilter = null) {
public void loadAssembly(string assemblyName) {
bts.Log.Gen.info($"PrimitiveManager.loadAssembly assemblyName: {assemblyName}");
if (myAssemblyNames.Contains(assemblyName))
return;
@@ -61,11 +56,7 @@ namespace BCG {
GetType().Assembly :
Assembly.LoadFrom(assemblyName);
IEnumerable<Type> types = loadedAssembly.GetTypes();
if (typeFilter is not null) {
types = types.Where(typeFilter);
}
foreach (Type type in types)
foreach (Type type in loadedAssembly.GetTypes())
if (!type.IsInterface
&& !type.IsAbstract
&& type.IsSubclassOf(typeof(ViewStream))
@@ -82,7 +73,7 @@ namespace BCG {
break;
}
if (isVisible) {
Log.assert_d(!myTypeLookupTable.ContainsKey(userRep));
Log.assert_d(myTypeLookupTable[userRep] == null, type.FullName);
myTypeLookupTable[userRep] = type;
}
}
@@ -98,30 +89,22 @@ namespace BCG {
return retVal;
}
public IEnumerable<Type> GetSupportedTypes() =>
myTypeLookupTable.Values.Cast<Type>();
public IEnumerable<IDataStreamMetadata> GetSupportedStreams() =>
GenericDictionaryWrapper.Create<string, Type>(myTypeLookupTable).Select(kv => new DataStreamMetadata() {
Name = kv.Key,
AlternateNames = new() { kv.Value.Name, kv.Value.FullName }
});
public Type ResolveType(string streamId) =>
TryResolveType(streamId, out var t) ? t
: throw new ArgumentException($"Failed to resolve ViewStream type '{streamId}'", nameof(streamId));
public bool TryResolveType(string streamId, [NotNullWhen(true)] out Type result) {
result = GetSupportedStreams()
.Where(s => s.IsMatch(streamId))
.Select(s => s.Name)
.Prepend(streamId)
.Select(myTypeLookupTable.GetValueOrDefault)
.ExceptNull()
.FirstOrDefault();
return result is not null;
public static PrimitiveManager instance {
get { return mySelf = mySelf == null ? new PrimitiveManager() : mySelf; }
}
/// <summary>
/// Returns the Type corresponding to the primitive name
/// </summary>
/// <param name="friendlyName"></param>
/// <returns></returns>
public Type find(string friendlyName) =>
myTypeLookupTable.TryGetValue(friendlyName, out var t)
? t
: throw new ArgumentException($"'{friendlyName}' is not the friendly name of any of the loaded types", nameof(friendlyName));
/// <summary>
/// Constructs and returns a ViewStream of type corresponding to the friendly name
/// </summary>
@@ -135,110 +118,8 @@ namespace BCG {
constructorArgs = new object[args.Count];
args.CopyTo(constructorArgs, 0);
}
if (myTypeLookupTable[friendlyName] == null)
throw new ApplicationException(friendlyName + " is not the friendly name of any of the loaded types");
try {
return
constructorArgs == null ?
Activator.CreateInstance(myTypeLookupTable[friendlyName] as Type) as ViewStream :
Activator.CreateInstance(myTypeLookupTable[friendlyName] as Type, constructorArgs) as ViewStream;
} catch (Exception ex) {
Log.Gen.warn($"PrimitiveManager failed to construct ViewStream type '{friendlyName}': {ex}");
}
return null;
}
public ViewStream Create(string streamId, IEnumerable<(string Name, int? Index, object Value)> args) =>
TryResolveType(streamId, out var type) ? Create(type, args) : null;
/// <summary>
/// Constructs and returns a ViewStream of type corresponding to the friendly name
/// </summary>
/// <param name="friendlyName">friendly primitive name </param>
/// <param name="args">args can be null or an empty ICollection if the paramterless constructor
/// is the desired one</param>
/// <returns></returns>
public ViewStream Create(Type type, IEnumerable<(string Name, int? Index, object Value)> args) {
var ctrs = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public);
var argList = args.ToList();
const int MAX_ALLOWED_ARGS = 64;
if (argList.Count > MAX_ALLOWED_ARGS) {
throw new ArgumentException(
$"ViewStream arg count ({argList.Count}) exceeds max allowed ({MAX_ALLOWED_ARGS})", nameof(args));
}
if (argList.Any(a => !(a.Name?.Length > 0) && !a.Index.HasValue)) {
throw new ArgumentException(
"One or more ViewStream args lack both Name and Index (unable to assign a constructor param)", nameof(args));
}
var indexedArgs = argList.Where(a => a.Index.HasValue).ToList();
var namedArgs = argList.Where(a => a.Name?.Length > 0).ToList();
if (!namedArgs.Select(a => a.Name).IsDistinct()) {
throw new ArgumentException("ViewStream arg names are not unique");
}
if (!indexedArgs.Select(a => a.Index.Value).IsDistinct()) {
throw new ArgumentException("ViewStream arg indices are not unique");
}
// Constructor params with default values may be omitted from the input 'args'. Therefore,
// 'minArgCount' >= 'argList.Count'.
var minArgCount = Math.Max(indexedArgs.Select(a => a.Index.Value).MaxOrDefault(), argList.Count);
foreach (var ctr in ctrs) {
if (TryAssignConstructorArgs(ctr, minArgCount, argList, indexedArgs, namedArgs, out var ctrArgs)) {
return ctr.Invoke(ctrArgs) as ViewStream;
}
}
return null;
static bool TryAssignConstructorArgs(
ConstructorInfo ctr,
int minArgCount,
List<(string Name, int? Index, object Value)> argList,
List<(string Name, int? Index, object Value)> indexedArgs,
List<(string Name, int? Index, object Value)> namedArgs,
[NotNullWhen(true)] out object[] ctrArgs
) {
ctrArgs = null;
var ctrParams = ctr.GetParameters();
if (ctrParams.Length < minArgCount) {
return false;
}
var ctrArgList = new List<object>();
int remInputArgs = argList.Count;
for (int i = 0; i < ctrParams.Length; i++) {
var ctrParam = ctrParams[i];
var paramType = ctrParams[i].ParameterType;
if (indexedArgs.TryGetFirst(a => CanAssignInputArg(ctrParam, a), out var a)) {
remInputArgs--;
ctrArgList.Add(a.Value);
} else if (ctrParam.HasDefaultValue) {
ctrArgList.Add(ctrParam.DefaultValue);
} else {
return false;
}
}
if (remInputArgs > 0) {
return false;
}
Log.assert(ctrArgList.Count == ctrParams.Length);
ctrArgs = ctrArgList.ToArray();
return true;
}
static bool CanAssignInputArg(ParameterInfo pi, (string Name, int? Index, object Value) arg) {
if (!string.IsNullOrEmpty(arg.Name) && arg.Name != pi.Name) {
return false;
}
if (arg.Index.HasValue && arg.Index.Value != pi.Position) {
return false;
}
if (arg.Value is null) {
if (!pi.ParameterType.IsValueType && !pi.ParameterType.IsNullable()) {
return false;
}
} else if (!pi.ParameterType.IsAssignableFrom(arg.Value.GetType())) {
return false;
}
return true;
}
var type = find(friendlyName);
return Activator.CreateInstance(type, constructorArgs) as ViewStream;
}
/// <summary>
@@ -1,9 +0,0 @@
#nullable enable
namespace Bts.DataStreamCore;
using System.Collections.Generic;
public class PrimitiveManagerConfig {
public List<string>? Assemblies { get; set; }
public List<string>? ExcludeTypes { get; set; }
}
@@ -9,7 +9,6 @@ namespace BCG
{
public interface IDataPacket
{
IBlueTableSchema Schema { get; }
//this is an ICollection of IDictionary
void get_new_added_deleted(out ICollection new_rows, out ICollection added_rows, out ICollection deleted_rows);
}
@@ -19,8 +18,6 @@ namespace BCG
IBlueRowCollection<T> addedRows { get; }
IBlueRowCollection<T> deletedRows { get; }
IBlueTableSchema IDataPacket.Schema => newRows.schema;
void IDataPacket.get_new_added_deleted(out ICollection new_rows, out ICollection added_rows, out ICollection deleted_rows) {
new_rows = newRows;
added_rows = addedRows;
@@ -3,9 +3,6 @@ namespace Bts.DataStreamCore.Services;
using BCG;
using BCG.Utilities.General;
using Bts.DataStream;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
public static class DataStreamCoreServices {
@@ -17,11 +14,4 @@ public static class DataStreamCoreServices {
ViewStreamDatabase.SetGlobalInstance(new ViewStreamDatabase(sp.GetRequiredService<ISubscriberCore>()));
return ViewStreamDatabase.instance;
});
public static IServiceCollection AddPrimitiveDataStreamFactory(this IServiceCollection services, IConfiguration configurationSection) =>
services.Configure<PrimitiveManagerConfig>(configurationSection)
.AddSingleton<IDataStreamFactory, PrimitiveManager>();
public static IServiceCollection AddCompositeDataStreamFactory(this IServiceCollection services) =>
services.AddSingleton<CompositeDataStreamFactory>();
}
@@ -2,40 +2,15 @@ namespace Bts.DataStreamExpressions;
public class Argument {
public Argument() { }
public Argument(string str, string? name = null) {
String = str;
Name = name;
}
public Argument(bool? boolean, string? name = null) {
Boolean = boolean;
Name = name;
}
public Argument(int? integer, string? name = null) {
Integer = integer;
Name = name;
}
public Argument(double? real, string? name = null) {
Real = real;
Name = name;
}
public Argument(DateTime? dateTime, string? name = null) {
DateTime = dateTime;
Name = name;
}
public Argument(DBNull? @null, string? name = null) {
Null = @null;
Name = name;
}
public Argument(StatementExpression? statementExp, string? name = null) {
StatementExp = statementExp;
Name = name;
}
public Argument(QuotedView? quotedView, string? name = null) {
QuotedView = quotedView;
Name = name;
}
public Argument(string str) => String = str;
public Argument(bool? boolean) => Boolean = boolean;
public Argument(int? integer) => Integer = integer;
public Argument(double? real) => Real = real;
public Argument(DateTime? dateTime) => DateTime = dateTime;
public Argument(DBNull? @null) => Null = @null;
public Argument(StatementExpression? statementExp) => StatementExp = statementExp;
public Argument(QuotedView? quotedView) => QuotedView = quotedView;
public string? Name { get; }
public string? String { get; }
public bool? Boolean { get; }
public int? Integer { get; }
@@ -2,8 +2,6 @@ namespace Bts.DataStreamExpressions;
using BCG;
using Bts.DataStream;
class Assignment : StatementExpression {
readonly StatementExpression expression;
readonly string id;
@@ -21,8 +19,8 @@ class Assignment : StatementExpression {
return result;
}
public override ViewStream? myGetView(IDictionary<string, StatementExpression> env, IDataStreamFactory streamFactory) {
var result = expression.getView(env, streamFactory);
public override ViewStream? myGetView(IDictionary<string, StatementExpression> env) {
var result = expression.getView(env);
env[id] = expression;
return result;
}
@@ -2,8 +2,6 @@ namespace Bts.DataStreamExpressions;
using BCG;
using Bts.DataStream;
using System.Text;
public class DataStreamExpression {
@@ -33,10 +31,10 @@ public class DataStreamExpression {
return sb.ToString();
}
public ViewStream? getView(IDictionary<string, StatementExpression> env, IDataStreamFactory streamFactory) {
public ViewStream? getView(IDictionary<string, StatementExpression> env) {
for (int i = 0; i < Statements.Count - 1; i++) {
Statements[i].getView(env, streamFactory);
Statements[i].getView(env);
}
return Statements[^1].getView(env, streamFactory);
return Statements[^1].getView(env);
}
}
@@ -22,7 +22,7 @@
}
%token BOOLEAN_CONSTANT, INTEGER_CONSTANT, REAL_CONSTANT, DATE_TIME_CONSTANT, CS_STRING_CONSTANT, NULL_CONSTANT
%token PARENTHESIS_OPEN, PARENTHESIS_CLOSE, COMMA, COLON, IDENTIFIER, EQUALS, QUOTE_OPEN, QUOTE_CLOSE
%token PARENTHESIS_OPEN, PARENTHESIS_CLOSE, COMMA, IDENTIFIER, EQUALS, QUOTE_OPEN, QUOTE_CLOSE
%token LINE_COMMENT
%nonassoc EQUALS
@@ -72,21 +72,13 @@ Arguments :
Argument :
CS_STRING_CONSTANT { $$ = new($1.CsString); }
| IDENTIFIER COLON CS_STRING_CONSTANT { $$ = new($3.CsString, $1.Identifier); }
| BOOLEAN_CONSTANT { $$ = new($1.Boolean); }
| IDENTIFIER COLON BOOLEAN_CONSTANT { $$ = new($3.Boolean, $1.Identifier); }
| INTEGER_CONSTANT { $$ = new($1.Integer); }
| IDENTIFIER COLON INTEGER_CONSTANT { $$ = new($3.Integer, $1.Identifier); }
| REAL_CONSTANT { $$ = new($1.Real); }
| IDENTIFIER COLON REAL_CONSTANT { $$ = new($3.Real, $1.Identifier); }
| DATE_TIME_CONSTANT { $$ = new($1.DateTime); }
| IDENTIFIER COLON DATE_TIME_CONSTANT { $$ = new($3.DateTime, $1.Identifier); }
| NULL_CONSTANT { $$ = new($1.Null); }
| IDENTIFIER COLON NULL_CONSTANT { $$ = new($3.Null, $1.Identifier); }
| Statement { $$ = new($1); }
| IDENTIFIER COLON Statement { $$ = new($3, $1.Identifier); }
| QuotedView { $$ = new($1); }
| IDENTIFIER COLON QuotedView { $$ = new($3, $1.Identifier); }
;
QuotedView :
@@ -31,7 +31,6 @@ Identifier {Letter}({Letter}*[0-9]*_*)*
OpenParenthesis \(
CloseParenthesis \)
Comma ,
Colon :
QuoteOpen \[
QuoteClose \]
@@ -91,10 +90,6 @@ LineComment \/\/[^\n\r]*(\n|\r|\r\n)
return (int)DataStreamExpressionToken.COMMA;
}
{Colon} {
return (int)DataStreamExpressionToken.COLON;
}
{QuoteOpen} {
return (int)DataStreamExpressionToken.QUOTE_CLOSE;
}
@@ -2,8 +2,7 @@ namespace Bts.DataStreamExpressions;
using BCG;
using Bts.DataStream;
using System.Collections;
using System.Collections.Immutable;
public class PrimitiveExpression : StatementExpression {
@@ -21,7 +20,7 @@ public class PrimitiveExpression : StatementExpression {
}
public override string print(int startCol, IList<string> activeBindings) {
string prelude = "new " + myName + " (";
string prelude = "new " + PrimitiveManager.instance.find(myName).Name + " (";
return prelude + myPrintArgs(startCol + prelude.Length, activeBindings) + ")";
}
@@ -32,19 +31,21 @@ public class PrimitiveExpression : StatementExpression {
return prelude + myPrintViewArgs(startCol + prelude.Length) + ")";
}
public override ViewStream? myGetView(IDictionary<string, StatementExpression> env, IDataStreamFactory streamFactory) {
var resolvedArgs = myArgs.Select((arg, i) => ((string?)arg.Name, string.IsNullOrEmpty(arg.Name) ? (int?)i : null, ResolveArg(arg)));
return streamFactory.Create(myName, resolvedArgs);
public override ViewStream myGetView(IDictionary<string, StatementExpression> env) {
ArrayList resolvedArgs = new ArrayList();
object? ResolveArg(Argument arg) {
foreach (var arg in myArgs) {
if (arg.StatementExp is not null) {
return arg.StatementExp.myGetView(env, streamFactory);
resolvedArgs.Add(arg.StatementExp.myGetView(env));
continue;
}
if (arg.QuotedView is not null) {
return arg.QuotedView.Value;
resolvedArgs.Add(arg.QuotedView.Value);
continue;
}
return arg.Obj;
resolvedArgs.Add(arg.Obj);
}
return PrimitiveManager.instance.build(myName, resolvedArgs);
}
string myPrintSingleArg(int atColumn, object? o, IList<string> activeBindings) {
@@ -2,8 +2,6 @@ namespace Bts.DataStreamExpressions;
using BCG;
using Bts.DataStream;
public abstract class StatementExpression {
ViewStream? view;
@@ -15,10 +13,10 @@ public abstract class StatementExpression {
public virtual string? printView(int startCol) =>
null;
public ViewStream? getView(IDictionary<string, StatementExpression> env, IDataStreamFactory streamFactory) =>
view ??= myGetView(env, streamFactory);
public ViewStream? getView(IDictionary<string, StatementExpression> env) =>
view ??= myGetView(env);
public virtual ViewStream? myGetView(IDictionary<string, StatementExpression> env, IDataStreamFactory streamFactory) =>
public virtual ViewStream? myGetView(IDictionary<string, StatementExpression> env) =>
null;
public static string makeCSString(string s) {
@@ -2,8 +2,6 @@ namespace Bts.DataStreamExpressions;
using BCG;
using Bts.DataStream;
public class ViewNameExpression : StatementExpression {
readonly string id;
@@ -16,6 +14,6 @@ public class ViewNameExpression : StatementExpression {
public override string printView(int startCol) =>
id;
public override ViewStream? myGetView(IDictionary<string, StatementExpression> env, IDataStreamFactory streamFactory) =>
env.TryGetValue(id, out var exp) ? exp.getView(env, streamFactory) : new TableStream(id);
public override ViewStream? myGetView(IDictionary<string, StatementExpression> env) =>
env.TryGetValue(id, out var exp) ? exp.getView(env) : new TableStream(id);
}
@@ -1,8 +1,8 @@
FROM bluetradingsystems/linux_dotnet7
ARG DS_PUBLISHER_CS_SOURCE_DIR="build/Release/bin/net7.0/linux-x64/publish"
ARG DS_PUBLISHER_CPP_SOURCE_DIR="install/data-stream-suite-linux"
ARG DS_PUBLISHER_CONF_FILE="src/DataStreamSuite/DataStreamPublisher/spark.conf"
ARG CS_SOURCE_DIR="build/Release/bin/net7.0/linux-x64/publish"
ARG CPP_SOURCE_DIR="install/data-stream-suite-linux"
ARG CONF_FILE="src/DataStreamSuite/DataStreamPublisher/spark.conf"
ENV DOCKERIZE_VERSION=v0.7.0
USER root
@@ -12,7 +12,7 @@ RUN apt-get update \
&& apt-get autoremove -yqq --purge wget && rm -rf /var/lib/apt/lists/*
USER bts
COPY --chown=bts:bts "${DS_PUBLISHER_CPP_SOURCE_DIR}/lib" "bin"
COPY --chown=bts:bts "${DS_PUBLISHER_CS_SOURCE_DIR}" "bin"
COPY --chown=bts:bts "${DS_PUBLISHER_CONF_FILE}" ".local/share/bts/DataStreamPublisher/conf/spark.conf"
COPY --chown=bts:bts "${CPP_SOURCE_DIR}/lib" "bin"
COPY --chown=bts:bts "${CS_SOURCE_DIR}" "bin"
COPY --chown=bts:bts "${CONF_FILE}" ".local/share/bts/DataStreamPublisher/conf/spark.conf"
WORKDIR "/home/bts/bin"
@@ -4,7 +4,6 @@ using BCG;
using bts;
using Bts.DataStream;
using Bts.DataStreamCore;
using Bts.DataStreamExpressions;
using Bts.PubSub;
@@ -29,28 +28,13 @@ public class DataStreamPublisherService : IHostedService, IDisposable {
readonly DataStreamPublisherConfig config;
readonly IPubTableBroker pubTableBroker;
readonly IWorkScheduler workScheduler;
readonly IDataStreamFactory streamFactory;
List<IDisposable>? resources = new();
public DataStreamPublisherService(
IPubTableBroker pubTableBroker,
IEnumerable<IDataStreamFactory> streamFactories,
IWorkScheduler workScheduler,
IOptions<DataStreamPublisherConfig> config
)
: this(pubTableBroker, streamFactories, workScheduler, config.Value) { }
public DataStreamPublisherService(IPubTableBroker pubTableBroker, IWorkScheduler workScheduler, IOptions<DataStreamPublisherConfig> config)
: this(pubTableBroker, workScheduler, config.Value) { }
public DataStreamPublisherService(
IPubTableBroker pubTableBroker,
IEnumerable<IDataStreamFactory> streamFactories,
IWorkScheduler workScheduler,
DataStreamPublisherConfig config
) {
public DataStreamPublisherService(IPubTableBroker pubTableBroker, IWorkScheduler workScheduler, DataStreamPublisherConfig config) {
this.pubTableBroker = pubTableBroker;
var streamFactoryList = streamFactories.ToList();
streamFactory = streamFactoryList.Count == 1
? streamFactoryList[0]
: new CompositeDataStreamFactory(streamFactoryList);
this.workScheduler = workScheduler;
this.config = config;
}
@@ -64,7 +48,7 @@ public class DataStreamPublisherService : IHostedService, IDisposable {
Log.Gen.warn("Skipping PublishedStream because it doesn't have a name (must define 'Name' in config json).");
continue;
}
var vs = CreateViewStream(streamFactory, publishedStream);
var vs = CreateViewStream(publishedStream);
if (vs is null) {
continue;
}
@@ -88,7 +72,7 @@ public class DataStreamPublisherService : IHostedService, IDisposable {
resources?.Clear();
}
static ViewStream? CreateViewStream(IDataStreamFactory streamFactory, PublishedStream ps) {
static ViewStream? CreateViewStream(PublishedStream ps) {
Log.Gen.info(
$"Instantiating ViewPublisher for: {JsonSerializer.Serialize(ps)}");
if (string.IsNullOrEmpty(ps.Name)) {
@@ -96,7 +80,7 @@ public class DataStreamPublisherService : IHostedService, IDisposable {
$"Failed to instantiate ViewPublisher. Null/empty 'name': {JsonSerializer.Serialize(ps)}");
return null;
}
var vs = buildStream(streamFactory, ps.Expression);
var vs = buildStream(ps.Expression);
if (vs is null) {
Log.Gen.warn(
$"Failed to instantiate ViewPublisher. Invalid 'expression': {JsonSerializer.Serialize(ps)}");
@@ -105,14 +89,14 @@ public class DataStreamPublisherService : IHostedService, IDisposable {
return vs;
}
static ViewStream? buildStream(IDataStreamFactory streamFactory, string? streamExpression) {
static ViewStream? buildStream(string? streamExpression) {
if (streamExpression is null) {
return null;
}
try {
Log.Gen.info($"Building ViewStream: {streamExpression}");
var exp = DataStreamExpressionsInterpreter.Evaluate(streamExpression);
return exp.getView(new Dictionary<string, StatementExpression>(0), streamFactory);
return exp.getView(new Dictionary<string, StatementExpression>(0));
} catch (Exception ex) {
Log.assert(false, $"Could not build stream. expression: {streamExpression}; ex: {ex}");
return null;
@@ -19,7 +19,6 @@ public static class DataStreamPublisherServices {
.AddPubSubClient(configurationSection.GetSection("PubSub"))
.AddPubTableBroker()
.AddRemoteQueryManager()
.AddPrimitiveDataStreamFactory(configurationSection.GetSection("PrimitiveStream"))
.Configure<DataStreamPublisherConfig>(configurationSection.GetSection("DataStreamPublisher"))
.AddHostedService<DataStreamPublisherService>();
}
@@ -12,16 +12,6 @@
"ViewStreamDatabase": {
"Interval": "00:00:00.1"
},
"PrimitiveStream": {
"Assemblies": [
"DataStream.dll",
"SharedStreams.dll"
],
"ExcludedTypes": [
"SettlementPriceStream",
"OHLCVStream"
]
},
"PubSub": {
"HeartbeatInterval": 15000,
"MissingHeartbeatsBeforeGc": 50,
-6
View File
@@ -44,8 +44,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bts.Extensions.Hosting", "B
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bts.Net", "Bts.Net\Bts.Net.csproj", "{D92E1456-91C1-43FA-AA87-67BFE663944F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PandasExpressions", "PandasExpressions\PandasExpressions.csproj", "{32F45D7C-6DE5-4660-AD45-EDEE3B41E26F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -139,9 +137,5 @@ Global
{D92E1456-91C1-43FA-AA87-67BFE663944F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D92E1456-91C1-43FA-AA87-67BFE663944F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D92E1456-91C1-43FA-AA87-67BFE663944F}.Release|Any CPU.Build.0 = Release|Any CPU
{32F45D7C-6DE5-4660-AD45-EDEE3B41E26F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{32F45D7C-6DE5-4660-AD45-EDEE3B41E26F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32F45D7C-6DE5-4660-AD45-EDEE3B41E26F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32F45D7C-6DE5-4660-AD45-EDEE3B41E26F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
@@ -1,114 +0,0 @@
namespace Bts.DataStreamTests;
using BCG;
using Bts.DataStreamCore;
using Spark.Core;
[TestFixture]
public class BlueColumnTableTests {
static IBlueTableSchema defaultSchema = new BlueTableSchemaBuilder("addTestTable") {
("id", typeof(int)),
("name", typeof(string)),
("value", typeof(double)),
}
.ToSchema();
static BlueColumnTable getTable(params Variant[][] rows) {
var bct = new BlueColumnTable(defaultSchema);
for (int i = 0; i < rows.Length; i++) {
bct.Add(rows[i]);
}
return bct;
}
[Test]
public void TestAdd() {
var rows = new Variant[][] {
new Variant[] { 1, "Joe", 32 },
new Variant[] { 2, "Angie", 26.5 },
};
var bct = getTable();
Is.EqualTo(bct.Count == 0);
bct.Add(rows[0]);
bct.Add(rows[1]);
Is.EqualTo(bct.Count == 2);
assertRowEquals(bct[rows[0][0].GetInt32()], rows[0]);
assertRowEquals(bct[rows[1][0].GetInt32()], rows[1]);
assertRowSequenceEqual(bct, rows);
rows[0] = new Variant[] { 1, "Mika", 4.2 };
bct.AddOrUpdate(rows[0]);
Is.EqualTo(bct.Count == 2);
assertRowEquals(bct[rows[0][0].GetInt32()], rows[0]);
}
[Test]
public void TestRemove() {
var rows = new Variant[][] {
new Variant[] { 5, "Angie", 26.5 },
new Variant[] { 1, "Joe", 32 },
new Variant[] { 8, "Myrtle", 103 },
};
var bct = getTable(rows);
bct.Remove(rows[1][0].GetInt32());
assertRowSequenceEqual(bct, new[] { rows[0], rows[2] });
}
[Test]
public void TestRemoveReAdd() {
var rows = new Variant[][] {
new Variant[] { 5, "Angie", 26.5 },
new Variant[] { 1, "Joe", 32 },
new Variant[] { 8, "Myrtle", 103 },
};
var bct = getTable(rows);
bct.Remove(rows[1][0].GetInt32());
bct.Add(rows[1]);
assertRowSequenceEqual(bct, rows);
}
[Test]
public void TestClear() {
var rows = new Variant[][] {
new Variant[] { 5, "Angie", 26.5 },
new Variant[] { 1, "Joe", 32 },
new Variant[] { 8, "Myrtle", 103 },
};
var bct = getTable(rows);
bct.Clear();
assertRowSequenceEqual(bct, Array.Empty<Variant[]>());
}
static void assertRowEquals(IBlueRow row, Variant[] expectedValues, IBlueTableSchema? schema = null) {
schema ??= defaultSchema;
for (int i = 0; i < schema.columnInfo.Length; i++) {
Is.EqualTo(Equals(row[schema.columnInfo[i].colNo], expectedValues[i]));
if (!schema.columnInfo.TryGetFirst(c => c!.colNo == i, out var col)) {
throw new InvalidOperationException($"Schema has no column at index {i}");
}
Is.EqualTo(Equals(row[col.colName], expectedValues[i]));
}
}
static void assertRowSequenceEqual(BlueColumnTable bct, IEnumerable<Variant[]> rowValues) {
if (!bct.schema.columnInfo.TryGetFirst(c => c!.colName == "id", out var idCol)) {
throw new ArgumentException("Schema has no id column", nameof(bct));
}
Assert.That(bct.Cast<IBlueRow>().SequenceEqual(
rowValues.OrderBy(r => r[idCol.colNo].GetInt32()).Select(r => new MyRow(r, bct.schema)),
BlueRowEqualityComparer.Instance));
}
class MyRow : IBlueRow {
readonly Variant[] values;
public MyRow(Variant[] values, IBlueTableSchema schema) {
this.values = values;
this.schema = schema;
}
public IBlueTableSchema schema { get; }
public object? this[int index] => values[index].GetValue();
}
}
@@ -1,18 +0,0 @@
<Project>
<Import Project="..\Directory.Build.targets" />
<Target
Name="GenerateRowTypes"
BeforeTargets="BeforeCompile"
Condition="'@(RowType)'!=''"
Inputs="%(RowType.SchemaJson);%(RowType.Identity)"
Outputs="$(IntermediateOutputPath)/RowTypes/%(RowType.TypeName).cs">
<MakeDir Directories="$(IntermediateOutputPath)/RowTypes" />
<Exec Command="dotnet t4 %(RowType.Identity) -p SchemaJson=%(RowType.SchemaJson) -p TypeName=%(RowType.TypeName) -p TableName=%(RowType.TableName) -o $(IntermediateOutputPath)/RowTypes/%(RowType.TypeName).cs" />
<ItemGroup>
<Compile Include="$(IntermediateOutputPath)/RowTypes/%(RowType.TypeName).cs" />
</ItemGroup>
</Target>
</Project>
@@ -1,6 +0,0 @@
namespace PandasExpressions;
public class Class1
{
}
@@ -1,72 +0,0 @@
%namespace Bts.DataStreamExpressions
%partial
%visibility internal
%tokentype DataStreamExpressionToken
%parsertype DataStreamExpressionParser
%union {
public int Integer;
public double Real;
public bool Boolean;
public None None;
public string Identifier;
public DataFrameExpression DataFrame;
public StatementExpression Statement;
public PrimitiveExpression Primitive;
public ImmutableList<Argument> Arguments;
public Argument Argument;
}
%token BOOLEAN_CONSTANT, INTEGER_CONSTANT, REAL_CONSTANT, NONE_CONSTANT
%token PARENTHESIS_OPEN, PARENTHESIS_CLOSE, BRACKET_OPEN, BRACKET_CLOSE, DOT, COMMA, COLON, IDENTIFIER, EQUALS
%token AND, OR, PLUS, MINUS, TIMES, DIVIDE
%nonassoc EQUALS
%type <DataFrame> DataFrame
%type <Statement> Statement
%type <Argument> Argument
%type <Arguments> Arguments
%%
ArithmeticColumnExpression :
DataFrameExpression :
IDENTIFIER O
Arguments :
{
$$ = System.Collections.Immutable.ImmutableList<Argument>.Empty;
}
| Argument {
$$ = System.Collections.Immutable.ImmutableList<Argument>.Empty.Add($1);
}
| Arguments COMMA Argument {
$$ = $1.Add($3);
}
;
ColumnExpression :
IDENTIFIER DOT IDENTIFIER {
$$ = new ColumnNameExpression($1, $3);
}
| IDENTIFIER OPEN_BRACKET IDENTIFIER CLOSE_BRACKET {
$$ = new ColumnNameExpression($1, $3);
}
| ColumnExpression PLUS ColumnExpression {
$$ = $1.Plus($3);
}
| ColumnExpression MINUS ColumnExpression {
$$ = $1.Minus($3);
}
| ColumnExpression TIMES ColumnExpression {
$$ = $1.Multiply($3);
}
| ColumnExpression DIVIDE ColumnExpression {
}
| ColumnExpression AND ColumnExpression {
}
| ColumnExpression OR ColumnExpression
@@ -1,93 +0,0 @@
%namespace Bts.PandasExpressions
%scannertype PandasExpressionScanner
%visibility internal
%tokentype PandasExpressionToken
%option stack, minimize, parser, verbose, persistbuffer, noembedbuffers
Identifier [a-zA-Z_][a-zA-Z_0-9]*
Equals =
IntegerConstant -?[0-9]+
RealConstant -?[0-9]+\.[0-9]+
BooleanConstant True|False
And &&
Or ||
None None
OpenParenthesis \(
CloseParenthesis \)
OpenBracket \[
CloseBracket \]
Dot .
Comma ,
Colon :
Plus +
Minus -
Times *
Divide /
%%
{Equals} {
return (int)PandasExpressionToken.EQUALS;
}
{BooleanConstant} {
yylval.Boolean = yytext.ToLower() switch {
"True" => true,
"False" => false,
_ => throw new Exception(
string.Format("Expected yytext to be 'True' or 'False' (actually '{0}')", yytext))
};
return (int)PandasExpressionToken.BOOLEAN_CONSTANT;
}
{IntegerConstant} {
yylval.Integer = int.Parse(yytext);
return (int)PandasExpressionToken.INTEGER_CONSTANT;
}
{RealConstant} {
yylval.Real = double.Parse(yytext);
return (int)PandasExpressionToken.REAL_CONSTANT;
}
{None} {
yylval.None = None.Value;
return (int)PandasExpressionToken.NONE_CONSTANT;
}
{OpenParenthesis} {
return (int)PandasExpressionToken.PARENTHESIS_OPEN;
}
{CloseParenthesis} {
return (int)PandasExpressionToken.PARENTHESIS_CLOSE;
}
{Dot} {
return (int)PandasExpressionToken.DOT;
}
{Comma} {
return (int)PandasExpressionToken.COMMA;
}
{Colon} {
return (int)PandasExpressionToken.COLON;
}
{OpenBracket} {
return (int)PandasExpressionToken.BRACKET_OPEN;
}
{CloseBracket} {
return (int)PandasExpressionToken.BRACKET_CLOSE;
}
{Identifier} {
yylval.Identifier = yytext;
return (int)PandasExpressionToken.IDENTIFIER;
}
%%
@@ -1,9 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
@@ -182,9 +182,9 @@ namespace bts
}
n_transfers++;
total_bytes_read += bytes_read;
Log.DataFlow.diag($"AsyncTCPConnection_AsyncWrapper._read_fixed_size_msg n_bytes: {n_bytes} bytes_read: {bytes_read} total_bytes_read: {total_bytes_read} buff_len: {buffer.Length}");
Log.Gen.info($"AsyncTCPConnection_AsyncWrapper._read_fixed_size_msg n_bytes: {n_bytes} bytes_read: {bytes_read} total_bytes_read: {total_bytes_read} buff_len: {buffer.Length}");
}
Log.DataFlow.diag($"AsyncTCPConnection_AsyncWrapper._read_fixed_size_msg read {n_bytes} bytes in {n_transfers} transfers");
Log.Gen.info($"AsyncTCPConnection_AsyncWrapper._read_fixed_size_msg read {n_bytes} bytes in {n_transfers} transfers");
return buffer;
}
@@ -223,13 +223,13 @@ namespace bts
BinaryReader br = new BinaryReader(new MemoryStream(sz_arr));
br.BaseStream.Position = 0;
int msg_sz = br.ReadInt32();
Log.DataFlow.diag($"AsyncTCPConnection_AsyncWrapper._read_one_message read msg_sz: {msg_sz}");
Log.Gen.info($"AsyncTCPConnection_AsyncWrapper._read_one_message read msg_sz: {msg_sz}");
if (msg_sz < 0)
{
throw new IOException("Read 0 bytes, the connection was closed before all data could be read.");
}
byte[] msg = _read_fixed_size_msg(msg_sz);
Log.DataFlow.diag($"AsyncTCPConnection_AsyncWrapper._read_one_message read msg sz: {msg.Length}");
Log.Gen.info($"AsyncTCPConnection_AsyncWrapper._read_one_message read msg sz: {msg.Length}");
// TODO, read first 4 bytes and decide hb or data
OneRecvMessage rv = new OneRecvMessage(msg);
return rv;
@@ -336,4 +336,4 @@ namespace bts
return _connection_id.ToString();
}
}
}
}
@@ -19,9 +19,6 @@ namespace bts {
/// </summary>
public class SingleTagData {
private readonly IPubSubServerTagHandler? _tag_handler;
readonly HashSet<string> noisyTags = new() {
"pss_hb"
};
public SingleTagData(string tag, string tag_type, IPubSubServerTagHandler tag_handler) {
bts.Log.Gen.info($"SingleTagData constructed for tag: {tag} tag_type: {tag_type}");
@@ -39,9 +36,7 @@ namespace bts {
// PubsubState calls this
public void handle_publish(PublishMsg pub_msg) {
string handler_str = _tag_handler != null ? _tag_handler.GetType().ToString() : "NULL";
if (pub_msg.tag is not null && !noisyTags.Contains(pub_msg.tag)) {
bts.Log.Gen.info($"SingleTagData.handle_publish tag: {tag} pub_msg: {pub_msg} handler: {handler_str} ");
}
bts.Log.Gen.info($"SingleTagData.handle_publish tag: {tag} pub_msg: {pub_msg} handler: {handler_str} ");
try {
// tag_handler may be null. If it throws (a null reference ex), we'll catch and handle
// it like any other ex.
@@ -111,14 +111,7 @@ namespace bts.SilverPine {
:
this(symbol, Enum.Parse<OHLCVBarSize>(bar_size)) { }
internal OHLCVStream(ViewStream source, OHLCVBarSize bar_size) {
ViewStream rs = source;
rs = new SchemaGuesserStream(rs);
if (bar_size == OHLCVBarSize.ONE_DAY) {
rs = new DateStandardizationStream(rs, "ts_event", DateConversion.ChopTime);
}
rs = new NewProjectStream(rs, "symbol:contract");
mySetRealSource(rs);
}
}
}
}
@@ -50,4 +50,4 @@ namespace bts.SilverPine {
}
}
}
}
@@ -1,6 +0,0 @@
namespace bts.SilverPine;
public class DatabaseConfig {
public string? Path { get; set; }
public string? Query { get; set; }
}
@@ -1,77 +0,0 @@
namespace bts.SilverPine;
using Spark.Core.Json;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
public class NLPromptConfig {
static NLPromptConfig? instance;
public static NLPromptConfig Instance =>
instance ?? throw new InvalidOperationException(
"SetGlobalConfig must be called to initialize config static singleton");
public List<FewShotPrompt> prompts { get; } = new(0);
public static void SetGlobalConfig(NLPromptConfig cfg) {
// This should only be called once
Log.isnull(instance);
instance = Log.notnull(cfg);
}
public static NLPromptConfig Default() => new();
// Represents a pair of a natural language query and its dsl
public class FewShotExample {
public string? natural_language { get; set; }
public string? dsl { get; set; }
}
public class FewShotPrompt {
public string? category { get; set; }
public string? description { get; set; }
public bool IsEnabled { get; set; } = true;
// MultiLineDescription is simply here for convenience. It is functionally redundant, as it
// serves the same purpose as 'description'. If 'description' is too long, you can break it
// up into an array of strings and assign it to MultiLineDescription instead.
public List<string>? MultiLineDescription { get; set; }
// the actual prompt given by the user/CME
public string? prompt { get; set; }
public List<FewShotExample> examples { get; } = new(0);
public override string ToString() {
var desc = description ?? string.Concat(MultiLineDescription.empty_if_null());
string rv = $"System: {desc}\n";
int ex_no = 0;
foreach (FewShotExample ex in examples ?? new(0)) {
rv += $"{++ex_no}. Natural Language: \"{ex.natural_language}\"" + "\n";
rv += $" DSL: {ex.dsl}\n";
}
return rv;
}
// Reads normal strings and arrays of lines
public class FewShotPromptLinesConverter : JsonConverter<string> {
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
if (reader.ReadString(out var str)) {
return str;
}
if (reader.ReadArray(out var array)) {
return string.Join('\n', array.Select(v => v?.AsString()));
}
throw new JsonException("Failed to parse FewShotPrompt lines");
}
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) =>
writer.WriteStringValue(value);
}
}
}
@@ -1,42 +0,0 @@
namespace bts.SilverPine;
using BCG;
using Bts.DataStream;
using mbts.utils;
using System.Collections.Generic;
public class NLStreamFactory : IRecursiveDataStreamFactory {
IDataStreamFactory? compositeFactory;
public ViewStream? Create(string streamId, IEnumerable<(string? Name, int? Index, object? Value)> args) {
if (SupportedStreams.NaturalLanguage.IsMatch(streamId)) {
if (compositeFactory is null) {
Log.DataFlow.warn("Failed to create NLStream because 'compositeFactory' is null.");
return null;
}
if (args.SingleOrDefault().Value is string prompt) {
return new NLStream(prompt, compositeFactory);
}
}
return null;
}
public IEnumerable<IDataStreamMetadata> GetSupportedStreams() {
yield return SupportedStreams.NaturalLanguage;
}
public void OnCompositeFactory(IDataStreamFactory compositeFactory) =>
this.compositeFactory = compositeFactory;
static class SupportedStreams {
public static readonly DataStreamMetadata NaturalLanguage = new() {
Name = "nl",
AlternateNames = new() { "naturalLanguage" }
};
public static string NlName => ExceptionAssert.not_null(NaturalLanguage.Name);
}
}
@@ -40,4 +40,4 @@ namespace bts.SilverPine {
}
}
}
@@ -7,16 +7,38 @@ namespace bts.SilverPine {
using System.Text.Json;
using Root = NLPromptConfig;
using FewShotExample = NLPromptConfig.FewShotExample;
using FewShotPrompt = NLPromptConfig.FewShotPrompt;
[DataStreamUserRespresentation("nl_to_indexed_fewshot_prompt")]
// given an nl prompt, retreives the correct fewshot prompt and converts it into a form
// that be sent to openAI as a prompt
public class NLToIndexedFewShotPromptStream : ViewStream {
// Represents a pair of a natural language query and its dsl
public class FewShotExample {
public string natural_language { get; set; }
public string dsl { get; set; }
}
// Class to represent each prompt category
public class FewShotPrompt {
public string category { get; set; }
// this is the explaination of the DSL
public string description { get; set; }
// th actual prompt given by the user/CME
public string prompt { get; set; }
public List<FewShotExample> examples { get; set; }
public override string ToString() {
string rv = $"System: {description}\n";
int ex_no = 0;
foreach (FewShotExample ex in examples) {
rv += $"{++ex_no}. Natural Language: \"{ex.natural_language}\"" + "\n";
rv += $" DSL: {ex.dsl}\n";
}
return rv;
}
}
// this is how it should be prepended to the actual prompt:
// System: This is a few-shot learning example for converting natural language prompts to a DSL function for settlement data retrieval. The DSL function can take either a single date or a date range. Assume today's date is 10/11/2024. The function is structured as follows:
@@ -44,6 +66,21 @@ namespace bts.SilverPine {
// [Your Real Prompt Goes Here]
// Root class to map the JSON structure
public class Root {
public List<FewShotPrompt> prompts { get; set; }
}
public static Root read_json_file() {
// TODO
string json_file_path = @"C:\Users\m_tah\development\spark\src\DataStreamSuite\SharedStreams\NL\FewShotPrompts.json";
string json_string = File.ReadAllText(json_file_path);
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
Root root = JsonSerializer.Deserialize<Root>(json_string, options);
return root;
}
private readonly string _nl_prompt;
private string _fewshot_index_prompt;
private string _fewshot_index_reponse;
@@ -64,7 +101,7 @@ namespace bts.SilverPine {
}
protected override void myOnFirstSubscriber() {
var root = NLPromptConfig.Instance;
Root root = read_json_file();
string meta_prompt = build_fewshot_index_prompt(root, _nl_prompt);
_fewshot_index_prompt = meta_prompt;
Log.Gen.info($"NLToDSLStream.myOnFirstSubscriber _fewshot_index_prompt: {_fewshot_index_prompt}");
@@ -101,7 +138,7 @@ namespace bts.SilverPine {
string[] toks = response.Split('.');
index = Int32.Parse(toks[0]);
}
var root = NLPromptConfig.Instance;
var root = read_json_file();
FewShotPrompt fs_prompt = root.prompts[index];
string final_prompt = _build_fewshot_prompt(fs_prompt, _nl_prompt);
_fire_event(final_prompt);
@@ -129,10 +166,6 @@ namespace bts.SilverPine {
string meta_promp = "Take a look at the following prompt examples:\n";
int rv = 0;
foreach (FewShotPrompt fs_entry in root.prompts) {
if (!fs_entry.IsEnabled) {
continue;
}
var prompt = fs_entry.prompt;
meta_promp += $"{rv++}. {fs_entry.prompt}\n";
}
meta_promp += "Based on the above prompts, tell me which prompt (give me answer as the index only, and nothing else) does the following prompt most closely matches to: \n";
@@ -161,4 +194,4 @@ namespace bts.SilverPine {
return rv;
}
}
}
}
@@ -1,32 +0,0 @@
namespace bts.SilverPine;
// public class DateRange {
// public DateTime Begin { get; set; }
// public DateTime End { get; set; }
// }
public class OhlcvDatabaseConfig {
public class DatasetConfig {
public string? Path { get; set; }
public string? Query { get; set; }
public List<string>? QueryParams { get; set; }
}
public DatasetConfig? Bbo1s { get; set; }
public DatasetConfig? Bbo1m { get; set; }
public DatasetConfig? Ohlcv1s { get; set; }
public DatasetConfig? Ohlcv1m { get; set; }
public DatasetConfig? Ohlcv1h { get; set; }
public DatasetConfig? Ohlcv1d { get; set; }
public DatasetConfig? Mbo { get; set; }
// public List<string>? Symbols { get; set; }
// public List<string>? Venues { get; set; }
// public List<string>? Types { get; set; }
// public List<DateRange>? DateRanges { get; set; }
// public string? Path { get; set; }
}
public class OhlcvStreamConfig {
public OhlcvDatabaseConfig? Database { get; set; }
}
@@ -103,10 +103,6 @@ namespace bts.SilverPine {
Tuple.Create("id", TypeCode.Int32),
Tuple.Create("prompt", TypeCode.String),
Tuple.Create("response", TypeCode.String));
const string DSL_PREFIX = "DSL: ";
if (response.StartsWith(DSL_PREFIX)) {
response = response.Remove(0, DSL_PREFIX.Length);
}
IBlueRow nr = new BlueRow(schema, 0, _prompt, response);
BlueTable nt = new BlueTable(schema);
nt.addRow(nr);
@@ -1,34 +0,0 @@
{
"IsTimeSeries": true,
"Columns": [
{
"Name": "Id",
"Type": "int",
"Key": "id"
},
{
"Name": "Timestamp",
"Type": "DateTime"
},
{
"Name": "AvgLatency",
"Type": "TimeSpan"
},
{
"Name": "Venue",
"Type": "string"
},
{
"Name": "Symbol",
"Type": "string"
},
{
"Name": "OrderCount",
"Type": "int"
},
{
"Name": "Volume",
"Type": "long"
}
]
}
@@ -1,38 +0,0 @@
{
"IsTimeSeries": true,
"Columns": [
{
"Name": "Id",
"Type": "int",
"Key": "id"
},
{
"Name": "Timestamp",
"Type": "DateTime"
},
{
"Name": "Venue",
"Type": "string"
},
{
"Name": "Symbol",
"Type": "string"
},
{
"Name": "BidPrice",
"Type": "double"
},
{
"Name": "BidQuantity",
"Type": "double"
},
{
"Name": "AskPrice",
"Type": "double"
},
{
"Name": "AskQuantity",
"Type": "double"
}
]
}
@@ -1,54 +0,0 @@
{
"IsTimeSeries": true,
"Columns": [
{
"Name": "Id",
"Type": "int",
"Key": "id"
},
{
"Name": "SendTimestamp",
"Type": "DateTime"
},
{
"Name": "Timestamp",
"Type": "DateTime"
},
{
"Name": "SequenceNumber",
"Type": "int"
},
{
"Name": "Venue",
"Type": "string"
},
{
"Name": "Symbol",
"Type": "string"
},
{
"Name": "Contract",
"Type": "string"
},
{
"Name": "Side",
"Type": "bts.api.Side"
},
{
"Name": "Price",
"Type": "double"
},
{
"Name": "Quantity",
"Type": "double"
},
{
"Name": "OrderId",
"Type": "int"
},
{
"Name": "Action",
"Type": "string"
}
]
}
@@ -1,170 +0,0 @@
{
"IsTimeSeries": true,
"Columns": [
{
"Name": "Id",
"Type": "int",
"Key": "id"
},
{
"Name": "Timestamp",
"Type": "DateTime"
},
{
"Name": "Venue",
"Type": "string"
},
{
"Name": "Symbol",
"Type": "string"
},
{
"Name": "BidPrices",
"Key": null,
"Type": "double[]",
"Value": "{ get; } = new double[5];",
"Equals": "BidPrices.SequenceEqual(other.BidPrices)"
},
{
"Name": "BidQuantities",
"Key": null,
"Type": "double[]",
"Value": "{ get; } = new double[5];",
"Equals": "BidQuantities.SequenceEqual(other.BidQuantities)"
},
{
"Name": "BidPrice1",
"Type": "double",
"Value": "BidPrices[0]",
"Equals": null
},
{
"Name": "BidQuantity1",
"Type": "double",
"Value": "BidQuantities[0]",
"Equals": null
},
{
"Name": "BidPrice2",
"Type": "double",
"Value": "BidPrices[1]",
"Equals": null
},
{
"Name": "BidQuantity2",
"Type": "double",
"Value": "BidQuantities[1]",
"Equals": null
},
{
"Name": "BidPrice3",
"Type": "double",
"Value": "BidPrices[2]",
"Equals": null
},
{
"Name": "BidQuantity3",
"Type": "double",
"Value": "BidQuantities[2]",
"Equals": null
},
{
"Name": "BidPrice4",
"Type": "double",
"Value": "BidPrices[3]",
"Equals": null
},
{
"Name": "BidQuantity4",
"Type": "double",
"Value": "BidQuantities[3]",
"Equals": null
},
{
"Name": "BidPrice5",
"Type": "double",
"Value": "BidPrices[4]",
"Equals": null
},
{
"Name": "BidQuantity5",
"Type": "double",
"Value": "BidQuantities[4]",
"Equals": null
},
{
"Name": "AskPrices",
"Key": null,
"Type": "double[]",
"Value": "{ get; } = new double[5];",
"Equals": "AskPrices.SequenceEqual(other.AskPrices)"
},
{
"Name": "AskQuantities",
"Key": null,
"Type": "double[]",
"Value": "{ get; } = new double[5];",
"Equals": "AskQuantities.SequenceEqual(other.AskQuantities)"
},
{
"Name": "AskPrice1",
"Type": "double",
"Value": "AskPrices[0]",
"Equals": null
},
{
"Name": "AskQuantity1",
"Type": "double",
"Value": "AskQuantities[0]",
"Equals": null
},
{
"Name": "AskPrice2",
"Type": "double",
"Value": "AskPrices[1]",
"Equals": null
},
{
"Name": "AskQuantity2",
"Type": "double",
"Value": "AskQuantities[1]",
"Equals": null
},
{
"Name": "AskPrice3",
"Type": "double",
"Value": "AskPrices[2]",
"Equals": null
},
{
"Name": "AskQuantity3",
"Type": "double",
"Value": "AskQuantities[2]",
"Equals": null
},
{
"Name": "AskPrice4",
"Type": "double",
"Value": "AskPrices[3]",
"Equals": null
},
{
"Name": "AskQuantity4",
"Type": "double",
"Value": "AskQuantities[3]",
"Equals": null
},
{
"Name": "AskPrice5",
"Type": "double",
"Value": "AskPrices[4]",
"Equals": null
},
{
"Name": "AskQuantity5",
"Type": "double",
"Value": "AskQuantities[4]",
"Equals": null
}
]
}
@@ -1,200 +0,0 @@
{
"IsTimeSeries": true,
"Columns": [
{
"Name": "Id",
"Type": "int",
"Key": "id"
},
{
"Name": "Timestamp",
"Type": "DateTime"
},
{
"Name": "Venue",
"Type": "string"
},
{
"Name": "Symbol",
"Type": "string"
},
{
"Name": "BidPrices",
"Key": null,
"Type": "double[]",
"Value": "{ get; } = new double[5];",
"Equals": "BidPrices.SequenceEqual(other.BidPrices)"
},
{
"Name": "BidQuantities",
"Key": null,
"Type": "double[]",
"Value": "{ get; } = new double[5];",
"Equals": "BidQuantities.SequenceEqual(other.BidQuantities)"
},
{
"Name": "BidPrice1",
"Type": "double",
"Value": "BidPrices[0]",
"Equals": null
},
{
"Name": "BidQuantity1",
"Type": "double",
"Value": "BidQuantities[0]",
"Equals": null
},
{
"Name": "BidPrice2",
"Type": "double",
"Value": "BidPrices[1]",
"Equals": null
},
{
"Name": "BidQuantity2",
"Type": "double",
"Value": "BidQuantities[1]",
"Equals": null
},
{
"Name": "BidPrice3",
"Type": "double",
"Value": "BidPrices[2]",
"Equals": null
},
{
"Name": "BidQuantity3",
"Type": "double",
"Value": "BidQuantities[2]",
"Equals": null
},
{
"Name": "BidPrice4",
"Type": "double",
"Value": "BidPrices[3]",
"Equals": null
},
{
"Name": "BidQuantity4",
"Type": "double",
"Value": "BidQuantities[3]",
"Equals": null
},
{
"Name": "BidPrice5",
"Type": "double",
"Value": "BidPrices[4]",
"Equals": null
},
{
"Name": "BidQuantity5",
"Type": "double",
"Value": "BidQuantities[4]",
"Equals": null
},
{
"Name": "AskPrices",
"Key": null,
"Type": "double[]",
"Value": "{ get; } = new double[5];",
"Equals": "AskPrices.SequenceEqual(other.AskPrices)"
},
{
"Name": "AskQuantities",
"Key": null,
"Type": "double[]",
"Value": "{ get; } = new double[5];",
"Equals": "AskQuantities.SequenceEqual(other.AskQuantities)"
},
{
"Name": "AskPrice1",
"Type": "double",
"Value": "AskPrices[0]",
"Equals": null
},
{
"Name": "AskQuantity1",
"Type": "double",
"Value": "AskQuantities[0]",
"Equals": null
},
{
"Name": "AskPrice2",
"Type": "double",
"Value": "AskPrices[1]",
"Equals": null
},
{
"Name": "AskQuantity2",
"Type": "double",
"Value": "AskQuantities[1]",
"Equals": null
},
{
"Name": "AskPrice3",
"Type": "double",
"Value": "AskPrices[2]",
"Equals": null
},
{
"Name": "AskQuantity3",
"Type": "double",
"Value": "AskQuantities[2]",
"Equals": null
},
{
"Name": "AskPrice4",
"Type": "double",
"Value": "AskPrices[3]",
"Equals": null
},
{
"Name": "AskQuantity4",
"Type": "double",
"Value": "AskQuantities[3]",
"Equals": null
},
{
"Name": "AskPrice5",
"Type": "double",
"Value": "AskPrices[4]",
"Equals": null
},
{
"Name": "AskQuantity5",
"Type": "double",
"Value": "AskQuantities[4]",
"Equals": null
},
{
"Name": "Imbalance1",
"Type": "double",
"Value": "BidQuantity1 - AskQuantity1",
"Equals": null
},
{
"Name": "Imbalance2",
"Type": "double",
"Value": "Imbalance1 + BidQuantity2 - AskQuantity2",
"Equals": null
},
{
"Name": "Imbalance3",
"Type": "double",
"Value": "Imbalance2 + BidQuantity3 - AskQuantity3",
"Equals": null
},
{
"Name": "Imbalance4",
"Type": "double",
"Value": "Imbalance3 + BidQuantity4 - AskQuantity4",
"Equals": null
},
{
"Name": "Imbalance5",
"Type": "double",
"Value": "Imbalance4 + BidQuantity5 - AskQuantity5",
"Equals": null
}
]
}
@@ -1,48 +0,0 @@
{
"IsTimeSeries": true,
"Columns": [
{
"Name": "Id",
"Type": "int",
"Key": "id"
},
{
"Name": "SendTimestamp",
"Type": "DateTime"
},
{
"Name": "Timestamp",
"Type": "DateTime"
},
{
"Name": "Latency",
"Type": "TimeSpan",
"Value": "Timestamp - SendTimestamp",
"Equals": null
},
{
"Name": "Venue",
"Type": "string"
},
{
"Name": "Symbol",
"Type": "string"
},
{
"Name": "Price",
"Type": "double"
},
{
"Name": "Quantity",
"Type": "double"
},
{
"Name": "Flags",
"Type": "string"
},
{
"Name": "SequenceNumber",
"Type": "int"
}
]
}
@@ -1,62 +0,0 @@
{
"IsTimeSeries": true,
"Columns": [
{
"Name": "Id",
"Type": "int",
"Key": "id"
},
{
"Name": "Venue",
"Type": "string"
},
{
"Name": "Symbol",
"Type": "string"
},
{
"Name": "Timestamp",
"Type": "DateTime"
},
{
"Name": "SendTimestamp",
"Type": "DateTime"
},
{
"Name": "Latency",
"Type": "TimeSpan",
"Value": "Timestamp - SendTimestamp",
"Equals": null
},
{
"Name": "FinalTimestamp",
"Type": "DateTime"
},
{
"Name": "Lifespan",
"Type": "TimeSpan",
"Value": "FinalTimestamp - Timestamp",
"Equals": null
},
{
"Name": "State",
"Type": "string"
},
{
"Name": "Side",
"Type": "bts.api.Side"
},
{
"Name": "Price",
"Type": "double"
},
{
"Name": "Quantity",
"Type": "double"
},
{
"Name": "FilledQuantity",
"Type": "double"
}
]
}
@@ -1,121 +0,0 @@
<#@ template language="C#" #>
<#@ output extension=".cs" #>
<#@ parameter name="TableName" type="System.String" #>
<#@ parameter name="TypeName" type="System.String" #>
<#@ parameter name="SchemaJson" type="System.String" #>
<#@ import namespace="System.Text.Json" #>
<#@ import namespace="System.Text.Json.Nodes" #>
<#
System.Console.WriteLine($"Reading json file '{SchemaJson}'");
var schemaStr = System.IO.File.ReadAllText(SchemaJson);
var schemaJ = JsonSerializer.Deserialize<JsonObject>(schemaStr);
if (schemaJ is null) {
throw new ArgumentException($"Failed to parse json from '{schemaJ}'");
}
if (!schemaJ.TryGetPropertyValue("Columns", out JsonNode? jn)) {
throw new ArgumentException($"Failed to get 'Columns' field from '{schemaJ}'");
}
var columnsJArray = jn.AsArray();
if (columnsJArray is null) {
throw new ArgumentException($"Failed to get 'Columns' field from '{schemaJ}'");
}
var columns = new System.Collections.Generic.List<System.Tuple<string, string, string, string, string>>();
for (int colIdx = 0; colIdx < columnsJArray.Count; colIdx++) {
var columnJ = columnsJArray[colIdx].AsObject();
string name = columnJ["Name"].GetValue<string>();
string type = columnJ["Type"].GetValue<string>();
string key = columnJ.TryGetPropertyValue("Key", out var keyJ) ? keyJ.GetValue<string>() : name;
string getter = columnJ.TryGetPropertyValue("Getter", out var getterJ) ? getterJ.GetValue<string>() : "{ get; init; }";
string equalsOp = columnJ.TryGetPropertyValue("Equals", out var equalsJ) ? equalsJ.GetValue<string>() : $"EqualityComparer<{type}>.Default.Equals({name}, other.{name})";
columns.Add(new(name, type, key, getter, equalsOp));
}
bool isTimeSeries = schemaJ.TryGetPropertyValue("IsTimeSeries", out jn) && jn.GetValue<bool>();
#>
#nullable disable
namespace bts.SilverPine.Tables;
using BCG;
using System;
public class <#=TypeName#> : <#=isTimeSeries ? "ITimeSeriesRow" : "IBlueRow" #>, IEquatable<<#=TypeName#>> {
public static IBlueTableSchema Schema { get; } =
new BlueTableSchema(
new ColumnInfo[] {
<#
// Generate properties
for (int i = 0; i < columns.Count; i++) {
var type = columns[i].Item2;
var key = columns[i].Item3;
#>
new("<#=key#>", <#=i#>, typeof(<#=type#>)),
<#
}
#>
},
"<#= TableName #>");
IBlueTableSchema IBlueRow.schema => Schema;
<#
// Generate properties
foreach (var column in columns) {
var name = column.Item1;
var type = column.Item2;
var getter = column.Item4;
#>
public <#= type #> <#= name #> <#= getter #>
<#
}
#>
// Indexer by column name
public object? this[string col] => col switch {
<#
// Generate case statements for each column name
for (int i = 0; i < columns.Count; i++) {
var name = columns[i].Item1;
var key = columns[i].Item3;
#>
"<#= key #>" => <#= name #>,
<#
}
#>
_ => throw new IndexOutOfRangeException()
};
// Indexer by column index
public object? this[int col] => col switch {
<#
// Generate case statements for each column index
for (int i = 0; i < columns.Count; i++) {
var name = columns[i].Item1;
#>
<#= i #> => <#= name #>,
<#
}
#>
_ => throw new KeyNotFoundException()
};
public bool Equals(<#= TypeName #> other) =>
other is not null
<#
// Generate properties
for (int i = 0; i < columns.Count; i++) {
var equalsOp = columns[i].Item5;
if (string.IsNullOrEmpty(equalsOp)) {
continue;
}
equalsOp = equalsOp.Insert(0, "&& ");
#>
<#=equalsOp#>
<#}#>
;
public override bool Equals(object? obj) =>
obj is <#=TypeName#> other && Equals(other);
public override int GetHashCode() =>
Id;
}
@@ -1,152 +0,0 @@
<#@ template language="C#" #>
<#@ output extension=".cs" #>
<#@ parameter name="TableName" type="System.String" #>
<#@ parameter name="TypeName" type="System.String" #>
<#@ parameter name="SchemaJson" type="System.String" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Text.Json" #>
<#@ import namespace="System.Text.Json.Nodes" #>
<#
System.Console.WriteLine($"Reading json file '{SchemaJson}'");
var schemaStr = System.IO.File.ReadAllText(SchemaJson);
System.Console.WriteLine("Dingo");
var schemaJ = JsonSerializer.Deserialize<JsonObject>(schemaStr);
if (schemaJ is null) {
throw new ArgumentException($"Failed to parse json from '{schemaJ}'");
}
System.Console.WriteLine("Blingo");
if (!schemaJ.TryGetPropertyValue("Columns", out JsonNode? jn)) {
throw new ArgumentException($"Failed to get 'Columns' field from '{schemaJ}'");
}
var columnsJArray = jn.AsArray();
if (columnsJArray is null) {
throw new ArgumentException($"Failed to get 'Columns' field from '{schemaJ}'");
}
System.Console.WriteLine("Ringo");
var columns = new System.Collections.Generic.List<System.Tuple<string, string, string, string, string>>();
for (int colIdx = 0; colIdx < columnsJArray.Count; colIdx++) {
var columnJ = columnsJArray[colIdx].AsObject();
string name = columnJ["Name"].GetValue<string>();
System.Console.WriteLine($"Adding prop {name}");
string type = columnJ["Type"]?.GetValue<string>();
string key = columnJ.TryGetPropertyValue("Key", out var keyJ) ? keyJ?.GetValue<string>() : name;
string value = null;
if (columnJ.TryGetPropertyValue("Value", out var valueJ)) {
value = valueJ.GetValue<string>().Trim();
if (!value.StartsWith('{')) {
value = $"=> {valueJ.GetValue<string>()};";
}
} else {
value = "{ get; init; }";
}
string equalsOp = null;
if (columnJ.TryGetPropertyValue("Equals", out var equalsJ)) {
equalsOp = equalsJ?.GetValue<string>();
} else {
equalsOp = $"EqualityComparer<{type}>.Default.Equals({name}, other.{name})";
}
columns.Add(new(name, type, key, value, equalsOp));
}
bool isTimeSeries = schemaJ.TryGetPropertyValue("IsTimeSeries", out jn) && jn.GetValue<bool>();
#>
#nullable disable
namespace bts.SilverPine.Tables;
using BCG;
using System;
public class <#=TypeName#> : <#=isTimeSeries ? "ITimeSeriesRow" : "IBlueRow" #>, IEquatable<<#=TypeName#>> {
public static IBlueTableSchema Schema { get; } =
new BlueTableSchema(
new ColumnInfo[] {
<#
// Generate properties
int offset = 0;
for (int i = 0; i < columns.Count; i++) {
var type = columns[i].Item2;
var key = columns[i].Item3;
if (key is null) {
offset++;
continue;
}
#>
new("<#=key#>", <#=i - offset#>, typeof(<#=type#>)),
<#
}
#>
},
"<#= TableName #>");
IBlueTableSchema IBlueRow.schema => Schema;
<#
// Generate properties
foreach (var column in columns) {
var name = column.Item1;
var type = column.Item2;
var getter = column.Item4;
#>
public <#= type #> <#= name #> <#= getter #>
<#
}
#>
// Indexer by column name
public object? this[string col] => col switch {
<#
// Generate case statements for each column name
for (int i = 0; i < columns.Count; i++) {
var name = columns[i].Item1;
var key = columns[i].Item3;
if (key is null) {
continue;
}
#>
"<#= key #>" => <#= name #>,
<#
}
#>
_ => throw new IndexOutOfRangeException()
};
// Indexer by column index
public object? this[int col] => col switch {
<#
// Generate case statements for each column index
offset = 0;
for (int i = 0; i < columns.Count; i++) {
var name = columns[i].Item1;
var key = columns[i].Item3;
if (key is null) {
offset++;
continue;
}
#>
<#= i - offset #> => <#= name #>,
<#
}
#>
_ => throw new KeyNotFoundException()
};
public bool Equals(<#= TypeName #> other) =>
other is not null
<#
// Generate properties
for (int i = 0; i < columns.Count; i++) {
var equalsOp = columns[i].Item5;
if (string.IsNullOrEmpty(equalsOp)) {
continue;
}
equalsOp = equalsOp.Insert(0, "&& ");
#>
<#=equalsOp#>
<#}#>
;
public override bool Equals(object? obj) =>
obj is <#=TypeName#> other && Equals(other);
public override int GetHashCode() =>
Id;
}
@@ -1,123 +0,0 @@
<#@ template language="C#" #>
<#@ output extension=".cs" #>
<#@ parameter name="TableName" type="System.String" #>
<#@ parameter name="TypeName" type="System.String" #>
<#@ parameter name="SchemaJson" type="System.String" #>
<#@ import namespace="System.Text.Json" #>
<#@ import namespace="System.Text.Json.Nodes" #>
<#
System.Console.WriteLine($"Reading json file '{SchemaJson}'");
var schemaStr = System.IO.File.ReadAllText(SchemaJson);
var schemaJ = JsonSerializer.Deserialize<JsonObject>(schemaStr);
if (schemaJ is null) {
throw new ArgumentException($"Failed to parse json from '{schemaJ}'");
}
if (!schemaJ.TryGetPropertyValue("Columns", out JsonNode? columnsJNode)) {
throw new ArgumentException($"Failed to get 'Columns' field from '{schemaJ}'");
}
var columnsJArray = columnsJNode.AsArray();
if (columnsJArray is null) {
throw new ArgumentException($"Failed to get 'Columns' field from '{schemaJ}'");
}
var columns = new System.Collections.Generic.List<System.Tuple<string, string, string, string, string>>();
for (int colIdx = 0; colIdx < columnsJArray.Count; colIdx++) {
var columnJ = columnsJArray[colIdx].AsObject();
string name = columnJ["Name"].GetValue<string>();
string type = columnJ["Type"].GetValue<string>();
string key = columnJ.TryGetPropertyValue("Key", out var keyJ) ? keyJ.GetValue<string>() : name;
string getter = columnJ.TryGetPropertyValue("Getter", out var getterJ) ? getterJ.GetValue<string>() : "{ get; init; }";
string equalsOp = columnJ.TryGetPropertyValue("Equals", out var equalsJ) ? equalsJ.GetValue<string>() : $"EqualityComparer<{type}>.Default.Equals({name}, other.{name})";
columns.Add(new(name, type, key, getter, equalsOp));
}
#>
namespace bts.SilverPine.Tables;
using BCG;
using System;
public readonly struct <#=TypeName#> : IBlueRow, IEquatable<<#=TypeName#>> {
public static IBlueTableSchema Schema { get; } =
new BlueTableSchema(
new ColumnInfo[] {
<#
// Generate properties
for (int i = 0; i < columns.Count; i++) {
var type = columns[i].Item2;
var key = columns[i].Item3;
#>
new("<#=key#>", <#=i#>, typeof(<#=type#>)),
<#
}
#>
},
"<#= TableName #>");
IBlueTableSchema IBlueRow.schema => Schema;
<#
// Generate properties
foreach (var column in columns) {
var name = column.Item1;
var type = column.Item2;
var getter = column.Item4;
#>
public <#= type #> <#= name #> <#= getter #>
<#
}
#>
// Indexer by column name
public readonly object? this[string col] => col switch {
<#
// Generate case statements for each column name
for (int i = 0; i < columns.Count; i++) {
var name = columns[i].Item1;
var key = columns[i].Item3;
#>
"<#= key #>" => <#= name #>,
<#
}
#>
_ => throw new IndexOutOfRangeException()
};
// Indexer by column index
public readonly object? this[int col] => col switch {
<#
// Generate case statements for each column index
for (int i = 0; i < columns.Count; i++) {
var name = columns[i].Item1;
#>
<#= i #> => <#= name #>,
<#
}
#>
_ => throw new KeyNotFoundException()
};
public bool Equals(<#= TypeName #> other) =>
<#
// Generate properties
bool isFirstComp = true;
for (int i = 0; i < columns.Count; i++) {
var equalsOp = columns[i].Item5;
if (string.IsNullOrEmpty(equalsOp)) {
continue;
}
if (!isFirstComp) {
equalsOp = equalsOp.Insert(0, "&& ");
} else {
isFirstComp = false;
}
#>
<#=equalsOp#>
<#}#>
;
public override bool Equals(object? obj) =>
obj is <#=TypeName#> other && Equals(other);
public override int GetHashCode() =>
Id;
}
@@ -1,43 +0,0 @@
// namespace Bts.SharedStreams.Services;
// using BCG;
// using BCG.Utilities.General;
// using bts.SilverPine;
// using Microsoft.Extensions.Hosting;
// using Microsoft.Extensions.Options;
// using System;
// using System.Collections.Generic;
// using System.Threading;
// using System.Threading.Tasks;
// /// <summary>
// /// An <see cref="IHostedService"/> that imports a collection of <see cref="IWorker"/>s and
// /// calls each <see cref="IWorker.doWork"/> in a loop.
// /// </summary>
// public class NaturalLanguageStreamService : BackgroundService {
// readonly IWorker masterWorker;
// readonly NLPromptConfig config;
// public NaturalLanguageStreamService(IOptions<NLPromptConfig> config)
// : this(config.Value) { }
// public NaturalLanguageStreamService(NLPromptConfig config) {
// this.config = config;
// }
// protected override Task ExecuteAsync(CancellationToken cancellationToken) {
// return Task.Factory.StartNew(
// Execute, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default);
// void Execute() {
// while (!cancellationToken.IsCancellationRequested) {
// masterWorker.doWork();
// Thread.Sleep(config.Interval);
// }
// }
// }
// }
@@ -1,36 +0,0 @@
namespace Bts.SharedStreams.Services;
using bts.SilverPine;
using Bts.DataStream;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
public static class SharedStreamsServices {
public static IServiceCollection AddOhlcvStreams(this IServiceCollection services, IConfigurationSection configurationSection) =>
services.Configure<DatabaseConfig>(configurationSection)
.addStreamFactory<OhlcvStreamFactory>()
.addStreamFactory<BboStreamFactory>()
.addStreamFactory<SettlementStreamFactory>()
.addStreamFactory<MbpStreamFactory>()
.addStreamFactory<MboStreamFactory>()
.addStreamFactory<OrderLatencyStreamFactory>()
.addStreamFactory<AvgOrderLatencyStreamFactory>()
.addStreamFactory<OrderImbalanceStreamFactory>()
.addStreamFactory<OrderLifespanStreamFactory>()
;
public static IServiceCollection AddNaturalLanguageStreams(this IServiceCollection services, IConfigurationSection configurationSection) {
services.Configure<NLPromptConfig>(configurationSection);
NLPromptConfig.SetGlobalConfig(configurationSection.Get<NLPromptConfig>() ?? NLPromptConfig.Default());
// TODO: integrate config into a NLStream provider (so it can provide config values
// instead of the ViewStream's getting them from a static singleton)
return services.AddSingleton<NLStreamFactory>()
.AddSingleton<IDataStreamFactory, NLStreamFactory>(sp => sp.GetRequiredService<NLStreamFactory>());
}
static IServiceCollection addStreamFactory<T>(this IServiceCollection services) where T : class, IDataStreamFactory =>
services.AddSingleton<T>()
.AddSingleton<IDataStreamFactory, T>(sp => sp.GetRequiredService<T>());
}
@@ -16,19 +16,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
<PackageReference Include="OpenAI" Version="2.0.0" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
</ItemGroup>
<ItemGroup>
<RowType Include="RowTemplates\RefTypeRow.tt" SchemaJson="RowTemplates\BboSchema.json" TypeName="BboRow" TableName="Bbo" />
<RowType Include="RowTemplates\RefTypeVariableColumnRow.tt" SchemaJson="RowTemplates\MbpSchema.json" TypeName="MbpRow" TableName="Mbp" />
<RowType Include="RowTemplates\RefTypeRow.tt" SchemaJson="RowTemplates\MboSchema.json" TypeName="MboRow" TableName="Mbo" />
<RowType Include="RowTemplates\RefTypeVariableColumnRow.tt" SchemaJson="RowTemplates\OrderImbalanceSchema.json" TypeName="OrderImbalanceRow" TableName="OrderImbalance" />
<RowType Include="RowTemplates\RefTypeVariableColumnRow.tt" SchemaJson="RowTemplates\OrderLatencySchema.json" TypeName="OrderLatencyRow" TableName="OrderLatency" />
<RowType Include="RowTemplates\RefTypeVariableColumnRow.tt" SchemaJson="RowTemplates\AvgOrderLatencySchema.json" TypeName="AvgOrderLatencyRow" TableName="AvgOrderLatency" />
<RowType Include="RowTemplates\RefTypeVariableColumnRow.tt" SchemaJson="RowTemplates\OrderLifespanSchema.json" TypeName="OrderLifespanRow" TableName="OrderLifespan" />
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
<PackageReference Include="OpenAI" Version="2.0.0" />
</ItemGroup>
</Project>
@@ -1,106 +0,0 @@
namespace bts.SilverPine;
using BCG;
using bts.api.core;
using bts.SilverPine.StreamFactory.Logging;
using bts.SilverPine.Tables;
using Bts.DataStream;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Reactive.Linq;
public class AvgOrderLatencyStreamFactory : IDataStreamFactory {
readonly DatabaseConfig config;
readonly ILogger logger;
public AvgOrderLatencyStreamFactory(IOptions<DatabaseConfig> config, ILogger<AvgOrderLatencyStreamFactory> logger)
: this(config.Value, logger) { }
public AvgOrderLatencyStreamFactory(DatabaseConfig config, ILogger<AvgOrderLatencyStreamFactory> logger) {
this.config = config;
this.logger = logger;
}
public static IObservable<TableUpdate<AvgOrderLatencyRow, int>>? CreateAvgOrderLatencyTables(
DatabaseConfig config, HistoricDataParams args, ILogger logger) {
args.Validate().ValidateInterval();
var orderLatencyParams = new OrderLatencyStreamParams() {
Venue = args.Venue,
Symbol = args.Symbol,
BeginDate = args.BeginDate,
EndDate = args.EndDate,
};
return OrderLatencyStreamFactory.CreateOrderLatencyTables(config, orderLatencyParams, logger)
?.BufferTable(TimeSpan.FromMilliseconds(500))
?.GroupTable(
r => r.Id,
r => (int)((r.Timestamp.Ticks - args.BeginDate.Ticks) / args.Interval!.Value.Ticks))
?.SelectKeyValue(ComputeAvgLatency, r => r.Id);
AvgOrderLatencyRow ComputeAvgLatency(TableUpdateGrouping<OrderLatencyRow, int, int> group) {
double avgLatency = 0;
double latencySum = 0;
int nTotal = 0;
int n = 0;
foreach (var r in group.table.state.Values) {
if ((double.MaxValue - latencySum) > r.Latency.Ticks) {
latencySum += r.Latency.Ticks;
n++;
} else if (n > 0) {
nTotal += n;
avgLatency += ((latencySum / n) - avgLatency) * (n / nTotal);
latencySum = 0;
n = 0;
} else {
throw new InvalidOperationException("Latency is too long (unable to compute the avg)");
}
}
if (n > 0) {
nTotal += n;
avgLatency += ((latencySum / n) - avgLatency) * (n / nTotal);
latencySum = 0;
n = 0;
}
return new AvgOrderLatencyRow() {
Symbol = args.Symbol,
Venue = args.Venue,
Id = group.group_key,
AvgLatency = new((long)avgLatency),
Timestamp = args.BeginDate + new TimeSpan(group.group_key * args.Interval!.Value.Ticks),
OrderCount = group.table.state.Count,
Volume = (long)group.table.state.Values.Sum(r => r.Quantity)
};
}
}
public ViewStream? Create(string streamId, IEnumerable<(string? Name, int? Index, object? Value)> args) {
if (SupportedStreams.AvgOrderLatency.IsMatch(streamId)) {
return createOrderLatency(args);
}
return null;
}
public IEnumerable<IDataStreamMetadata> GetSupportedStreams() {
yield return SupportedStreams.AvgOrderLatency;
}
ViewStream? createOrderLatency(IEnumerable<(string? Name, int? Index, object? Value)> args) {
if (!StreamFactoryArgStore.TryGetParams(args, out OrderLatencyStreamParams? sp, out var err)) {
logger.LogInvalidParams("avgOrderLatency", err);
return null;
}
return OrderLatencyStreamFactory.CreateOrderLatencyTables(config, sp, logger)
?.AsViewStream(AvgOrderLatencyRow.Schema);
}
static class SupportedStreams {
public static readonly DataStreamMetadata AvgOrderLatency = new() {
Name = AvgOrderLatencyRow.Schema.tableName
};
}
}
@@ -1,34 +0,0 @@
namespace bts.SilverPine;
using System.Diagnostics.CodeAnalysis;
public class AvgOrderLatencyStreamParams {
[StreamArgument("venue", "exchange", "exch", IsRequired = true)]
public string? Venue { get; set; }
[StreamArgument("symbol", "sym", "product", "asset", IsRequired = true)]
public string? Symbol { get; set; }
[StreamArgument("begin", "beginDate", "start", "startDate", "from", IsRequired = true)]
public DateTime BeginDate { get; set; }
[StreamArgument("end", "endDate", "stop", "stopDate", "to", IsRequired = true)]
public DateTime EndDate { get; set; }
[StreamArgument("interval", IsRequired = false)]
public TimeSpan Interval { get; set; } = TimeSpan.Zero;
[MemberNotNull(nameof(Venue), nameof(Symbol))]
public AvgOrderLatencyStreamParams Validate() {
if (Venue is null) {
throw new InvalidOperationException("Venue is null");
}
if (Symbol is null) {
throw new InvalidOperationException("Symbol is null");
}
if (Interval < TimeSpan.Zero) {
throw new InvalidOperationException("Interval < TimeSpan.Zero");
}
return this;
}
}
@@ -1,80 +0,0 @@
namespace bts.SilverPine;
using BCG;
using bts.api.core;
using bts.SilverPine.StreamFactory.Logging;
using bts.SilverPine.Tables;
using Bts.DataStream;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
public class BboStreamFactory : IDataStreamFactory {
readonly DatabaseConfig config;
readonly ILogger logger;
public BboStreamFactory(IOptions<DatabaseConfig> config, ILogger<BboStreamFactory> logger)
: this(config.Value, logger) { }
public BboStreamFactory(DatabaseConfig config, ILogger<BboStreamFactory> logger) {
this.config = config;
this.logger = logger;
}
public static IObservable<TableUpdate<BboRow, int>>? CreateBboTables(DatabaseConfig config, BboStreamParams args, ILogger logger) {
args.Validate();
return new HistoricDataLoader(config, logger).LoadTablesObservable(
new HistoricDataParams() {
DataType = args.DataType,
Venue = args.Venue,
Symbol = args.Symbol,
BeginDate = args.BeginDate,
EndDate = args.EndDate,
Interval = args.Interval,
TsNanosCol = "ts_event_ns"
},
RowFactory);
BboRow RowFactory(IBlueRow row, int groupKey, DateTime timestamp) =>
new() {
Id = groupKey,
Timestamp = timestamp,
Symbol = args.Symbol,
Venue = args.Venue,
BidPrice = row.GetFloat64("bid_px_00") ?? double.NaN,
BidQuantity = row.GetFloat64("bid_sz_00") ?? double.NaN,
AskPrice = row.GetFloat64("ask_px_00") ?? double.NaN,
AskQuantity = row.GetFloat64("ask_sz_00") ?? double.NaN,
};
}
public ViewStream? Create(string streamId, IEnumerable<(string? Name, int? Index, object? Value)> args) {
if (SupportedStreams.Bbo.IsMatch(streamId)) {
return createBbo(args);
}
return null;
}
public IEnumerable<IDataStreamMetadata> GetSupportedStreams() {
yield return SupportedStreams.Bbo;
}
ViewStream? createBbo(IEnumerable<(string? Name, int? Index, object? Value)> args) {
if (!StreamFactoryArgStore.TryGetParams(args, out BboStreamParams? bsp, out var err)) {
logger.LogInvalidParams("bbo", err);
return null;
}
return CreateBboTables(config, bsp, logger)?.AsViewStream(BboRow.Schema);
}
static class SupportedStreams {
public static readonly DataStreamMetadata Bbo = new() {
Name = "bbo"
};
}
}
@@ -1,56 +0,0 @@
namespace bts.SilverPine;
using System.Diagnostics.CodeAnalysis;
public class BboStreamParams {
[StreamArgument("venue", "exchange", "exch", IsRequired = true)]
public string? Venue { get; set; }
[StreamArgument("symbol", "sym", "product", "asset", IsRequired = true)]
public string? Symbol { get; set; }
[StreamArgument("begin", "beginDate", "start", "startDate", "from", IsRequired = true)]
public DateTime BeginDate { get; set; }
[StreamArgument("end", "endDate", "stop", "stopDate", "to", IsRequired = true)]
public DateTime EndDate { get; set; }
[StreamArgument("interval", IsRequired = true)]
public TimeSpan Interval { get; set; }
// Normally, the dataType is inferred from Interval, though it can be explicitly specified as well.
public string? DataType { get; set; }
[MemberNotNull(nameof(Venue), nameof(Symbol), nameof(DataType))]
public BboStreamParams Validate() {
if (Venue is null) {
throw new InvalidOperationException("Venue is null");
}
if (Symbol is null) {
throw new InvalidOperationException("Symbol is null");
}
if (Interval <= TimeSpan.Zero) {
throw new InvalidOperationException("Interval <= TimeSpan.Zero");
}
DataType ??= DataTypeFromInterval(Interval);
if (DataType is null) {
throw new InvalidOperationException(
$"DataType is null. Interval {Interval} did not map to well-known data type (bbo1s, bbo1m, bbo1h, bbo1d).");
}
return this;
}
public static string? DataTypeFromInterval(TimeSpan interval) {
if (interval == TimeSpan.FromSeconds(1)) {
return "bbo1s";
} else if (interval == TimeSpan.FromMinutes(1)) {
return "bbo1m";
} else if (interval == TimeSpan.FromHours(1)) {
return "bbo1h";
} else if (interval == TimeSpan.FromDays(1)) {
return "bbo1d";
} else {
return null;
}
}
}
@@ -1,133 +0,0 @@
namespace bts.SilverPine;
using BCG;
using bts.api.core;
using Bts.DataStream;
using Bts.DataStreamCore;
using Microsoft.Extensions.Logging;
using System.Data;
using System.Data.SQLite;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
public class HistoricDataLoader {
readonly DatabaseConfig dbConfig;
readonly ILogger? logger;
public HistoricDataLoader(DatabaseConfig dbConfig, ILogger? logger) {
this.dbConfig = dbConfig;
this.logger = logger;
}
public IEnumerable<StreamQueryMetadata> LoadQueryMetadata() {
try {
var connectionStr = $"Data Source={dbConfig.Path};Version=3";
var dataTable = new DataTable();
using var conn = new SQLiteConnection($"Data Source={dbConfig.Path};Version=3");
conn.Open();
using var sqlCmd = new SQLiteCommand(dbConfig.Query, conn);
using var adapter = new SQLiteDataAdapter(sqlCmd);
adapter.Fill(dataTable);
return dataTable.Rows.OfType<DataRow>()
.Select(r => new StreamQueryMetadata() {
Symbol = r["symbol"] as string,
DataType = r["data_type"] as string,
BeginDate = DateTime.Parse(r["begin_date"]?.ToString()!),
EndDate = DateTime.Parse(r["end_date"]?.ToString()!),
Location = r["location"] as string,
Query = r["query"] as string,
});
} catch (Exception ex) {
logger?.LogError("Failed to load query metadata from '{dbPath}'. Query: {query}. Exception {ex}", dbConfig.Path, dbConfig.Query, ex);
}
return Enumerable.Empty<StreamQueryMetadata>();
}
public IAsyncEnumerable<IBlueRowCollection> LoadRowsAsync(HistoricDataParams args) {
args.Validate();
var venue = resolveVenue(args.Venue);
var queryMetadatas = LoadQueryMetadata().ToList();
var perDateQueries = queryMetadatas
.Where(m => m.DataType == args.DataType && m.Symbol == args.Symbol && m.BeginDate < args.EndDate && m.EndDate > args.BeginDate)
.ToList();
if (perDateQueries.Count == 0) {
return AsyncEnumerable.Empty<IBlueRowCollection>();
}
return perDateQueries.ToAsyncEnumerable().SelectMany(q => {
var location = q.Location ?? throw new InvalidOperationException($"'location' is null in metadata for {args.DataType} stream");
if (!Path.IsPathRooted(location)) {
var dir = Path.GetDirectoryName(dbConfig.Path) ?? Assembly.GetExecutingAssembly().Location;
location = Path.Combine(dir, location);
}
var query = q.Query ?? throw new InvalidOperationException($"'query' is null in metadata for {args.DataType} stream");
var andFilters = new List<string>(2);
if (args.BeginDate > DateTime.MinValue) {
var unixTicks = args.BeginDate.Ticks - DateTime.UnixEpoch.Ticks;
andFilters.Add($"{args.TsNanosCol} > {unixTicks * 100 - 1}");
}
if (args.EndDate < DateTime.MaxValue) {
var unixTicks = args.EndDate.Ticks - DateTime.UnixEpoch.Ticks;
andFilters.Add($"{args.TsNanosCol} < {unixTicks * 100}");
}
if (andFilters.Count > 0) {
if (query.EndsWith(';')) {
query = query.Substring(0, query.Length - 1);
}
query += " where " + string.Join(" and ", andFilters) + ';';
}
return new SQLiteEnumerable(location, query);
});
}
public IObservable<TableUpdate<T, int>>? LoadTablesObservable<T>(HistoricDataParams args, Func<IBlueRow, int, DateTime, T> rowFactory) where T : ITimeSeriesRow {
args.Validate();
var seed = TableUpdate<T, int>.mk_empty(
args.Interval.HasValue
? r => GroupKey(r.Timestamp)
: r => r.Id);
return LoadRowsAsync(args).ToObservable().Scan(seed, AddPage);
int GroupKey(DateTime ts) =>
(int)((ts.Ticks - args.BeginDate.Ticks) / args.Interval!.Value.Ticks);
TableUpdate<T, int> AddPage(TableUpdate<T, int> res, IBlueRowCollection blueRows) {
var tsNanosIdx = blueRows.schema.ResolveIndices(args.TsNanosCol);
var bd = res.to_builder();
foreach (IBlueRow blueRow in blueRows) {
var tsNanos = blueRow.GetInt64(tsNanosIdx);
if (!tsNanos.HasValue) {
continue;
}
var ts = DateTime.UnixEpoch.Add(new TimeSpan(tsNanos.Value / 100));
if (ts < args.BeginDate || ts >= args.EndDate) {
continue;
}
if (!args.Interval.HasValue) {
bd.add_or_update(rowFactory(blueRow, blueRow.Id(), ts));
continue;
}
var groupKey = GroupKey(ts);
if (!bd.TryGetValue(groupKey, out var row) || row.Timestamp < ts) {
row = rowFactory(blueRow, groupKey, ts);
if (row is null) {
continue;
}
bd.add_or_update(row);
}
}
return bd.to_table();
}
}
static string resolveVenue(string venue) =>
venue switch {
"CME" or "XCME" or "CBOT" or "XCBT" => "GLBX",
_ => venue
};
}
Loaded 100 of 309 files, more files were not shown because too many files have changed in this diff. Show more