Compare commits

..
Author SHA1 Message Date
John Colvin f5dac896af check pt: StreamFactoryLogs.cs 2025-11-19 15:00:06 -05:00
John Colvin 220c518e0e check pt: BookStreamFactory/Params.cs 2025-11-19 15:00:04 -05:00
John Colvin e91fc75667 TradeStreamParams.cs 2025-11-19 14:56:48 -05:00
John Colvin 66a1c8eb81 Rm GroupByApplyStreamParams.cs 2025-11-19 14:56:48 -05:00
John Colvin f7bfd4bce7 GroupByApplyStreamFactory.cs 2025-11-19 14:56:48 -05:00
John Colvin a75b8172db BookStreamFactory.cs 2025-11-19 14:56:22 -05:00
John Colvin 04b04e9e40 fix replace stream arg tuples with dedicated type 2025-11-19 14:55:50 -05:00
John Colvin f7927b7aed Get rid of unused FilterStreamFactory 2025-11-19 14:55:04 -05:00
John Colvin e8152259fc fix replace stream arg tuples with dedicated type 2025-11-19 14:55:04 -05:00
John Colvin d17ba42c45 Add ViewStreamFactory.cs 2025-11-19 14:55:04 -05:00
John Colvin 990f8f2df5 Add StreamFactoryLog "LogCreatingStream" 2025-11-19 14:55:04 -05:00
John Colvin 1b81e3b5f6 fix check pt misc changes 2025-11-19 14:55:01 -05:00
John Colvin baa5ff62ed fix replace stream arg tuples with dedicated 2025-11-19 14:53:04 -05:00
John Colvin 3241aaeb72 fix load IDataStreamFactory(s) - adds Enumerable ext TryGetExactCount 2025-11-19 14:53:04 -05:00
John Colvin c95e27e4c0 refactor: Add MaybeNullWhen(false) attr to TryGetSingle ext fn 2025-11-19 14:53:04 -05:00
John Colvin 1a09e560c5 fix load IDataStreamFactory(s) (DataStreamFactory.cs) 2025-11-19 14:53:04 -05:00
John Colvin e637d0be50 Add SecType, Venue arg parser for streams 2025-11-19 14:53:02 -05:00
John Colvin a020c2c97e check pt: misc changes 2025-11-19 14:51:28 -05:00
John Colvin bb7f16089f refactor: Replace stream arg tuples with dedicated type 2025-11-19 14:49:34 -05:00
John Colvin c9b4b523bb refactor: Rename ArrayHelper -> ArrayUtils
Also change snake_case fn names to PascalCase
2025-11-19 14:47:29 -05:00
John Colvin 205e55b1de fix: Check for 0 length array when expanding for insert 2025-11-19 14:44:56 -05:00
John Colvin 40ea3e3afb chore: Add AsyncViewStream wrapper
Converts a Task<ViewStream> to a ViewStream.
2025-11-19 14:44:56 -05:00
John Colvin b1a73a6399 feat: Add general purpose ReplayStream
Takes a single time series table and emits the rows in real time
according to the intervals between rows.
2025-11-19 14:44:56 -05:00
John Colvin 06f4f6623d vscode: Launch DSQA with config via DOTNET_ENVIRONMENT env var 2025-11-19 14:44:56 -05:00
John Colvin 74ce1fc98e Add appsettings.Local.json to gitignore (DataStreamSuite) 2025-11-19 14:44:56 -05:00
John Colvin 3a3352eef4 feat: Add modulus operator to stream DSL 2025-11-19 14:44:56 -05:00
John Colvin 28316c20d2 chore: Add a few DataStreamExtension fns
* AsDataPackets
  - converts IObservable<IBlueRowCollection> to IObservable<IDataPacket>
* Filter
* OrderBy
* Project
* TimeSeries
* SelectRow - transforms row
* SelectColumn - transforms column
2025-11-19 14:44:56 -05:00
John Colvin b171ce2ff0 improve: Support arbitrary columns in py table_formatter
Not just key/values
2025-11-19 14:44:55 -05:00
John Colvin 3431700c38 chore: Add IDataStreamFactory for FilterStream 2025-11-19 14:44:55 -05:00
John Colvin 3638b26bdf More jupyter/pandas analysis for Brandon Chen 2025-11-19 14:44:55 -05:00
John Colvin 9395fe70b3 refactor: Map TimeSpan to TypeCode.Int64 2025-11-19 14:44:55 -05:00
John Colvin ecc524d1ba chore: Add OnWorkScheduler ext to recv IDataPackets on scheduler/thread 2025-11-19 14:44:55 -05:00
John Colvin 680761dabc perf: Reset grid when the schema changes in DSQA (not every update) 2025-11-19 14:44:55 -05:00
John Colvin 96116c7f8f cleanup: Remove commented out code from DSQA MainForm.cs 2025-11-19 14:44:55 -05:00
John Colvin 23c20bb255 fix: Dispose last stream sub before evaluting next expression in DSQA
If the expression throws, we don't want the old stream to keep ticking.
2025-11-19 14:44:55 -05:00
John Colvin f86795d18d fix: Add null check to BlueDataPropertyDescriptor
So DSQA doesn't crash on a bad query/table impl. Mainly useful during development
when the stream might have a null row.
2025-11-19 14:44:55 -05:00
John Colvin d7d3c43302 cleanup: Enable nullable ref types in IBlueRowCollection.cs 2025-11-19 14:44:55 -05:00
John Colvin 71bd231a4a improve: Support enum types in ColumnInfo 2025-11-19 14:44:55 -05:00
John Colvin 7ecf705f53 refactor: Add default impl for columnInfo indexers in IBlueTableSchema 2025-11-19 14:44:55 -05:00
John Colvin 8f3a3fa3ec chore: Add SumTable (non cummulative)
This is more analgous to the DiffTable, as it computes the sum for
2 rows separated a fixed number of indices.
2025-11-19 14:44:55 -05:00
John Colvin 30ee997040 chore: Add prompt examples for BrandonChen 2025-11-19 14:44:54 -05:00
John Colvin 87307f226d improve: Generate MLP at runtime with list of example files
Adds prompt file list to NaturalLanguageStreamFactoryConfig
2025-11-19 14:44:54 -05:00
John Colvin c8d52e0577 chore: Add TableDropIndexEvaluator 2025-11-19 14:44:54 -05:00
John Colvin a0511639f9 chore: Add SelectorStream/Table 2025-11-19 14:44:54 -05:00
John Colvin e569d8cc1d chore: Add IndexStream/Table and DropIndex ext fn similar to pandas 2025-11-19 14:44:54 -05:00
John Colvin 6d58fb76a2 fix: Make PythonDataframeStream thread-safe
Input/output happen on different threads
2025-11-19 14:44:54 -05:00
John Colvin c2409d3edd fix: Support some mutations with convert_back delegate in SelectorList 2025-11-19 14:44:54 -05:00
John Colvin 2934346769 chore: Add arithmetic operators to PrimitiveVariant 2025-11-19 14:44:54 -05:00
John Colvin f00bb309e1 feat: Add a bunch of stream/table ext evaluators 2025-11-19 14:44:54 -05:00
John Colvin 2c8b2b6918 cleanup: Add NotNull attr to "assert_value" nullable ext 2025-11-19 14:44:54 -05:00
John Colvin c1cf1eab0a chore: Add BookDelta class 2025-11-19 14:44:54 -05:00
John Colvin af248ddd69 chore: Add DoesNotReturnIf(false) attr to unit test Assert condition 2025-11-19 14:44:53 -05:00
John Colvin 0d33b19a39 Add some half-baked Brandon Chen streams 2025-11-19 14:44:53 -05:00
John Colvin 5665d18249 fix: Support numeric conversions in ArgumentComparer 2025-11-19 14:44:53 -05:00
John Colvin a3615f1d7e chore: Add MergeSorted ext fn to merge 2 already sorted enumerables 2025-11-19 14:44:53 -05:00
John Colvin 7242c87e0f Add TableUpdate 'WithHistory' ext fn
A buffer of the last N updates is included with each update
2025-11-19 14:44:53 -05:00
John Colvin 2bd65ad48f Show IDataStreamFactory operators in DSQA 2025-11-19 14:44:53 -05:00
John Colvin 08163ca6c0 Tweak databento_client.py 2025-11-19 14:44:53 -05:00
John Colvin 1640e3854c refactor: Launch ThrottleStream via IDataStreamFactory
Not PrimitiveManager
2025-11-19 14:44:53 -05:00
John Colvin c4bd606017 feat: Add BboPriceSummaryStream for Brandon Chen 2025-11-19 14:44:50 -05:00
John Colvin 0d75dc011f feat: Add GroupByApplyStream 2025-11-19 14:42:04 -05:00
John Colvin ce455ac52c feat: Add SliceTable/Stream 2025-11-19 14:40:50 -05:00
John Colvin a181bde099 Jupyter/pandas analysis for brandon chen 2025-11-19 14:40:50 -05:00
John Colvin e1b482e374 fix: Preserve clipboard (copy/paste) when DSQA exits 2025-11-19 14:40:50 -05:00
John Colvin e6dd8ccafb ui: Improve dateTime formatting in DSQA 2025-11-19 14:40:50 -05:00
John Colvin 36366cb7c4 feat: Add pseudo member (extension) function calls to DSL 2025-11-19 14:40:49 -05:00
John Colvin dbc5304546 refactor: Move StreamQueryMetadata into StreamFactories namespace 2025-11-19 14:40:49 -05:00
John Colvin b17b8fc7c8 improve: Add config for natural language streams
They're created by a stream factory now, which has the config handy.
Config includes api key, model name, mlp file, etc.

fix add config for natural language streams
2025-11-19 14:40:17 -05:00
John Colvin 7a65758fa5 refactor: Drop null type modifier in TryGetFirst ext
Replace w/ MaybeNullWhen attributes
2025-11-19 14:40:17 -05:00
John Colvin 79aa541bd7 cleanup: Remove unnecessary code 2025-11-19 14:40:17 -05:00
John Colvin f35dae7110 qol: Don't connect to sparkd by default in Spark.Console cli 2025-11-19 14:40:17 -05:00
John Colvin 02290f2f34 qol: Change default pubsub url from 'pubsub' to 127.0.0.1
The hostname 'pubsub' works when running pubsub and other services
via docker compose. In practice, it seems we usually just run them on a
single host, without using docker compose.
2025-11-19 14:40:17 -05:00
John Colvin 04200a330a feat: Add historic market data streams factory
Loads historic md from Databento files in structured paths. It is
designed to work with files downloaded via databento_client.py
2025-11-19 14:40:15 -05:00
John Colvin 0ea884b505 fix: Don't modify input env in BoundBooleanExpression.Evaluate 2025-11-19 14:35:12 -05:00
John Colvin 2d0df72f46 refactor: Pass IMarketDataClient to streams via dependency injection
In general, the streams should not instantiate services, etc. themselves.
2025-11-19 14:35:12 -05:00
John Colvin 8766786109 improve: Support all integer types in IBlueRow Id ext fn 2025-11-19 14:35:12 -05:00
John Colvin e48eeb4df3 cleanup: Remove unused code CSharpString.cs 2025-11-19 14:35:11 -05:00
John Colvin b1bfc6f65b feat: Add DiffStream to compute row diffs (like pandas) 2025-11-19 14:35:11 -05:00
John Colvin 6f209b97d4 refactor: Rewrite SortStream now that IBlueRowCollection is IList 2025-11-19 14:35:11 -05:00
John Colvin 751980ba67 refactor: Add param for TimeSeriesStream timestamp col name
Default it to 'ts_event' instead of 'timestamp' (this is consistent w/
Databento schema fwiw)
2025-11-19 14:35:11 -05:00
John Colvin 05379f6ac4 fix: Trim backticks from nl generated dsl 2025-11-19 14:35:11 -05:00
John Colvin fc1a89a60f feat: Add FeatherReader stream
We don't need this right now, but I already wrote it...it might be
useful later.
2025-11-19 14:35:11 -05:00
John Colvin 45a9322090 feat: Add function defs to DataStream DSL 2025-11-19 14:35:11 -05:00
John Colvin a0276ac34f chore: Support mutations in AggregateDictionary (optionally) 2025-11-19 14:35:11 -05:00
John Colvin 827079f5f5 chore: Implement IComparable in BookTuple 2025-11-19 14:35:11 -05:00
John Colvin 75f0cd7831 improve: Serialize to/from parquet in PythonDataframeStream 2025-11-19 14:35:08 -05:00
John Colvin 79c3e544aa feat: Add ParquetReader/Writer streams 2025-11-19 14:32:37 -05:00
John Colvin 4aebdad90b fix: Fire multiple rows at once in replay bbo/trade streams doWork fn
If necessary to keep pace with timestamps in the file.
2025-11-19 14:32:34 -05:00
John Colvin d7300cbddb refactor: Launch LiveTradeStream via IDataStreamFactory 2025-11-19 14:27:48 -05:00
John Colvin c21cd2ba6d chore: Add id field to TradeRecord
Need this for streams
2025-11-19 14:27:48 -05:00
John Colvin 488fbe3c43 fix: Actually use sec filter in MarketDataClient.Trades 2025-11-19 14:27:48 -05:00
John Colvin d25db044af style: Rename dispose_thread_safe in PascalCase 2025-11-19 14:27:48 -05:00
John Colvin d3e31e4e44 refactor: Make IBlueRowCollection inherit IList<IBlueRow> 2025-11-19 14:27:46 -05:00
John Colvin 4fd2073b63 improvement: Lookup timestamp col by index rather than name 2025-11-19 14:19:21 -05:00
John Colvin 0b64bd6544 refactor: Remove hardcoded csv path from ReplayFutureLiveBBOStream
Add constructor overload to accept an input ViewStream
2025-11-19 14:19:19 -05:00
John Colvin d1fc4d4ce9 improvement: Don't mutate IBlueRowCollection after firing in FilterStream 2025-11-19 14:17:05 -05:00
John Colvin 654ef44664 improvement: Make MultiRowRollingMathStream more performant
Avoid creating extra lists and o(n) enumeration
2025-11-19 14:17:05 -05:00
John Colvin 2f83499524 refactor: Move IBlueTableSchema ext fns into DataStreamCore
The csproj in which IBlueTableSchema is defined.
2025-11-19 14:17:03 -05:00
John Colvin cfbd324b6f tool: Add databento_client.py to fetch (and store) databento data 2025-11-19 14:15:01 -05:00
John Colvin ae513ed247 chore: Copy/paste some log utils from py interop dir to tools/py 2025-11-19 14:15:01 -05:00
John Colvin c965f125ed refactor: Support dict and list of tuples in py fmt_table 2025-11-19 14:15:01 -05:00
John Colvin 41aa5ec834 refactor: Replace old NLStreamFactory w/ new BlueOrb one
Replace hardcoded file paths with proper config while we're at it
2025-11-19 14:15:01 -05:00
John Colvin 7651397e22 refactor: Pass composite stream factory back to recursive factories as service 2025-11-19 14:15:01 -05:00
John Colvin 41a800aac6 build: Add appsettings.Local.json to DSQA 2025-11-19 14:15:01 -05:00
John Colvin c549b51d80 refactor: Support random access (IList) to data in TableUpdate
We're doing the same thing to IBlueRowCollection. This will allow us to
implement column based operators, like python pandas.
2025-11-19 14:15:01 -05:00
John Colvin 37c1eda425 Add TestCommand back to Spark.Console cli 2025-11-19 14:15:01 -05:00
John Colvin 52ad459c19 new: Add GetColumn fn to IBlueRowCollection interface
This allows us to implement python/pandas functionality, much of which is based
on column operations.
2025-11-19 14:14:58 -05:00
John Colvin d6158bc9a0 chore: Add various collection classes
These will be useful in stream operators, row indexing, etc.

