Compare commits

..
Author SHA1 Message Date
John Colvin 1f685c7e95 WellKnownColumns.cs 2025-11-18 11:57:39 -05:00
John Colvin 5568c4051d TradeStreamParams.cs 2025-11-18 11:57:33 -05:00
John Colvin 389550e5cd Rm GroupByApplyStreamParams.cs 2025-11-18 11:57:23 -05:00
John Colvin d73d12ec31 GroupByApplyStreamFactory.cs 2025-11-18 11:57:08 -05:00
John Colvin 387891521b BookStreamParams.cs 2025-11-18 11:56:55 -05:00
John Colvin 1d329760f0 BookStreamFactory.cs 2025-11-18 11:56:46 -05:00
John Colvin f6fe4194d2 fix replace stream arg tuples with dedicated type 2025-11-18 11:56:14 -05:00
John Colvin f93f13048a Get rid of unused FilterStreamFactory 2025-11-18 11:55:48 -05:00
John Colvin 5995a00d69 fix replace stream arg tuples with dedicated type 2025-11-18 11:55:32 -05:00
John Colvin 226999ae5d Add ViewStreamFactory.cs 2025-11-18 11:55:00 -05:00
John Colvin 7b88b35b03 Add StreamFactoryLog "LogCreatingStream" 2025-11-18 11:54:51 -05:00
John Colvin fdf2cacd1d fix check pt misc changes 2025-11-18 11:54:24 -05:00
John Colvin 865940daf3 fix replace stream arg tuples with dedicated 2025-11-18 11:52:48 -05:00
John Colvin 10770c64e4 fix load IDataStreamFactory(s) - adds Enumerable ext TryGetExactCount 2025-11-18 11:50:49 -05:00
John Colvin c6a4b91c27 refactor: Add MaybeNullWhen(false) attr to TryGetSingle ext fn 2025-11-18 11:50:15 -05:00
John Colvin c0b7b39091 fix load IDataStreamFactory(s) (DataStreamFactory.cs) 2025-11-18 11:49:31 -05:00
John Colvin 4e198183eb Add SecType, Venue arg parser for streams 2025-11-17 10:34:46 -05:00
John Colvin eda34e0e3b check pt: misc changes 2025-11-17 10:34:13 -05:00
John Colvin 6532c329ac refactor: Replace stream arg tuples with dedicated type 2025-11-17 10:33:01 -05:00
John Colvin 588af742bf refactor: Rename ArrayHelper -> ArrayUtils
Also change snake_case fn names to PascalCase
2025-11-15 12:47:24 -05:00
John Colvin fe5fab1fa0 fix: Check for 0 length array when expanding for insert 2025-11-15 12:46:26 -05:00
John Colvin b8944da8ed chore: Add AsyncViewStream wrapper
Converts a Task<ViewStream> to a ViewStream.
2025-11-15 12:46:26 -05:00
John Colvin fca10887ce 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-15 12:46:26 -05:00
John Colvin 8f53ff5ff9 vscode: Launch DSQA with config via DOTNET_ENVIRONMENT env var 2025-11-15 12:46:26 -05:00
John Colvin 1178aaf410 Add appsettings.Local.json to gitignore (DataStreamSuite) 2025-11-15 12:46:26 -05:00
John Colvin 62b790a156 feat: Add modulus operator to stream DSL 2025-11-15 12:46:25 -05:00
John Colvin 1fc77869e9 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-15 12:46:25 -05:00
John Colvin 3b91a697e9 improve: Support arbitrary columns in py table_formatter
Not just key/values
2025-11-15 12:46:25 -05:00
John Colvin 8908cc300a chore: Add IDataStreamFactory for FilterStream 2025-11-15 12:46:25 -05:00
John Colvin 5d039b54f5 More jupyter/pandas analysis for Brandon Chen 2025-11-15 12:46:25 -05:00
John Colvin 21f044cc3b refactor: Map TimeSpan to TypeCode.Int64 2025-11-15 12:46:25 -05:00
John Colvin 55c132a033 chore: Add OnWorkScheduler ext to recv IDataPackets on scheduler/thread 2025-11-15 12:46:25 -05:00
John Colvin ddd0de8eca perf: Reset grid when the schema changes in DSQA (not every update) 2025-11-15 12:46:25 -05:00
John Colvin f4451ad2f4 cleanup: Remove commented out code from DSQA MainForm.cs 2025-11-15 12:46:25 -05:00
John Colvin 12d9185b6e 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-15 12:46:25 -05:00
John Colvin 84c3759d17 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-15 12:46:25 -05:00
John Colvin 618a18b927 refactor: Automatically add id column in BlueTableSchemaBuilder 2025-11-15 12:46:25 -05:00
John Colvin 1e25099c62 cleanup: Enable nullable ref types in IBlueRowCollection.cs 2025-11-15 12:46:25 -05:00
John Colvin afeb5babd4 improve: Support enum types in ColumnInfo 2025-11-15 12:46:25 -05:00
John Colvin 0ff372a63e refactor: Add default impl for columnInfo indexers in IBlueTableSchema 2025-11-15 12:46:25 -05:00
John Colvin 197a81443c 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-15 12:46:25 -05:00
John Colvin ef98a7eda6 chore: Add prompt examples for BrandonChen 2025-11-15 12:46:24 -05:00
John Colvin 94e9c8c3f2 improve: Generate MLP at runtime with list of example files
Adds prompt file list to NaturalLanguageStreamFactoryConfig
2025-11-15 12:46:24 -05:00
John Colvin 0f9e515d01 chore: Add TableDropIndexEvaluator 2025-11-15 12:46:24 -05:00
John Colvin 342ecd9a52 chore: Add SelectorStream/Table 2025-11-15 12:46:24 -05:00
John Colvin d0c316b3fc chore: Add IndexStream/Table and DropIndex ext fn similar to pandas 2025-11-15 12:46:24 -05:00
John Colvin 44db6190a1 fix: Make PythonDataframeStream thread-safe
Input/output happen on different threads
2025-11-15 12:46:24 -05:00
John Colvin 9856e10eb6 fix: Support some mutations with convert_back delegate in SelectorList 2025-11-15 12:46:24 -05:00
John Colvin deda37b057 chore: Add arithmetic operators to PrimitiveVariant 2025-11-15 12:46:24 -05:00
John Colvin ce5095523b feat: Add a bunch of stream/table ext evaluators 2025-11-15 12:46:24 -05:00
John Colvin 7d374346f2 cleanup: Add NotNull attr to "assert_value" nullable ext 2025-11-15 12:46:24 -05:00
John Colvin ad0e9fa3c9 chore: Add BookDelta class 2025-11-15 12:46:24 -05:00
John Colvin befa17d29d chore: Add DoesNotReturnIf(false) attr to unit test Assert condition 2025-11-15 12:46:24 -05:00
John Colvin 3165ba1235 Add some half-baked Brandon Chen streams 2025-11-15 12:46:23 -05:00
John Colvin 53a9e6a62d fix: Support numeric conversions in ArgumentComparer 2025-11-15 12:46:23 -05:00
John Colvin ff816befb0 chore: Add MergeSorted ext fn to merge 2 already sorted enumerables 2025-11-15 12:46:23 -05:00
John Colvin a69c79b6a9 Add TableUpdate 'WithHistory' ext fn
A buffer of the last N updates is included with each update
2025-11-15 12:46:23 -05:00
John Colvin 7219ff5a95 Show IDataStreamFactory operators in DSQA 2025-11-15 12:46:23 -05:00
John Colvin 603f01699c Tweak databento_client.py 2025-11-15 12:46:23 -05:00
John Colvin 0ca318b222 refactor: Launch ThrottleStream via IDataStreamFactory
Not PrimitiveManager
2025-11-15 12:45:08 -05:00
John Colvin 4addb58743 feat: Add BboPriceSummaryStream for Brandon Chen 2025-11-15 12:45:08 -05:00
John Colvin c365330b08 feat: Add GroupByApplyStream 2025-11-15 12:45:08 -05:00
John Colvin 4bb4008ac6 feat: Add SliceTable/Stream 2025-11-15 12:45:07 -05:00
John Colvin 001892ab34 Jupyter/pandas analysis for brandon chen 2025-11-15 12:45:07 -05:00
John Colvin e77ee859a3 fix: Preserve clipboard (copy/paste) when DSQA exits 2025-11-15 12:45:07 -05:00
John Colvin c1a8702a47 ui: Improve dateTime formatting in DSQA 2025-11-15 12:45:07 -05:00
John Colvin 6045c602dc feat: Add pseudo member (extension) function calls to DSL 2025-11-15 12:45:07 -05:00
John Colvin 4a2a814af0 refactor: Move StreamQueryMetadata into StreamFactories namespace 2025-11-15 12:45:07 -05:00
John Colvin 08a655b1d5 Change WellKnownColumn names to match those in mlp
This is easier than changing them everywhere in the master learning prompt.
2025-11-15 12:45:07 -05:00
John Colvin 2830af5644 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-15 12:45:07 -05:00
John Colvin 659eb83f4d refactor: Drop null type modifier in TryGetFirst ext
Replace w/ MaybeNullWhen attributes
2025-11-15 12:45:07 -05:00
John Colvin 5d9d364de3 cleanup: Remove unnecessary code 2025-11-15 12:45:07 -05:00
John Colvin 07c1ad3204 qol: Don't connect to sparkd by default in Spark.Console cli 2025-11-15 12:45:07 -05:00
John Colvin e61829f8e0 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-15 12:45:07 -05:00
John Colvin 0cf44fcc40 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-15 12:45:04 -05:00
John Colvin 2549e34a17 Create LiveBBOStream via BookStreamFactory
Instead of PrimitiveManager
2025-11-15 12:42:40 -05:00
John Colvin 693f2deb67 fix: Don't modify input env in BoundBooleanExpression.Evaluate 2025-11-15 12:42:40 -05:00
John Colvin 63665e9b10 refactor: Pass IMarketDataClient to streams via dependency injection
In general, the streams should not instantiate services, etc. themselves.
2025-11-15 12:42:40 -05:00
John Colvin a79f5ff860 improve: Support all integer types in IBlueRow Id ext fn 2025-11-15 12:42:40 -05:00
John Colvin dae75c507e cleanup: Remove unused code CSharpString.cs 2025-11-15 12:42:40 -05:00
John Colvin 4db9cdd357 feat: Add DiffStream to compute row diffs (like pandas) 2025-11-15 12:42:40 -05:00
John Colvin 18b19ac787 refactor: Rewrite SortStream now that IBlueRowCollection is IList 2025-11-15 12:42:40 -05:00
John Colvin b5ea918378 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-15 12:42:40 -05:00
John Colvin 9df4ecf6c8 fix: Trim backticks from nl generated dsl 2025-11-15 12:42:40 -05:00
John Colvin 957f6cb33f feat: Add FeatherReader stream
We don't need this right now, but I already wrote it...it might be
useful later.
2025-11-15 12:42:39 -05:00
John Colvin 03294ffe37 feat: Add function defs to DataStream DSL 2025-11-15 12:42:39 -05:00
John Colvin 0550078de0 chore: Support mutations in AggregateDictionary (optionally) 2025-11-15 12:42:39 -05:00
John Colvin 89129f726e chore: Implement IComparable in BookTuple 2025-11-15 12:42:39 -05:00
John Colvin 97eefb1a1e improve: Serialize to/from parquet in PythonDataframeStream 2025-11-15 12:42:39 -05:00
John Colvin 805693e170 feat: Add ParquetReader/Writer streams 2025-11-15 12:40:48 -05:00
John Colvin 3a7f3ac608 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-15 12:38:51 -05:00
John Colvin b82cc31d2d refactor: Launch LiveTradeStream via IDataStreamFactory 2025-11-15 12:38:51 -05:00
John Colvin 34859d7506 chore: Add id field to TradeRecord
Need this for streams
2025-11-15 12:38:51 -05:00
John Colvin 8a9bc1872e fix: Actually use sec filter in MarketDataClient.Trades 2025-11-15 12:38:51 -05:00
John Colvin 6b4ff90a56 style: Rename dispose_thread_safe in PascalCase 2025-11-15 12:38:51 -05:00
John Colvin d2a1b67717 refactor: Make IBlueRowCollection inherit IList<IBlueRow> 2025-11-15 12:38:51 -05:00
John Colvin 144b29c728 improvement: Lookup timestamp col by index rather than name 2025-11-15 12:38:51 -05:00
John Colvin e7625341e3 refactor: Remove hardcoded csv path from ReplayFutureLiveBBOStream
Add constructor overload to accept an input ViewStream
2025-11-15 12:38:51 -05:00
John Colvin fb15b3fb24 improvement: Don't mutate IBlueRowCollection after firing in FilterStream 2025-11-15 12:38:51 -05:00
John Colvin ea0246929a improvement: Make MultiRowRollingMathStream more performant
Avoid creating extra lists and o(n) enumeration
2025-11-15 12:38:50 -05:00
John Colvin 52f35cc421 refactor: Move IBlueTableSchema ext fns into DataStreamCore
The csproj in which IBlueTableSchema is defined.
2025-11-15 12:38:50 -05:00
John Colvin 7e968fec76 tool: Add databento_client.py to fetch (and store) databento data 2025-11-15 12:38:50 -05:00
John Colvin 188acc08ca chore: Copy/paste some log utils from py interop dir to tools/py 2025-11-15 12:38:50 -05:00
John Colvin 29be8c3ff5 refactor: Support dict and list of tuples in py fmt_table 2025-11-15 12:38:50 -05:00
John Colvin eb28e04bf1 refactor: Replace old NLStreamFactory w/ new BlueOrb one
Replace hardcoded file paths with proper config while we're at it
2025-11-15 12:38:50 -05:00
John Colvin 4ef2dbe4d7 refactor: Pass composite stream factory back to recursive factories as service 2025-11-15 12:38:50 -05:00
John Colvin 979051f147 build: Add appsettings.Local.json to DSQA 2025-11-15 12:38:50 -05:00
John Colvin 35a17b0fd0 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-15 12:38:50 -05:00
John Colvin d93ce336f6 Add TestCommand back to Spark.Console cli 2025-11-15 12:38:50 -05:00
John Colvin 0d10c2ba5d 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-15 12:38:50 -05:00
John Colvin 2a4d62a1e9 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-15 12:38:50 -05:00
John Colvin b9e191eeb9 refactor: Drop nullable modifier for TValue in GetValueOrDefault ext fn 2025-11-15 12:38:50 -05:00
John Colvin 75f2678df6 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-15 12:38:49 -05:00
John Colvin 8bbe850549 chore: Add 'Rows' prop to BlueTable to avoid accidentally iterating over KeyValuePairs 2025-11-15 12:38:49 -05:00
John Colvin 25860af7bf refactor: Use dependency injection in Spark.Console 2025-11-15 12:38:49 -05:00
John Colvin 68198a058e cleanup: Remove broken settings cmd from Spark.Console 2025-11-15 12:38:49 -05:00
John Colvin 7297d667a2 refactor: Use top-level statements for Spark.Console 'main' 2025-11-15 12:38:49 -05:00
John Colvin ace1b5f04d refactor: Consolidate SparkApp and ShellBootstrapper into single class 2025-11-15 12:38:49 -05:00
John Colvin bd252c251c feat: Add stream driven price lines to ladder 2025-11-15 12:38:49 -05:00
John Colvin 57b82cf10d 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-15 12:38:49 -05:00
John Colvin f07d83e7bb refactor: Make CompositeDataStreamFactory class internal 2025-11-15 12:38:49 -05:00
John Colvin 619be37379 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-15 12:38:49 -05:00
John Colvin 9348106585 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-15 12:38:49 -05:00
John Colvin 760cdc0fba 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-15 12:38:49 -05:00
John Colvin fcd1b0e799 refactor: Use generic collection types in PrimitiveManager 2025-11-15 12:38:49 -05:00
John Colvin eb102a5898 cleanup: Remove unused code in PrimitiveManager.cs 2025-11-15 12:38:48 -05:00
John Colvin fb3262edea cleanup: Use file-scoped namespace in PrimitiveManager.cs
Also sort using statements
2025-11-15 12:38:48 -05:00
John Colvin 6e1e5d4bbf refactor: Add IDataStreamFactory(s) for spatial arb streams 2025-11-15 12:38:48 -05:00
John Colvin 256f7025a2 tool: Run Spark.exe from its own directory
It looks in the working directory for runtime config appsettings.json files.
2025-11-15 12:38:48 -05:00
John Colvin 24892fb1a8 cleanup: Remove unused BooleanExpressionToken Negate 2025-11-15 12:38:48 -05:00
John Colvin e3c2efcfcb 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-15 12:38:48 -05:00
John Colvin 29080795f7 refactor: Rename AddRemoteQueryManager -> AddRemoteQueryStreams 2025-11-15 12:38:48 -05:00
John Colvin cd291b7a0d refactor: Hide md client behind interface, create via Dep Injection 2025-11-15 12:38:48 -05:00
John Colvin 8af9d13802 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-15 12:38:48 -05:00
John Colvin 318d8fb275 cleanup: Remove unused MarketDataClient from OrderSample 2025-11-15 12:38:48 -05:00
John Colvin f33d19c185 build: Launch console for Spark Debug builds, otherwise hide it 2025-11-15 12:36:28 -05:00
John Colvin fb43d214f3 cleanup: Remove ancient winforms sdk ui example 2025-11-15 12:16:33 -05:00
John Colvin c4f12be3e4 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-15 12:16:33 -05:00
John Colvin 54d7279832 style: Normalize indentation in PubsubHeartbeatStream.cs 2025-11-15 12:16:33 -05:00
John Colvin 03df578a81 chore: Add GetValue, TryGetValue ext fns to IBlueRow 2025-11-15 12:16:32 -05:00
John Colvin 7a50ad2cc5 chore: Add 'Empty' fn for BlueTable/Schema (for convenience) 2025-11-15 12:16:32 -05:00
John Colvin 0cc4b83417 cleanup: Remove redundant code block in HistoricalBarsStream.cs 2025-11-15 12:16:32 -05:00
John Colvin 4f1efea066 cleanup: Fix indentation and build warnings in TestStream.cs 2025-11-15 12:16:32 -05:00
John Colvin ef4bcf4c44 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-15 12:16:32 -05:00
John Colvin 5a6139f3c5 cleanup: Make MDConsumer.on_security nonnull
It's always initialized in the constructor.
2025-11-15 12:16:32 -05:00
John Colvin e2816aaa33 refactor: Decouple ViewStreamDatabase from TableStream, ISubscriberCore 2025-11-15 12:16:32 -05:00
John Colvin fc702db7fa check pt: PingPong algo
This is a work-in-progress; putting a pin in it for now to focus on
other priorities.
2025-11-15 12:16:32 -05:00
John Colvin 787cf0becd 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-15 12:16:32 -05:00
John Colvin b4e26d2fbe Drop ".exe" from log dir name on windows 2025-11-15 12:16:32 -05:00
John Colvin e2a101eb20 feat: Add SpreadBook stream 2025-11-15 12:16:32 -05:00
John Colvin 6300f8f7c2 fix: Check settings Subject.IsDisposed before using 2025-11-15 12:16:32 -05:00
John Colvin 1c6f4669be 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-15 12:16:32 -05:00
John Colvin 45fcb2e20b refactor: Use HostBuilder to launch spark w/ streams 2025-11-15 12:16:32 -05:00
John Colvin 75dee2a05a build (cleanup): Get rid of global.json
We don't need it anymore
2025-11-15 12:16:31 -05:00
John Colvin dcb02feb90 build: Update System.Composition pkg refs to 8.0.0 2025-11-15 12:16:31 -05:00
John Colvin 81444c1045 cleanup: Mark a few classes "internal" in Spark 2025-11-15 12:16:31 -05:00
John Colvin 934c0ae70a 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-15 12:16:31 -05:00
John Colvin efdd91935e refactor: Consolidate ShellBootstrapper and base class into one 2025-11-15 12:16:31 -05:00
John Colvin da96744f7f feat: Add Stream Reports shell window 2025-11-15 12:16:31 -05:00
John Colvin 6ed6a36ee1 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-15 12:12:55 -05:00
John Colvin e0fc22121f 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-14 15:02:16 -05:00
John Colvin 3671e4d713 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-14 15:02:16 -05:00
John Colvin dd69842a3c vscode: Add DSQA debug cfg to skip build 2025-11-14 15:02:16 -05:00
John Colvin 15883e42b6 fix (dsqa): Resolve name conflicts with new BlueOrb streams 2025-11-14 15:02:16 -05:00
John Colvin 34caf54015 fix: Fix typo in "DateTimeExpansionStream" type name
Previously, it was "DateTimeExpanstion" (erroneous "t")
2025-11-14 15:02:16 -05:00
John Colvin 84a4182077 chore: Add CircularBuffer class (c#) 2025-11-14 15:02:16 -05:00
John Colvin e8197a81cc 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-14 15:02:15 -05:00
John Colvin 17d2f6d701 chore: Add OffsetList class
This supports mutations (unlike the SubRangeWrapper class).
2025-11-14 15:02:15 -05:00
John Colvin b728a8cb46 cleanup: Remove "partial" class modifiers from OrderBook vm classes
They simply weren't necessary.
2025-11-14 15:02:15 -05:00
John Colvin 6766e48706 check pt: Proof of concept ladder left panel price point collection 2025-11-14 15:02:15 -05:00
John Colvin e7246b22bc ui: Adjust ladder ticks-per-row with ctrl+shift+mousewheel 2025-11-14 15:02:15 -05:00
John Colvin 5550166d03 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-14 15:02:15 -05:00
John Colvin 489c683d2e build: Add scripts to publish data stream apps 2025-11-14 15:02:15 -05:00
John Colvin ff18d39d58 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-14 15:02:15 -05:00
John Colvin 05644ed6cf feat: Add DataStream WebApp
Included in DataStreamSuite.sln, but not DataStreamQueryAnalyzer.sln
2025-11-14 15:00:45 -05:00
John Colvin d6a88a7ac9 cleanup: Remove unused code 2025-11-14 14:59:33 -05:00
John Colvin f7e620db3e Reduce log noise in AsyncTCPConnection_AsyncWrapper 2025-11-14 14:59:33 -05:00
John Colvin c745407ae7 TEMP changes to appsettings.json (at least i should review them before making permanent) 2025-11-14 14:59:33 -05:00
John Colvin 1bb7d6eca2 Add ColumnIndex class to resolve column names to indices
Improves performance compared to accessing columns by name
2025-11-14 14:59:33 -05:00
John Colvin 519ef8b0af Increase SQLiteEnumerator page size to 65536
This performs better, and doesn't seem to be too large in practice
2025-11-14 14:59:33 -05:00
John Colvin 6b93c07271 Serialize TimeSpan w/ microsecond precision in DataPacket json 2025-11-14 14:59:33 -05:00
John Colvin c3288c721d Format DateTimes w/ microsecond precision in DataPacketJsonConverter 2025-11-14 14:59:33 -05:00
John Colvin 2622626eed Add BlueColumnTable for pandas integration
This will make it easier/more natural to support pandas column expressions
2025-11-14 14:59:33 -05:00
John Colvin d9470bdb6e Add c# Variant class, modeled after std::variant in c++ 2025-11-14 14:59:33 -05:00
John Colvin f32e9bba10 refactor: Rename snake_case fns PascalCase 2025-11-14 14:58:14 -05:00
John Colvin f119f30b62 fix load IDataStreamFactory(s) with depdendency injection 2025-11-14 14:57:43 -05:00
John Colvin 4877355fee refactor: Use dependency injection in DSQA 2025-11-14 14:52:07 -05:00
John Colvin ada7d4bed1 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-14 14:52:07 -05:00
John Colvin f65b6b82f2 refactor: Replace DataPacket w/ IDataPacket in stream handlers
This commit also renames BlueTableSchemaAdaptor to BlueTableSchemaAdapter,
and makes it implement IBlueTableSchema.
2025-11-03 16:21:32 -05:00
John Colvin 1155fd6997 refactor: Change namespace to Bts.DataStreamCore for a few types
Since they're in the DataStreamCore csproj
2025-11-03 16:19:50 -05:00
John Colvin f423ea518e Make 'name' default to CallerArgumentExpression for convenience 2025-11-03 16:19:50 -05:00
John Colvin 088b543e48 refactor: Replace if/else with switch cases 2025-11-03 16:19:50 -05:00
John Colvin 2163821b43 cleanup: Remove over-engineered subcommands
We don't need explicit subcommands to 'run onixs' md (just use config).
2025-11-03 16:19:50 -05:00
John Colvin abf4cdacf8 fix: Ensure IDGenerator instance is created before deref 2025-11-03 16:19:49 -05:00
John Colvin 70fc4f9ae7 fix: Expand undefined vars to empty string
Bugs (fixed):
  * Md shared memory file names have unexpanded variable: '${BTS_SHARED_MEMORY_PREFIX}'
2025-11-03 16:19:49 -05:00
John Colvin 797ba111ec fix: Emplace MDConsumerHandle instead of copy assign 2025-11-03 16:19:49 -05:00
John Colvin a51b015907 cleanup: Remove unnecessary code
Trim includes as well
2025-11-03 16:19:49 -05:00
John Colvin ec057605a5 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-03 16:19:49 -05:00
John Colvin e0903d1c82 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-03 16:19:49 -05:00
John Colvin 1616e1a8b9 cleanup: Remove redundant macro def 2025-11-03 16:19:49 -05:00
John Colvin cfd5579703 Skip md recording thread loop if no recorders configured to run 2025-11-03 16:19:49 -05:00
John Colvin 849737b2ef cleanup: Trivial whitespace cleanup in Protobuf.cmake 2025-11-03 16:19:49 -05:00
John Colvin 85a5b72ec5 build: Clean up data_files cmake proj (support --target clean) 2025-11-03 16:19:49 -05:00
John Colvin e9ac78b438 fix build: Add ShmSegmentName.cpp to windows client sources 2025-11-03 16:19:49 -05:00
John Colvin e5288f1be2 Add api server to docker compose 2025-11-03 16:19:49 -05:00
John Colvin 50c2a57c5c Change api server ports so they don't conflict w/ ds webapp 2025-11-03 16:19:49 -05:00
John Colvin 8f293f73e3 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-03 16:19:49 -05:00
John Colvin ac42732a19 Don't log "initializing spark config" if it has already been initialized 2025-11-03 16:19:48 -05:00
John Colvin 97246e7ce7 vscode: Add debug cfg for spark api server 2025-11-03 16:19:48 -05:00
John Colvin 0815f9316d Use dependency injection in spark api server 2025-11-03 16:19:48 -05:00
John Colvin cc00ff2a0a Add SparkLoggerAdapter for Msft ILogger 2025-11-03 16:19:48 -05:00
John Colvin 28545b5099 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-03 16:19:48 -05:00
John Colvin 04c1ad7be8 Assert that local ip address is successfully obtained 2025-11-03 16:19:48 -05:00
John Colvin 2441a5267d 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-03 16:19:48 -05:00
John Colvin 12f44f63aa Assert TcpConnection endpoint matches expected 2025-11-03 16:19:48 -05:00
John Colvin c2fe578ccb 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-03 16:19:48 -05:00
John Colvin a01fc6cc15 fix: Format PubSubRoute as json in log msgs
It was previously using the default ToString, which simply returns the type name
2025-11-03 16:19:48 -05:00
John Colvin d80cd9c0fd build: Upgrade packages (deps) in DataStreamTests 2025-11-03 16:19:48 -05:00
John Colvin d707847079 cleanup: Remove unused ViewStreamDatabase config 2025-11-03 16:19:48 -05:00
John Colvin 96d98a239f vscode: Add PubSubServer debug cfg to skip build 2025-11-03 16:19:48 -05:00
John Colvin 118e2a9f2e 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-03 16:19:48 -05:00
John Colvin 1481a3fd07 refactor: Run RemoteQueryManager as an IHostedService
This paradigm more accurately represents the RemoteQueryManager as a
Service that can be started/stopped.
2025-11-03 16:19:45 -05:00
John Colvin a5e58c26ce 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-03 16:13:44 -05:00
John Colvin 8637bb9254 Assert ViewStreamDatabase.instance is only created once
The instance must be initialized via ViewStreamDatabase.SetGlobalInstance
2025-11-03 16:13:44 -05:00
John Colvin 7c61c2ef6d cleanup: Remove unused code from ViewStreamDatabase 2025-11-03 16:13:44 -05:00
John Colvin 0668e0cd8c fix: Define BoooleanConstant before StringConstant
Otherwise the former masks the latter and prevents it from matching
2025-11-03 16:13:44 -05:00
John Colvin 3ae88248cc cleanup: Remove unnecessary deps from csprojs 2025-11-03 16:13:43 -05:00
John Colvin 1367cea9ca fix: Catch exception when creating TcpClient 2025-11-03 16:13:43 -05:00
John Colvin b384d89820 fix: Make SystemSubscriptionMapPublisher threadsafe
The PubSubState.onTagMapChanged event may not be invoked on
the same thread as doWork.
2025-11-03 16:13:43 -05:00
John Colvin d669d24aea fix: Add a few null checks
* TimeBasedFilter
* TableAccumulator2
* NonIncGroupByStream
2025-11-03 16:13:43 -05:00
John Colvin e5bc6667e7 Reduce task wait/timeout code in AsyncTCPConnector 2025-11-03 16:13:43 -05:00
John Colvin 41512cb811 fix build warnings: Make props nullable strings in SharedStreamConfig 2025-11-03 16:13:43 -05:00
John Colvin 01d600b821 Assert that System publishers 'doWork' is always called on same thread 2025-11-03 16:13:43 -05:00
John Colvin d6197f7d1f deploy: Add pubsub and md-publisher services to compose.yaml 2025-11-03 16:13:43 -05:00
John Colvin 9e1cb14523 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-03 16:13:41 -05:00
John Colvin cd7c04dce5 Add ErrorChanged event to propagate DataStream exceptions 2025-11-03 16:13:19 -05:00
John Colvin 6aee76590f fix build: Fix Spark build after making config init async 2025-11-03 16:13:19 -05:00
John Colvin fc79c451b2 refactor: Change Configuration.initialize to be async
Tweak SparkCoreServices to synchronize Configuration.init globally
2025-11-03 16:13:19 -05:00
John Colvin b916f1b836 vscode: Add 'isSparkApiEnabled' input to launch.json 2025-11-03 16:13:19 -05:00
John Colvin 826e03af94 cleanup: Remove unused PubSubConfig props 2025-11-03 16:13:19 -05:00
John Colvin 57217c9adf 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-03 16:13:19 -05:00
John Colvin deae8c7fb0 Fix dsqa config json 2025-11-03 16:13:18 -05:00
John Colvin 81d3b10c2b fix: Always call 'handle_internal_pubs' in PubSubMessageReactor main loop 2025-11-03 16:13:18 -05:00
John Colvin f2cc576c51 Fix mismatched types in PubSubServerTagStatsStream schema 2025-11-03 16:13:18 -05:00
John Colvin b25038fac6 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-03 16:13:18 -05:00
John Colvin 9b95caa8e6 refactor: Decouple ViewPublisher from IPubTableBroker impl 2025-11-03 16:13:18 -05:00
John Colvin b4cb6ac856 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-03 16:13:18 -05:00
John Colvin 83cbb0f47f refactor: Decouple RemoteQueryManager from IManagedSubscriber, IPubTableBroker impl 2025-11-03 16:13:18 -05:00
John Colvin 4e6d1a2335 refactor: Decouple ManagedSubscriber from ISubscriberCore impl 2025-11-03 16:13:18 -05:00
John Colvin 41cd59e6f9 refactor: Only support 1 tag type in IPubSubServerTagHandler 2025-11-03 16:13:18 -05:00
John Colvin f2ace4ae32 refactor: Decouple PubTableBroker from IPublisherCore impl
Reference interface rather than the PublisherCore impl.
2025-11-03 16:13:18 -05:00
John Colvin 8db5655151 fix: Dispose market data sub in various md streams 2025-11-03 16:13:18 -05:00
John Colvin 135db27733 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-03 16:13:18 -05:00
John Colvin f240f005f4 refactor: Rename CloudStreamManager.cs -> RemoteQueryManager.cs
This matches the file name
2025-11-03 16:13:18 -05:00
John Colvin 67b1a213b2 Change pub sub schema prefix 'bluto://' -> 'bts-cloud://'
In config json files (not actual code change)
2025-11-03 16:13:17 -05:00
John Colvin e8a16b11bb chore: Fix typos in log msgs
* Bad string interopolation syntax
2025-11-03 16:13:17 -05:00
John Colvin d3f6ca8afb vscode: Add DataStreamPublisher debug cfg to skip build 2025-11-03 16:13:17 -05:00
John Colvin 8e42eb5d18 Add spark service modules designed for Dependency Injection 2025-11-03 16:13:17 -05:00
John Colvin 70d4a33b2b 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-03 16:13:17 -05:00
John Colvin f289c3d2b0 fix build (warn): Only use Registry on windows 2025-11-03 16:13:17 -05:00
John Colvin 6e77eda16c 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-03 16:13:17 -05:00
John Colvin 41be31a7b0 cleanup: Disable nullability warnings
* BZip2InputStream
* TarInputStream
* Inflater
* InflaterDynHeader
* BZip2OutputStream
* DeflaterHuffman
* ParallelJoinTest
2025-11-03 16:13:17 -05:00
John Colvin 96976b3fcd cleanup: Modernize PubSubRoute code, json parsing 2025-11-03 16:13:17 -05:00
John Colvin 58d6a4534e cleanup: Remove unused DateTime grammar from exp parser 2025-11-03 16:13:17 -05:00
John Colvin bf913f2b99 chore: Add json facilities for DataStream websocket api 2025-11-03 16:13:17 -05:00
John Colvin 003b0a3fa0 refactor: Add handy WebsocketExtensions
* IsSocketStateOpen
* IsClosedProperly
2025-11-03 16:13:17 -05:00
John Colvin de7c94014e chore: Add JsonRpc types for websocket paradigm 2025-11-03 16:13:17 -05:00
John Colvin 714cf3572e cleanup: Remove empty file (clearly added by accident) 2025-11-03 16:13:16 -05:00
John Colvin 3a6bdbc27e 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-03 16:13:16 -05:00
John Colvin bf1ce9467d Add generic ViewStream, BlueRow types/interfaces 2025-11-03 16:13:16 -05:00
John Colvin 906e48743d Add BlueRowColumnMask to hide columns w/ minimal overhead
Less overhead than ProjectStream
2025-11-03 16:13:16 -05:00
John Colvin 33764aaa28 fix: Remove query from literal error stream
The parser can't handle colons in the expression (it throws/crashes)
2025-11-03 16:13:16 -05:00
John Colvin bdfef3d9b1 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-03 16:13:16 -05:00
John Colvin 1df508a16c fix Impl IDictionary<int, IBlueRow> in BlueTable 2025-11-03 16:13:16 -05:00
John Colvin 57423e449d 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-03 16:13:16 -05:00
John Colvin 934ea76f5a Add async SQLiteEnumerator to read sqlite tables as IBlueRowCollection 2025-11-03 16:13:16 -05:00
John Colvin 97efe8396e 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-03 16:13:16 -05:00
John Colvin cc80785d8a ui: Add long/short avg cost lines to ladder 2025-11-03 16:13:16 -05:00
John Colvin a9267394ef ui: Add exec fee columns to fills grid 2025-11-03 16:13:16 -05:00
John Colvin 6939934399 chore: Add exec fee to sec metadata config 2025-11-03 16:13:16 -05:00
John Colvin b6b7be7f25 chore: Add fill_aggressor prop to ITradeLogRow 2025-11-03 16:13:16 -05:00
31 changed files with 442 additions and 540 deletions

No files matched your search

Submodule
+1
Submodule docker/linux added at 6537812262.
+1
Submodule docker/windows added at de7d40c09d.
@@ -47,6 +47,8 @@ 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;
@@ -58,21 +60,19 @@ 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.AddOrUpdate(ar);
addedRowsBd.Add(new(id, ar));
if (oldRow is not null) {
deletedRowsBd.AddOrUpdate(oldRow);
deletedRowsBd.Add(new(oldRow.Id(), oldRow));
}
}
var addedRows = addedRowsBd.ToImmutable();
var deletedRows = deletedRowsBd.ToImmutable();
var addedRows = new BlueTableImmutable(_table.schema, addedRowsBd.ToImmutable());
var deletedRows = new BlueTableImmutable(_table.schema, deletedRowsBd.ToImmutable());
var dp_out = new DataPacket(_table, addedRows, deletedRows);
myFireEvent(dp_out);
}
@@ -8,6 +8,7 @@ using bts.BlueOrb;
using Bts.DataStreamCore;
using Bts.PubSub;
using Bts.SharedStreams.StreamFactories;
using mbts.utils;
@@ -138,7 +139,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.Count > 0 || t.Dp.deletedRows.Count > 0))
.Where(t => t.Dp is not null && (t.Dp.addedRows.Any() || t.Dp.deletedRows.Any()))
.Select(t => t.Dp!);
public static IObservable<IDataPacket> AsDataPackets<T>(this IObservable<TableUpdate<T, int>> source, IBlueTableSchema schema) where T : IBlueRow =>
@@ -155,7 +156,7 @@ public static class DataStreamExtensions {
var resDp = bd.ToDataPacket(out var newRows);
return (newRows, resDp);
})
.Where(t => t.Dp.addedRows.Count > 0 || t.Dp.deletedRows.Count > 0)
.Where(t => t.Dp.addedRows.Any() || t.Dp.deletedRows.Any())
.Select(t => t.Dp);
public static IObservable<IDataPacket> AsDataPackets<T, TKey>(this IObservable<TableUpdate<T, TKey>> source, BlueTableSchemaAdapter<T> schema) where TKey : IEquatable<TKey> {
@@ -170,8 +171,9 @@ public static class DataStreamExtensions {
(BlueTableImmutableAdapter<T> Adapter, IDataPacket DataPacket) state, TableUpdate<T, TKey> tableUpdate) {
var bd = state.Adapter.ToDataPacketBuilder();
bd.Update(tableUpdate);
var newRows = bd.ToImmutable(out var dp);
return (newRows, dp);
var dp = bd.ToDataPacket();
var tableAdapter = bd.ToImmutable();
return (bd.ToImmutable(), dp);
}
}
}
@@ -195,46 +197,6 @@ 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));
@@ -1,62 +0,0 @@
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,16 +538,6 @@ 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) {
@@ -702,7 +692,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());
}
@@ -793,7 +783,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,8 +3,6 @@ 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>())) { }
BlueTableImmutable(IBlueTableSchema schema, ImmutableSortedSet<KeyValuePair<int, IBlueRow>> rows) {
public BlueTableImmutable(IBlueTableSchema schema, ImmutableSortedSet<KeyValuePair<int, IBlueRow>> rows) {
this.schema = schema;
this.rows = rows;
}
@@ -43,12 +43,6 @@ 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;
@@ -197,12 +191,12 @@ public class BlueTableImmutable : IBlueRowCollection, IImmutableRowCollection {
}
// public class BlueTableImmutable<T> : IBlueRowCollection<T> where T : IBlueRow {
// readonly ImmutableDictionary<int, T> rows;
// readonly ImmutableSortedSet<KeyValuePair<int, T>> rows;
// public BlueTableImmutable(IBlueTableSchema schema)
// : this(schema, ImmutableDictionary<int, T>.Empty) { }
// : this(schema, ImmutableSortedSet<KeyValuePair<int, T>>.Empty) { }
// public BlueTableImmutable(IBlueTableSchema schema, ImmutableDictionary<int, T> rows) {
// public BlueTableImmutable(IBlueTableSchema schema, ImmutableSortedSet<KeyValuePair<int, T>> rows) {
// this.schema = schema;
// this.rows = rows;
// }
@@ -212,19 +206,32 @@ public class BlueTableImmutable : IBlueRowCollection, IImmutableRowCollection {
// public IBlueTableSchema schema { get; }
// public object SyncRoot => this;
// public T this[int id] => rows[id];
// public T this[int id] =>
// #nullable disable
// rows.TryGetValue(new(id, default), out var kv)
// #nullable enable
// ? kv.Value
// : throw new KeyNotFoundException();
// public bool containsID(int id) =>
// rows.ContainsKey(id);
// #nullable disable
// rows.Contains(new(id, default));
// #nullable enable
// public IList<object?> GetColumn(int index) =>
// rows.WrapSelector(kv => kv.Value[index], null);
// public void CopyTo(Array array, int index) {
// foreach (var r in rows.Values) {
// array.SetValue(r, index++);
// foreach (var (k, v) in rows) {
// array.SetValue(v, index++);
// }
// }
// public IEnumerator<T> GetEnumerator() =>
// rows.Values.GetEnumerator();
// public IEnumerator<T> GetEnumerator() {
// foreach (var kv in rows) {
// yield return kv.Value;
// }
// }
// IEnumerator IEnumerable.GetEnumerator() =>
// GetEnumerator();
@@ -234,11 +241,11 @@ public class BlueTableImmutable : IBlueRowCollection, IImmutableRowCollection {
// public class DataPacketBuilder {
// readonly IBlueTableSchema schema;
// 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();
// 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();
// public DataPacketBuilder(BlueTableImmutable<T> table) {
// schema = table.schema;
@@ -247,30 +254,37 @@ public class BlueTableImmutable : IBlueRowCollection, IImmutableRowCollection {
// public void AddOrUpdate(T row, IEqualityComparer<IBlueRow>? comparer = null) {
// var id = row.Id();
// if (table.TryGetValue(id, out var orig)) {
// if (comparer?.Equals(row, orig) ?? false) {
// #nullable disable
// if (table.TryGetValue(new(id, default), out var orig)) {
// #nullable enable
// if (comparer?.Equals(row, orig.Value) ?? false) {
// // No op: This row was already in the table (according to 'comparer').
// // Remove it from 'added' and 'removed'.
// added.Remove(id);
// removed.Remove(id);
// added.Remove(new(id, row));
// removed.Remove(new(id, orig.Value));
// return;
// }
// // Change op: A row with this id was already in the table,
// // but its values have changed (according to 'comparer').
// removed[id] = orig;
// removed.Remove(orig);
// removed.Add(orig);
// }
// added[id] = row;
// added.Remove(new(id, row));
// added.Add(new(id, row));
// }
// public void Remove(int id) {
// added.Remove(id);
// if (table.TryGetValue(id, out var orig)) {
// removed[id] = orig;
// #nullable disable
// added.Remove(new(id, default));
// if (table.TryGetValue(new(id, default), out var orig)) {
// #nullable enable
// removed.Remove(orig);
// removed.Remove(orig);
// }
// }
// public DataPacket ToDataPacket(bool reset = true) {
// table.RemoveRange(removed.Keys);
// table.RemoveRange(removed);
// table.AddRange(added);
// var newRows = new BlueTableImmutable<T>(schema, table.ToImmutable());
// var addedRows = new BlueTableImmutable<T>(schema, added.ToImmutable());
@@ -283,7 +297,7 @@ public class BlueTableImmutable : IBlueRowCollection, IImmutableRowCollection {
// }
// public IDataPacket<T> ToDataPacketT(bool reset = true) {
// table.RemoveRange(removed.Keys);
// table.RemoveRange(removed);
// table.AddRange(added);
// var newRows = new BlueTableImmutable<T>(schema, table.ToImmutable());
// var addedRows = new BlueTableImmutable<T>(schema, added.ToImmutable());
@@ -301,7 +315,7 @@ public class BlueTableImmutable : IBlueRowCollection, IImmutableRowCollection {
// out BlueTableImmutable<T> removedRows,
// bool reset = true
// ) {
// table.RemoveRange(removed.Keys);
// table.RemoveRange(removed);
// table.AddRange(added);
// newRows = new BlueTableImmutable<T>(schema, table.ToImmutable());
// addedRows = new BlueTableImmutable<T>(schema, added.ToImmutable());
@@ -325,7 +339,7 @@ public class BlueTableImmutableAdapter<T> : IBlueRowCollection {
public BlueTableImmutableAdapter(BlueTableSchemaAdapter<T> schemaAdapter)
: this(schemaAdapter, ImmutableSortedSet.Create(KeyValuePairComparer.KeyComparer<int, T>())) { }
BlueTableImmutableAdapter(BlueTableSchemaAdapter<T> schemaAdapter, ImmutableSortedSet<KeyValuePair<int, T>> rows) {
public BlueTableImmutableAdapter(BlueTableSchemaAdapter<T> schemaAdapter, ImmutableSortedSet<KeyValuePair<int, T>> rows) {
this.schemaAdapter = schemaAdapter;
this.rows = rows;
}
@@ -467,19 +481,23 @@ public class BlueTableImmutableAdapter<T> : IBlueRowCollection {
AddOrUpdate(r);
}
}
public IDataPacket ToDataPacket(bool reset = true) =>
ToDataPacket(out _, reset);
public BlueTableImmutableAdapter<T> ToImmutable(out IDataPacket dp, bool reset = true) {
public IDataPacket ToDataPacket(out IBlueRowCollection newRows, bool reset = true) {
table.RemoveRange(removed);
table.AddRange(added);
var newRows = new BlueTableImmutableAdapter<T>(schemaAdapter, table.ToImmutable());
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();
}
dp = new DataPacket(newRows, addedRows, removedRows);
return newRows;
return new DataPacket(newRows, addedRows, removedRows);
}
public BlueTableImmutableAdapter<T> ToImmutable() =>
new(schemaAdapter, table.ToImmutable());
}
}
@@ -1,5 +0,0 @@
namespace Bts.DataStreamCore;
using System;
public class LiveMarketDataStreamAttribute : Attribute { }
@@ -1,5 +0,0 @@
namespace Bts.DataStreamCore;
using System;
public class MarketDataClientParamAttribute : Attribute { }
@@ -1,49 +0,0 @@
#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,15 +8,9 @@ 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 Asset = "symbol,sym,product,asset";
public const string Symbol = "symbol,sym,product,asset";
/// <summary>
/// Aliases for a Type argument.
@@ -29,19 +29,21 @@ public static class WellKnownStreamArgumentAliases {
/// </summary>
public const string SecurityName = "name,sec,secName,security";
public const string QualifiedSecurityType = "secType,secTyp,securityType,securityTyp";
public const string QualifiedSecurityName = "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 VenueRegex = "^(venue|exchange|exch)$";
public const string AssetRegex = "^(symbol|sym|asset|product)$";
public const string AssetRegex = "^(symbol|sym|product|asset)$";
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 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 ShowAssetRegex = "^(show|include)(Symbol|Sym|Asset|Product)$";
public const string ShowExpirationRegex = "^(show|include)(Exp(iration|Date)?)$";
public const string ShowCashPerPointRegex = "^(show|include)(CashPerPoint|Cpp)$";
@@ -53,6 +55,7 @@ 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,7 +3,6 @@ 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;
@@ -117,8 +116,6 @@ 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();
@@ -127,8 +124,6 @@ namespace BCG {
else
_workers.RemoveFirst(tup => tup.Worker == w);
}
void IWorkScheduler.Remove(IWorker worker) =>
removeWorker(worker);
internal static void SetGlobalInstance(ViewStreamDatabase vsd) {
lock (myInstanceLockObject) {
@@ -137,6 +132,12 @@ 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 sealed class DataStreamPublisherService : IHostedService, IDisposable {
public 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://127.0.0.1:8000/config"
"ConfigServer": "http://localhost:8000/config"
},
"SparkApi": {
"Credentials": {
@@ -186,7 +186,8 @@ public class DataPacketBuilderTests {
}
static BlueTableImmutable getTable(params (int Id, double Value)[] rows) =>
BlueTableImmutable.CreateRange(schema, rows.Select(tup => new KeyValuePair<int, IBlueRow>(tup.Id, getBlueRow(tup))));
new(schema, ImmutableSortedSet.CreateRange(
rows.Select(tup => new KeyValuePair<int, IBlueRow>(tup.Id, getBlueRow(tup)))));
static MutableBlueRow getBlueRow((int id, double value) tup) =>
getBlueRow(tup.id, tup.value);
@@ -204,10 +205,11 @@ 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(
BlueTableImmutable.CreateRange(schema, GetRowKvps(newRows)),
BlueTableImmutable.CreateRange(schema, GetRowKvps(addedRows)),
BlueTableImmutable.CreateRange(schema, GetRowKvps(removedRows)));
new BlueTableImmutable(schema, ImmutableSortedSet.CreateRange(kvpComp, GetRowKvps(newRows))),
new BlueTableImmutable(schema, ImmutableSortedSet.CreateRange(kvpComp, GetRowKvps(addedRows))),
new BlueTableImmutable(schema, ImmutableSortedSet.CreateRange(kvpComp, 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,4 +84,3 @@ public class WorkScheduler : IWorkScheduler, IDisposable {
}
}
}
@@ -35,10 +35,4 @@ 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,81 +0,0 @@
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,35 +1,26 @@
namespace bts.BlueOrb {
namespace bts.BlueOrb
{
using System;
using BCG;
using Bts.DataStream;
using Bts.DataStreamCore;
using Bts.SharedStreams.MarketData;
using bts.api;
using Spark.Api.MarketData;
[LiveMarketDataStream]
[DataStreamUserRespresentation("LiveBBOStream")]
[DataStreamUserRespresentation(false)]
public class LiveBBOStream : ViewStream {
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;
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;
}
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());
mySetRealSource(rs);
}
}
}
@@ -42,9 +42,9 @@ public class SecurityTypeArgumentParser : IStreamArgumentParser<SecurityType> {
if (TryParse(str, out result)) {
return true;
}
error = $"Failed to parse SecurityType from string '{str}'";
error = $"Failed to parse secType from string '{str}'";
} else {
error = $"SecurityType parser received input of unexpected type '{val?.GetType()}'";
error = $"Venue 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 result) {
public static bool TryParse(string? str, out Venue v) {
if (str is null) {
result = Venue.Unset;
v = Venue.Unset;
return false;
}
if (Enum.TryParse(str, true, out result) || Enum.TryParse("VENUE_" + str, true, out result)) {
if (Enum.TryParse(str, true, out v) || Enum.TryParse("VENUE_" + str, true, out v)) {
return true;
}
result = str.ToUpper() switch {
v = str.ToUpper() switch {
"CME" or "XCME" or "CEC" or "XCEC" => Venue.Glbx,
"CBOT" or "CBT" or "XCBT" => Venue.Cboe,
_ => Venue.Unset,
};
return result != Venue.Unset;
return v != Venue.Unset;
}
public bool CanParse(StreamArgument arg, Type outputType) =>
@@ -1,26 +1,25 @@
namespace Bts.SharedStreams.MarketData;
namespace Bts.SharedStreams.StreamFactories;
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; } = 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 int NumLevels { get; set; }
public bool ShowNotional { get; set; }
public bool ShowQuantity { get; set; } = true;
public bool ShowNotional { get; set; } = true;
public bool ShowPrice { get; set; } = true;
public bool ShowVenue { get; set; }
public bool ShowAsset { get; set; } = true;
public bool ShowSecurityType { get; set; }
public bool ShowSecurityName { get; set; }
public bool ShowSecurityId { get; set; } = true;
public bool ShowCashPerPoint { get; set; }
public string TableName { get; set; } = "Book";
public IBlueTableSchema ToSchema() {
@@ -30,12 +29,12 @@ public class BookSchemaBuilder {
if (ShowVenue) {
bd.Add(WellKnownColumns.Venue);
}
if (ShowAsset) {
bd.Add(WellKnownColumns.Asset);
}
if (ShowSecurityType) {
bd.Add(WellKnownColumns.SecurityType);
}
if (ShowAsset) {
bd.Add(WellKnownColumns.Asset);
}
if (ShowSecurityName) {
bd.Add(WellKnownColumns.SecurityName);
}
@@ -67,12 +66,12 @@ public class BookSchemaBuilder {
if (ShowVenue) {
adapter.AddColumn(WellKnownColumns.Venue.Name, b => b.security.Exchange.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 (ShowAsset) {
adapter.AddColumn(WellKnownColumns.Asset.Name, b => b.security.Symbol);
}
if (ShowSecurityName) {
adapter.AddColumn(WellKnownColumns.SecurityName.Name, b => b.security.ToString());
}
@@ -104,12 +103,12 @@ public class BookSchemaBuilder {
if (ShowVenue) {
adapter.AddColumn(WellKnownColumns.Venue.Name, b => b.security.Exchange.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 (ShowAsset) {
adapter.AddColumn(WellKnownColumns.Asset.Name, b => b.security.Symbol);
}
if (ShowSecurityName) {
adapter.AddColumn(WellKnownColumns.SecurityName.Name, b => b.security.ToString());
}
@@ -149,4 +148,3 @@ public class BookSchemaBuilder {
static Func<BookTuple, double> notionalGetter(Side side, int level) =>
b => b.book[side, level].NotionalValue(b.security.ContractSpec.CashPerPoint);
}
@@ -2,113 +2,202 @@ 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 {
readonly VenueArgumentParser venueParser = VenueArgumentParser.Instance;
readonly SecurityTypeArgumentParser secTypeParser = SecurityTypeArgumentParser.Instance;
readonly StringArgumentParser strParser = StringArgumentParser.Instance;
readonly IMarketDataClient mdClient;
public class BookStreamFactory : DataStreamFactory, IRecursiveDataStreamFactory {
readonly LiveBboStreamFactory liveBboFactory;
readonly IMarketDataClient? mdClient;
readonly ILogger logger;
readonly IWorkScheduler workScheduler;
IDataStreamFactory? compositeFactory = null;
public BookStreamFactory(IMarketDataClient mdClient, ViewStreamDatabase viewStreamDatabase, ILogger<BookStreamFactory> logger)
: this(mdClient, viewStreamDatabase, (ILogger)logger) { }
BookStreamFactory(IMarketDataClient mdClient, IWorkScheduler workScheduler, ILogger logger) {
public BookStreamFactory(IMarketDataClient? mdClient, ILogger<BookStreamFactory> logger, ViewStreamDatabase workScheduler) {
this.mdClient = mdClient;
this.logger = logger;
this.workScheduler = workScheduler;
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);
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);
}
}
ViewStream? createBook(Venue venue) =>
createBook(new BookStreamParams() { Venue = venue });
public void OnCompositeFactory(IDataStreamFactory compositeFactory) {
this.compositeFactory = compositeFactory;
liveBboFactory.OnCompositeFactory(compositeFactory);
}
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");
ViewStream? createBboPriceSummary(BookStreamParams streamParams) {
if (!checkHasLiveMd()) {
return null;
}
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 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 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; }
}
}
@@ -0,0 +1,55 @@
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,11 +2,14 @@ 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;
@@ -15,8 +18,11 @@ 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) {
@@ -1,69 +1,71 @@
namespace Bts.SharedStreams;
namespace Bts.SharedStreams.StreamFactories;
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) 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) 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) Asset => (WellKnownColumnNames.Asset, 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) SecurityName => (WellKnownColumnNames.SecurityName, typeof(string));
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) Venue => (WellKnownColumnNames.Venue, typeof(string));
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) 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) LegAsset(int legNum) => (WellKnownColumnNames.LegAsset(legNum), typeof(string));
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}";
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));
}
@@ -22,7 +22,7 @@ namespace mbts.ui.converters {
break;
default:
if (values[0] is IMultiValueConverter mvc) {
values = ArrayUtils.RemoveAt(values, 0);
ArrayUtils.remove_at(ref 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().ToLower(),
_ => side.ToString(),
};
// public static Brush badge_fill(AlgoDefinitionMetadata? algo_meta, OrderMetadata order_meta) =>
+16 -16
View File
@@ -10,24 +10,25 @@ using System.Threading;
using System.Threading.Tasks;
public class AtomicGate {
const int maxAttemptsBeforeSleep = 32;
const int MAX_ATTEMPTS_BEFORE_SLEEP = 32;
static readonly int[] increasingSleeps = new[] { 1, 10, 100, 200, 500, 1000 };
static int idCounter = 0;
static int id_counter = 0;
readonly string name;
readonly int id = Interlocked.Increment(ref idCounter);
readonly int id = Interlocked.Increment(ref id_counter);
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 < maxAttemptsBeforeSleep; n++) {
for (n = 0; n < MAX_ATTEMPTS_BEFORE_SLEEP; n++) {
if (TryEnter()) {
return;
}
@@ -45,13 +46,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() {
@@ -115,7 +116,7 @@ public class AtomicGate {
int version = Interlocked.Increment(ref versionIn) - 1;
int n = 0;
var end = Disposable.Create(EndTransaction);
for (n = 0; n < maxAttemptsBeforeSleep; n++) {
for (n = 0; n < MAX_ATTEMPTS_BEFORE_SLEEP; n++) {
if (Volatile.Read(ref versionOut) == version) {
return end;
}
@@ -147,10 +148,9 @@ 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);
}
}