* AggregateList
* DeltaList
* DictionarySetWrapper
* ImmutableOrderedDictionary
* IMutableListFacade
  - inherits IReadOnlyList<T> and IList<T>
  - throws NotSupportedException in default impls for mutation ops
* IMutableCollectionFacade
  - inherits IReadOnlyCollection<T> and ICollection<T>
  - throws NotSupportedException in default impls for mutation ops
* MergedSortedList
* OrderedDictionary
* ReverseArrayList
* SliceList
* StepList
2025-11-19 14:03:46 -05:00
John Colvin 85babef37b refactor: Drop nullable modifier for TValue in GetValueOrDefault ext fn 2025-11-19 14:03:46 -05:00
John Colvin fb3a6262b3 cleanup: Fix build warnings
* BlueOrbNLStream.cs
* ChangeTableNameStream.cs
* FilterStream.cs
* FilterStream.cs
* GroupByOutputTable.cs
* GroupByStream.cs
* JoinForeignTable.cs
* MultiRowRollingMathStream.cs
* MutableAppendStream.cs
* PersistWindowState.cs
* PubsubEchoTestStream.cs
* RowDictionaryTable.cs
* SortedTable.cs
* TimeSeriesStream.cs

Also add Metadata prop to IDataPacket
2025-11-19 14:03:43 -05:00
John Colvin a3bee2313a chore: Add 'Rows' prop to BlueTable to avoid accidentally iterating over KeyValuePairs 2025-11-19 14:01:02 -05:00
John Colvin d8d9a25f22 refactor: Use dependency injection in Spark.Console 2025-11-19 14:01:02 -05:00
John Colvin 45fcea5670 cleanup: Remove broken settings cmd from Spark.Console 2025-11-19 14:01:01 -05:00
John Colvin 33f42c3097 refactor: Use top-level statements for Spark.Console 'main' 2025-11-19 14:01:01 -05:00
John Colvin 0c5e9c6945 refactor: Consolidate SparkApp and ShellBootstrapper into single class 2025-11-19 14:01:01 -05:00
John Colvin 65a9f31f14 feat: Add stream driven price lines to ladder 2025-11-19 14:01:01 -05:00
John Colvin 789e8debc6 fix: Use IDictionary.GetEnumerator explicitly to iterate keyvals
We really need to replace these old non-generic collections with generic ones.
BlueRow's IDictionaryEnumerator implementation iterates over values when passed
to a foreach loop, not key value pairs.
2025-11-19 14:01:01 -05:00
John Colvin c821a7ed43 refactor: Make CompositeDataStreamFactory class internal 2025-11-19 14:01:01 -05:00
John Colvin 12a3e7a2f4 feat (ui): Add ConfigServerPicker dialog
So we can connect to different config servers without manually editing spark.conf,
or changing command line args. This will run before the Login dialog.
2025-11-19 14:01:01 -05:00
John Colvin 5045b1889d refactor: Move ComboBox styles to ComboBoxes.xaml
I'm working on a ConfigServerPicker dialog, which will run before the main
application starts. This allows the dialog to use the same dropdown styles as
the main app without the overhead of loading generic.xaml.
2025-11-19 14:01:01 -05:00
John Colvin a50a71f058 cleanup: Fix build warnings, remove unused/redundant code
* Make a few member vars and fns local to the constructor
* Replace a couple non-generic collections with generic ones
* Fix warnings and clean code in:
  - DictionaryExtensions.cs
    * Remove ext fns that are part of dotnet now
  - JoinNameResolver.cs
  - MainForm.cs

cleanup: Remove unused code in ladder

Some props in cs, and StaticResources in xaml were leftover after I combined
the panel price points into a single collection, bound to an ItemsControl.
2025-11-19 14:01:01 -05:00
John Colvin f67ab3c458 refactor: Use generic collection types in PrimitiveManager 2025-11-19 14:01:01 -05:00
John Colvin 88233a6315 cleanup: Remove unused code in PrimitiveManager.cs 2025-11-19 14:01:01 -05:00
John Colvin 20db00e832 cleanup: Use file-scoped namespace in PrimitiveManager.cs
Also sort using statements
2025-11-19 14:01:01 -05:00
John Colvin 6d298a0e34 refactor: Add IDataStreamFactory(s) for spatial arb streams 2025-11-19 14:01:01 -05:00
John Colvin 8f76487974 tool: Run Spark.exe from its own directory
It looks in the working directory for runtime config appsettings.json files.
2025-11-19 14:01:01 -05:00
John Colvin 0d60cc75cc cleanup: Remove unused BooleanExpressionToken Negate 2025-11-19 14:01:01 -05:00
John Colvin 62d07ada0f doc: Comment why MDConsumer must be kept alive
The language server suggests removing it because it isn't referenced.
However, it contains unmanaged resources that mustn't be prematurely
garbage collected.
2025-11-19 14:01:00 -05:00
John Colvin bb29c32059 refactor: Rename AddRemoteQueryManager -> AddRemoteQueryStreams 2025-11-19 14:01:00 -05:00
John Colvin a358fe6229 refactor: Hide md client behind interface, create via Dep Injection 2025-11-19 14:00:58 -05:00
John Colvin f7ce346925 Drop dump-books and dump-securities from spark_api_server cli
These were subcommands were out of place. This exe has one purpose: run
the Spark Api Server.
2025-11-19 13:57:48 -05:00
John Colvin e7129b1513 cleanup: Remove unused MarketDataClient from OrderSample 2025-11-19 13:57:48 -05:00
John Colvin 27ee09ade8 build: Launch console for Spark Debug builds, otherwise hide it 2025-11-19 13:57:47 -05:00
John Colvin 63a64af46b cleanup: Remove ancient winforms sdk ui example 2025-11-19 13:57:47 -05:00
John Colvin a4efaa0089 cleanup: Fix a ton of build warnings (cs)
All these warnings have been bothering me for a while. The work isn't done, but
this commit chops them down from ~1000 to ~600.

* AppendCollection.cs
* AppendStreamModified.cs
* BenzingaNewsStream.cs
* BenzingaNewsSymbolStateStream.cs
* BookStream.cs
* CSVSaverStream.cs
* ChartsUtils.cs
* ColumnInfo.cs
* ContractUtils.cs
* ContractUtils.cs
* DiffStream.cs
* FuturesActiveContractStream.cs
* FuzzyJoinOverTimeStream.cs
* HistoricalBarsStream.cs
* HistoricalBarsStream.cs
* InstrumentsStream.cs
* JoinOperandRowIndex.cs
* JoinRow.cs
* JoinStream.cs
* JoinTable.cs
* JoinTableSchema.cs
* LatencyStream.cs
* LeftInternalJoinStream.cs
* LightForeignJoinRow.cs
* LightForeignJoinRowList.cs
* LightLeftForeignJoinRow.cs
* LiteralStream.cs
* NLStream.cs
* NLToDSLStream.cs
* NLToIndexedFewShotPromptStream.cs
* NonIncGroupByStream.cs
* NonIncJoinBookkeeper.cs
* NonIncJoinSpec.cs
* OHLCVStream.cs
* OpenAIPromptStream.cs
* PriceStatsStream.cs
* PriceStatsStream.cs
* ProjectHelper.cs
* ProjectStream.cs
* PubSubServerStateStream.cs
* PubsubHeartbeatStream.cs
* SimpleNode.cs
* SortRow.cs
* SpatialArbStatsStream.cs
* SpatialFuturePairsStream.cs
* SpreadPerformanceEvaluator.cs
* SpreadPerformanceStream.cs
* StreamIOCommon.cs
* SuperJoinStream.cs
* TableMetaDataStream.cs
* TextWriterStream.cs
* TimeAverageStream.cs
2025-11-19 13:57:47 -05:00
John Colvin 7418f5b5a1 style: Normalize indentation in PubsubHeartbeatStream.cs 2025-11-19 13:57:47 -05:00
John Colvin b854390c85 chore: Add GetValue, TryGetValue ext fns to IBlueRow 2025-11-19 13:57:47 -05:00
John Colvin a2f9359077 chore: Add 'Empty' fn for BlueTable/Schema (for convenience) 2025-11-19 13:57:47 -05:00
John Colvin c2db246ce5 cleanup: Remove redundant code block in HistoricalBarsStream.cs 2025-11-19 13:57:47 -05:00
John Colvin 961f55388a cleanup: Fix indentation and build warnings in TestStream.cs 2025-11-19 13:57:47 -05:00
John Colvin 360e80b3fe cleanup: Make MDConsumer.on_security nonnull
It's always initialized in the constructor.
2025-11-19 13:57:47 -05:00
John Colvin 5096912fbf refactor: Decouple ViewStreamDatabase from TableStream, ISubscriberCore 2025-11-19 13:57:44 -05:00
John Colvin 46e853a55f check pt: PingPong algo
This is a work-in-progress; putting a pin in it for now to focus on
other priorities.
2025-11-19 13:55:44 -05:00
John Colvin 72adf58889 Add environment variable override for app data path
Historically, ~/sparkdata on linux, and ~/AppData/Roaming/bts on win.
Now that we may run multiple exes at once (api server, pubsub, sparkd, etc.),
we need to support multiple data dirs on the same host.
2025-11-19 13:55:43 -05:00
John Colvin f334955acf Drop ".exe" from log dir name on windows 2025-11-19 13:55:43 -05:00
John Colvin d1ee1e2798 feat: Add SpreadBook stream 2025-11-19 13:55:43 -05:00
John Colvin 152ea18266 fix: Check settings Subject.IsDisposed before using 2025-11-19 13:55:43 -05:00
John Colvin ed897c4969 ui (launcher): Move "X" button to left of report menu items
This is a more natural location. Also, it looked weird on the right because the
menu labels aren't fixed width, so the buttons weren't vertically aligned.
2025-11-19 13:55:43 -05:00
John Colvin 4397df4628 refactor: Use HostBuilder to launch spark w/ streams 2025-11-19 13:55:43 -05:00
John Colvin e2b3e543ea build (cleanup): Get rid of global.json
We don't need it anymore
2025-11-19 13:55:43 -05:00
John Colvin 92064bfb48 build: Update System.Composition pkg refs to 8.0.0 2025-11-19 13:55:43 -05:00
John Colvin 3c7f56b0c9 cleanup: Mark a few classes "internal" in Spark 2025-11-19 13:55:43 -05:00
John Colvin cd121defc5 cleanup: Use consistent access modifier in TabDocument partial class
It should be marked internal in both xaml and xaml.cs (it was previously
only so in the latter).
2025-11-19 13:55:43 -05:00
John Colvin ca9a6527ee refactor: Consolidate ShellBootstrapper and base class into one 2025-11-19 13:55:43 -05:00
John Colvin 6cf1b02a75 feat: Add Stream Reports shell window 2025-11-19 13:55:43 -05:00
John Colvin e7ee0e29cc build: Define custom "main" (entry point) for Spark exe
This is a prelude to integrating streams into spark. The plan is to use
an ms HostBuilder to instantiate stream services/dependencies alongside
the spark Application object.
2025-11-19 13:55:43 -05:00
John Colvin 3e28003d80 cleanup: Get rid of stunnel code
This looks like it hasn't been hooked up for years...it would only start
if a stunnel.conf file was detected, which hasn't been around for a very
long time.
2025-11-19 13:55:42 -05:00
John Colvin 63f74222ad fix: Only create mutex if spark app is NOT running in vs designer
I accidentally droped the '!' in a cleanup commit 4 years ago
2025-11-19 13:55:42 -05:00
John Colvin 2ccbfee087 vscode: Add DSQA debug cfg to skip build 2025-11-19 13:55:42 -05:00
John Colvin 2f73a79a6b fix (dsqa): Resolve name conflicts with new BlueOrb streams 2025-11-19 13:55:42 -05:00
John Colvin 4f00d4a20c fix: Fix typo in "DateTimeExpansionStream" type name
Previously, it was "DateTimeExpanstion" (erroneous "t")
2025-11-19 13:55:42 -05:00
John Colvin 8e4d6f9ae3 chore: Add CircularBuffer class (c#) 2025-11-19 13:55:42 -05:00
John Colvin a2b3a4e05d refactor: Rename ExceptionAssert.is_valid ExceptionAssert.Index.IsValid
Like the ExceptionAssert.Argument class that provides methods that throw
ArgumentException if a condition isn't satisfied, the ExceptionAssert.Index
class provides methods that throw IndexOutOfRange exceptions.
2025-11-19 13:55:42 -05:00
John Colvin 47dacc5d1a chore: Add OffsetList class
This supports mutations (unlike the SubRangeWrapper class).
2025-11-19 13:55:42 -05:00
John Colvin 9e842fd453 cleanup: Remove "partial" class modifiers from OrderBook vm classes
They simply weren't necessary.
2025-11-19 13:55:42 -05:00
John Colvin fd98be8f03 check pt: Proof of concept ladder left panel price point collection 2025-11-19 13:55:42 -05:00
John Colvin 43388affc3 ui: Adjust ladder ticks-per-row with ctrl+shift+mousewheel 2025-11-19 13:55:42 -05:00
John Colvin 3e5e6fd487 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.
2025-11-19 13:55:42 -05:00
John Colvin 38daf3fe80 build: Add scripts to publish data stream apps 2025-11-19 13:55:42 -05:00
John Colvin 24040768a2 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.
2025-11-19 13:55:42 -05:00
John Colvin 78138eff26 feat: Add DataStream WebApp
Included in DataStreamSuite.sln, but not DataStreamQueryAnalyzer.sln
2025-11-19 13:55:41 -05:00
John Colvin f7c9ee7f6a Reduce log noise in AsyncTCPConnection_AsyncWrapper 2025-11-19 13:55:41 -05:00
John Colvin 054b819eb9 TEMP changes to appsettings.json (at least i should review them before making permanent) 2025-11-19 13:55:41 -05:00
John Colvin 8787c6752b Add ColumnIndex class to resolve column names to indices
Improves performance compared to accessing columns by name
2025-11-19 13:55:41 -05:00
John Colvin 5cd390d78a Increase SQLiteEnumerator page size to 65536
This performs better, and doesn't seem to be too large in practice
2025-11-19 13:55:41 -05:00
John Colvin 8804dbe093 Serialize TimeSpan w/ microsecond precision in DataPacket json 2025-11-19 13:55:41 -05:00
John Colvin fd24abc085 Format DateTimes w/ microsecond precision in DataPacketJsonConverter 2025-11-19 13:55:41 -05:00
John Colvin dce7e73d84 Add BlueColumnTable for pandas integration
This will make it easier/more natural to support pandas column expressions
2025-11-19 13:55:41 -05:00
John Colvin 6bb142f51f Add c# Variant class, modeled after std::variant in c++ 2025-11-19 13:55:41 -05:00
John Colvin b7df16ffd7 refactor: Rename snake_case fns PascalCase 2025-11-19 13:55:41 -05:00
John Colvin 63aaeae401 refactor: Use dependency injection in DSQA 2025-11-19 13:55:08 -05:00
John Colvin ea0a1fbcd3 refactor: Load IDataStreamFactory(s) with dependency injection
So we can create ViewStreams that depend on other services, config, etc.
Previously, the PrimitiveManager relied purely on static construction of
types through reflection.
2025-11-19 13:53:18 -05:00
John Colvin 7566b2560f refactor: Change namespace to Bts.DataStreamCore for a few types
Since they're in the DataStreamCore csproj
2025-11-19 13:29:16 -05:00
John Colvin 6f2d47c630 Make 'name' default to CallerArgumentExpression for convenience 2025-11-19 13:29:16 -05:00
John Colvin 61456be744 refactor: Replace if/else with switch cases 2025-11-19 13:29:16 -05:00
John Colvin 583420b161 cleanup: Remove over-engineered subcommands
We don't need explicit subcommands to 'run onixs' md (just use config).
2025-11-19 13:29:16 -05:00
John Colvin 090aacd0c7 fix: Ensure IDGenerator instance is created before deref 2025-11-19 13:29:16 -05:00
John Colvin 79be65b94c fix: Expand undefined vars to empty string
Bugs (fixed):
  * Md shared memory file names have unexpanded variable: '${BTS_SHARED_MEMORY_PREFIX}'
2025-11-19 13:29:16 -05:00
John Colvin 70ed999116 fix: Emplace MDConsumerHandle instead of copy assign 2025-11-19 13:29:16 -05:00
John Colvin 166651a45e cleanup: Remove unnecessary code
Trim includes as well
2025-11-19 13:29:16 -05:00
John Colvin 79c9dd8221 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.
2025-11-19 13:29:16 -05:00
John Colvin aba6adc141 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.
2025-11-19 13:29:16 -05:00
John Colvin 9352976190 cleanup: Remove redundant macro def 2025-11-19 13:29:16 -05:00
John Colvin 1840386e62 Skip md recording thread loop if no recorders configured to run 2025-11-19 13:29:16 -05:00
John Colvin 247251a52a cleanup: Trivial whitespace cleanup in Protobuf.cmake 2025-11-19 13:29:15 -05:00
John Colvin f31b887638 build: Clean up data_files cmake proj (support --target clean) 2025-11-19 13:29:15 -05:00
John Colvin 760510e4d9 fix build: Add ShmSegmentName.cpp to windows client sources 2025-11-19 13:29:15 -05:00
John Colvin 3b6e0d004b Add api server to docker compose 2025-11-19 13:29:15 -05:00
John Colvin 0b630264d2 Change api server ports so they don't conflict w/ ds webapp 2025-11-19 13:29:15 -05:00
John Colvin 733fd3e378 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.
2025-11-19 13:29:15 -05:00
John Colvin 010af2f175 Don't log "initializing spark config" if it has already been initialized 2025-11-19 13:29:15 -05:00
John Colvin e2127bbe94 vscode: Add debug cfg for spark api server 2025-11-19 13:29:15 -05:00
John Colvin f6b07508cd Use dependency injection in spark api server 2025-11-19 13:29:12 -05:00
John Colvin 2fb254594b Add SparkLoggerAdapter for Msft ILogger 2025-11-19 13:23:43 -05:00
John Colvin 43b6211913 fix: Don't assert on exception in BooleanExpression.evaluate
Bugs (fixed):
  * DataStream WebApp Server sometimes crashes when the user enters a
    bad query.
2025-11-19 13:23:43 -05:00
John Colvin 5b793b5a0f Assert that local ip address is successfully obtained 2025-11-19 13:23:43 -05:00
John Colvin fbee781ab3 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).
2025-11-19 13:23:43 -05:00
John Colvin d3df3f974a Assert TcpConnection endpoint matches expected 2025-11-19 13:23:43 -05:00
John Colvin 075619c375 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.
2025-11-19 13:23:43 -05:00
John Colvin a5e6a71e0e fix: Format PubSubRoute as json in log msgs
It was previously using the default ToString, which simply returns the type name
2025-11-19 13:23:43 -05:00
John Colvin 64feb470da build: Upgrade packages (deps) in DataStreamTests 2025-11-19 13:23:43 -05:00
John Colvin f81dc08621 cleanup: Remove unused ViewStreamDatabase config 2025-11-19 13:23:43 -05:00
John Colvin 9ff23db7a2 vscode: Add PubSubServer debug cfg to skip build 2025-11-19 13:23:43 -05:00
John Colvin d603adcf7a 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.
2025-11-19 13:23:43 -05:00
John Colvin 7932ede1e8 refactor: Run RemoteQueryManager as an IHostedService
This paradigm more accurately represents the RemoteQueryManager as a
Service that can be started/stopped.
2025-11-19 13:23:42 -05:00
John Colvin 3e2bf37cc3 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.
2025-11-19 13:23:42 -05:00
John Colvin 95683de1c6 Assert ViewStreamDatabase.instance is only created once
The instance must be initialized via ViewStreamDatabase.SetGlobalInstance
2025-11-19 13:23:42 -05:00
John Colvin 00c8081516 cleanup: Remove unused code from ViewStreamDatabase 2025-11-19 13:23:40 -05:00
John Colvin 9b1aef6f22 fix: Define BoooleanConstant before StringConstant
Otherwise the former masks the latter and prevents it from matching
2025-11-19 13:21:51 -05:00
John Colvin 7c5459291e cleanup: Remove unnecessary deps from csprojs 2025-11-19 13:21:51 -05:00
John Colvin 0871a15961 fix: Catch exception when creating TcpClient 2025-11-19 13:21:51 -05:00
John Colvin 1fab109987 fix: Make SystemSubscriptionMapPublisher threadsafe
The PubSubState.onTagMapChanged event may not be invoked on
the same thread as doWork.
2025-11-19 13:21:51 -05:00
John Colvin 12ea9a060a fix: Add a few null checks
* TimeBasedFilter
* TableAccumulator2
* NonIncGroupByStream
2025-11-19 13:21:51 -05:00
John Colvin a3794fdac4 Reduce task wait/timeout code in AsyncTCPConnector 2025-11-19 13:21:51 -05:00
John Colvin 4e7eab8e47 fix build warnings: Make props nullable strings in SharedStreamConfig 2025-11-19 13:21:51 -05:00
John Colvin 754dfa87c7 Assert that System publishers 'doWork' is always called on same thread 2025-11-19 13:21:51 -05:00
John Colvin ef51954549 deploy: Add pubsub and md-publisher services to compose.yaml 2025-11-19 13:21:50 -05:00
John Colvin 50a1dfae14 refactor: Model config profiles after dotnet appsettings
Namely, in DataStreamPublisher and PubSubServer. This is part of a
broader paradigm shift towards dotnet factory pattern.

https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines

* Rename Configurations/<name>.json -> appsettings.<name>.json
* Move reusable startup code into new Bts.Extensions.Hosting proj
2025-11-19 13:21:50 -05:00
John Colvin 6e09854337 Add ErrorChanged event to propagate DataStream exceptions 2025-11-19 13:21:50 -05:00
John Colvin df049622cb fix build: Fix Spark build after making config init async 2025-11-19 13:21:48 -05:00
John Colvin 0f0420ba4c refactor: Change Configuration.initialize to be async
Tweak SparkCoreServices to synchronize Configuration.init globally
2025-11-19 13:18:10 -05:00
John Colvin 9b4c67fb41 vscode: Add 'isSparkApiEnabled' input to launch.json 2025-11-19 13:17:12 -05:00
John Colvin 6c65cabda8 cleanup: Remove unused PubSubConfig props 2025-11-19 13:17:12 -05:00
John Colvin 64ad720ed6 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
2025-11-19 13:17:12 -05:00
John Colvin 550bf5dea5 Fix dsqa config json 2025-11-19 13:17:10 -05:00
John Colvin c4cbaff350 fix: Always call 'handle_internal_pubs' in PubSubMessageReactor main loop 2025-11-19 13:16:06 -05:00
John Colvin 82c99f0eb3 Fix mismatched types in PubSubServerTagStatsStream schema 2025-11-19 13:16:06 -05:00
John Colvin cbf1ef3d89 Add DataStreamPublisher service modules designed for Dependency Injection
* Change DataStreamPublisher json fields to match cs class property names verbatim
* Rename Publishers -> PublisherNodes in DataStreamPublisherConfig
2025-11-19 13:16:04 -05:00
John Colvin a86ddff4be refactor: Decouple ViewPublisher from IPubTableBroker impl 2025-11-19 12:27:23 -05:00
John Colvin f51d0ae973 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
2025-11-19 12:27:20 -05:00
John Colvin b9125fb44b refactor: Decouple RemoteQueryManager from IManagedSubscriber, IPubTableBroker impl 2025-11-19 12:24:39 -05:00
John Colvin d51e09719d refactor: Decouple ManagedSubscriber from ISubscriberCore impl 2025-11-19 12:24:39 -05:00
John Colvin 520f6311c7 refactor: Only support 1 tag type in IPubSubServerTagHandler 2025-11-19 12:24:39 -05:00
John Colvin a19658c1e3 refactor: Decouple PubTableBroker from IPublisherCore impl
Reference interface rather than the PublisherCore impl.
2025-11-19 12:24:39 -05:00
John Colvin 22b46b38ee fix: Dispose market data sub in various md streams 2025-11-19 12:24:38 -05:00
John Colvin c66511a089 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
2025-11-19 12:24:38 -05:00
John Colvin b2e94db451 refactor: Rename CloudStreamManager.cs -> RemoteQueryManager.cs
This matches the file name
2025-11-19 12:24:38 -05:00
John Colvin 010f46a2a0 Change pub sub schema prefix 'bluto://' -> 'bts-cloud://'
In config json files (not actual code change)
2025-11-19 12:24:38 -05:00
John Colvin b4f59fd13b chore: Fix typos in log msgs
* Bad string interopolation syntax
2025-11-19 12:24:38 -05:00
John Colvin 1aed9f49e2 vscode: Add DataStreamPublisher debug cfg to skip build 2025-11-19 12:24:38 -05:00
John Colvin d5840bf0cc Add spark service modules designed for Dependency Injection 2025-11-19 12:24:38 -05:00
John Colvin e654bdbe6f cleanup: Fix benign typos in build scripts
Change BUILD_SPARK_PY var name to BUILD_PY (these scripts build other
projects than 'spark').
2025-11-19 12:24:38 -05:00
John Colvin a569fec6cc fix build (warn): Only use Registry on windows 2025-11-19 12:24:38 -05:00
John Colvin 77c616af3e 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
2025-11-19 12:24:38 -05:00
John Colvin 4d0d7cf279 cleanup: Disable nullability warnings
* BZip2InputStream
* TarInputStream
* Inflater
* InflaterDynHeader
* BZip2OutputStream
* DeflaterHuffman
* ParallelJoinTest
2025-11-19 12:24:37 -05:00
John Colvin a81a4772d3 cleanup: Modernize PubSubRoute code, json parsing 2025-11-19 12:24:37 -05:00
John Colvin 24114b067b cleanup: Remove unused DateTime grammar from exp parser 2025-11-19 12:24:37 -05:00
John Colvin 57e0f0e8bd refactor: Add handy WebsocketExtensions
* IsSocketStateOpen
* IsClosedProperly
2025-11-19 12:24:37 -05:00
John Colvin 8fed31c86d chore: Add JsonRpc types for websocket paradigm 2025-11-19 12:24:37 -05:00
John Colvin b04b9db005 cleanup: Remove empty file (clearly added by accident) 2025-11-19 12:24:37 -05:00
John Colvin 4adcb41413 editorconfig: Use standard c# capitalization rules
* Use 'PascalCase' for public members (instead of 'snake_case')
* Use 'camelCase' for private members (instead of '_snake_case')
2025-11-19 12:24:37 -05:00
John Colvin f571b4992f Add generic ViewStream, BlueRow types/interfaces 2025-11-19 12:24:37 -05:00
John Colvin 1d70eecb44 Add BlueRowColumnMask to hide columns w/ minimal overhead
Less overhead than ProjectStream
2025-11-19 12:24:36 -05:00
John Colvin ca82b8f001 fix: Remove query from literal error stream
The parser can't handle colons in the expression (it throws/crashes)
2025-11-19 12:24:36 -05:00
John Colvin c5df486cf4 fix: Remove erroneous "DSL: " prefix in ai prompt stream
This causes parse errors since we extended the dsl syntax to support
named params in the form of "name: arg".
2025-11-19 12:24:36 -05:00
John Colvin 69632d7327 fix Impl IDictionary<int, IBlueRow> in BlueTable 2025-11-19 12:24:36 -05:00
John Colvin ae21ae02d2 feat: Extend dsl to support explicit param names
When using explicit names, the argument order doesn't matter, and args
with defaults are automatically populated if omitted.
2025-11-19 12:24:36 -05:00
John Colvin 706d1b8de4 Add async SQLiteEnumerator to read sqlite tables as IBlueRowCollection 2025-11-19 12:24:36 -05:00
John Colvin eb30e16ef6 ui: Add long/short avg cost lines to ladder 2025-11-19 12:24:36 -05:00
John Colvin acb86b2658 ui: Add exec fee columns to fills grid 2025-11-19 12:24:36 -05:00
John Colvin 05a9d87773 chore: Add exec fee to sec metadata config 2025-11-19 12:24:36 -05:00
John Colvin 8c6dfa4019 chore: Add fill_aggressor prop to ITradeLogRow 2025-11-19 12:24:35 -05:00
John Colvin 0746b27a40 cleanup: Remove docker submodules again
I lost count of the number of times they have been deleted long ago.
Taha, please remove them from your repo so they don't come back :)
2025-11-19 10:33:31 -05:00
John Colvin 3a3c3d45d3 cleanup: Change default DSQA sparkd config server to 127.0.0.1 2025-11-19 10:33:31 -05:00
John Colvin 2014931653 feat: Add live bbo stream 2025-11-19 10:33:31 -05:00
John Colvin aa740e786c refactor: Automatically add id column in BlueTableSchemaBuilder 2025-11-19 00:36:56 -05:00
John Colvin 12c6b367f4 fix (preemptive): Avoid StackOverflow in md client securities fn
One of the 'securities_incremental' fns was calling back into itself instead of
the other overload. This is a 'preemptive' fix because the problematic code path
wasn't running in practice (though it was a ticking time bomb).
2025-11-19 00:36:56 -05:00
John Colvin 5080960b97 refactor: Replace DataPacket w/ IDataPacket in stream handlers
This commit also renames BlueTableSchemaAdaptor to BlueTableSchemaAdapter,
and makes it implement IBlueTableSchema.
2025-11-19 00:36:56 -05:00
John Colvin 770e14752a chore: Add json facilities for DataStream websocket api 2025-11-19 00:36:56 -05:00
John Colvin e24b877e58 Add gen purpose blue row/table utils for data streams
* Add BlueRowReflection<T> so ViewStream implementations can use
  strong row types without needing to implement IBlueRow
* Add default implementations for IDictionary members in IBlueRow to make
  implementing IBlueRow less tedious
2025-11-19 00:31:53 -05:00
Taha Afzal 09abe4a432 Added RowBasedAppendStream 2025-11-18 03:10:30 +00:00
31 changed files with 539 additions and 441 deletions

No files matched your search

Submodule docker/linux deleted from 6537812262.
Submodule docker/windows deleted from de7d40c09d.
@@ -47,8 +47,6 @@ namespace bts.BlueOrb {
private void _onstream_change(object o) {
var dp = ExceptionAssert.Argument.Is<IDataPacket>(o);
DateTime now = DateTime.Now;
var addedRowsBd = ImmutableSortedSet.CreateBuilder(KeyValuePairComparer.KeyComparer<int, IBlueRow>());
var deletedRowsBd = ImmutableSortedSet.CreateBuilder(KeyValuePairComparer.KeyComparer<int, IBlueRow>());
using var _ = gate.BeginTransaction();
if (_table == null) {
var colInfos = dp.newRows.schema.columnInfo;
@@ -60,19 +58,21 @@ namespace bts.BlueOrb {
}
_table = new CircularBufferTable(new BlueTableSchema(colInfos, dp.newRows.schema.tableName), _size);
}
var addedRowsBd = BlueTableImmutable.CreateBuilder(_table.schema);
var deletedRowsBd = BlueTableImmutable.CreateBuilder(_table.schema);
foreach (IBlueRow r in dp.addedRows) {
MutableBlueRow ar = new MutableBlueRow(_table.schema, r);
ar.setValue(timeColumn, now);
int id = _id++;
ar.setValue("id", id);
_table = _table.Add(ar, out var oldRow);
addedRowsBd.Add(new(id, ar));
addedRowsBd.AddOrUpdate(ar);
if (oldRow is not null) {
deletedRowsBd.Add(new(oldRow.Id(), oldRow));
deletedRowsBd.AddOrUpdate(oldRow);
}
}
var addedRows = new BlueTableImmutable(_table.schema, addedRowsBd.ToImmutable());
var deletedRows = new BlueTableImmutable(_table.schema, deletedRowsBd.ToImmutable());
var addedRows = addedRowsBd.ToImmutable();
var deletedRows = deletedRowsBd.ToImmutable();
var dp_out = new DataPacket(_table, addedRows, deletedRows);
myFireEvent(dp_out);
}
@@ -8,7 +8,6 @@ using bts.BlueOrb;
using Bts.DataStreamCore;
using Bts.PubSub;
using Bts.SharedStreams.StreamFactories;
using mbts.utils;
@@ -139,7 +138,7 @@ public static class DataStreamExtensions {
var resDp = bd.ToDataPacket(out var newRows);
return (newRows, resDp);
})
.Where(t => t.Dp is not null && (t.Dp.addedRows.Any() || t.Dp.deletedRows.Any()))
.Where(t => t.Dp is not null && (t.Dp.addedRows.Count > 0 || t.Dp.deletedRows.Count > 0))
.Select(t => t.Dp!);
public static IObservable<IDataPacket> AsDataPackets<T>(this IObservable<TableUpdate<T, int>> source, IBlueTableSchema schema) where T : IBlueRow =>
@@ -156,7 +155,7 @@ public static class DataStreamExtensions {
var resDp = bd.ToDataPacket(out var newRows);
return (newRows, resDp);
})
.Where(t => t.Dp.addedRows.Any() || t.Dp.deletedRows.Any())
.Where(t => t.Dp.addedRows.Count > 0 || t.Dp.deletedRows.Count > 0)
.Select(t => t.Dp);
public static IObservable<IDataPacket> AsDataPackets<T, TKey>(this IObservable<TableUpdate<T, TKey>> source, BlueTableSchemaAdapter<T> schema) where TKey : IEquatable<TKey> {
@@ -171,9 +170,8 @@ public static class DataStreamExtensions {
(BlueTableImmutableAdapter<T> Adapter, IDataPacket DataPacket) state, TableUpdate<T, TKey> tableUpdate) {
var bd = state.Adapter.ToDataPacketBuilder();
bd.Update(tableUpdate);
var dp = bd.ToDataPacket();
var tableAdapter = bd.ToImmutable();
return (bd.ToImmutable(), dp);
var newRows = bd.ToImmutable(out var dp);
return (newRows, dp);
}
}
}
@@ -197,6 +195,46 @@ public static class DataStreamExtensions {
public static ViewStream AsViewStream<T>(this IObservable<TableUpdate<T, int>> source, IBlueTableSchema schema) where T : IBlueRow =>
source.AsDataPackets(schema).AsViewStream();
public static IObservable<IDataPacket> OnWorkScheduler(this IObservable<IDataPacket> source, IWorkScheduler scheduler) =>
Observable.Create<IDataPacket>(o => {
var inputGate = new AtomicGate();
var outputGate = new AtomicGate();
BlueTableImmutable? lastTable = null;
BlueTableImmutable.DataPacketBuilder? bd = null;
IDataPacket? pendingDpOut = null;
return Disposable.Create(source.Subscribe(OnNext), scheduler.Subscribe(DoWork));
void OnNext(IDataPacket value) {
using (inputGate.BeginTransaction()) {
lastTable ??= new(value.newRows.schema);
bd ??= lastTable.ToDataPacketBuilder();
if (pendingDpOut is null) {
// The last outgoing datapacket was just fired. Don't merge this update with the
// previous one (otherwise its adds/removes would be fired twice).
bd.Reset();
}
foreach (IBlueRow r in value.deletedRows) {
bd.Remove(r.Id());
}
foreach (IBlueRow r in value.addedRows) {
bd.AddOrUpdate(r);
}
pendingDpOut = bd.ToDataPacket(out lastTable);
}
}
void DoWork() {
IDataPacket? dpOut = null;
if (outputGate.TryEnter()) {
using var exit = Disposable.Create(outputGate.Exit);
(dpOut, pendingDpOut) = (pendingDpOut, dpOut);
if (dpOut is not null) {
o.OnNext(dpOut);
}
}
}
});
public static ViewStream GroupByApply(this ViewStream viewStream, string[] columns, Func<IBlueRowCollection, IBlueRowCollection> apply) {
var groupByTables = GroupByApplyStream.CreateGroupByTable(viewStream, columns);
var applyTables = GroupByApplyStream.CreateApplyTable(groupByTables, FunctionEvaluator.Create(apply));
@@ -0,0 +1,62 @@
namespace bts.BlueOrb {
using BCG;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Intrinsics.Arm;
// given a parent stream, creates a stream for each row in the parent stream and appends them all together
public class RowBasedAppendStream : ViewStream {
private Dictionary<string, ViewStream> _stream_map = new Dictionary<string, ViewStream>();
private MutableAppendStream _final_stream = new MutableAppendStream();
private ViewStream _parent_stream;
private string[] _key_cols;
private Func<IBlueRow, ViewStream> _make_stream;
public RowBasedAppendStream(ViewStream parent_stream,
Func<IBlueRow, ViewStream> make_stream,
params string[] key_cols) {
_parent_stream = parent_stream;
_make_stream = make_stream;
_key_cols = key_cols;
}
protected override void myOnFirstSubscriber() {
_parent_stream.onChange += _on_parent_stream_change;
_final_stream.onChange += _on_final_stream_change;
}
protected override void myOnLastSubscriber() {
_parent_stream.onChange -= _on_parent_stream_change;
_final_stream.onChange -= _on_final_stream_change;
}
private void _on_parent_stream_change(object dp_obj) {
DataPacket dp = dp_obj as DataPacket;
foreach (IBlueRow r in dp.deletedRows) {
string hash = r.get_row_cols_as_string(_key_cols);
ViewStream hash_str = null;
if (_stream_map.TryGetValue(hash, out hash_str)) {
_stream_map.Remove(hash);
_final_stream.removeStream(hash_str);
} else {
Log.assert(false);
}
}
foreach (IBlueRow r in dp.addedRows) {
string hash = r.get_row_cols_as_string(_key_cols);
ViewStream hash_str = null;
if (!_stream_map.TryGetValue(hash, out hash_str)) {
hash_str = _make_stream(r);
_stream_map.Add(hash, hash_str);
}
}
}
private void _on_final_stream_change(object dp_obj) {
myFireEvent(dp_obj);
}
}
}
@@ -517,7 +517,7 @@ namespace BCG {
rv.Add(r.get<T>(col));
return rv;
}
public static IEnumerable<T> get_colvalues<T>(IEnumerable<IBlueRow> rows, int start_idx, int n_items,string col) {
public static IEnumerable<T> get_colvalues<T>(IEnumerable<IBlueRow> rows, int start_idx, int n_items, string col) {
List<T> rv = new List<T>();
int end_idx = Math.Min(start_idx + n_items, rows.Count());
for (int i = start_idx; i < end_idx; i++) {
@@ -538,6 +538,16 @@ namespace BCG {
return rv;
}
public static string get_row_cols_as_string(this IBlueRow r, params string[] cols) {
string rv = String.Empty;
foreach (string c in cols) {
object o = r[c];
o = o ?? DBNull.Value;
rv += o.ToString() + "_";
}
return rv;
}
public static IDictionary<T, IEnumerable<IBlueRow>> group_by<T>(IBlueRowCollection rows, string gp_col) {
Dictionary<T, List<IBlueRow>> rv = new Dictionary<T, List<IBlueRow>>();
foreach (IBlueRow r in rows) {
@@ -692,7 +702,7 @@ namespace BCG {
#region json
public static void write_to_json_file(this IBlueRowCollection table, string file_path) {
public static void write_to_json_file(this IBlueRowCollection table, string file_path) {
write_dict_to_json_file(file_path, table.to_namevalue_pairs());
}
@@ -783,7 +793,7 @@ namespace BCG {
return rv;
}
public static List<Dictionary<string,string>> to_namevalue_pairs(this IBlueRowCollection table) {
public static List<Dictionary<string, string>> to_namevalue_pairs(this IBlueRowCollection table) {
List<Dictionary<string, string>> rv = new List<Dictionary<string, string>>();
foreach (IBlueRow r in table) {
rv.Add(r.to_dict());
@@ -3,6 +3,8 @@ namespace Bts.DataStreamCore;
using BCG;
using Bts.DataStream;
public class BlueRowAdapter<T> : IBlueRow {
readonly BlueTableSchemaAdapter<T> adapter;
readonly T data;
@@ -21,7 +21,7 @@ public class BlueTableImmutable : IBlueRowCollection, IImmutableRowCollection {
public BlueTableImmutable(IBlueTableSchema schema)
: this(schema, ImmutableSortedSet.Create(KeyValuePairComparer.KeyComparer<int, IBlueRow>())) { }
public BlueTableImmutable(IBlueTableSchema schema, ImmutableSortedSet<KeyValuePair<int, IBlueRow>> rows) {
BlueTableImmutable(IBlueTableSchema schema, ImmutableSortedSet<KeyValuePair<int, IBlueRow>> rows) {
this.schema = schema;
this.rows = rows;
}
@@ -43,6 +43,12 @@ public class BlueTableImmutable : IBlueRowCollection, IImmutableRowCollection {
set => throw new NotSupportedException();
}
public static DataPacketBuilder CreateBuilder(IBlueTableSchema schema) =>
new(new(schema));
public static BlueTableImmutable CreateRange(IBlueTableSchema schema, IEnumerable<KeyValuePair<int, IBlueRow>> rows) =>
new(schema, ImmutableSortedSet.CreateRange(KeyValuePairComparer.KeyComparer<int, IBlueRow>(), rows));
public IBlueRow GetRow(int index) =>
rows[index].Value;
@@ -191,12 +197,12 @@ public class BlueTableImmutable : IBlueRowCollection, IImmutableRowCollection {
}
// public class BlueTableImmutable<T> : IBlueRowCollection<T> where T : IBlueRow {
// readonly ImmutableSortedSet<KeyValuePair<int, T>> rows;
// readonly ImmutableDictionary<int, T> rows;
// public BlueTableImmutable(IBlueTableSchema schema)
// : this(schema, ImmutableSortedSet<KeyValuePair<int, T>>.Empty) { }
// : this(schema, ImmutableDictionary<int, T>.Empty) { }
// public BlueTableImmutable(IBlueTableSchema schema, ImmutableSortedSet<KeyValuePair<int, T>> rows) {
// public BlueTableImmutable(IBlueTableSchema schema, ImmutableDictionary<int, T> rows) {
// this.schema = schema;
// this.rows = rows;
// }
@@ -206,32 +212,19 @@ public class BlueTableImmutable : IBlueRowCollection, IImmutableRowCollection {
// public IBlueTableSchema schema { get; }
// public object SyncRoot => this;
// public T this[int id] =>
// #nullable disable
// rows.TryGetValue(new(id, default), out var kv)
// #nullable enable
// ? kv.Value
// : throw new KeyNotFoundException();
// public T this[int id] => rows[id];
// public bool containsID(int id) =>
// #nullable disable
// rows.Contains(new(id, default));
// #nullable enable
// public IList<object?> GetColumn(int index) =>
// rows.WrapSelector(kv => kv.Value[index], null);
// rows.ContainsKey(id);
// public void CopyTo(Array array, int index) {
// foreach (var (k, v) in rows) {
// array.SetValue(v, index++);
// foreach (var r in rows.Values) {
// array.SetValue(r, index++);
// }
// }
// public IEnumerator<T> GetEnumerator() {
// foreach (var kv in rows) {
// yield return kv.Value;
// }
// }
// public IEnumerator<T> GetEnumerator() =>
// rows.Values.GetEnumerator();
// IEnumerator IEnumerable.GetEnumerator() =>
// GetEnumerator();
@@ -241,11 +234,11 @@ public class BlueTableImmutable : IBlueRowCollection, IImmutableRowCollection {
// public class DataPacketBuilder {
// readonly IBlueTableSchema schema;
// readonly ImmutableSortedSet<KeyValuePair<int, T>>.Builder table;
// readonly ImmutableSortedSet<KeyValuePair<int, T>>.Builder added =
// ImmutableSortedSet<KeyValuePair<int, T>>.Empty.ToBuilder();
// readonly ImmutableSortedSet<KeyValuePair<int, T>>.Builder removed =
// ImmutableSortedSet<KeyValuePair<int, T>>.Empty.ToBuilder();
// readonly ImmutableDictionary<int, T>.Builder table;
// readonly ImmutableDictionary<int, T>.Builder added =
// ImmutableDictionary<int, T>.Empty.ToBuilder();
// readonly ImmutableDictionary<int, T>.Builder removed =
// ImmutableDictionary<int, T>.Empty.ToBuilder();
// public DataPacketBuilder(BlueTableImmutable<T> table) {
// schema = table.schema;
@@ -254,37 +247,30 @@ public class BlueTableImmutable : IBlueRowCollection, IImmutableRowCollection {
// public void AddOrUpdate(T row, IEqualityComparer<IBlueRow>? comparer = null) {
// var id = row.Id();
// #nullable disable
// if (table.TryGetValue(new(id, default), out var orig)) {
// #nullable enable
// if (comparer?.Equals(row, orig.Value) ?? false) {
// if (table.TryGetValue(id, out var orig)) {
// if (comparer?.Equals(row, orig) ?? false) {
// // No op: This row was already in the table (according to 'comparer').
// // Remove it from 'added' and 'removed'.
// added.Remove(new(id, row));
// removed.Remove(new(id, orig.Value));
// added.Remove(id);
// removed.Remove(id);
// return;
// }
// // Change op: A row with this id was already in the table,
// // but its values have changed (according to 'comparer').
// removed.Remove(orig);
// removed.Add(orig);
// removed[id] = orig;
// }
// added.Remove(new(id, row));
// added.Add(new(id, row));
// added[id] = row;
// }
// public void Remove(int id) {
// #nullable disable
// added.Remove(new(id, default));
// if (table.TryGetValue(new(id, default), out var orig)) {
// #nullable enable
// removed.Remove(orig);
// removed.Remove(orig);
// added.Remove(id);
// if (table.TryGetValue(id, out var orig)) {
// removed[id] = orig;
// }
// }
// public DataPacket ToDataPacket(bool reset = true) {
// table.RemoveRange(removed);
// table.RemoveRange(removed.Keys);
// table.AddRange(added);
// var newRows = new BlueTableImmutable<T>(schema, table.ToImmutable());
// var addedRows = new BlueTableImmutable<T>(schema, added.ToImmutable());
@@ -297,7 +283,7 @@ public class BlueTableImmutable : IBlueRowCollection, IImmutableRowCollection {
// }
// public IDataPacket<T> ToDataPacketT(bool reset = true) {
// table.RemoveRange(removed);
// table.RemoveRange(removed.Keys);
// table.AddRange(added);
// var newRows = new BlueTableImmutable<T>(schema, table.ToImmutable());
// var addedRows = new BlueTableImmutable<T>(schema, added.ToImmutable());
@@ -315,7 +301,7 @@ public class BlueTableImmutable : IBlueRowCollection, IImmutableRowCollection {
// out BlueTableImmutable<T> removedRows,
// bool reset = true
// ) {
// table.RemoveRange(removed);
// table.RemoveRange(removed.Keys);
// table.AddRange(added);
// newRows = new BlueTableImmutable<T>(schema, table.ToImmutable());
// addedRows = new BlueTableImmutable<T>(schema, added.ToImmutable());
@@ -339,7 +325,7 @@ public class BlueTableImmutableAdapter<T> : IBlueRowCollection {
public BlueTableImmutableAdapter(BlueTableSchemaAdapter<T> schemaAdapter)
: this(schemaAdapter, ImmutableSortedSet.Create(KeyValuePairComparer.KeyComparer<int, T>())) { }
public BlueTableImmutableAdapter(BlueTableSchemaAdapter<T> schemaAdapter, ImmutableSortedSet<KeyValuePair<int, T>> rows) {
BlueTableImmutableAdapter(BlueTableSchemaAdapter<T> schemaAdapter, ImmutableSortedSet<KeyValuePair<int, T>> rows) {
this.schemaAdapter = schemaAdapter;
this.rows = rows;
}
@@ -481,23 +467,19 @@ public class BlueTableImmutableAdapter<T> : IBlueRowCollection {
AddOrUpdate(r);
}
}
public IDataPacket ToDataPacket(bool reset = true) =>
ToDataPacket(out _, reset);
public IDataPacket ToDataPacket(out IBlueRowCollection newRows, bool reset = true) {
public BlueTableImmutableAdapter<T> ToImmutable(out IDataPacket dp, bool reset = true) {
table.RemoveRange(removed);
table.AddRange(added);
newRows = new BlueTableImmutableAdapter<T>(schemaAdapter, table.ToImmutable());
var newRows = new BlueTableImmutableAdapter<T>(schemaAdapter, table.ToImmutable());
var addedRows = new BlueTableImmutableAdapter<T>(schemaAdapter, added.ToImmutable());
var removedRows = new BlueTableImmutableAdapter<T>(schemaAdapter, removed.ToImmutable());
if (reset) {
added.Clear();
removed.Clear();
}
return new DataPacket(newRows, addedRows, removedRows);
dp = new DataPacket(newRows, addedRows, removedRows);
return newRows;
}
public BlueTableImmutableAdapter<T> ToImmutable() =>
new(schemaAdapter, table.ToImmutable());
}
}
@@ -0,0 +1,5 @@
namespace Bts.DataStreamCore;
using System;
public class LiveMarketDataStreamAttribute : Attribute { }
@@ -0,0 +1,5 @@
namespace Bts.DataStreamCore;
using System;
public class MarketDataClientParamAttribute : Attribute { }
@@ -0,0 +1,49 @@
#nullable enable
namespace Bts.DataStreamCore;
using BCG;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
/// <summary>
/// This class is a temp hack to pass a shared md client to stream constructors.
/// </summary>
public class MarketDataStreamFactory {
readonly object mdClient;
public MarketDataStreamFactory(object mdClient) =>
this.mdClient = mdClient;
public ViewStream? Create(Type type, object?[]? args) {
var ctrs = type.GetConstructors();
int nArgs = args?.Length ?? 0;
// Since this class is a temp hack, it isn't super robust. Find a constructor with nArgs + 1
// or 2 params (for mdClient and optional viewStreamDatabase), inject the special args and
// instantiate the stream. If no such constructor exists, just use the original args.
foreach (var ctr in ctrs) {
var ctrParams = ctr.GetParameters();
if (!ctrParams.TryGetFirst(p => p!.GetCustomAttribute<MarketDataClientParamAttribute>() is not null, out var mdClientParam, out int mdClientIdx)) {
continue;
}
var paramInjections = new List<(int Index, object Value)>(2) {
(mdClientIdx, mdClient)
};
if (ctrParams.TryGetFirst(p => p.ParameterType == typeof(ViewStreamDatabase), out var vsdParam, out int vsdIdx)) {
paramInjections.Add((vsdIdx, ViewStreamDatabase.instance));
}
if (nArgs != ctrParams.Length - paramInjections.Count) {
continue;
}
paramInjections.Sort(Comparer<(int, object)>.Create((a, b) => a.Item1.CompareTo(b.Item1)));
var argList = args?.ToList() ?? new();
foreach (var (idx, val) in paramInjections) {
argList.Insert(idx, val);
}
return Activator.CreateInstance(type, argList.ToArray()) as ViewStream;
}
return Activator.CreateInstance(type, args) as ViewStream;
}
}
@@ -8,9 +8,15 @@ public static partial class StreamFactoryLogs {
public static void LogCreatingStream(this ILogger logger, string streamType) =>
logger.LogCreatingStream(streamType, DateTime.Now);
public static void LogCreatingStreamError(this ILogger logger, string streamType, string error) =>
logger.LogCreatingStreamError(streamType, error, DateTime.Now);
[LoggerMessage(EventId = 1, Level = LogLevel.Warning, Message = """[crt_stream.json]{{ "typ": {StreamType}, "ts": {Timestamp} }}""")]
public static partial void LogCreatingStream(this ILogger logger, string streamType, DateTime timestamp);
[LoggerMessage(EventId = 1, Level = LogLevel.Warning, Message = """[crt_stream_err.json]{{ "typ": {StreamType}, "err": {Error}, "ts": {Timestamp} }}""")]
public static partial void LogCreatingStreamError(this ILogger logger, string streamType, string error, DateTime timestamp);
[LoggerMessage(EventId = 1, Level = LogLevel.Warning, Message = "Unable to create '{StreamType}' stream. Missing required parameter '{Parameter}'.")]
public static partial void LogMissingParam(this ILogger logger, string streamType, string parameter);
@@ -10,7 +10,7 @@ public static class WellKnownStreamArgumentAliases {
/// <summary>
/// Aliases for a Type argument.
/// </summary>
public const string Symbol = "symbol,sym,product,asset";
public const string Asset = "symbol,sym,product,asset";
/// <summary>
/// Aliases for a Type argument.
@@ -29,21 +29,19 @@ public static class WellKnownStreamArgumentAliases {
/// </summary>
public const string SecurityName = "name,sec,secName,security";
/// <summary>
/// Aliases for a SecurityType argument, excluding a few potentially ambiguous ones from
/// <see cref="SecurityType"/>.
/// </summary>
public const string QualifiedSecurityType = "secType,secTyp";
public const string QualifiedSecurityType = "secType,secTyp,securityType,securityTyp";
public const string QualifiedSecurityName = "sec,secName,security";
public const string VenueRegex = "^(venue|exchange|exch)$";
public const string AssetRegex = "^(symbol|sym|product|asset)$";
public const string AssetRegex = "^(symbol|sym|asset|product)$";
public const string TypeRegex = "^(type|typ)$";
public const string SecurityTypeRegex = "^(secType|secTyp|type|typ)$";
public const string SecurityNameRegex = "^(sec|security|name|secName|securityName)$";
public const string ShowVenueRegex = "^(show|include)(Exch|Venue)$";
public const string ShowSecurityTypeRegex = "^(show|include)(Sec|Security)?(Type|Typ)$";
public const string ShowAssetRegex = "^(show|include)(Symbol|Sym|Asset|Product)$";
public const string ShowSecurityTypeRegex = "^(show|include)(Sec|Security)?(Type|Typ)$";
public const string ShowSecurityNameRegex = "^(show|include)(Sec|Security|Name|SecName|SecurityName)$";
public const string ShowExpirationRegex = "^(show|include)(Exp(iration|Date)?)$";
public const string ShowCashPerPointRegex = "^(show|include)(CashPerPoint|Cpp)$";
@@ -55,7 +53,6 @@ public static class WellKnownStreamArgumentAliases {
public const string ShowLegSecurityTypeRegex = "^(show|include)Leg(Sec|Security)?(Type|Typ)$";
public const string ShowLegAssetRegex = "^(show|include)Leg(Symbol|Sym|Asset|Product)$";
public const string ShowLegExpirationRegex = "^(show|include)Leg(Exp(iration|Date)?)$";
public const string ShowLegSymbolRegex = "^(show|include)Leg(Symbol|Sym|Asset|Product)$";
public const string ShowLegNameRegex = "^(show|include)Leg(Name|Desc)$";
public const string ShowLegCashPerPointRegex = "^(show|include)Leg(CashPerPoint|Cpp)$";
@@ -3,6 +3,7 @@ using BCG.Utilities.General;
using System.Collections.Generic;
using System.Threading;
using System.Diagnostics;
using Bts.PubSub;
using bts;
using Bts.PubSub;
using bts.collections;
@@ -116,6 +117,8 @@ namespace BCG {
else
_workers.Add((w, interval, DateTime.MinValue));
}
void IWorkScheduler.Add(IWorker worker, TimeSpan interval) =>
addWorker(worker, interval > TimeSpan.Zero ? interval : null);
public void removeWorker(IWorker w) {
verifyAccess();
@@ -124,6 +127,8 @@ namespace BCG {
else
_workers.RemoveFirst(tup => tup.Worker == w);
}
void IWorkScheduler.Remove(IWorker worker) =>
removeWorker(worker);
internal static void SetGlobalInstance(ViewStreamDatabase vsd) {
lock (myInstanceLockObject) {
@@ -132,12 +137,6 @@ namespace BCG {
}
}
void IWorkScheduler.Add(IWorker worker, TimeSpan interval) =>
addWorker(worker, interval);
void IWorkScheduler.Remove(IWorker worker) =>
removeWorker(worker);
private List<PendingAddRemoveWorker> _pending_worker_ops = new List<PendingAddRemoveWorker>();
private bool _is_worker_call_in_progress = false;
class PendingAddRemoveWorker {
@@ -25,7 +25,7 @@ using System.Threading.Tasks;
/// An <see cref="IHostedService"/> that publishes <see cref="ViewStream"/>s to the PubSub.
/// It can be added to the application's services via <see cref="DataStreamPublisherServices.AddDataStreamPublisher"/>
/// </summary>
public class DataStreamPublisherService : IHostedService, IDisposable {
public sealed class DataStreamPublisherService : IHostedService, IDisposable {
readonly DataStreamPublisherConfig config;
readonly IPubTableBroker pubTableBroker;
readonly IWorkScheduler workScheduler;
@@ -6,7 +6,7 @@
"Interval": "00:00:00.1"
},
"Spark": {
"ConfigServer": "http://localhost:8000/config"
"ConfigServer": "http://127.0.0.1:8000/config"
},
"SparkApi": {
"Credentials": {
@@ -186,8 +186,7 @@ public class DataPacketBuilderTests {
}
static BlueTableImmutable getTable(params (int Id, double Value)[] rows) =>
new(schema, ImmutableSortedSet.CreateRange(
rows.Select(tup => new KeyValuePair<int, IBlueRow>(tup.Id, getBlueRow(tup)))));
BlueTableImmutable.CreateRange(schema, rows.Select(tup => new KeyValuePair<int, IBlueRow>(tup.Id, getBlueRow(tup))));
static MutableBlueRow getBlueRow((int id, double value) tup) =>
getBlueRow(tup.id, tup.value);
@@ -205,11 +204,10 @@ public class DataPacketBuilderTests {
(int Id, double Value)[]? removedRows = null) {
addedRows ??= Array.Empty<(int, double)>();
removedRows ??= Array.Empty<(int, double)>();
var kvpComp = new ComparerBuilder<KeyValuePair<int, IBlueRow>>() { kv => kv.Key };
return new DataPacket(
new BlueTableImmutable(schema, ImmutableSortedSet.CreateRange(kvpComp, GetRowKvps(newRows))),
new BlueTableImmutable(schema, ImmutableSortedSet.CreateRange(kvpComp, GetRowKvps(addedRows))),
new BlueTableImmutable(schema, ImmutableSortedSet.CreateRange(kvpComp, GetRowKvps(removedRows))));
BlueTableImmutable.CreateRange(schema, GetRowKvps(newRows)),
BlueTableImmutable.CreateRange(schema, GetRowKvps(addedRows)),
BlueTableImmutable.CreateRange(schema, GetRowKvps(removedRows)));
static IEnumerable<KeyValuePair<int, IBlueRow>> GetRowKvps((int Id, double Value)[] rows) =>
rows.Select(tup => new KeyValuePair<int, IBlueRow>(tup.Id, getBlueRow(tup)));
@@ -84,3 +84,4 @@ public class WorkScheduler : IWorkScheduler, IDisposable {
}
}
}
@@ -35,4 +35,10 @@ public static class WorkSchedulerExtensions {
public static IDisposable Subscribe(this IWorkScheduler scheduler, Action work, TimeSpan? interval = null) =>
scheduler.Subscribe(new AnonymousWorker(work), interval);
public static void Add(this IWorkScheduler scheduler, Action work) =>
scheduler.Add(new AnonymousWorker(work), TimeSpan.Zero);
public static void Add(this IWorkScheduler scheduler, Action work, TimeSpan interval) =>
scheduler.Add(new AnonymousWorker(work), interval);
}
@@ -1,25 +1,26 @@
namespace Bts.SharedStreams.StreamFactories;
namespace Bts.SharedStreams.MarketData;
using BCG;
using bts.api;
using bts.md;
using Bts.DataStream;
using Bts.DataStreamCore;
using Spark.Api.MarketData;
public class BookSchemaBuilder {
public int NumLevels { get; set; }
public bool ShowNotional { get; set; }
public bool ShowQuantity { get; set; } = true;
public bool ShowPrice { get; set; } = true;
public bool ShowVenue { get; set; }
public int NumLevels { get; set; } = 5;
public bool ShowVenue { get; set; } = true;
public bool ShowAsset { get; set; } = true;
public bool ShowSecurityType { get; set; }
public bool ShowSecurityName { get; set; }
public bool ShowSecurityType { get; set; } = true;
public bool ShowSecurityName { get; set; } = true;
public bool ShowSecurityId { get; set; } = true;
public bool ShowCashPerPoint { get; set; }
public bool ShowCashPerPoint { get; set; } = true;
public bool ShowPrice { get; set; } = true;
public bool ShowQuantity { get; set; } = true;
public bool ShowNotional { get; set; } = true;
public string TableName { get; set; } = "Book";
public IBlueTableSchema ToSchema() {
@@ -29,12 +30,12 @@ public class BookSchemaBuilder {
if (ShowVenue) {
bd.Add(WellKnownColumns.Venue);
}
if (ShowSecurityType) {
bd.Add(WellKnownColumns.SecurityType);
}
if (ShowAsset) {
bd.Add(WellKnownColumns.Asset);
}
if (ShowSecurityType) {
bd.Add(WellKnownColumns.SecurityType);
}
if (ShowSecurityName) {
bd.Add(WellKnownColumns.SecurityName);
}
@@ -66,12 +67,12 @@ public class BookSchemaBuilder {
if (ShowVenue) {
adapter.AddColumn(WellKnownColumns.Venue.Name, b => b.security.Exchange.ToString());
}
if (ShowSecurityType) {
adapter.AddColumn(WellKnownColumns.SecurityType.Name, b => b.security.security_type().ToString());
}
if (ShowAsset) {
adapter.AddColumn(WellKnownColumns.Asset.Name, b => b.security.Symbol);
}
if (ShowSecurityType) {
adapter.AddColumn(WellKnownColumns.SecurityType.Name, b => b.security.security_type().ToString());
}
if (ShowSecurityName) {
adapter.AddColumn(WellKnownColumns.SecurityName.Name, b => b.security.ToString());
}
@@ -103,12 +104,12 @@ public class BookSchemaBuilder {
if (ShowVenue) {
adapter.AddColumn(WellKnownColumns.Venue.Name, b => b.security.Exchange.ToString());
}
if (ShowSecurityType) {
adapter.AddColumn(WellKnownColumns.SecurityType.Name, b => b.security.security_type().ToString());
}
if (ShowAsset) {
adapter.AddColumn(WellKnownColumns.Asset.Name, b => b.security.Symbol);
}
if (ShowSecurityType) {
adapter.AddColumn(WellKnownColumns.SecurityType.Name, b => b.security.security_type().ToString());
}
if (ShowSecurityName) {
adapter.AddColumn(WellKnownColumns.SecurityName.Name, b => b.security.ToString());
}
@@ -148,3 +149,4 @@ public class BookSchemaBuilder {
static Func<BookTuple, double> notionalGetter(Side side, int level) =>
b => b.book[side, level].NotionalValue(b.security.ContractSpec.CashPerPoint);
}
@@ -0,0 +1,81 @@
namespace Bts.SharedStreams.MarketData;
using bts.api;
using Bts.DataStreamCore;
using System.Text.RegularExpressions;
public class BookStreamParams {
public Venue? Venue { get; set; }
public string? Asset { get; set; }
public SecurityType? SecurityType { get; set; }
public string? Name { get; set; }
public string? NameOrAsset { get; set; }
public string? SecurityName { get; set; }
public int NumLevels { get; set; } = 5;
public bool ShowVenue { get; set; } = true;
public bool ShowAsset { get; set; } = true;
public bool ShowSecurityType { get; set; } = true;
public bool ShowSecurityName { get; set; } = true;
public bool ShowSecurityId { get; set; } = true;
public bool ShowCashPerPoint { get; set; } = true;
public bool ShowPrice { get; set; } = true;
public bool ShowQuantity { get; set; } = true;
public bool ShowNotional { get; set; } = true;
public static BookStreamParams FromStringArg(string str, VenueArgumentParser venueParser, SecurityTypeArgumentParser secTypeParser) {
var opts = new BookStreamParams();
var arg = new StreamArgument(null, null, str);
if (venueParser.TryParse(arg, out var venue, out _)) {
opts.Venue = venue;
} else if (secTypeParser.TryParse(arg, out var secType, out _)) {
opts.SecurityType = secType;
} else {
opts.NameOrAsset = str;
}
return opts;
}
public Func<SecurityImmutable, bool> GetSecurityFilter() {
var nameRx = CreateRegex(Name);
var nameOrAssetRx = CreateRegex(NameOrAsset);
return SecurityFilter;
static Regex? CreateRegex(string? pattern) =>
pattern is null ? null : new(pattern);
bool SecurityFilter(SecurityImmutable s) {
if (Venue.HasValue && Venue.Value != s.Exchange) {
return false;
}
if (SecurityType.HasValue && SecurityType != s.security_type()) {
return false;
}
if (Asset is not null && Asset != s.Symbol) {
return false;
}
if (nameRx is not null && !nameRx.IsMatch(s.ToString()!)) {
return false;
}
if (nameOrAssetRx is not null && NameOrAsset != s.Symbol && !nameOrAssetRx.IsMatch(s.ToString()!)) {
return false;
}
return true;
}
}
public BookSchemaBuilder GetSchemaBuilder() =>
new() {
NumLevels = NumLevels,
ShowVenue = ShowVenue,
ShowAsset = ShowAsset,
ShowSecurityType = ShowSecurityType,
ShowSecurityName = ShowSecurityName,
ShowSecurityId = ShowSecurityId,
ShowCashPerPoint = ShowCashPerPoint,
ShowPrice = ShowPrice,
ShowQuantity = ShowQuantity,
ShowNotional = ShowNotional,
};
}
@@ -1,26 +1,35 @@
namespace bts.BlueOrb
{
using System;
namespace bts.BlueOrb {
using BCG;
using bts.api;
using Bts.DataStream;
using Bts.DataStreamCore;
using Bts.SharedStreams.MarketData;
[DataStreamUserRespresentation(false)]
using Spark.Api.MarketData;
[LiveMarketDataStream]
[DataStreamUserRespresentation("LiveBBOStream")]
public class LiveBBOStream : ViewStream {
public LiveBBOStream(string target)
: this(SecurityType.Future, target) { }
public LiveBBOStream(SecurityType sec_type, string target) {
ViewStream rs = null;
switch (sec_type) {
case SecurityType.Future:
rs = new LiveFuturesBBOStream(target);
break;
default:
Log.assert(false);
break;
public LiveBBOStream([MarketDataClientParam] IMarketDataClient mdClient, ViewStreamDatabase vsd)
: this(mdClient, vsd, null, null, null) { }
public LiveBBOStream([MarketDataClientParam] IMarketDataClient mdClient, ViewStreamDatabase vsd, string? venueStr, string? nameOrAsset)
: this(mdClient, vsd, venueStr, null, nameOrAsset) { }
public LiveBBOStream([MarketDataClientParam] IMarketDataClient mdClient, ViewStreamDatabase vsd, string? venueStr, string? secTypeStr, string? nameOrAsset) {
var opts = new BookStreamParams() {
NameOrAsset = nameOrAsset,
};
if (VenueArgumentParser.TryParse(venueStr, out var venue)) {
opts.Venue = venue;
}
mySetRealSource(rs);
if (SecurityTypeArgumentParser.TryParse(secTypeStr, out var secType)) {
opts.SecurityType = secType;
}
var schemaBd = opts.GetSchemaBuilder();
var adapter = schemaBd.BboAdapter();
var secFilter = opts.GetSecurityFilter();
mySetRealSource(mdClient.Books(secFilter).AsDataPackets(adapter).OnWorkScheduler(vsd).AsViewStream());
}
}
}
@@ -42,9 +42,9 @@ public class SecurityTypeArgumentParser : IStreamArgumentParser<SecurityType> {
if (TryParse(str, out result)) {
return true;
}
error = $"Failed to parse secType from string '{str}'";
error = $"Failed to parse SecurityType from string '{str}'";
} else {
error = $"Venue parser received input of unexpected type '{val?.GetType()}'";
error = $"SecurityType parser received input of unexpected type '{val?.GetType()}'";
}
return false;
}
@@ -7,20 +7,20 @@ using Bts.DataStreamCore;
public class VenueArgumentParser : IStreamArgumentParser<Venue> {
public static readonly VenueArgumentParser Instance = new();
public static bool TryParse(string? str, out Venue v) {
public static bool TryParse(string? str, out Venue result) {
if (str is null) {
v = Venue.Unset;
result = Venue.Unset;
return false;
}
if (Enum.TryParse(str, true, out v) || Enum.TryParse("VENUE_" + str, true, out v)) {
if (Enum.TryParse(str, true, out result) || Enum.TryParse("VENUE_" + str, true, out result)) {
return true;
}
v = str.ToUpper() switch {
result = str.ToUpper() switch {
"CME" or "XCME" or "CEC" or "XCEC" => Venue.Glbx,
"CBOT" or "CBT" or "XCBT" => Venue.Cboe,
_ => Venue.Unset,
};
return v != Venue.Unset;
return result != Venue.Unset;
}
public bool CanParse(StreamArgument arg, Type outputType) =>
@@ -1,71 +1,69 @@
namespace Bts.SharedStreams.StreamFactories;
namespace Bts.SharedStreams;
using bts.api;
using Spark.Api.Orders;
public static class WellKnownColumnNames {
public const string Price = "price";
public const string Quantity = "qty";
public const string Timestamp = "ts_event";
public const string BidPrice = "bid_price";
public const string BidQuantity = "bid_size";
public const string BidNotional = "bid_notl";
public const string AskPrice = "ask_price";
public const string Currency = "currency";
public const string AskQuantity = "ask_size";
public const string AskNotional = "ask_notl";
public const string SecurityType = "sec_type";
public const string Asset = "asset";
public const string SecurityName = "sec_name";
public const string SecurityId = "SecId";
public const string Expiration = "expiration";
public const string Venue = "exchange";
public const string TradingStatus = "trading_status";
public const string NumLegs = "num_legs";
public const string CashPerPoint = "cpp";
public static string DepthPrice(Side side, int level) => $"{side.BidAskName()}Price{level}";
public static string DepthQuantity(Side side, int level) => $"{side.BidAskName()}Qty{level}";
public static string DepthNotional(Side side, int level) => $"{side.BidAskName()}Notl{level}";
public static string LegSecurityId(int legNum) => $"Leg{legNum}SecId";
public static string LegVenue(int legNum) => $"Leg{legNum}Venue";
public static string LegSecurityType(int legNum) => $"Leg{legNum}SecType";
public static string LegAsset(int legNum) => $"Leg{legNum}Asset";
public static string LegSecurityName(int legNum) => $"Leg{legNum}Sec";
public static string LegExpiration(int legNum) => $"Leg{legNum}Exp";
public static string LegCashPerPoint(int legNum) => $"Leg{legNum}Cpp";
}
public static class WellKnownColumns {
public static (string Name, Type Type) SecurityId => (WellKnownColumnNames.SecurityId, typeof(uint));
public static (string Name, Type Type) Venue => (WellKnownColumnNames.Venue, typeof(string));
public static (string Name, Type Type) SecurityType => (WellKnownColumnNames.SecurityType, typeof(string));
public static (string Name, Type Type) AskNotional => (WellKnownColumnNames.AskNotional, typeof(double));
public static (string Name, Type Type) AskPrice => (WellKnownColumnNames.AskPrice, typeof(double));
public static (string Name, Type Type) AskQuantity => (WellKnownColumnNames.AskQuantity, typeof(double));
public static (string Name, Type Type) Asset => (WellKnownColumnNames.Asset, typeof(string));
public static (string Name, Type Type) SecurityName => (WellKnownColumnNames.SecurityName, typeof(string));
public static (string Name, Type Type) BidNotional => (WellKnownColumnNames.BidNotional, typeof(double));
public static (string Name, Type Type) BidPrice => (WellKnownColumnNames.BidPrice, typeof(double));
public static (string Name, Type Type) BidQuantity => (WellKnownColumnNames.BidQuantity, typeof(double));
public static (string Name, Type Type) CashPerPoint => (WellKnownColumnNames.CashPerPoint, typeof(double));
public static (string Name, Type Type) Price => (WellKnownColumnNames.Price, typeof(double));
public static (string Name, Type Type) Quantity => (WellKnownColumnNames.Quantity, typeof(double));
public static (string Name, Type Type) SecurityId => (WellKnownColumnNames.SecurityId, typeof(uint));
public static (string Name, Type Type) SecurityName => (WellKnownColumnNames.SecurityName, typeof(string));
public static (string Name, Type Type) SecurityType => (WellKnownColumnNames.SecurityType, typeof(string));
public static (string Name, Type Type) Timestamp => (WellKnownColumnNames.Timestamp, typeof(DateTime));
public static (string Name, Type Type) TimestampNanos => ("ts_event_ns", typeof(long));
public static (string Name, Type Type) BidPrice => (WellKnownColumnNames.BidPrice, typeof(double));
public static (string Name, Type Type) BidQuantity => (WellKnownColumnNames.BidQuantity, typeof(double));
public static (string Name, Type Type) BidNotional => ("bid_notl", typeof(double));
public static (string Name, Type Type) AskPrice => (WellKnownColumnNames.AskPrice, typeof(double));
public static (string Name, Type Type) AskQuantity => (WellKnownColumnNames.AskQuantity, typeof(double));
public static (string Name, Type Type) AskNotional => ("ask_notl", typeof(double));
public static (string Name, Type Type) Venue => (WellKnownColumnNames.Venue, typeof(string));
public static (string Name, Type Type) DepthNotional(Side side, int level) => (WellKnownColumnNames.DepthNotional(side, level), typeof(double));
public static (string Name, Type Type) DepthPrice(Side side, int level) => (WellKnownColumnNames.DepthPrice(side, level), typeof(double));
public static (string Name, Type Type) DepthQuantity(Side side, int level) => (WellKnownColumnNames.DepthQuantity(side, level), typeof(double));
public static (string Name, Type Type) DepthNotional(Side side, int level) => (WellKnownColumnNames.DepthNotional(side, level), typeof(double));
public static (string Name, Type Type) LegSecurityId(int legNum) => (WellKnownColumnNames.LegSecurityId(legNum), typeof(uint));
public static (string Name, Type Type) LegVenue(int legNum) => (WellKnownColumnNames.LegVenue(legNum), typeof(string));
public static (string Name, Type Type) LegSecurityType(int legNum) => (WellKnownColumnNames.LegSecurityType(legNum), typeof(string));
public static (string Name, Type Type) LegAsset(int legNum) => (WellKnownColumnNames.LegAsset(legNum), typeof(string));
public static (string Name, Type Type) LegSecurityName(int legNum) => (WellKnownColumnNames.LegSecurityName(legNum), typeof(string));
public static (string Name, Type Type) LegExpiration(int legNum) => (WellKnownColumnNames.LegExpiration(legNum), typeof(DateTime));
public static (string Name, Type Type) LegCashPerPoint(int legNum) => (WellKnownColumnNames.LegCashPerPoint(legNum), typeof(double));
public static (string Name, Type Type) LegExpiration(int legNum) => (WellKnownColumnNames.LegExpiration(legNum), typeof(DateTime));
public static (string Name, Type Type) LegSecurityId(int legNum) => (WellKnownColumnNames.LegSecurityId(legNum), typeof(uint));
public static (string Name, Type Type) LegSecurityName(int legNum) => (WellKnownColumnNames.LegSecurityName(legNum), typeof(string));
public static (string Name, Type Type) LegSecurityType(int legNum) => (WellKnownColumnNames.LegSecurityType(legNum), typeof(string));
public static (string Name, Type Type) LegVenue(int legNum) => (WellKnownColumnNames.LegVenue(legNum), typeof(string));
}
public static class WellKnownColumnNames {
public const string AskNotional = "ask_notl";
public const string AskPrice = "ask_price";
public const string AskQuantity = "ask_qty";
public const string Asset = "asset";
public const string BidNotional = "bid_notl";
public const string BidPrice = "bid_price";
public const string BidQuantity = "bid_qty";
public const string CashPerPoint = "cpp";
public const string Currency = "currency";
public const string Expiration = "exp";
public const string NumLegs = "num_legs";
public const string Price = "price";
public const string Quantity = "qty";
public const string SecurityId = "sec_id";
public const string SecurityName = "sec_name";
public const string SecurityType = "sec_type";
public const string Timestamp = "ts_event";
public const string TradingStatus = "trd_status";
public const string Venue = "venue";
public static string DepthNotional(Side side, int level) => $"{side.BidAskNameLower()}_notl_{level}";
public static string DepthPrice(Side side, int level) => $"{side.BidAskNameLower()}_price_{level}";
public static string DepthQuantity(Side side, int level) => $"{side.BidAskNameLower()}_qty_{level}";
public static string LegAsset(int legNum) => $"leg_{legNum}_{Asset}";
public static string LegCashPerPoint(int legNum) => $"leg_{legNum}_{CashPerPoint}";
public static string LegExpiration(int legNum) => $"leg_{legNum}_{Expiration}";
public static string LegSecurityId(int legNum) => $"leg_{legNum}_{SecurityId}";
public static string LegSecurityName(int legNum) => $"leg_{legNum}_{SecurityName}";
public static string LegSecurityType(int legNum) => $"leg_{legNum}_{SecurityType}";
public static string LegVenue(int legNum) => $"leg_{legNum}_{Venue}";
}
@@ -2,202 +2,113 @@ namespace Bts.SharedStreams.StreamFactories;
using BCG;
using bts.api;
using bts.BlueOrb;
using bts.collections;
using Bts.DataStream;
using Bts.DataStreamCore;
using Bts.DataStreamCore.StreamFactories;
using Bts.PubSub;
using Bts.SharedStreams.MarketData;
using Microsoft.Extensions.Logging;
using Spark.Api.MarketData;
using Spark.Core.Reactive;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reactive.Linq;
public class BookStreamFactory : DataStreamFactory, IRecursiveDataStreamFactory {
readonly LiveBboStreamFactory liveBboFactory;
readonly IMarketDataClient? mdClient;
public class BookStreamFactory : DataStreamFactory {
readonly VenueArgumentParser venueParser = VenueArgumentParser.Instance;
readonly SecurityTypeArgumentParser secTypeParser = SecurityTypeArgumentParser.Instance;
readonly StringArgumentParser strParser = StringArgumentParser.Instance;
readonly IMarketDataClient mdClient;
readonly ILogger logger;
readonly IWorkScheduler workScheduler;
IDataStreamFactory? compositeFactory = null;
public BookStreamFactory(IMarketDataClient? mdClient, ILogger<BookStreamFactory> logger, ViewStreamDatabase workScheduler) {
public BookStreamFactory(IMarketDataClient mdClient, ViewStreamDatabase viewStreamDatabase, ILogger<BookStreamFactory> logger)
: this(mdClient, viewStreamDatabase, (ILogger)logger) { }
BookStreamFactory(IMarketDataClient mdClient, IWorkScheduler workScheduler, ILogger logger) {
this.mdClient = mdClient;
this.logger = logger;
this.workScheduler = workScheduler;
this.Add<BookStreamParams>(new[] { "book" }, CreateBook, logger);
this.Add<BookStreamParams>(new[] { "bbo" }, CreateBbo, logger);
this.Add<BookStreamParams>(new[] { "bboPriceSummary" }, createBboPriceSummary, logger);
liveBboFactory = new LiveBboStreamFactory(mdClient, logger);
Add(liveBboFactory);
ViewStream? CreateBook(BookStreamParams streamParams) {
if (!checkHasLiveMd()) {
return null;
}
return this.mdClient.Books(streamParams.GetSecurityFilter())
.AsDataPackets(streamParams.GetSchemaBuilder().BookTupleAdapter())
.AsViewStream()
.TimeSeries(streamParams.BufferSize, WellKnownColumnNames.Timestamp);
}
ViewStream? CreateBbo(BookStreamParams streamParams) {
if (!checkHasLiveMd()) {
return null;
}
return this.mdClient.Books(streamParams.GetSecurityFilter())
.AsDataPackets(streamParams.GetSchemaBuilder().BboAdapter())
.AsViewStream()
.TimeSeries(streamParams.BufferSize, WellKnownColumnNames.Timestamp);
}
this.Add("book", createBook, venueParser);
this.Add("book", createBook, secTypeParser);
this.Add("book", createBook, strParser);
this.Add("book", createBook, venueParser, secTypeParser);
this.Add("book", createBook, venueParser, strParser);
this.Add("book", createBook, secTypeParser, strParser);
this.Add("bbo", createBbo, strParser);
this.Add("bbo", createBbo, venueParser);
this.Add("bbo", createBbo, venueParser, secTypeParser);
this.Add("bboPriceSummary", createBboPriceSummary, StringArgumentParser.Instance);
this.Add("bboPriceSummary", createBboPriceSummary, venueParser);
this.Add("bboPriceSummary", createBboPriceSummary, venueParser, secTypeParser);
}
public void OnCompositeFactory(IDataStreamFactory compositeFactory) {
this.compositeFactory = compositeFactory;
liveBboFactory.OnCompositeFactory(compositeFactory);
}
ViewStream? createBook(Venue venue) =>
createBook(new BookStreamParams() { Venue = venue });
ViewStream? createBboPriceSummary(BookStreamParams streamParams) {
if (!checkHasLiveMd()) {
ViewStream? createBook(SecurityType secType) =>
createBook(new BookStreamParams() { SecurityType = secType });
ViewStream? createBook(Venue venue, SecurityType secType) =>
createBook(new BookStreamParams() { Venue = venue, SecurityType = secType });
ViewStream? createBook(Venue venue, string nameOrAsset) =>
createBook(new BookStreamParams() { Venue = venue, NameOrAsset = nameOrAsset });
ViewStream? createBook(SecurityType secType, string nameOrAsset) =>
createBook(new BookStreamParams() { SecurityType = secType, NameOrAsset = nameOrAsset });
ViewStream? createBook(string str) =>
createBook(BookStreamParams.FromStringArg(str, venueParser, secTypeParser));
ViewStream? createBook(BookStreamParams streamParams) =>
mdClient.Books(streamParams.GetSecurityFilter())
.AsDataPackets(streamParams.GetSchemaBuilder().BookTupleAdapter())
.AsViewStream();
ViewStream? createBbo(string str) =>
createBbo(BookStreamParams.FromStringArg(str, venueParser, secTypeParser));
ViewStream? createBbo(Venue venue) =>
createBbo(new BookStreamParams() { Venue = venue });
ViewStream? createBbo(Venue venue, SecurityType secType) =>
createBbo(new BookStreamParams() { Venue = venue, SecurityType = secType });
ViewStream? createBbo(BookStreamParams streamParams) =>
mdClient.Books(streamParams.GetSecurityFilter())
.AsDataPackets(streamParams.GetSchemaBuilder().BboAdapter())
.AsViewStream();
ViewStream? createBboPriceSummary(string str) =>
createBboPriceSummary(BookStreamParams.FromStringArg(str, venueParser, secTypeParser));
ViewStream? createBboPriceSummary(Venue venue) =>
createBboPriceSummary(new BookStreamParams() { Venue = venue });
ViewStream? createBboPriceSummary(SecurityType secType) =>
createBboPriceSummary(new BookStreamParams() { SecurityType = secType });
ViewStream? createBboPriceSummary(Venue venue, SecurityType secType) =>
createBboPriceSummary(new BookStreamParams() { Venue = venue, SecurityType = secType });
ViewStream? createBboPriceSummary(BookStreamParams opts) {
var bboStream = createBbo(opts);
if (bboStream is null) {
logger.LogCreatingStreamError("bboPriceSummary", "failed to create bboStream");
return null;
}
var secCriteria = streamParams.GetSecurityFilter();
mdClient.Books(secCriteria).WithHistory().Select(t => t.Table);
// var bboStream = mdClient.Books(secCriteria).AsDataPackets(streamParams.GetSchemaBuilder().BboAdapter());
var bboStream = liveBboFactory.Create(streamParams)
?? throw new ArgumentException("Failed to create bbo stream", nameof(streamParams));
var bboTimeSeriesStream = new TimeSeriesStream(bboStream, streamParams.BufferSize, WellKnownColumnNames.Timestamp);
ViewStream? tradeStream = null;
if (streamParams.Asset == "ES" && compositeFactory is not null) {
tradeStream = compositeFactory.Create("ReplayFutureLiveTradeStream", new (string?, int?, object?)[] { ("contract", 0, streamParams.Asset) });
if (tradeStream is null) {
logger.LogError("Failed to create trade stream for bboPriceSummary stream");
return null;
}
tradeStream = tradeStream.RenameColumns(true, ("trade_price", WellKnownColumnNames.Price), ("trade_size", WellKnownColumnNames.Quantity));
} else {
var tradeSchemaBd = new TradeSchemaBuilder();
var tradeSchema = tradeSchemaBd.ToSchema();
var tradeAdapter = tradeSchemaBd.TradeInfoAdapter();
tradeStream = LiveTradeStream.TradeStream(mdClient.Trades(secCriteria), tradeSchema, tradeAdapter).AsViewStream();
}
var bboTimeSeriesStream = new TimeSeriesStream(bboStream, opts.BufferSize, WellKnownColumnNames.Timestamp);
var tradeSchemaBd = new TradeSchemaBuilder();
var tradeSchema = tradeSchemaBd.ToSchema();
var tradeAdapter = tradeSchemaBd.TradeInfoAdapter();
var secFilter = opts.GetSecurityFilter();
var tradeStream = LiveTradeStream.TradeStream(mdClient.Trades(secFilter), tradeSchema, tradeAdapter).AsViewStream();
var bboPriceSummaryStream = new BboPriceSummaryStream(bboStream.AsObservable(), tradeStream.AsObservable(), workScheduler, TimeSpan.FromSeconds(1));
return bboPriceSummaryStream;
}
static bool checkHasLiveMd([NotNullWhen(true)] IMarketDataClient? mdClient, ILogger? logger = null) {
if (mdClient is not null) {
return true;
}
logger?.LogError("Market data is unavailable (md client is null)");
return false;
}
[MemberNotNullWhen(true, nameof(mdClient))]
bool checkHasLiveMd() =>
checkHasLiveMd(mdClient, logger);
class LiveBboStreamFactory : IDataStreamFactory, IRecursiveDataStreamFactory {
static readonly IDataStreamMetadata metadata = new DataStreamMetadata() {
Name = "LiveBBOStream",
AlternateNames = new(),
Signatures = new() {
new (string?, Type?)[] {
("symbol", typeof(string))
},
new (string?, Type?)[] {
("secType", typeof(string)),
("symbol", typeof(string)),
}
}
};
readonly IMarketDataClient? mdClient;
readonly ILogger logger;
IDataStreamFactory? compositeFactory = null;
public LiveBboStreamFactory(IMarketDataClient? mdClient, ILogger logger) {
this.mdClient = mdClient;
this.logger = logger;
}
public void OnCompositeFactory(IDataStreamFactory compositeFactory) =>
this.compositeFactory = compositeFactory;
public ViewStream? Create(string streamId, IEnumerable<StreamArgument> args) {
if (!metadata.IsMatch(streamId)) {
return null;
}
var argList = args.ToList();
switch (argList.Count) {
case 1: {
var sym = argList[0].Value?.ToString() ?? throw new ArgumentException("Symbol arg is null or empty", nameof(args));
if (sym == "ES" && compositeFactory is not null) {
return compositeFactory.Create("ReplayFutureLiveBBOStream", new (string?, int?, object?)[] { ("contract", 0, sym) });
}
var streamParams = new BookStreamParams() {
Asset = sym
};
return mdClient?.Books(streamParams.GetSecurityFilter())
?.AsDataPackets(streamParams.GetSchemaBuilder().BboAdapter())
?.AsViewStream()
?? throw new InvalidOperationException("Live md not available");
}
case 2: {
var secTypeStr = argList[0].Value?.ToString() ?? string.Empty;
if (!MarketDataArgStore.TryParseSecurityType(secTypeStr, out var secType)) {
throw new ArgumentException("Failed to parse SecurityType arg", nameof(args));
}
var sym = argList[1].Value?.ToString() ?? throw new ArgumentException("Symbol arg is null or empty", nameof(args));
if (sym == "ES" && secType == bts.api.SecurityType.Future && compositeFactory is not null) {
return compositeFactory.Create("ReplayFutureLiveBBOStream", new (string?, int?, object?)[] { ("contract", 0, sym) });
}
var streamParams = new BookStreamParams() {
Type = secType,
Asset = sym,
};
return mdClient?.Books(streamParams.GetSecurityFilter())
?.AsDataPackets(streamParams.GetSchemaBuilder().BboAdapter())
?.AsViewStream()
?? throw new InvalidOperationException("Live md not available");
}
default:
return null;
}
}
public ViewStream? Create(BookStreamParams streamParams) {
var secType = streamParams.Type;
if (streamParams.Asset == "ES" && (!secType.HasValue || secType.Value == bts.api.SecurityType.Future) && compositeFactory is not null) {
return compositeFactory.Create("ReplayFutureLiveBBOStream", new (string?, int?, object?)[] { ("contract", 0, streamParams.Asset) });
}
if (!checkHasLiveMd()) {
return null;
}
return mdClient.Books(streamParams.GetSecurityFilter())
.AsDataPackets(streamParams.GetSchemaBuilder().BboAdapter())
.AsViewStream();
}
public IEnumerable<IDataStreamMetadata> GetSupportedStreams() {
yield return metadata;
}
[MemberNotNullWhen(true, nameof(mdClient))]
bool checkHasLiveMd() =>
BookStreamFactory.checkHasLiveMd(mdClient, logger);
}
public class LiveBboStreamParams {
[StreamArgument(RegexNames = new[] { WellKnownStreamArgumentAliases.SecurityTypeRegex }, Position = 0, IsRequired = false)]
public string? SecurityType { get; set; }
[StreamArgument(RegexNames = new[] { WellKnownStreamArgumentAliases.AssetRegex }, Position = 1, IsRequired = false)]
public string? ContinuousContract { get; set; }
}
}
@@ -1,55 +0,0 @@
namespace Bts.SharedStreams.StreamFactories;
using bts.api;
public class BookStreamParams {
public Venue? Venue { get; set; }
public string? Asset { get; set; }
public string? Name { get; set; }
public string? NameOrAsset { get; set; }
public SecurityType? Type { get; set; }
public string? SecurityName { get; set; }
public int NumLevels { get; set; } = 5;
public bool ShowPrice { get; set; } = true;
public bool ShowQuantity { get; set; } = true;
public bool ShowNotional { get; set; }
public bool ShowCashPerPoint { get; set; } = true;
public int BufferSize { get; set; } = 10_000;
public Func<SecurityImmutable, bool> GetSecurityFilter() {
return SecurityFilter;
bool SecurityFilter(SecurityImmutable s) {
if (Venue.HasValue && Venue.Value != s.Exchange) {
return false;
}
if (Type.HasValue && Type != s.security_type()) {
return false;
}
if (Asset is not null && Asset != s.Symbol) {
return false;
}
if (Name is not null && Name != s.ToString()) {
return false;
}
if (NameOrAsset is not null && NameOrAsset != s.Symbol && NameOrAsset != s.ToString()) {
return false;
}
return true;
}
}
public BookSchemaBuilder GetSchemaBuilder() =>
new() {
NumLevels = NumLevels,
ShowVenue = true,
ShowSecurityType = true,
ShowAsset = true,
ShowSecurityName = true,
ShowSecurityId = true,
ShowPrice = ShowPrice,
ShowQuantity = ShowQuantity,
ShowNotional = ShowNotional,
ShowCashPerPoint = ShowCashPerPoint,
};
}
@@ -2,14 +2,11 @@ namespace Bts.SharedStreams.StreamFactories;
using BCG;
using bts;
using bts.api;
using bts.BlueOrb;
using bts.BlueOrb.Parquet;
using bts.collections;
using Bts.DataStream;
using Bts.DataStream.BlueOrb;
using Bts.DataStreamCore;
using Bts.DataStreamCore.StreamFactories;
using Bts.SharedStreams.MarketData;
@@ -18,11 +15,8 @@ using Microsoft.Extensions.Logging;
using Spark.Core.Collections;
using System.Collections;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Reactive.Linq;
using System.Threading.Tasks;
class HistoricParquetDataLoader : DataStreamFactory {
public HistoricParquetDataLoader(ViewStreamDatabase viewStreamDatabase, ILogger<HistoricParquetDataLoader> logger) {
@@ -22,7 +22,7 @@ namespace mbts.ui.converters {
break;
default:
if (values[0] is IMultiValueConverter mvc) {
ArrayUtils.remove_at(ref values, 0);
values = ArrayUtils.RemoveAt(values, 0);
return mvc.Convert(values, targetType, parameter, culture);
}
break;
@@ -405,7 +405,7 @@ namespace Spark.Api.Orders {
side switch {
Side.Buy => "bid",
Side.Sell => "ask",
_ => side.ToString(),
_ => side.ToString().ToLower(),
};
// public static Brush badge_fill(AlgoDefinitionMetadata? algo_meta, OrderMetadata order_meta) =>
+16 -16
View File
@@ -10,25 +10,24 @@ using System.Threading;
using System.Threading.Tasks;
public class AtomicGate {
const int MAX_ATTEMPTS_BEFORE_SLEEP = 32;
const int maxAttemptsBeforeSleep = 32;
static readonly int[] increasingSleeps = new[] { 1, 10, 100, 200, 500, 1000 };
static int id_counter = 0;
static int idCounter = 0;
readonly string name;
readonly int id = Interlocked.Increment(ref id_counter);
readonly int id = Interlocked.Increment(ref idCounter);
readonly object syncRoot = new();
int versionIn = 0;
int versionOut = 0;
public AtomicGate(string? name = null) {
public AtomicGate(string? name = null) =>
this.name = name ?? string.Empty;
}
public bool IsBusy => Volatile.Read(ref versionIn) != Volatile.Read(ref versionOut);
public void Enter() {
int n;
for (n = 0; n < MAX_ATTEMPTS_BEFORE_SLEEP; n++) {
for (n = 0; n < maxAttemptsBeforeSleep; n++) {
if (TryEnter()) {
return;
}
@@ -46,13 +45,13 @@ public class AtomicGate {
}
public bool TryEnter() {
var vin = Volatile.Read(ref versionIn);
var vout = Volatile.Read(ref versionOut);
Log.DataFlow.diag($"[atomic_gate_try_entering.json]{{ \"id\": {id}, \"name\": \"{name}\", \"vin\": {vin}, \"vout\": {vout}, \"ts\": \"{DateTime.Now}\" }}");
// var vin = Volatile.Read(ref versionIn);
// var vout = Volatile.Read(ref versionOut);
// Log.DataFlow.diag($$"""[atomic_gate_try_entering.json]{ "id": {{id}}, "name": "{{name}}", "vin": {{vin}}, "vout": {{vout}}, "ts": "{{DateTime.Now}}" }""");
bool res = Impl();
vin = Volatile.Read(ref versionIn);
vout = Volatile.Read(ref versionOut);
Log.DataFlow.diag($"[atomic_gate_try_entered.json]{{ \"id\": {id}, \"name\": \"{name}\", \"vin\": {vin}, \"vout\": {vout}, \"res\": {res}, \"ts\": \"{DateTime.Now}\" }}");
// vin = Volatile.Read(ref versionIn);
// vout = Volatile.Read(ref versionOut);
// Log.DataFlow.diag($$"""[atomic_gate_try_entered.json]{ "id": {{id}}, "name": "{{name}}", "vin": {{vin}}, "vout": {{vout}}, "res": {{res}}, "ts": "{{DateTime.Now}}" }""");
return res;
bool Impl() {
@@ -116,7 +115,7 @@ public class AtomicGate {
int version = Interlocked.Increment(ref versionIn) - 1;
int n = 0;
var end = Disposable.Create(EndTransaction);
for (n = 0; n < MAX_ATTEMPTS_BEFORE_SLEEP; n++) {
for (n = 0; n < maxAttemptsBeforeSleep; n++) {
if (Volatile.Read(ref versionOut) == version) {
return end;
}
@@ -148,9 +147,10 @@ public class AtomicGate {
}
void sleep(int sleepMillis, int? version = null) {
var vout = Volatile.Read(ref versionOut);
var vin = Volatile.Read(ref versionIn);
Log.DataFlow.warn($"[atomic_gate_sleep.json]{{ \"id\": {id}, \"name\": \"{name}\", \"vin\": {vin}, \"vout\": {vout}, \"v\": {version}, \"dur\": {sleepMillis}ms, \"ts\": \"{DateTime.Now}\" }}");
// var vout = Volatile.Read(ref versionOut);
// var vin = Volatile.Read(ref versionIn);
// Log.DataFlow.warn($$"""[atomic_gate_sleep.json]{ "id": {id}, "name": "{{name}}", "vin": {vin}, "vout": {{vout}}, "v": {{version}}, "dur": {{sleepMillis}}ms, "ts": "{{DateTime.Now}}" }""");
Thread.Sleep(sleepMillis);
}
}