Compare commits

...
Author SHA1 Message Date
Mohammad Arham 40e44db597 Fix: Added GCHandle in attempt to fix ui crash 2025-01-22 02:15:55 -05:00
Mohammad Arham 4a447e44e1 Add: Version to first log 2025-01-22 02:14:19 -05:00
John Colvin b1f4654b3c Fix benign typo in publish_spark.py 2025-01-21 17:28:50 -05:00
John Colvin 9d350334c0 fix build (installer): Fix install path typo in publish_spark.py 2025-01-21 17:28:05 -05:00
John Colvin ac597f505f fix build (installer): Set CMAKE_INSTALL_PATH for spark preset 2025-01-21 17:19:15 -05:00
John Colvin 147d9ceb1f ui: Enable net change price display for synths in ladder 2025-01-21 13:11:16 -05:00
John Colvin 377804dfb9 fix (ui): Don't crash on bad regex in grid filters
Wrap Regex construction in try/catch. This is from user input, so of
course we can't assume it will be valid.
2025-01-21 09:26:37 -05:00
John Colvin 0d26401961 fix: Preserve tick_factors in TickDisplaySettings "with" ext
Not sure this was a bug in practice, but it was definitely wrong.
2025-01-21 01:24:23 -05:00
John Colvin 889e226151 feat: Add setting to show notional values in ladder 2025-01-21 01:24:23 -05:00
John Colvin 986d53f4e0 ui: Make ladder size format text editable
This is the best of both worlds: the user can choose from one of the
canned presets in the dropdown, or specify a custom string format.
2025-01-21 01:24:23 -05:00
Taha d13ba2968a DateTimeExpanstionStream col name changes 2025-01-20 15:34:11 -05:00
John Colvin 1a192c8117 feat: Add setting to show ladder prices in basis pts from mid 2025-01-20 14:15:49 -05:00
John Colvin 5e36d4801c cleanup: Remove redundant if/else code 2025-01-20 13:31:41 -05:00
John Colvin 2cf6411ca7 fix: Send exchangeInfo req via our HttpClient instead of ccapi
Bugs (fixed):
  * We get a boost beast exception about the read limit being exceeded
    when requesting securities from Binance prod url (testnet works).
2025-01-19 14:21:11 +00:00
John Colvin e99df129d5 cleanup: Remove erroneous include 2025-01-18 22:54:06 +00:00
John Colvin d07c01bd17 ui: Support ladder ticks-per-row setting for synths
The ticks-per-row control was likely hidden by accident in the first
place. I'm guessing the original intent was to only hide the Price Terms
control for synthetics. At the time, there were no other controls in the
group box, so it made since to hide the whole thing. Some time later, we
added the ticks-per-row setting to the same group box, and forgot to
make the groupbox visible again.
2025-01-17 13:11:09 -05:00
Taha Afzal d69673c524 Previous change was not setting the create time for orders, fixing that + changign one log level to Dag 2025-01-17 07:00:57 +00:00
Taha Afzal 4ee595cdaf Fixing the issue were fractional fills on a working order could result in overfills being reported (it was only a reporting issue) 2025-01-17 06:38:38 +00:00
Mohammad Arham 6fcaf07df0 Fix: binance execution working on sim 2025-01-17 00:59:22 +00:00
Taha Afzal 492d48d886 Added qty_multiplier to spreader.proto 2025-01-16 20:18:26 +00:00
Taha Afzal b6a56e768b Adding config boolan config publish_boostrap_books_only to MarketDataReplayConfig which allows MDReplayer to only publish the bootstrap of all the books 2025-01-16 20:18:26 +00:00
Taha Afzal 3e2b5976a8 Minor changes and ocmments, to allow for fractional qtys 2025-01-16 20:18:26 +00:00
Mohammad Arham edd3f6cbd6 FIX: Orderbook quantity display fixed 2025-01-15 14:47:09 -05:00
John Colvin 2e9347341b fix: Don't throw ex just because leg qty is 0
ContractLegEnumerable shouldn't be responsible for security validation.
2025-01-14 12:58:58 -05:00
John Colvin 2061e38349 fix: Hook up mview venue filter in vm 2025-01-13 00:11:43 -05:00
Mohammad Arham 650dd57b6d FIX: increase max symbo count from 32 to 1024 2025-01-13 04:42:13 +00:00
Mohammad Arham 82747f7dc0 Fix: OKX perp swaps are same as futures now 2025-01-13 04:41:29 +00:00
John Colvin 04b26904a3 ui: Sort exchanges in grid filter dropdown 2025-01-12 15:10:20 -05:00
John Colvin f123e4c72c ui: Add Exchange col to mview 2025-01-12 15:10:20 -05:00
John Colvin b887cbebab fix: Actually capture emumba deribit cfg obj by value
The previous "fix" didn't actually work because, although the lambda
captured the obj by value, the thread captured the lambda by reference!
2025-01-09 16:53:22 +00:00
John Colvin 14e975bc7e vscode: Add Spark (Skip Build) entry to tasks.json 2025-01-09 11:23:30 -05:00
John Colvin 064f557057 ui: Sort accounts in ladder/ticket dropdown 2025-01-09 11:23:30 -05:00
John Colvin 4d106b5e6c ui: Fix alignment issues in order ticket 2025-01-09 11:23:30 -05:00
John Colvin 9fbeaf0d68 ui: Make TickUpDown style match default (dark) Button
* Set BorderThickness to 0
* Use same mouse-hover background as Button
2025-01-09 11:23:30 -05:00
John Colvin 4018ac8b78 ui: Add tooltips to order ticket 2025-01-09 11:23:30 -05:00
John Colvin 81c9503300 ui: Apply dark theme to order ticket buttons 2025-01-09 11:23:30 -05:00
John Colvin 477210926d ui: Fix text color in algo popup 2025-01-09 11:23:29 -05:00
John Colvin 0aa48a79a9 ui: Remove single quotes from tooltip
It looked busy
2025-01-09 01:38:26 -05:00
John Colvin 9e71c7af02 ui: Add tooltips to ladder 2025-01-09 01:38:26 -05:00
John Colvin 4647751409 vscode: Add Spark Test entry to launch.json, tasks.json 2025-01-09 01:03:33 -05:00
John Colvin a3087b874b cleanup: Remove unused code 2025-01-09 01:03:32 -05:00
John Colvin 5b2f5c5c8f Don't throw/catch type exception needlessly 2025-01-09 01:03:32 -05:00
John Colvin 479227090e ui: Remove overly rigid validation for "ticks per row" setting 2025-01-09 01:03:32 -05:00
John Colvin 2dfcf320f5 fix: Refresh ladder levels when "ticks per row" changes 2025-01-09 01:03:32 -05:00
John Colvin b4c7213cb2 fix: Refresh ladder prices in view when they change
Bugs (fixed):
  * Sometimes the prices in the left panel of the ladder get stale
2025-01-09 01:03:32 -05:00
John Colvin c7dfd69b90 Consolidate tick rounding functions
Too many functions were performing essentially the same service:
rounding ticks. It was confusing.
2025-01-09 01:03:32 -05:00
John Colvin 4fdc866485 build: Propagate py script exit codes back to the shell
I noticed the build cmds were broken on Windows because the debugger
would still start even when the preLaunchTask (build) failed!
2025-01-09 01:03:32 -05:00
John Colvin f5d5ca9a1c ui: Use appropriate colors in font picker control
We must have missed this when we switched to dark theme.
2025-01-09 01:03:32 -05:00
John Colvin cfafcf77b9 ui: Use appropriate text color in ladder settings dialog
We must have missed this black text when we switched to dark theme.
While we're at it, freeze the brushes it uses, since they are stored in
a StaticResource which could potentially be shared by multiple controls.
2025-01-09 01:03:32 -05:00
John Colvin 8ebe783139 fix: Clear low/high lines in ladder when scrolled out of view 2025-01-09 01:03:32 -05:00
John Colvin 1851a96a58 vscode: Add Spark (Skip Build) entry to launch.json 2025-01-09 01:03:32 -05:00
John Colvin 0b334ef870 fix: Remove erroneous early exit condition in Ladder render
Bugs (fixed):
  * The ladder doesn't always render ask prices
2025-01-09 01:03:32 -05:00
John Colvin ee811a729a ui: Make ladder price line colors match button text 2025-01-09 01:03:32 -05:00
John Colvin cb15be276d ui: Show bid, ask, mid lines in ladder by default 2025-01-09 01:03:32 -05:00
John Colvin 7d5af6729b ui: Show bid, ask, mid prices in ladder left panel 2025-01-09 01:03:31 -05:00
John Colvin e5679cf082 ui: Make ladder price links prettier
* Change them from hyperlinks to buttons
* Add mouse hover animation
* Align text to the right (so decimal places line up)
2025-01-09 01:03:31 -05:00
John Colvin 98f89b9ad4 fix: Capture emumba deribit cfg by value in thread body
Bugs (fixed):
  * Sparkd occasionally assert crashes on startup because it fails to
    read emumba deribit config.

    Cause: This is a simple race bug. The config obj isn't captured by
    value, so it can be destructed before the thread references it.
2025-01-08 19:03:01 +00:00
John Colvin ba4f8f67e3 fix: Disable binance usds futs symbol filter config
This was a premature optimization, and has become a burden now that we
need to subscribe to large symbol sets for Alphaworx.
2025-01-06 05:00:04 +00:00
John Colvin 9c78380490 fix build (gcc rel): Don't fallthrough switch cases 2025-01-06 03:52:25 +00:00
John Colvin f6166f0674 fix: Show iceberg show qty in algo params grid
Bugs (fixed):
  * In the ladder, you can't view/edit the iceberg show quantity.

  Cause: No view template was defined for nullable double params. Most
  likely, we changed the show qty in the vm from double to nullable
  double at some point, and forgot to reflect this change in the view.
  An easy mistake to make as it would not have broken the build.
2025-01-05 15:58:29 -05:00
John Colvin 6334a902e7 fix: Reclaim keyboard focus when mview popup is clicked 2025-01-05 15:29:11 -05:00
John Colvin 082ca62fff fix: Focus price textbox when mview order ticket popup opens 2025-01-05 15:18:46 -05:00
John Colvin 9dbd8dd7c6 fix: Set focus through FocusScope element if one exists 2025-01-05 15:18:46 -05:00
John Colvin 8f7a4d0df6 Fix typo in AutoFocusTargetProvider dependency prop 2025-01-05 15:18:46 -05:00
John Colvin f372dd56b3 fix: Return synthetic name in get_security_desc
If the security is a synthetic (of course).

Bugs (fixed):
  * Changing limits in the risk ui causes sparkd to assert crash
2025-01-05 20:15:10 +00:00
John Colvin 86eba20d61 build: Make compiler/build type case insensitive 2025-01-05 06:25:11 +00:00
John Colvin 35a985280d fix: Run heartbeat timer in MarketDataGateway
This is why we were getting those heartbeat asserts. Previously, only
ExchangeSimApplication would start this timer. Since MarketDataGateway
doesn't share its MDProducer with ExchangeSimApplication, it must start
one itself.
2025-01-03 16:59:07 +00:00
John Colvin f5e1b3319c cleanup: Don't write to cout in SparkDataDirectory
Don't need that noise.
2025-01-03 16:59:07 +00:00
John Colvin 65c3948e4f cleanup: Remove unused code 2025-01-03 16:59:07 +00:00
John Colvin 002bdff9a3 fix: Don't parse non-existent json files in SparkDataDirectory
Bugs (fixed):
  * Sparkd assert crashes if binance_usds_futures_execution.json file
    doesn't exist
2025-01-03 16:59:07 +00:00
John Colvin bb6e5dd548 diag: Fix sparkd log path in show_last_run.sh 2025-01-03 16:59:07 +00:00
John Colvin 7c1aed3339 fix build (api server): Remove old cpps from source file list 2025-01-03 16:59:07 +00:00
John Colvin 22ae51fb99 build: Clean up build py scripts 2025-01-03 16:59:07 +00:00
John Colvin a53d347f81 fix build (warns): Update cmake min version to 3.10
Versions < 3.10 will soon be deprecated (we just started getting
warnings about it).
2025-01-03 16:59:07 +00:00
John Colvin 838dbcd886 build: In py build scripts, exit with code 1 on failure
Previously, they would exit with 0 no matter what happened.
2025-01-03 16:59:07 +00:00
John Colvin 5ed2309d22 fix: Add switch case for VENUE_BTS in VenueIndex 2025-01-03 16:59:07 +00:00
John Colvin 4d41704dd2 build: Configure admin webapp build entirely in CMakeLists
Get rid of build_admin_web_app.py
2025-01-03 16:59:07 +00:00
John Colvin bd1000e518 build: Get rid of explicit "targets" in CMakePresets for sparkd
So we don't have to worry about keeping it synchronized with the actual
targets in the build (just one more thing that can break). You can build
specific targets via the cmake cli anyways.
2025-01-03 16:59:07 +00:00
John Colvin dd4aa3c2ed build: Update fable in dotnet-tools.json 2025-01-03 16:59:07 +00:00
John Colvin fee9d3f597 build: Update webpack-cli in admin webapp 2025-01-03 16:59:07 +00:00
John Colvin 416f9aef31 vscode: Add Sparkd Test to launch.json 2025-01-03 16:59:07 +00:00
John Colvin 48f5e4c201 vscode: Fix sparkd launch.json 2025-01-03 16:59:07 +00:00
John Colvin 4438b4ca8d feat: Support fractional quantities in synthetic book 2025-01-03 16:59:07 +00:00
John Colvin 73356e0d61 cli: Add cmd to print security info from the db to console
Usage: sparkd md dump --db
2025-01-03 16:59:07 +00:00
John Colvin 4a285fa581 cleanup: Make fn const 2025-01-03 16:59:07 +00:00
John Colvin cfa82c2177 cleanup: Remove erroneous fwd declared class 2025-01-03 16:59:07 +00:00
John Colvin 0410a0431a fix: Encode exp in valid date int when storing in sqlite
The strange bit shifting we were doing before doesn't support large
dates.

Bugs (fixed):
  * Synthetics with Binance perpetual future legs don't appear on the
    front end after sparkd restarts.

  * Cause: Exp dates for Binance perpetual futures aren't properly
    encoded in the database. As a result, when loaded from disk, they
    are mistakenly identified as expired, along with any synthetics that
    have them as legs.
2025-01-03 16:59:07 +00:00
John Colvin 02843f14ff cleanup: Get rid of trivial fn that's only used in one place
Move the logic into the function that used to call it.
2025-01-03 16:59:07 +00:00
John Colvin f1f1d4259c diag: Add logs around security discovery in MDConsumer
This is multi-threaded event driven code. Without descriptive logs in
the right places, diagnosing problems is difficult and tedious.
2025-01-03 16:59:07 +00:00
John Colvin 71fc43af68 cleanup: Flatten nested if blocks with continue statements
Easier to read
2025-01-03 16:59:07 +00:00
John Colvin 350144ffd5 fix: Use security desc to get unique id (along with venue, type)
I'm guessing this typo went unnoticed because it didn't break the build
(another arg of the same was being used instead).
2025-01-03 16:59:07 +00:00
John Colvin da227a5550 Support synths at Binance, Binance USDS Futures, Deribit, and Okx 2025-01-03 16:59:07 +00:00
John Colvin 0c92cf3a87 Add double ops to IntPrice 2025-01-03 16:59:07 +00:00
John Colvin 1188af391e Add trivial fns to TimeStamp for convenience
* += and -= operators
* 'date' fn
* 'to_date_int' fn
2025-01-03 16:59:07 +00:00
John Colvin 0bb85cf915 refactor: Change month and day params from uint32 to uint8
They only need to hold values 1-12 and 1-31, respectively. A single byte
is more than sufficient. Also add ymdhms TimeStamp constructor.
2025-01-03 16:59:07 +00:00
John Colvin d77f5996fe cleanup: Make a couple TimeStamp fns const 2025-01-03 16:59:07 +00:00
John Colvin 940c41a274 Add int32 arithmetic operators to domain::Price 2025-01-03 16:59:07 +00:00
John Colvin e671960dc3 Add double arithmetic operators to domain::Price 2025-01-03 16:59:07 +00:00
John Colvin 8fa34b6b39 fix: Multiply additive factors by exponent in domain::Price
We must not have been using these operators because the implementation
was very broken (yet it doesn't seemed to have caused any bugs).
2025-01-03 16:59:07 +00:00
John Colvin f9d5429b5b cleanup: Remove unused code 2025-01-03 16:53:05 +00:00
Mohammad Arham 2c342482eb Merge branch 'development.crypto' of bitbucket.org:bluetradesys/spark into development.crypto 2025-01-02 15:30:13 -05:00
Mohammad Arham 3aefac26c2 fix: Timer condition was incorrect 2025-01-02 20:15:38 +00:00
John Colvin 1554594cdd diag: Throttle noisy log in Ladder 2025-01-02 12:15:22 -05:00
John Colvin 727a0f38b5 fix: Prevent arithmetic overflow when converting long to int in Ladder 2025-01-02 12:13:58 -05:00
John Colvin 7a0e8cdb26 fix: Properly format spread/synth leg qty in generated desc 2025-01-01 13:12:17 -05:00
John Colvin a4e97b04d7 fix: Don't propagate security criteria changes to other windows
Bugs (fixed):
  * Security criteria changes in mview and time/sales propagate to other
    windows of the same type.
2024-12-31 11:06:24 -05:00
John Colvin 463980a6ef Fix typo in get_binance_md.py 2024-12-29 18:37:54 +00:00
John Colvin 9b25857042 fix: Ensure "earliest_expiration" fn actually returns min exp
Previously, it assumed the first leg would have the earliest exp. While
this may be true for most securities, it isn't guarenteed by our data
model.
2024-12-29 13:26:44 -05:00
John Colvin ef19e7d76b fix: Don't assume all securities have expirations in pnl vm
Bugs (fixed):
  * PNL window crashes if data has Spots (i.e., securities without exps)
2024-12-29 13:17:17 -05:00
Taha Afzal 471faec2cb Added more etsts for fractional qtys 2024-12-28 08:31:16 +00:00
Taha Afzal ce96d42db8 Merging 2024-12-26 07:01:30 +00:00
Mohammad Arham 187894ca84 FIX: makes gateway factory work 2024-12-24 17:56:49 +00:00
John Colvin 63492c4575 fix: Remove erroneous assert in LadderRender 2024-12-21 23:24:52 -05:00
John Colvin e150c611f6 fix: Round fractional order qty to 6 decimal places
It was previously rounding to 2 decimal places, which isn't enough for
many crypto currencies (e.g., BTCUSDT at Binance)
2024-12-21 23:17:52 -05:00
John Colvin bb837d6c26 fix build: Trivial changes in DatabentoDataHandler 2024-12-22 04:00:23 +00:00
John Colvin 965e541ac8 Fix json structures and endpoints for Binance USDS Futures md 2024-12-22 04:00:23 +00:00
John Colvin 1b232aa610 feat: Add Binance USDS Futures execution 2024-12-22 04:00:23 +00:00
John Colvin 25439b094a improvement: Perform sec lookup in const time 2024-12-21 20:08:19 +00:00
John Colvin f927ef2c5e Add conf_json dir to spark data file tree
So we don't have to add proto files to the build for trivial config.
2024-12-21 20:08:19 +00:00
John Colvin d42389b75a Don't needlessly download cme config xml
When we aren't subscribing to cme md (i.e., no glbx symbols in spark
conf).
2024-12-21 20:08:19 +00:00
John Colvin 671cbcf03a Fix bts::Quantity string fmt so it includes fractional qty 2024-12-21 20:01:55 +00:00
John Colvin e589e23f41 Add py script to get md at binance
For quick/easy troubleshooting (much easier than doing stuff in c++)
2024-12-21 20:01:55 +00:00
John Colvin 11fadc0a3e Expose "run" fn in WebsocketClient
So it doesn't have to be hidden in a background thread
2024-12-21 20:01:55 +00:00
John Colvin b8c2d93866 Add ping/pong facilities to WebsocketClient 2024-12-21 20:01:55 +00:00
John Colvin 67a23dd196 Add open/close callbacks to WebsocketClient 2024-12-21 20:01:55 +00:00
John Colvin ff22c4eb5f diag: Change WebsocketClient log category to "Http"
A better fit than "Gen"
2024-12-21 20:01:55 +00:00
John Colvin 5addff4c50 cleanup: Remove redundant fn HttpRequest::perform
This was doing the same thing as HttpClient::send. I likely refactored
this function during development, then forgot to remove it.
2024-12-21 20:01:55 +00:00
John Colvin 142e1bd2da Add HttpMethod "DELETE"
In Binance's REST api, orders are canceled via DELETE requests.
2024-12-21 20:01:55 +00:00
John Colvin d05d91bbe7 diag: Log Http requests 2024-12-21 20:01:55 +00:00
Mohammad Arham 3a90ca9cb5 Add: Johns changes to sec oracle 2024-12-17 14:47:37 +00:00
John Colvin 85a46727be Improve c++ http client facilities 2024-12-11 19:28:36 +00:00
Mohammad Arham 624fd2c976 FIX: BUILD Error 2024-12-09 01:34:29 +00:00
Mohammad Arham 16d599d2d6 Add: OKX execution gateway connected 2024-12-08 20:14:16 -05:00
John Colvin c00e4fcb6b feat: Add Binance Coin Futures market data 2024-12-06 00:39:41 +00:00
John Colvin 03e19afe1d Fix qty display conf for BTC/ETH futs at Binance
Need to show 3 decimal places (qty tick size is 0.001)
2024-12-05 21:05:53 +00:00
John Colvin 058e11eadc fix build: Fix install path for sparkd 2024-12-05 18:31:36 +00:00
John Colvin 6ff52e7107 fix build: Add onixs libs again
Taha accidentally reverted externals
2024-12-04 16:56:46 +00:00
John Colvin ee4c28ed41 Port ws client over from cme-websocket-connector
Need this to implement execution at crypto exchanges (beginning with
Binance and OKX)
2024-12-04 16:56:46 +00:00
Mohammad Arham 50d26f0060 Merge branch 'development' of bitbucket.org:bluetradesys/spark into development 2024-12-03 09:52:09 -05:00
Mohammad Arham 36378e6074 Add:: Recording md to sql 2024-12-03 14:51:32 +00:00
John Colvin 8cde8ee6e1 build: Add script to publish the api server
This will build the app as a self-contained executable here:
build/Release/bin/net7.0/linux-x64/publish
2024-11-28 16:35:21 +00:00
John Colvin 4f8fad444e fix build: Add missing csproj deps to Spark.Api.Server.sln 2024-11-28 16:35:20 +00:00
John Colvin 832c645a30 build: Map cmake install dir to associated preset
Cpp bins are now installed to "install/<cmake_preset>/bin". For example,
"install/sparkd-release-gcc/bin", "install/spark-api-server-linux/bin", etc.
2024-11-28 16:35:20 +00:00
John Colvin 691343d9b6 feat: Add datastreams to spark api server 2024-11-28 06:14:00 +00:00
Taha 461cbad338 Adding FakeBBO stream 2024-11-27 13:36:14 -05:00
Taha fecbcbb6b6 Merge branch 'development' of bitbucket.org:bluetradesys/spark into development 2024-11-26 22:33:31 -05:00
Taha aab8e83543 Some rewrite of streams that fetch bars, and making the other business streams thin wrapper over stream primitives 2024-11-26 22:33:26 -05:00
Mohammad Arham 420b46741e ADD: Config for sql writer in mdrecorder 2024-11-24 19:14:45 +00:00
Mohammad Arham e8f65f95bd Wrapped proto write in function in mdrecorder 2024-11-24 19:13:36 +00:00
335 changed files with 20788 additions and 22423 deletions

No files matched your search

+2 -2
View File
@@ -9,7 +9,7 @@
]
},
"fable": {
"version": "4.0.6",
"version": "4.24.0",
"commands": [
"fable"
]
@@ -27,4 +27,4 @@
]
}
}
}
}
+182 -11
View File
@@ -31,7 +31,39 @@
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/sparkd-debug-clang/server/sparkd",
"args": ["run"],
"args": [
"run"
],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/sparkd-debug-clang/server",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
},
{
"name": "spreader tests2",
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/sparkd-debug-clang/server/sparkd",
"args": [
"test",
"[spreader2]"
],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/sparkd-debug-clang/server",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
},
{
"name": "spreader tests",
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/sparkd-debug-clang/server/sparkd",
"args": [
"test",
"[spreader]"
],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/sparkd-debug-clang/server",
"environment": [],
@@ -91,6 +123,18 @@
"stopAtEntry": false,
"console": "integratedTerminal"
},
{
"name": "Spark (Skip Build)",
"type": "coreclr",
"request": "launch",
"program": "",
"windows": {
"program": "Spark.exe",
"cwd": "build/Debug/bin/net7.0-windows",
},
"stopAtEntry": false,
"console": "integratedTerminal"
},
{
"name": "Spark (Parameterized)",
"type": "coreclr",
@@ -109,21 +153,138 @@
"console": "integratedTerminal"
},
{
"name": "Sparkd",
"name": "Spark Test",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "Build Sparkd (Debug | Clang)",
"program": "",
"linux": {
"program": "sparkd",
"windows": {
"preLaunchTask": "Build Spark (Debug)",
"program": "Spark.Console.exe",
"cwd": "build/Debug/bin/net7.0",
"args": [
"test",
"${input:sparkTest}"
]
},
"args": [
"run"
],
"cwd": "build/sparkd-debug-clang/server",
"stopAtEntry": false,
"console": "integratedTerminal"
}
},
{
"name": "Spark Test (Skip Build)",
"type": "coreclr",
"request": "launch",
"program": "",
"windows": {
"program": "Spark.Console.exe",
"cwd": "build/Debug/bin/net7.0",
"args": [
"test",
"${input:sparkTest}"
]
},
"stopAtEntry": false,
"console": "integratedTerminal"
},
{
"name": "Sparkd",
"type": "cppdbg",
"request": "launch",
"program": "",
"linux": {
"preLaunchTask": "Build Sparkd (Debug)",
"program": "${workspaceFolder}/build/sparkd-debug-clang/server/sparkd",
"args": [
"run"
],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/build/sparkd-debug-clang/server",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
},
{
"name": "Sparkd (Skip Build)",
"type": "cppdbg",
"request": "launch",
"program": "",
"linux": {
"program": "${workspaceFolder}/build/sparkd-debug-clang/server/sparkd",
"args": [
"run"
],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/build/sparkd-debug-clang/server",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
},
{
"name": "Sparkd Test",
"type": "cppdbg",
"request": "launch",
"program": "",
"linux": {
"preLaunchTask": "Build Sparkd (Debug)",
"program": "${workspaceFolder}/build/sparkd-debug-clang/server/sparkd",
"args": [
"test",
"${input:sparkdTest}",
],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/build/sparkd-debug-clang/server",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
},
{
"name": "Sparkd Test (Skip Build)",
"type": "cppdbg",
"request": "launch",
"program": "",
"linux": {
"program": "${workspaceFolder}/build/sparkd-debug-clang/server/sparkd",
"args": [
"test",
"${input:sparkdTest}",
],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/build/sparkd-debug-clang/server",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
},
],
"inputs": [
{
@@ -154,5 +315,15 @@
"description": "Config Server URL",
"default": "http://localhost:8000/config",
},
{
"id": "sparkTest",
"type": "promptString",
"description": "Test to run",
},
{
"id": "sparkdTest",
"type": "promptString",
"description": "Test to run",
},
]
}
}
+41 -1
View File
@@ -641,6 +641,21 @@
"color": "terminal.ansiGreen",
}
},
{
"label": "Run Spark (Skip Build)",
"type": "process",
"command": "",
"windows": {
"command": "Spark.exe",
"options": {
"cwd": "${workspaceFolder}/build/${input:buildConfiguration}/bin/net7.0-windows"
},
},
"icon": {
"id": "run",
"color": "terminal.ansiGreen",
}
},
{
"label": "Run Spark Config Query",
"dependsOn": "Build Spark",
@@ -705,6 +720,26 @@
"color": "terminal.ansiGreen",
},
},
{
"label": "Test Spark (Debug)",
"dependsOn": "Build Spark (Debug)",
"type": "process",
"icon": {
"id": "coverage",
"color": "terminal.ansiYellow",
},
"command": "",
"windows": {
"command": "Spark.Console.exe",
"args": [
"test",
"${input:sparkTest}"
],
"options": {
"cwd": "${workspaceFolder}/build/Debug/bin/net7.0",
}
},
},
{
"label": "View DataStreamQueryAnalyzer Logs (Last Run)",
"type": "process",
@@ -823,6 +858,11 @@
"id": "sparkdConfigQuery",
"type": "promptString",
"description": "Config Type(s)"
}
},
{
"id": "sparkTest",
"type": "promptString",
"description": "Test to run",
},
]
}
+16 -7
View File
@@ -1,5 +1,6 @@
cmake_minimum_required(VERSION 3.12)
project(spark)
include(GNUInstallDirs)
set(CMAKE_CXX_STANDARD 20)
if(WIN32)
@@ -49,8 +50,6 @@ else()
# make sparkd look for libspark.so in its directory
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
set(CMAKE_INSTALL_RPATH ".")
set(CMAKE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/install")
set(SPARK_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/$<CONFIG>")
if(BUILD_SPARKD)
# Note: All macros that begin w/ 'CCAPI' are ccapi's macros (defined and/or
@@ -89,9 +88,15 @@ else()
add_compile_definitions(BINANCE_US_PRICE_STATS_PATH="/api/v3/ticker/24hr")
# ** Binance USDS Futures **
add_compile_definitions(CCAPI_BINANCE_USDS_FUTURES_URL_REST_BASE="https://dapi.binance.com")
add_compile_definitions(BINANCE_USDS_FUTURES_INSTRUMENTS_PATH="/dapi/v1/exchangeInfo")
add_compile_definitions(BINANCE_USDS_FUTURES_PRICE_STATS_PATH="/dapi/v1/ticker/24hr")
add_compile_definitions(CCAPI_BINANCE_USDS_FUTURES_URL_REST_BASE="https://fapi.binance.com")
add_compile_definitions(BINANCE_USDS_FUTURES_INSTRUMENTS_PATH="/fapi/v1/exchangeInfo")
add_compile_definitions(BINANCE_USDS_FUTURES_PRICE_STATS_PATH="/fapi/v1/ticker/24hr")
add_compile_definitions(BINANCE_USDS_FUTURES_USER_DATA_PATH="/api/v3/userDataStream")
# ** Binance Coin Futures **
add_compile_definitions(CCAPI_BINANCE_COIN_FUTURES_URL_REST_BASE="https://dapi.binance.com")
add_compile_definitions(BINANCE_COIN_FUTURES_INSTRUMENTS_PATH="/dapi/v1/exchangeInfo")
add_compile_definitions(BINANCE_COIN_FUTURES_PRICE_STATS_PATH="/dapi/v1/ticker/24hr")
# ** Deribit **
add_compile_definitions(DERIBIT_INSTRUMENT_PATH="/api/v2/public/get_instrument")
@@ -111,6 +116,7 @@ else()
# subdirs
if (BUILD_SPARKD)
add_subdirectory(src/lib_binance_execution)
add_subdirectory(src/server/sparkd)
add_subdirectory(src/data_files)
endif()
@@ -147,11 +153,14 @@ else()
install(
DIRECTORY "${CMAKE_BINARY_DIR}/server/plugins"
DESTINATION "${SPARK_INSTALL_PREFIX}/bin")
DESTINATION "${CMAKE_INSTALL_BINDIR}")
install(
FILES
"${SPARK_LIB}/libACE-5.7.5.so"
"${SPARK_LIB}/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.16.0.0"
DESTINATION "${SPARK_INSTALL_PREFIX}/bin")
"${SPARK_LIB}/libonixs-fix-engine.so.4.10.1"
"${SPARK_LIB}/libonixs-fix-engine.so.4"
"${SPARK_LIB}/libonixs-fix-engine.so"
DESTINATION "${CMAKE_INSTALL_BINDIR}")
endif()
endif()
+3 -11
View File
@@ -26,6 +26,7 @@
"CMAKE_CXX_FLAGS_DEBUG": "$env{SPARK_CMAKE_CXX_FLAGS} /MTd",
"CMAKE_CXX_FLAGS_RELEASE": "$env{SPARK_CMAKE_CXX_FLAGS} /MT",
"CMAKE_CXX_STANDARD": "20",
"CMAKE_INSTALL_PREFIX": "${sourceDir}/install/${presetName}",
"CMAKE_SHARED_LINKER_FLAGS_DEBUG": "/DEBUG",
"CMAKE_SHARED_LINKER_FLAGS_RELEASE": "/OPT:REF /OPT:ICF /LTCG",
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
@@ -61,7 +62,7 @@
},
"CMAKE_CXX_STANDARD": "20",
"CMAKE_INSTALL_RPATH": ".",
"CMAKE_INSTALL_PREFIX": "${sourceDir}/install/${presetName}/sparkd",
"CMAKE_INSTALL_PREFIX": "${sourceDir}/install/${presetName}",
"CMAKE_EXE_LINKER_FLAGS": "-Wno-undef",
"EXTERNALS_ROOT": "${sourceDir}/externals/linux",
"BUILD_SDK": {
@@ -302,16 +303,7 @@
"lhs": "${hostSystemName}",
"rhs": "Linux"
},
"hidden": true,
"targets": [
"data_files_dir",
"admin_web_app_bundle",
"install",
"libspark",
"rapidjson_project",
"server_layout",
"sparkd"
]
"hidden": true
},
{
+139
View File
@@ -14,6 +14,34 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "spark_test", "src\client\sp
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spark.Console", "src\client\Spark.Console\Spark.Console.csproj", "{89D666E6-B500-498F-B026-4D167F68ADFC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{267F81EF-A8AB-49FD-9F56-D7EC3C1F7C7C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DataStreamSuite", "DataStreamSuite", "{C09ED521-D2DF-40BC-A76F-EB3CB870B2F6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataStreamCore", "src\DataStreamSuite\DataStreamCore\DataStreamCore.csproj", "{EF26A475-6101-4C9C-A717-D60BB92A2302}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedStreams", "src\DataStreamSuite\SharedStreams\SharedStreams.csproj", "{911B08A3-515A-441D-84C8-8A43740B5FC1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PubSub", "src\DataStreamSuite\PubSub\PubSub.csproj", "{14140310-2233-430A-821B-F3058A25234B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BaseUtilities", "src\DataStreamSuite\BaseUtilities\BaseUtilities.csproj", "{43146EA8-6FCC-46B8-88B0-42F9BB5E6E0A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExpressionCore", "src\DataStreamSuite\ExpressionCore\ExpressionCore.csproj", "{6F22DB96-6EAB-4034-99D9-287021C36F01}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArithmeticExpressions", "src\DataStreamSuite\ArithmeticExpressions\ArithmeticExpressions.csproj", "{A5C33956-A41F-49B3-A41D-1082CBA59D02}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataStreamExpressions", "src\DataStreamSuite\DataStreamExpressions\DataStreamExpressions.csproj", "{2FE745C3-1EDF-4421-8CD7-A63EE0C1AE43}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bts.ColumnSortExpressions", "src\DataStreamSuite\Bts.ColumnSortExpressions\Bts.ColumnSortExpressions.csproj", "{FDA791E0-2B91-4AD5-B91D-CF920A4C4E38}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ColumnMapExpressions", "src\DataStreamSuite\ColumnMapExpressions\ColumnMapExpressions.csproj", "{B945A183-2174-4ACB-B289-8A5C546656FE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BooleanExpressions", "src\DataStreamSuite\BooleanExpressions\BooleanExpressions.csproj", "{9F6D7DE8-185B-4C1D-B809-3314D092DF28}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bts.ColumnExpressions", "src\DataStreamSuite\Bts.ColumnExpressions\Bts.ColumnExpressions.csproj", "{6BD3F6D8-B6FC-44E8-B8FE-06352F638B9B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataStream", "src\DataStreamSuite\DataStream\DataStream.csproj", "{39AEA075-6457-46C0-8821-1158BE6EFA57}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@@ -70,6 +98,102 @@ Global
{89D666E6-B500-498F-B026-4D167F68ADFC}.Release|x64.Build.0 = Release|x64
{89D666E6-B500-498F-B026-4D167F68ADFC}.RelWithDebInfo|x64.ActiveCfg = Debug|x64
{89D666E6-B500-498F-B026-4D167F68ADFC}.RelWithDebInfo|x64.Build.0 = Debug|x64
{EF26A475-6101-4C9C-A717-D60BB92A2302}.Debug|x64.ActiveCfg = Debug|Any CPU
{EF26A475-6101-4C9C-A717-D60BB92A2302}.Debug|x64.Build.0 = Debug|Any CPU
{EF26A475-6101-4C9C-A717-D60BB92A2302}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{EF26A475-6101-4C9C-A717-D60BB92A2302}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{EF26A475-6101-4C9C-A717-D60BB92A2302}.Release|x64.ActiveCfg = Release|Any CPU
{EF26A475-6101-4C9C-A717-D60BB92A2302}.Release|x64.Build.0 = Release|Any CPU
{EF26A475-6101-4C9C-A717-D60BB92A2302}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU
{EF26A475-6101-4C9C-A717-D60BB92A2302}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU
{911B08A3-515A-441D-84C8-8A43740B5FC1}.Debug|x64.ActiveCfg = Debug|Any CPU
{911B08A3-515A-441D-84C8-8A43740B5FC1}.Debug|x64.Build.0 = Debug|Any CPU
{911B08A3-515A-441D-84C8-8A43740B5FC1}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{911B08A3-515A-441D-84C8-8A43740B5FC1}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{911B08A3-515A-441D-84C8-8A43740B5FC1}.Release|x64.ActiveCfg = Release|Any CPU
{911B08A3-515A-441D-84C8-8A43740B5FC1}.Release|x64.Build.0 = Release|Any CPU
{911B08A3-515A-441D-84C8-8A43740B5FC1}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU
{911B08A3-515A-441D-84C8-8A43740B5FC1}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU
{14140310-2233-430A-821B-F3058A25234B}.Debug|x64.ActiveCfg = Debug|Any CPU
{14140310-2233-430A-821B-F3058A25234B}.Debug|x64.Build.0 = Debug|Any CPU
{14140310-2233-430A-821B-F3058A25234B}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{14140310-2233-430A-821B-F3058A25234B}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{14140310-2233-430A-821B-F3058A25234B}.Release|x64.ActiveCfg = Release|Any CPU
{14140310-2233-430A-821B-F3058A25234B}.Release|x64.Build.0 = Release|Any CPU
{14140310-2233-430A-821B-F3058A25234B}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU
{14140310-2233-430A-821B-F3058A25234B}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU
{43146EA8-6FCC-46B8-88B0-42F9BB5E6E0A}.Debug|x64.ActiveCfg = Debug|Any CPU
{43146EA8-6FCC-46B8-88B0-42F9BB5E6E0A}.Debug|x64.Build.0 = Debug|Any CPU
{43146EA8-6FCC-46B8-88B0-42F9BB5E6E0A}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{43146EA8-6FCC-46B8-88B0-42F9BB5E6E0A}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{43146EA8-6FCC-46B8-88B0-42F9BB5E6E0A}.Release|x64.ActiveCfg = Release|Any CPU
{43146EA8-6FCC-46B8-88B0-42F9BB5E6E0A}.Release|x64.Build.0 = Release|Any CPU
{43146EA8-6FCC-46B8-88B0-42F9BB5E6E0A}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU
{43146EA8-6FCC-46B8-88B0-42F9BB5E6E0A}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU
{6F22DB96-6EAB-4034-99D9-287021C36F01}.Debug|x64.ActiveCfg = Debug|Any CPU
{6F22DB96-6EAB-4034-99D9-287021C36F01}.Debug|x64.Build.0 = Debug|Any CPU
{6F22DB96-6EAB-4034-99D9-287021C36F01}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{6F22DB96-6EAB-4034-99D9-287021C36F01}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{6F22DB96-6EAB-4034-99D9-287021C36F01}.Release|x64.ActiveCfg = Release|Any CPU
{6F22DB96-6EAB-4034-99D9-287021C36F01}.Release|x64.Build.0 = Release|Any CPU
{6F22DB96-6EAB-4034-99D9-287021C36F01}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU
{6F22DB96-6EAB-4034-99D9-287021C36F01}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU
{A5C33956-A41F-49B3-A41D-1082CBA59D02}.Debug|x64.ActiveCfg = Debug|Any CPU
{A5C33956-A41F-49B3-A41D-1082CBA59D02}.Debug|x64.Build.0 = Debug|Any CPU
{A5C33956-A41F-49B3-A41D-1082CBA59D02}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{A5C33956-A41F-49B3-A41D-1082CBA59D02}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{A5C33956-A41F-49B3-A41D-1082CBA59D02}.Release|x64.ActiveCfg = Release|Any CPU
{A5C33956-A41F-49B3-A41D-1082CBA59D02}.Release|x64.Build.0 = Release|Any CPU
{A5C33956-A41F-49B3-A41D-1082CBA59D02}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU
{A5C33956-A41F-49B3-A41D-1082CBA59D02}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU
{2FE745C3-1EDF-4421-8CD7-A63EE0C1AE43}.Debug|x64.ActiveCfg = Debug|Any CPU
{2FE745C3-1EDF-4421-8CD7-A63EE0C1AE43}.Debug|x64.Build.0 = Debug|Any CPU
{2FE745C3-1EDF-4421-8CD7-A63EE0C1AE43}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{2FE745C3-1EDF-4421-8CD7-A63EE0C1AE43}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{2FE745C3-1EDF-4421-8CD7-A63EE0C1AE43}.Release|x64.ActiveCfg = Release|Any CPU
{2FE745C3-1EDF-4421-8CD7-A63EE0C1AE43}.Release|x64.Build.0 = Release|Any CPU
{2FE745C3-1EDF-4421-8CD7-A63EE0C1AE43}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU
{2FE745C3-1EDF-4421-8CD7-A63EE0C1AE43}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU
{FDA791E0-2B91-4AD5-B91D-CF920A4C4E38}.Debug|x64.ActiveCfg = Debug|Any CPU
{FDA791E0-2B91-4AD5-B91D-CF920A4C4E38}.Debug|x64.Build.0 = Debug|Any CPU
{FDA791E0-2B91-4AD5-B91D-CF920A4C4E38}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{FDA791E0-2B91-4AD5-B91D-CF920A4C4E38}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{FDA791E0-2B91-4AD5-B91D-CF920A4C4E38}.Release|x64.ActiveCfg = Release|Any CPU
{FDA791E0-2B91-4AD5-B91D-CF920A4C4E38}.Release|x64.Build.0 = Release|Any CPU
{FDA791E0-2B91-4AD5-B91D-CF920A4C4E38}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU
{FDA791E0-2B91-4AD5-B91D-CF920A4C4E38}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU
{B945A183-2174-4ACB-B289-8A5C546656FE}.Debug|x64.ActiveCfg = Debug|Any CPU
{B945A183-2174-4ACB-B289-8A5C546656FE}.Debug|x64.Build.0 = Debug|Any CPU
{B945A183-2174-4ACB-B289-8A5C546656FE}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{B945A183-2174-4ACB-B289-8A5C546656FE}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{B945A183-2174-4ACB-B289-8A5C546656FE}.Release|x64.ActiveCfg = Release|Any CPU
{B945A183-2174-4ACB-B289-8A5C546656FE}.Release|x64.Build.0 = Release|Any CPU
{B945A183-2174-4ACB-B289-8A5C546656FE}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU
{B945A183-2174-4ACB-B289-8A5C546656FE}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU
{39AEA075-6457-46C0-8821-1158BE6EFA57}.Debug|x64.ActiveCfg = Debug|Any CPU
{39AEA075-6457-46C0-8821-1158BE6EFA57}.Debug|x64.Build.0 = Debug|Any CPU
{39AEA075-6457-46C0-8821-1158BE6EFA57}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{39AEA075-6457-46C0-8821-1158BE6EFA57}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{39AEA075-6457-46C0-8821-1158BE6EFA57}.Release|x64.ActiveCfg = Release|Any CPU
{39AEA075-6457-46C0-8821-1158BE6EFA57}.Release|x64.Build.0 = Release|Any CPU
{39AEA075-6457-46C0-8821-1158BE6EFA57}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU
{39AEA075-6457-46C0-8821-1158BE6EFA57}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU
{9F6D7DE8-185B-4C1D-B809-3314D092DF28}.Debug|x64.ActiveCfg = Debug|Any CPU
{9F6D7DE8-185B-4C1D-B809-3314D092DF28}.Debug|x64.Build.0 = Debug|Any CPU
{9F6D7DE8-185B-4C1D-B809-3314D092DF28}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{9F6D7DE8-185B-4C1D-B809-3314D092DF28}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{9F6D7DE8-185B-4C1D-B809-3314D092DF28}.Release|x64.ActiveCfg = Release|Any CPU
{9F6D7DE8-185B-4C1D-B809-3314D092DF28}.Release|x64.Build.0 = Release|Any CPU
{9F6D7DE8-185B-4C1D-B809-3314D092DF28}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU
{9F6D7DE8-185B-4C1D-B809-3314D092DF28}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU
{6BD3F6D8-B6FC-44E8-B8FE-06352F638B9B}.Debug|x64.ActiveCfg = Debug|Any CPU
{6BD3F6D8-B6FC-44E8-B8FE-06352F638B9B}.Debug|x64.Build.0 = Debug|Any CPU
{6BD3F6D8-B6FC-44E8-B8FE-06352F638B9B}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{6BD3F6D8-B6FC-44E8-B8FE-06352F638B9B}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{6BD3F6D8-B6FC-44E8-B8FE-06352F638B9B}.Release|x64.ActiveCfg = Release|Any CPU
{6BD3F6D8-B6FC-44E8-B8FE-06352F638B9B}.Release|x64.Build.0 = Release|Any CPU
{6BD3F6D8-B6FC-44E8-B8FE-06352F638B9B}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU
{6BD3F6D8-B6FC-44E8-B8FE-06352F638B9B}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -80,4 +204,19 @@ Global
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{C09ED521-D2DF-40BC-A76F-EB3CB870B2F6} = {267F81EF-A8AB-49FD-9F56-D7EC3C1F7C7C}
{EF26A475-6101-4C9C-A717-D60BB92A2302} = {C09ED521-D2DF-40BC-A76F-EB3CB870B2F6}
{911B08A3-515A-441D-84C8-8A43740B5FC1} = {C09ED521-D2DF-40BC-A76F-EB3CB870B2F6}
{14140310-2233-430A-821B-F3058A25234B} = {C09ED521-D2DF-40BC-A76F-EB3CB870B2F6}
{43146EA8-6FCC-46B8-88B0-42F9BB5E6E0A} = {C09ED521-D2DF-40BC-A76F-EB3CB870B2F6}
{6F22DB96-6EAB-4034-99D9-287021C36F01} = {C09ED521-D2DF-40BC-A76F-EB3CB870B2F6}
{A5C33956-A41F-49B3-A41D-1082CBA59D02} = {C09ED521-D2DF-40BC-A76F-EB3CB870B2F6}
{2FE745C3-1EDF-4421-8CD7-A63EE0C1AE43} = {C09ED521-D2DF-40BC-A76F-EB3CB870B2F6}
{FDA791E0-2B91-4AD5-B91D-CF920A4C4E38} = {C09ED521-D2DF-40BC-A76F-EB3CB870B2F6}
{B945A183-2174-4ACB-B289-8A5C546656FE} = {C09ED521-D2DF-40BC-A76F-EB3CB870B2F6}
{39AEA075-6457-46C0-8821-1158BE6EFA57} = {C09ED521-D2DF-40BC-A76F-EB3CB870B2F6}
{9F6D7DE8-185B-4C1D-B809-3314D092DF28} = {C09ED521-D2DF-40BC-A76F-EB3CB870B2F6}
{6BD3F6D8-B6FC-44E8-B8FE-06352F638B9B} = {C09ED521-D2DF-40BC-A76F-EB3CB870B2F6}
EndGlobalSection
EndGlobal
+49
View File
@@ -0,0 +1,49 @@
include_guard(DIRECTORY)
function(protobuf_generate_fs)
set(_options)
set(_one_value_args TARGET_NAME OUT_DIR PROTOC)
set(_multi_value_args PROTO_INCLUDE_DIRS PROTO_SOURCES)
cmake_parse_arguments(_ARGS "${_options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN})
if (NOT DEFINED _ARGS_TARGET_NAME)
set(_ARGS_TARGET_NAME "${PROJECT_NAME}_generate_fs_protos")
endif()
if (NOT DEFINED _ARGS_PROTOC)
set(_ARGS_PROTOC "${SPARK_BIN}/protoc")
endif()
set(_proto_include_cli_args)
foreach(_dir ${_ARGS_PROTO_INCLUDE_DIRS})
list(APPEND _proto_include_cli_args -I ${_dir})
endforeach()
set(_outputs)
foreach(_proto_source ${_ARGS_PROTO_SOURCES})
# get_filename_component(_abs_proto_source ${_proto_source} ABSOLUTE)
get_filename_component(_proto_source_we ${_proto_source} NAME_WE)
# get_filename_component(_proto_source_dir ${_proto_source} DIRECTORY)
list(APPEND _outputs ${_ARGS_OUT_DIR}/${_proto_source_we}.fable.fs)
endforeach()
set(_venv_exec ${CMAKE_SOURCE_DIR}/tools/venv_exec.sh)
set(_plugin_py ${CMAKE_SOURCE_DIR}/src/lib/api/service_compiler/plugin.py)
set(_template_fs ${CMAKE_SOURCE_DIR}/src/lib/api/service_compiler/templates/fs/fable.fs)
add_custom_command(
OUTPUT ${_outputs}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND ${_venv_exec}
${_ARGS_PROTOC}
--plugin=protoc-gen-service=${_plugin_py}
--service_out=lang=fs,strip_out_path=1:${_ARGS_OUT_DIR}
${_proto_include_cli_args}
${_ARGS_PROTO_SOURCES}
DEPENDS
${_venv_exec}
${_plugin_py}
${_template_fs}
${CMAKE_CURRENT_SOURCE_DIR}/package-lock.json
${_ARGS_PROTO_SOURCES}
)
add_custom_target(${_ARGS_TARGET_NAME} ALL DEPENDS "${_outputs}")
endfunction()
+33
View File
@@ -0,0 +1,33 @@
include_guard(DIRECTORY)
function(protobuf_generate_js)
set(_options)
set(_one_value_args TARGET_NAME OUTPUT)
set(_multi_value_args PROTO_INCLUDE_DIRS PROTO_SOURCES)
cmake_parse_arguments(_ARGS "${_options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN})
if (NOT DEFINED _ARGS_TARGET_NAME)
set(_ARGS_TARGET_NAME "${PROJECT_NAME}_generate_js_protos")
endif()
set(_proto_include_cli_args)
foreach(_dir ${_ARGS_PROTO_INCLUDE_DIRS})
list(APPEND _proto_include_cli_args -p ${_dir})
endforeach()
add_custom_command(
OUTPUT ${_ARGS_OUTPUT}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND npx
--no-install pbjs
-t json-module
-w commonjs
-o ${_ARGS_OUTPUT}
${_proto_include_cli_args}
${_ARGS_PROTO_SOURCES}
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/package-lock.json
${_ARGS_PROTO_SOURCES}
)
add_custom_target(${_ARGS_TARGET_NAME} ALL DEPENDS ${_ARGS_OUTPUT})
endfunction()
+49
View File
@@ -0,0 +1,49 @@
include_guard(DIRECTORY)
include(${CMAKE_SOURCE_DIR}/cmake/TernaryOperator.cmake)
function(sym_link_dir)
set(_options)
set(_one_value_args TARGET_NAME SOURCE DEST COMMENT)
set(_multi_value_args)
cmake_parse_arguments(_ARGS "${_options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN})
add_custom_command(
OUTPUT ${_ARGS_DEST}
COMMAND ln ARGS -sf ${_ARGS_SOURCE} ${_ARGS_DEST}
)
set_ternary(_comment _ARGS_COMMENT "${_ARGS_COMMENT}" "linking dir ${_ARGS_SOURCE} -> ${_ARGS_DEST}")
add_custom_target(
${_ARGS_TARGET_NAME} ALL
DEPENDS ${_ARGS_DEST}
COMMENT "${_comment}"
)
endfunction()
function(sym_link_file)
set(_options)
set(_one_value_args SOURCE DEST INSTALL_DEST)
set(_multi_value_args)
cmake_parse_arguments(_ARGS "${_options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN})
file(
CREATE_LINK
${_ARGS_SOURCE}
${_ARGS_DEST}
SYMBOLIC
)
if (DEFINED _ARGS_INSTALL_DEST)
install(
FILES
${_ARGS_SOURCE}
DESTINATION ${_ARGS_INSTALL_DEST}
)
endif()
endfunction()
function(sym_link_local_lib LIB_NAME)
set(_lib_file_name "${CMAKE_SHARED_LIBRARY_PREFIX}${LIB_NAME}.so")
file(
CREATE_LINK
"${CMAKE_BINARY_DIR}/src/lib_${LIB_NAME}/${_lib_file_name}"
"${CMAKE_CURRENT_BINARY_DIR}/${_lib_file_name}"
SYMBOLIC
)
endfunction()
+9
View File
@@ -0,0 +1,9 @@
include_guard(GLOBAL)
macro(set_ternary RESULT CONDITION TRUE_VALUE FALSE_VALUE)
if(${CONDITION})
set(${RESULT} ${TRUE_VALUE})
else()
set(${RESULT} ${FALSE_VALUE})
endif()
endmacro()
@@ -4,6 +4,8 @@ namespace bts.SilverPine {
using System;
public class NullFilterStream : ViewStream {
public NullFilterStream(ViewStream parent_str, string col_name)
: this(parent_str, col_name, true) { }
public NullFilterStream(ViewStream parent_str, string col_name, bool filter_out_doublenans = false) {
ViewStream rs = new FilterStream(parent_str,
r => {
@@ -0,0 +1,91 @@
namespace bts.SilverPine {
using BCG;
using System;
using System.Collections.Generic;
using System.Linq;
using ZstdSharp.Unsafe;
public enum GroupByBundleOp {
Mean,
Median,
Mode,
StdDev,
Max,
Min,
First,
Last,
Sum
}
public class GroupByBundle {
public readonly List<Tuple<string, GroupByBundleOp>> operations;
public static GroupByBundle parse(string text) {
// text is inp_col: op, inp_col: op, inp_col:op
List<Tuple<string, GroupByBundleOp>> rv = new List<Tuple<string, GroupByBundleOp>>();
string[] calcs = text.Split(',');
foreach (string c in calcs) {
string[] toks = c.Split(':');
string inp_col = toks[0].Trim();
string op_txt = toks[1].Trim();
GroupByBundleOp op = Enum.Parse<GroupByBundleOp>(op_txt, true);
rv.Add(Tuple.Create(inp_col, op));
}
return new GroupByBundle(rv);
}
public GroupByBundle(List<Tuple<string, GroupByBundleOp>> operations_) {
operations = operations_;
}
public static IBlueRow group_by(List<IBlueRow> rows, GroupByBundle parameters) {
if (rows.Count() == 0) {
return null;
}
IBlueRow ir = rows.ElementAt(0);
IBlueTableSchema in_schema = ir.schema;
IBlueTableSchema out_schema = in_schema;
IDictionary<string, Tuple<string, GroupByBundleOp>> out_col_map =
new Dictionary<string, Tuple<string, GroupByBundleOp>>();
foreach (var t in parameters.operations) {
string out_col_name = $"{t.Item1}_{t.Item2}".ToLower();
out_schema = out_schema.add_column(TypeCode.Double, out_col_name);
out_col_map[out_col_name] = t;
}
MutableBlueRow or = new MutableBlueRow(out_schema, ir);
foreach (var kv in out_col_map) {
string out_col_name = kv.Key;
string in_col_name = kv.Value.Item1;
GroupByBundleOp op = kv.Value.Item2;
IEnumerable<double> col_vals = BlueDataUtility.get_colvalues<double>(rows, in_col_name);
double comp_res = compute_op(col_vals, op);
or.setValue(out_col_name, comp_res);
}
return or;
}
public static double compute_op(IEnumerable<double> col_vals, GroupByBundleOp op) {
switch (op) {
case GroupByBundleOp.Max:
return col_vals.Max<double>();
case GroupByBundleOp.Min:
return col_vals.Min<double>();
case GroupByBundleOp.Mean:
return col_vals.Sum() / col_vals.Count();
case GroupByBundleOp.Sum:
return col_vals.Sum();
case GroupByBundleOp.First:
return col_vals.First();
case GroupByBundleOp.Last:
return col_vals.Last();
default:
// TODO
Log.assert(false);
return Double.NaN;
}
}
}
}
@@ -2,10 +2,13 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.Json;
using bts;
using bts.SilverPine;
namespace BCG {
[DataStreamUserRespresentation("aggregate")]
public class NonIncGroupByStream : ViewStream {
private class DelNonIncGroupByEvaluator : INonIncGroupByEvaluator {
Func<string[]> _groupby_cols_func;
@@ -43,13 +46,31 @@ namespace BCG {
_main_stream = main_stream;
_groupby_func = groupby_func;
_groupby_cols = _groupby_func.get_group_by_cols();
_row_hasher = new BlueRowHasher(_groupby_cols.Length);
_row_hasher = new BlueRowHasher(_groupby_cols == null ? 0 : _groupby_cols.Length);
}
public NonIncGroupByStream(ViewStream main_stream, Func<string[]> groupby_cols_func,
Func<List<IBlueRow>, List<IBlueRow>> evaluate_func)
: this(main_stream, new DelNonIncGroupByEvaluator(groupby_cols_func, evaluate_func)) { }
public NonIncGroupByStream(ViewStream parent_str, string groupby_col, string standard_group_by_text, string optional_sort_col = null) {
GroupByBundle std_group_by = GroupByBundle.parse(standard_group_by_text);
Func<string[]> groupby_cols_func = () => {
return String.IsNullOrEmpty(groupby_col) ? null : new string[] { groupby_col };
};
Func<List<IBlueRow>, List<IBlueRow>> evaluate_func = (group_rows) => {
if (!String.IsNullOrEmpty(optional_sort_col))
group_rows.Sort((r1, r2) => BlueDataUtility.compare_rows(r2, r1, optional_sort_col));
IBlueRow rv = GroupByBundle.group_by(group_rows, std_group_by);
return new IBlueRow[] { rv }.ToList();
};
ViewStream rs = new NonIncGroupByStream(parent_str, groupby_cols_func, evaluate_func);
mySetRealSource(rs);
}
public NonIncGroupByStream(ViewStream parent_str, string standard_group_by_text) :
this(parent_str, String.Empty, standard_group_by_text) {
}
protected override void myOnFirstSubscriber() { _main_stream.onChange += _main_stream_onchange; }
@@ -79,7 +100,9 @@ namespace BCG {
Log.DataFlow.info(
$"Time to evaluate {grouped_rows.Count} groups {_timer.ElapsedMilliseconds}ms, ratio {_n_expensive_fires}/{_n_total_fires}");
}
myFireEvent(new DataPacket(total_state, total_state, myLastValue == null ? new BlueTable(total_state.schema) : (myLastValue as DataPacket).newRows));
// TODO,
if (total_state != null)
myFireEvent(new DataPacket(total_state, total_state, myLastValue == null ? new BlueTable(total_state.schema) : (myLastValue as DataPacket).newRows));
}
}
}
@@ -1,10 +1,12 @@
using bts;
using bts.collections;
using mbts;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security.Cryptography;
namespace BCG {
@@ -35,6 +37,38 @@ namespace BCG {
_is_tag_along = _join_spec.join_col_names.Length == 0;
}
private bool _filled_missing_data = false;
private void _fill_missing_data(DataPacket dp) {
if (_filled_missing_data)
return;
Func<string[], bool> null_or_empty = (str_arr) => {
if (str_arr == null || str_arr.Length == 0)
return true;
foreach (string s in str_arr) {
if (!String.IsNullOrEmpty(s)) {
return false;
}
}
return true;
};
Func<string[], string[]> remove_bad_cols = (col_names) => {
HashSet<string> schema_col_map = new HashSet<string>(dp.schema.columnNames);
List<string> cols_list = new List<string>(col_names);
cols_list.RemoveWhere(c => !schema_col_map.Contains(c));
return cols_list.ToArray();
};
if (null_or_empty(_join_spec.data_col_names)) {
_join_spec.data_col_names = dp.schema.columnNames;
}
_join_spec.data_col_names = remove_bad_cols(_join_spec.data_col_names);
if (null_or_empty(_join_spec.data_col_names)) {
_join_spec.data_col_names = dp.schema.columnNames;
}
if (null_or_empty(_join_spec.projected_col_names)) {
_join_spec.projected_col_names = _join_spec.data_col_names;
}
}
public void init() {
_join_spec.stream.onChange += _on_stream_cb;
}
@@ -43,6 +77,7 @@ namespace BCG {
Log.Gen.info($"NonIncJoinBookkeeper._on_stream_cb dp_obj: {dp_obj}");
var timer = Stopwatch.StartNew();
DataPacket dp = (DataPacket)dp_obj;
_fill_missing_data(dp);
if (_schema == null) {
_schema = dp.schema;
_join_col_indices = BlueDataUtility.get_column_indices(_schema, _join_spec.join_col_names);
@@ -7,11 +7,12 @@ using System.Threading.Tasks;
using mbts;
namespace BCG {
// this a collection of data, theres no joining logic in here
public class NonIncJoinSpec {
public ViewStream stream;
// join col names are the names on which the streams are joined (for equality),
// data col names are the names in the original stream
// projected col naes are their new names, ie if its x:l_x, then x is in the
// projected col names are their new names, ie if its x:l_x, then x is in the
// data_col_names and l_x in projected cols
public string[] join_col_names, data_col_names, projected_col_names;
@@ -1,9 +1,8 @@
using Google.Protobuf.WellKnownTypes;
using System;
using BCG.Utilities.General;
namespace BCG {
using BCG;
namespace bts.SilverPine {
// Keeps adding changing rows to the stream, hence preserving the history
// and creating a time series, for example this can be used to make bbo updates
// into a time series of prices/qty
@@ -9,161 +9,16 @@ using BCG;
using System.Linq;
using System.Data.Entity;
using System.Collections.Generic;
using System.Data.SQLite;
namespace bts.SilverPine {
public class DataRowWrapperRow : IBlueRow {
private readonly DataRow _row;
private readonly IBlueTableSchema _schema;
private readonly int _id;
public DataRowWrapperRow(IBlueTableSchema schema, DataRow row, int id) {
_row = row;
_id = id;
_schema = schema;
}
public virtual IBlueTableSchema schema { get { return _schema; } }
public virtual object this[string col_name] {
get {
ColumnInfo ci = _schema[col_name];
return this[ci.colNo];
}
}
public virtual object this[int index] {
get {
if (_schema[index].colName == "id")
return _id;
return _row[index];
}
}
public virtual int Count { get { return _row.ItemArray.Length; } }
public virtual bool IsSynchronized { get { return false; } }
public virtual object SyncRoot { get { return this; } }
public virtual void CopyTo(Array array, int index) { _row.ItemArray.CopyTo(array, index); }
public virtual bool IsFixedSize { get { return true; } }
virtual public bool IsReadOnly { get { return true; } }
/// <summary>
/// The key can either be the string corrsponding to the column name or
/// the order of the column
/// </summary>
public virtual object this[object key] {
get {
if (key is string)
return this[(string)key];
else if (key is int)
return this[(int)key];
throw new BlueDataException("Wrong type while indexing the row");
}
set { throw new NotSupportedException("The IBlueRow is readonly"); }
}
public virtual ICollection Keys { get { return _schema.columnNames; } }
public virtual ICollection Values { get { return _row.ItemArray; } }
public virtual void Add(object key, object value) { throw new NotSupportedException("The IBlueRow is readonly"); }
public virtual void Clear() { throw new NotSupportedException("The IBlueRow is readonly"); }
public virtual bool Contains(object key) { return _schema[(string)key] != null; }
public IDictionaryEnumerator GetEnumerator() { return new BlueRowDictionaryEnumerator(this); }
IEnumerator IEnumerable.GetEnumerator() { return new BlueRowEnumerator(this); }
public virtual void Remove(object key) { throw new NotSupportedException("The IBlueRow is readonly"); }
}
public class DataTableWrapperTable : IBlueRowCollection {
public class DataTableWrapperTableEnumerator : IEnumerator {
private IEnumerator _enumerator;
private IBlueTableSchema _schema;
private int _id = 0;
public DataTableWrapperTableEnumerator(DataTable table) {
_enumerator = table.Rows.GetEnumerator();
_schema = BlueDataUtility.getSchema(table);
}
public object Current {
get {
DataRow r = _enumerator.Current as DataRow;
return new DataRowWrapperRow(_schema, r, _id);
}
}
public bool MoveNext() {
_id++;
return _enumerator.MoveNext();
}
public void Reset() {
_id = 0;
_enumerator.Reset();
}
}
private readonly DataTable _table;
private IBlueTableSchema _schema;
private IEnumerator _enumerator;
public DataTableWrapperTable(DataTable table) {
_table = table;
_schema = BlueDataUtility.getSchema(_table);
_enumerator = new DataTableWrapperTableEnumerator(table);
}
public virtual IBlueRow this[int id] {
get {
// its assumed the rows index == id
DataRow dr = _table.Rows[id];
return new DataRowWrapperRow(_schema, dr, id);
}
set {
}
}
public virtual IBlueTableSchema schema { get { return _schema; } }
public virtual bool containsID(int id) { return id < _table.Rows.Count; }
// private List<IBlueRow> _rows() {
// List<IBlueRow> rv = new List<IBlueRow>();
// int id = 0;
// foreach (DataRow r in _table.Rows) {
// rv.Add(new DataRowWrapperRow(r, id++));
// }
// return rv;
// }
public virtual IEnumerator GetEnumerator() {
// var rows = _rows();
// return rows.GetEnumerator();
return _enumerator;
}
public virtual bool IsSynchronized { get { return false; } }
public virtual object SyncRoot { get { return this; } }
public virtual void CopyTo(Array array, int index) {
Log.assert(false);
//_rows().CopyTo(array as IBlueRow[], index);
}
public virtual int Count {
get { return _table.Rows.Count; }
}
}
public static class SQLiteHelper {
public static DataTable ExecuteSelectQuery(string connection_string, string query) {
public static string location = "C:\\Users\\m_tah\\development\\data\\";
public static string db_name = "stream_data.db";
public static DataTable execute_select_query(string connection_string, string query) {
// Create a new DataTable to store the results
DataTable dataTable = new DataTable();
@@ -183,48 +38,85 @@ namespace bts.SilverPine {
Console.WriteLine("An error occurred: " + ex.Message);
}
}
// Return the populated DataTable
return dataTable;
}
}
[DataStreamUserRespresentation("SQLite")]
public class SQLiteStream : ViewStream {
private readonly string _connection_string, _query;
private Stopwatch _stopwatch = new Stopwatch();
public SQLiteStream(string connection_string, string query) {
_connection_string = connection_string;
_query = query;
public static List<Dictionary<string, object>> ExecuteBulkRead(string connectionString, string query) {
var result = new List<Dictionary<string, object>>();
using (var connection = new SQLiteConnection(connectionString)) {
connection.Open();
// Begin a transaction to optimize read speed
using (var transaction = connection.BeginTransaction())
using (var command = new SQLiteCommand(query, connection, transaction))
using (var reader = command.ExecuteReader()) {
while (reader.Read()) {
var row = new Dictionary<string, object>();
for (int i = 0; i < reader.FieldCount; i++) {
string columnName = reader.GetName(i);
object columnValue = reader.IsDBNull(i) ? null : reader.GetValue(i);
row[columnName] = columnValue;
}
result.Add(row);
}
// Commit the transaction
transaction.Commit();
}
}
return result;
}
public static List<Dictionary<string, object>> get_table(string connectionString, string query) {
var result = new List<Dictionary<string, object>>();
private void _read_from_sqlite_nd_fire() {
_stopwatch.Start();
DataTable dt = SQLiteHelper.ExecuteSelectQuery(_connection_string, _query);
_stopwatch.Stop();
Log.Gen.info($"SQLiteStream._read_from_sqlite_and_fire time to read db: {_stopwatch.ElapsedMilliseconds} msecs");
_stopwatch.Start();
IBlueRowCollection rows = BCG.BlueDataUtility.toIBlueRowCollection(dt);
//IBlueRowCollection rows = new DataTableWrapperTable(dt);
_stopwatch.Stop();
Log.Gen.info($"SQLiteStream._read_from_sqlite_and_fire time to load into IBlueRowCollection: {_stopwatch.ElapsedMilliseconds} msecs");
DataPacket dp = BlueDataUtility.build_dp_inc(rows, myLastValue as DataPacket);
myFireEvent(dp);
using (var connection = new SQLiteConnection(connectionString)) {
try {
connection.Open();
using (var command = new SQLiteCommand(query, connection))
using (var reader = command.ExecuteReader()) {
while (reader.Read()) {
var row = new Dictionary<string, object>();
for (int i = 0; i < reader.FieldCount; i++) {
string columnName = reader.GetName(i);
object columnValue = reader.IsDBNull(i) ? null : reader.GetValue(i);
row[columnName] = columnValue;
}
result.Add(row);
}
}
} catch (Exception ex) {
Console.WriteLine("An error occurred: " + ex.Message);
}
}
return result;
}
protected override void myOnFirstSubscriber() {
_read_from_sqlite_nd_fire();
public static IEnumerable<Dictionary<string, object>> get_table_2(string connectionString, string query) {
using (var connection = new SQLiteConnection(connectionString)) {
connection.Open();
using (var command = new SQLiteCommand(query, connection))
using (var reader = command.ExecuteReader()) {
while (reader.Read()) {
var row = new Dictionary<string, object>();
for (int i = 0; i < reader.FieldCount; i++) {
string columnName = reader.GetName(i);
object columnValue = reader.IsDBNull(i) ? null : reader.GetValue(i);
row[columnName] = columnValue;
}
yield return row;
}
}
}
}
protected override void myOnLastSubscriber() {
// this value will be cleared by the base class
// but in this particular stream this behaviour
// is not usefull since any new subscribers
// would force the query to be recreated
//myLastDP = myLastValue;
}
}
}
@@ -0,0 +1,73 @@
namespace bts.SilverPine {
using System;
using System.Data;
using System.Data.SQLite;
using System.Diagnostics;
using System.Collections;
using BCG;
[DataStreamUserRespresentation("SQLite")]
public class SQLiteStream : ViewStream {
private readonly string _connection_string, _query;
private Stopwatch _stopwatch = new Stopwatch();
private SQLiteStream(string connection_string, string query, bool noop) {
_connection_string = connection_string;
_query = query;
}
public SQLiteStream(string db_name, string query)
:
this($"Data Source={SQLiteHelper.location}{db_name};Version=3", query, true) { }
public SQLiteStream(string query)
:
this(SQLiteHelper.db_name, query) {
}
private void _read_from_sqlite_and_fire() {
_stopwatch.Start();
DataTable dt = SQLiteHelper.execute_select_query(_connection_string, _query);
_stopwatch.Stop();
Log.Gen.info($"SQLiteStream.ExecuteSelectQuery time to read db: {_stopwatch.ElapsedMilliseconds} msecs");
_stopwatch.Restart();
#region perf measures
// SQLiteHelper.get_table(_connection_string, _query);
// _stopwatch.Stop();
// Log.Gen.info($"SQLiteStream.get_table time to read db: {_stopwatch.ElapsedMilliseconds} msecs");
// _stopwatch.Restart();
// SQLiteHelper.ExecuteBulkRead(_connection_string, _query);
// _stopwatch.Stop();
// Log.Gen.info($"SQLiteStream.ExecuteBulkRead time to read db: {_stopwatch.ElapsedMilliseconds} msecs");
// _stopwatch.Restart();
// SQLiteHelper.get_table_2(_connection_string, _query);
// _stopwatch.Stop();
// Log.Gen.info($"SQLiteStream.get_table_2 time to read db: {_stopwatch.ElapsedMilliseconds} msecs");
// _stopwatch.Restart();
#endregion
IBlueRowCollection rows = BCG.BlueDataUtility.toIBlueRowCollection(dt);
//IBlueRowCollection rows = new DataTableWrapperTable(dt);
_stopwatch.Stop();
Log.Gen.info($"SQLiteStream._read_from_sqlite_and_fire time to load into IBlueRowCollection: {_stopwatch.ElapsedMilliseconds} msecs");
DataPacket dp = BlueDataUtility.build_dp_inc(rows, myLastValue as DataPacket);
myFireEvent(dp);
}
protected override void myOnFirstSubscriber() {
_read_from_sqlite_and_fire();
}
protected override void myOnLastSubscriber() {
// this value will be cleared by the base class
// but in this particular stream this behaviour
// is not usefull since any new subscribers
// would force the query to be recreated
//myLastDP = myLastValue;
}
}
}
@@ -80,7 +80,7 @@ namespace BCG {
public IBlueRow map(IBlueRow row) {
MutableBlueRow rv = new MutableBlueRow(getOutputSchema(row.schema), row);
rv.setValue(myConversionColName, mapDateTime(row[myConversionColName], myConversion));
rv.setValue(myConversionColName, mapDateTime(row.get_datetime(myConversionColName), myConversion));
return rv;
//return BlueDataUtility.getNewReplacedRow(row, mapDateTime(row[myConversionColName], myConversion), myConversionColName);
}
@@ -4,15 +4,17 @@ namespace bts.SilverPine {
using BCG;
using System;
[DataStreamUserRespresentation("DateTimeExpanderStream")]
public class DateTimeExpanstionStream : ViewStream {
public DateTimeExpanstionStream(ViewStream parent_stream, string inp_col) {
Func<IBlueTableSchema, IBlueTableSchema> get_schema = (s) => {
IBlueTableSchema rv = s.add_column(System.TypeCode.DateTime, $"{inp_col}_date");
rv = rv.add_column(System.TypeCode.String, $"{inp_col}_day");
rv = rv.add_column(System.TypeCode.String, $"{inp_col}_month");
rv = rv.add_column(System.TypeCode.String, $"{inp_col}_hour");
rv = rv.add_column(System.TypeCode.String, $"{inp_col}_min");
rv = rv.add_column(System.TypeCode.Int32, $"{inp_col}_day_of_month");
rv = rv.add_column(System.TypeCode.Int32, $"{inp_col}_month");
rv = rv.add_column(System.TypeCode.Int32, $"{inp_col}_hour");
rv = rv.add_column(System.TypeCode.Int32, $"{inp_col}_min");
rv = rv.add_column(System.TypeCode.Int32, $"{inp_col}_seconds_of_day");
return rv;
};
ViewStream rs = new MapStream(parent_stream,
@@ -22,10 +24,12 @@ namespace bts.SilverPine {
IBlueTableSchema s = get_schema(r.schema);
MutableBlueRow rv = new MutableBlueRow(s, r);
rv.setValue($"{inp_col}_date", dt.Date);
rv.setValue($"{inp_col}_day", dt.DayOfWeek.ToString());
rv.setValue($"{inp_col}_month", dt.Date.Month.ToString());
rv.setValue($"{inp_col}_day_of_week", dt.DayOfWeek.ToString());
rv.setValue($"{inp_col}_day_of_month", dt.Day);
rv.setValue($"{inp_col}_month", dt.Date.Month);
rv.setValue($"{inp_col}_hour", dt.Hour);
rv.setValue($"{inp_col}_min", dt.Minute);
rv.setValue($"{inp_col}_minute", dt.Minute);
rv.setValue($"{inp_col}_seconds_of_day", (dt - dt.Date).TotalSeconds);
return rv;
});
mySetRealSource(rs);
@@ -39,7 +39,7 @@ namespace bts.SilverPine {
mySetRealSource(rs);
}
// for each row, does a row[new_ol_name] = row[col1] - rows[row_ffset][col2]
// for each row, does a row[new_col_name] = row[col1] - rows[row_ffset][col2]
// we should generlize this for any math operation
public ColumnDifferenceStream(ViewStream parent_stream, string sort_col_name, int row_offset, string col1, string col2, string diff_col_name, double optional_perc_diff_filter = 0.0) {
// use GlobalMapStream
@@ -91,19 +91,32 @@ namespace bts.SilverPine {
DataPacket dp = dp_obj as DataPacket;
List<IBlueRow> rows = dp.newRows.to_list();
var f1_rows = rows.Where(r => _f1(r));
Log.assert(f1_rows.Count() == 1);
//Log.assert(f1_rows.Count() == 1);
var f2_rows = rows.Where(r => _f2(r));
Log.assert(f2_rows.Count() == 1);
IBlueRow f1_r = f1_rows.ElementAt(0);
IBlueRow f2_r = f2_rows.ElementAt(0);
double diff = f1_r.get<double>(_c1) - f2_r.get<double>(_c2);
IBlueTableSchema out_schema = f1_r.schema.add_columns(Tuple.Create(typeof(double), "diff"));
BlueTable out_table = new BlueTable(out_schema);
MutableBlueRow out_row = new MutableBlueRow(out_schema, f1_r);
out_row.setValue("diff", diff);
out_table.addRow(out_row);
DataPacket dp_out = BlueDataUtility.build_dp(out_table, myLastValue as DataPacket);
myFireEvent(dp_out);
//Log.assert(f2_rows.Count() == 1);
if (f1_rows != null && f1_rows.Count() > 0 && f2_rows != null && f2_rows.Count() > 0) {
IBlueRow f1_r = f1_rows.ElementAt(0);
IBlueRow f2_r = f2_rows.ElementAt(0);
double diff = f1_r.get<double>(_c1) - f2_r.get<double>(_c2);
IBlueTableSchema out_schema = f1_r.schema.add_columns(Tuple.Create(typeof(double), "diff"));
BlueTable out_table = new BlueTable(out_schema);
MutableBlueRow out_row = new MutableBlueRow(out_schema, f1_r);
out_row.setValue("diff", diff);
out_table.addRow(out_row);
DataPacket dp_out = BlueDataUtility.build_dp(out_table, myLastValue as DataPacket);
myFireEvent(dp_out);
} else {
IBlueRow f1_r = rows.ElementAt(0);
IBlueRow f2_r = rows.ElementAt(0);
double diff = Double.NaN;
IBlueTableSchema out_schema = f1_r.schema.add_columns(Tuple.Create(typeof(double), "diff"));
BlueTable out_table = new BlueTable(out_schema);
MutableBlueRow out_row = new MutableBlueRow(out_schema, f1_r);
out_row.setValue("diff", diff);
out_table.addRow(out_row);
DataPacket dp_out = BlueDataUtility.build_dp(out_table, myLastValue as DataPacket);
myFireEvent(dp_out);
}
}
}
@@ -18,6 +18,7 @@ using System.Text.Json.Nodes;
using Newtonsoft.Json;
using BCG;
using System.Runtime.CompilerServices;
using bts;
namespace bts {
@@ -207,6 +208,10 @@ namespace BCG {
return rv;
}
public static bool contains_column(this IBlueTableSchema input_schema, string col) {
return input_schema.columnNames.Contains(col);
}
public static IBlueTableSchema add_columns(this IBlueTableSchema input_schema, params Tuple<Type, string>[] cols) {
List<ColumnInfo> cis = new List<ColumnInfo>(input_schema.columnInfo);
foreach (Tuple<Type, string> c in cols)
@@ -1719,8 +1724,10 @@ namespace BCG {
public static int[] get_column_indices(IBlueTableSchema schema, string[] col_names) {
List<int> rv = new List<int>();
foreach (string c in col_names)
foreach (string c in col_names) {
Log.assert(schema.contains_column(c), $"Could not find col: {c} in table: {schema.tableName}");
rv.Add(schema[c].colNo);
}
return rv.ToArray();
}
@@ -2207,23 +2214,53 @@ namespace BCG {
try {
if (v == null || v == DBNull.Value)
return error_val;
else {
if (v is DateTime)
return (DateTime)v;
else if (v is string) {
DateTime rv_dt = error_val;
DateTime.TryParse((string)v, out rv_dt);
if (v is DateTime) {
return (DateTime)v;
} else if (v is string) {
DateTime rv_dt;
// Try parsing the string as a DateTime
if (DateTime.TryParse((string)v,
System.Globalization.CultureInfo.InvariantCulture,
System.Globalization.DateTimeStyles.RoundtripKind,
out rv_dt)) {
return rv_dt;
} else {
double oa_date = (double)Convert.ChangeType(v, typeof(Double));
return DateTime.FromOADate(oa_date);
return error_val; // Return the error value if parsing fails
}
} else {
// Assume the input can be converted to a double and use FromOADate
double oa_date = (double)Convert.ChangeType(v, typeof(Double));
return DateTime.FromOADate(oa_date);
}
} catch {
return error_val;
}
}
// private static DateTime _get_datetime_value(object v, DateTime error_val) {
// try {
// if (v == null || v == DBNull.Value)
// return error_val;
// else {
// if (v is DateTime)
// return (DateTime)v;
// else if (v is string) {
// DateTime rv_dt = error_val;
// DateTime.TryParse((string)v, out rv_dt);
// return rv_dt;
// } else {
// double oa_date = (double)Convert.ChangeType(v, typeof(Double));
// return DateTime.FromOADate(oa_date);
// }
// }
// } catch {
// return error_val;
// }
// }
#endregion
#region CSV methods
@@ -47,6 +47,11 @@ namespace BCG {
/// </summary>
/// <param name="assemblyName"></param>
public void loadAssembly(string assemblyName) {
Func<Type, bool> is_silverpine_only = (t) => {
if (!assemblyName.Contains("SharedStreams.dll"))
return true;
return t.Namespace == "bts.SilverPine";
};
bts.Log.Gen.info($"PrimitiveManager.loadAssembly assemblyName: {assemblyName}");
if (myAssemblyNames.Contains(assemblyName))
return;
@@ -56,7 +61,7 @@ namespace BCG {
Assembly.LoadFrom(assemblyName);
foreach (Type type in loadedAssembly.GetTypes())
if (!type.IsInterface
if (!type.IsInterface && is_silverpine_only(type)
&& !type.IsAbstract
&& type.IsSubclassOf(typeof(ViewStream))
&& type.IsPublic) {
@@ -1,60 +1,62 @@
"user": "bts",
"pubsub": {
"milliseconds_between_sub_info_updates": 1000,
"milliseconds_between_time_slice_updates": 5000,
"milliseconds_between_no_buffer_space_retries": 100,
"retries_on_no_buffer_space": 1,
"do_it_all_seconds_between_logs": 0,
"heartbeat_interval": 15000,
"missing_heartbeats_before_gc": 50,
"use_managed_sockets": true,
"state_update_period": 5000,
"statistics_update_period": 5000,
"disposed_connection_check_period": 1000,
"message_processing_warning_threshold": 1000,
"server_port": 12345,
"publisher_routes": [
{
"name": "default",
"server_address": "bts-cloud://192.168.1.80:12345",
"tag": ".*"
}
],
"subscriber_routes": [
{
"name": "default",
"server_address": "bts-cloud://192.168.1.80:12345",
"tag": ".*"
}
],
"max_packet_size": 500000000,
"blue_comm_must_die": true,
"crushable_tags": [],
"tag_type_to_assembly_map": [
{
"handler_class": "BCG.PubSubServerTagHandler",
"assembly": "DataStreamCore.dll"
}
],
"server_queue": "PubSubQueue",
"port_range": {
"first": 30400,
"last": 31700
},
"bind_to_random_port": false,
"debug_tags": "^NOTAGS$",
"latency_report_threshold_ms": 1500,
"use_substations": false,
"use_dynamic_clients": false,
"is_substation": false,
"internally_publish_sub_info": true,
"use_managed_clients_in_native_code": false,
"compressed_tags": "^NOTAGS$",
"tags_to_report_compression_ratio": "^NOTAGS$",
"disable_msmq": false,
"use_prepublished_server_state": false,
"ip_translation": null,
"gc_activation_threshold": 0,
"wait_period_after_gc": 1000
}
{
"user": "bts",
"sparkd": "http://192.168.1.80:8000/config",
"pubsub": {
"milliseconds_between_sub_info_updates": 1000,
"milliseconds_between_time_slice_updates": 5000,
"milliseconds_between_no_buffer_space_retries": 100,
"retries_on_no_buffer_space": 1,
"do_it_all_seconds_between_logs": 0,
"heartbeat_interval": 15000,
"missing_heartbeats_before_gc": 50,
"use_managed_sockets": true,
"state_update_period": 5000,
"statistics_update_period": 5000,
"disposed_connection_check_period": 1000,
"message_processing_warning_threshold": 1000,
"server_port": 12345,
"publisher_routes": [
{
"name": "default",
"server_address": "bts-cloud://192.168.1.80:12345",
"tag": ".*"
}
],
"subscriber_routes": [
{
"name": "default",
"server_address": "bts-cloud://192.168.1.80:12345",
"tag": ".*"
}
],
"max_packet_size": 500000000,
"blue_comm_must_die": true,
"crushable_tags": [],
"tag_type_to_assembly_map": [
{
"handler_class": "BCG.PubSubServerTagHandler",
"assembly": "DataStreamCore.dll"
}
],
"server_queue": "PubSubQueue",
"port_range": {
"first": 30400,
"last": 31700
},
"bind_to_random_port": false,
"debug_tags": "^NOTAGS$",
"latency_report_threshold_ms": 1500,
"use_substations": false,
"use_dynamic_clients": false,
"is_substation": false,
"internally_publish_sub_info": true,
"use_managed_clients_in_native_code": false,
"compressed_tags": "^NOTAGS$",
"tags_to_report_compression_ratio": "^NOTAGS$",
"disable_msmq": false,
"use_prepublished_server_state": false,
"ip_translation": null,
"gc_activation_threshold": 0,
"wait_period_after_gc": 1000
}
}
+6
View File
@@ -36,6 +36,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bts.ColumnExpressions", "Bt
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bts.ColumnSortExpressions", "Bts.ColumnSortExpressions\Bts.ColumnSortExpressions.csproj", "{B038CB72-4E81-4AF1-976F-ED8655C2C3D6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataStreamQueryAnalyzer", "DataStreamQueryAnalyzer\DataStreamQueryAnalyzer.csproj", "{BFA5BA5C-927A-41A9-A696-1E2F4B5FFD15}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -113,5 +115,9 @@ Global
{B038CB72-4E81-4AF1-976F-ED8655C2C3D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B038CB72-4E81-4AF1-976F-ED8655C2C3D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B038CB72-4E81-4AF1-976F-ED8655C2C3D6}.Release|Any CPU.Build.0 = Release|Any CPU
{BFA5BA5C-927A-41A9-A696-1E2F4B5FFD15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BFA5BA5C-927A-41A9-A696-1E2F4B5FFD15}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BFA5BA5C-927A-41A9-A696-1E2F4B5FFD15}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BFA5BA5C-927A-41A9-A696-1E2F4B5FFD15}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
@@ -9,8 +9,90 @@ namespace bts.SilverPine {
using ZstdSharp.Unsafe;
public static class ContractUtils {
public class FutureTradableContract {
public string symbol;
public DateTime expiration;
public Exchange exchange;
public string contract_text;
}
public class FutureSpreadContract {
public List<Tuple<double, bts.SilverPine.ContractUtils.FutureTradableContract>> legs;
public string symbol {
get {
return legs == null || legs.Count == 0 ? String.Empty : legs.ElementAt(0).Item2.symbol;
}
}
public string contract_text;
}
public class FutureContinousContract {
public string symbol;
public Exchange exchange;
}
// this is for all future conytracts belonging to a symbol
public class FutureWildcardContract {
public string symbol;
public Exchange exchange;
}
public class FutureContract {
public FutureWildcardContract wildcard_contract = null;
public FutureTradableContract future_tradable_contract = null;
public FutureSpreadContract spread_tradable_contract = null;
public FutureContinousContract continous_constract = null;
public FutureContract(string txt) {
wildcard_contract = parse_wildcard_contract(txt);
if (wildcard_contract != null)
return;
FutureTradableContract fc = parse_future_contract(txt);
if (fc != null) {
future_tradable_contract = fc;
return;
}
FutureSpreadContract sc = parse_future_spread_contract(txt);
if (sc != null) {
spread_tradable_contract = sc;
return;
}
// this is not totally true, since ParseFutureSpreadContract does not
// parse ICS spreads
continous_constract = new FutureContinousContract() { symbol = txt };
}
public bool is_continous_contract => continous_constract != null;
public string symbol() {
if (wildcard_contract != null)
return wildcard_contract.symbol;
if (continous_constract != null) {
return continous_constract.symbol;
} else if (future_tradable_contract != null) {
return future_tradable_contract.symbol;
} else if (spread_tradable_contract != null) {
return spread_tradable_contract.symbol;
}
Log.assert(false);
return String.Empty;
}
public string contract_text {
get {
if (future_tradable_contract != null)
return future_tradable_contract.contract_text;
if (spread_tradable_contract != null)
return spread_tradable_contract.contract_text;
return null;
}
}
}
#region Parsing
public static FutureContract ParseFutureContract(string contract_txt, Exchange exchange = Exchange.CME) {
public static FutureWildcardContract parse_wildcard_contract(string contract_txt, Exchange exchange = Exchange.CME) {
if (contract_txt.EndsWith("*")) {
contract_txt = contract_txt.Replace("*", String.Empty);
return new FutureWildcardContract() {
symbol = contract_txt,
exchange = exchange
};
}
return null;
}
public static FutureTradableContract parse_future_contract(string contract_txt, Exchange exchange = Exchange.CME) {
// Validate the future string length
if (!contract_txt.All(c => Char.IsLetterOrDigit(c)))
return null;
@@ -56,23 +138,24 @@ namespace bts.SilverPine {
// Create the FutureContract instance and set its properties
DateTime expiration = new DateTime(year, month, 1);
var futureContract = new FutureContract {
var futureContract = new FutureTradableContract {
symbol = symbol,
expiration = expiration,
exchange = exchange
exchange = exchange,
contract_text = contract_txt
};
return futureContract;
}
// Method to parse a futures spread, e.g. "CLX4-CLZ4"
public static FutureSpreadContract ParseFutureSpreadContract(string spreadString, Exchange exchange = Exchange.CME) {
public static FutureSpreadContract parse_future_spread_contract(string contract_txt, Exchange exchange = Exchange.CME) {
// Split the string by '-' to handle each leg
var spreadParts = spreadString.Split('-');
var spreadParts = contract_txt.Split('-');
if (spreadParts.Length < 2) {
return null; // Not a valid spread format
}
var spreadContract = new FutureSpreadContract { legs = new List<Tuple<double, FutureContract>>() };
var spreadContract = new FutureSpreadContract { legs = new List<Tuple<double, FutureTradableContract>>() };
foreach (var part in spreadParts) {
// Split each leg by '*', assuming the format is weight*future or just future
@@ -92,33 +175,32 @@ namespace bts.SilverPine {
}
// Parse the future contract
var futureContract = ParseFutureContract(futurePart, exchange);
var futureContract = parse_future_contract(futurePart, exchange);
if (futureContract == null) {
return null; // Failed to parse the future part
}
// Add the leg to the spread contract
spreadContract.legs.Add(new Tuple<double, FutureContract>(weight, futureContract));
spreadContract.legs.Add(new Tuple<double, FutureTradableContract>(weight, futureContract));
}
return spreadContract;
}
public static ContractType DetermineFutureOrSpread(string contract) {
if (contract.Contains("-")) {
return ContractType.FutureSpread;
} else {
return ContractType.Future;
}
}
// public static ContractType DetermineFutureOrSpread(string contract) {
// if (contract.Contains("-")) {
// return ContractType.FutureSpread;
// } else {
// return ContractType.Future;
// }
// }
#endregion
public static ContractType get_type(string contract_txt) {
FutureContract fc = ParseFutureContract(contract_txt);
FutureTradableContract fc = parse_future_contract(contract_txt);
if (fc != null) {
return ContractType.Future;
}
FutureSpreadContract sc = ParseFutureSpreadContract(contract_txt);
FutureSpreadContract sc = parse_future_spread_contract(contract_txt);
if (sc != null) {
return ContractType.FutureSpread;
}
@@ -133,11 +215,11 @@ namespace bts.SilverPine {
}
public static Tuple<ContractCategory, string> get_symbol_category(string symbol) {
ContractUtils.FutureContract fc = ParseFutureContract(symbol);
ContractUtils.FutureTradableContract fc = parse_future_contract(symbol);
if (fc != null) {
return Tuple.Create(ContractCategory.FutureString, fc.symbol);
}
var sc = ParseFutureSpreadContract(symbol);
var sc = parse_future_spread_contract(symbol);
if (sc != null) {
return Tuple.Create(ContractCategory.FutureString, sc.symbol);
}
@@ -165,18 +247,6 @@ namespace bts.SilverPine {
public enum Exchange {
CME
}
public class FutureContract {
public string symbol;
public DateTime expiration;
public Exchange exchange;
}
public class FutureSpreadContract {
public List<Tuple<double, bts.SilverPine.ContractUtils.FutureContract>> legs;
public string symbol {
get {
return legs == null || legs.Count == 0 ? String.Empty : legs.ElementAt(0).Item2.symbol;
}
}
}
}
}
@@ -1,35 +1,105 @@
namespace BCG {
namespace bts.SilverPine {
using BCG.Utilities.General;
using bts.api;
using bts.api.domain;
using bts.api.synthetic;
using bts.md;
using bts.spark;
using bts.utils;
using mbts;
using mbts.co2;
using mbts.config;
using mbts.spark;
using mbts.ui;
using Spark.Core.Data;
using Spark.Core.Disposables;
using BCG;
using Spark.Core.Reactive;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using bts.api.core;
using Spark.Api.MarketData;
using SecurityKey = System.UInt32;
using System.Data;
using System.Reflection.Metadata;
using System.Data.Entity.Infrastructure;
public class FakeBBOStream : ViewStream, IWorker {
private string _symbol;
public BlueTable _table = new BlueTable(BBOStream._bbo_schema);
private DateTime _last_fired_time = DateTime.MinValue;
private TimeSpan _period = TimeSpan.FromMilliseconds(333);
private double _start_price;
private double _inc;
private int _total_count = 5;
private Random _rnd = new Random(DateTime.Now.Millisecond);
private string[] _months = new string[]{
"F","G", "H", "J","K", "M", "N","Q","U","V","X","Z"
};
private ViewStreamDatabase _vsd;
public FakeBBOStream(string contract_type, string exchange, string symbol, double start_price, double inc)
:
this(contract_type, exchange, symbol, start_price, inc, ViewStreamDatabase.instance) { }
public FakeBBOStream(string contract_type, string exchange, string symbol, double start_price, double inc, ViewStreamDatabase vsd) {
_symbol = symbol;
_start_price = start_price;
_inc = inc;
_vsd = vsd;
Log.assert(contract_type.ToLower() == "future");
}
protected override void myOnFirstSubscriber() {
_vsd.addWorker(this);
}
protected override void myOnLastSubscriber() {
_vsd.removeWorker(this);
}
private bool _random_truefase() {
int r = _rnd.Next(0, 100);
return r % 2 == 0;
}
private IBlueRow _perturb_row(IBlueRow er) {
MutableBlueRow rv = new MutableBlueRow(er);
double bid_price = rv.get<double>("bid_price");
bid_price += _inc;
double ask_price = bid_price + _inc;
rv.setValue("bid_price", bid_price);
rv.setValue("ask_price", ask_price);
return rv;
}
public void doWork() {
if (DateTime.Now - _last_fired_time < _period)
return;
_last_fired_time = DateTime.Now;
if (_table.Count == 0) {
// first fire_symbol
string year_code = DateTime.Now.Year.ToString().Last().ToString();
for (int i = 0; i < _total_count; i++) {
string month_code = _months[i % _months.Length];
string sec = $"{_symbol}{month_code}{year_code}";
double bid_price = _start_price + _rnd.Next(-10, 10) * _inc;
double ask_price = bid_price + _inc;
BlueRow nr = new BlueRow(_table.schema, _table.Count, _table.Count, sec,
bid_price, _rnd.Next(1, 10),
ask_price, _rnd.Next(1, 10));
_table.addRow(nr);
var dp = BlueDataUtility.build_dp(new BlueTable(_table), myLastValue as DataPacket);
myFireEvent(dp);
}
} else {
BlueTable deleted_rows = new BlueTable(_table.schema);
BlueTable added_rows = new BlueTable(_table.schema);
// go through all rows, select some at random, add them to deleted, get
// the replaced one and add it to added, updates table state and fire
for (int i = 0; i < _table.Count; i++) {
if (_random_truefase()) {
IBlueRow er = _table[i];
IBlueRow nr = _perturb_row(er);
_table.deleteRow(er);
_table.addRow(nr);
}
}
var dp = BlueDataUtility.build_dp_inc(new BlueTable(_table), myLastValue as DataPacket, true);
myFireEvent(dp);
}
}
}
[DataStreamUserRespresentation("bbo")]
public class BBOStream : ViewStream, IWorker {
@@ -39,7 +109,7 @@ namespace BCG {
private int _id = 0;
IReadOnlyDictionary<SecurityKey, BookTuple> _cached_state = null;
MarketDataClient _md_client;
private readonly IBlueTableSchema _bbo_schema = BlueDataUtility.form_schema("bbo",
internal static IBlueTableSchema _bbo_schema = BlueDataUtility.form_schema("bbo",
Tuple.Create("id", TypeCode.Int32),
Tuple.Create("sec_key", TypeCode.UInt32),
Tuple.Create("sec", TypeCode.String),
@@ -54,9 +124,11 @@ namespace BCG {
}
public BBOStream(ViewStreamDatabase vsd) {
bts.Log.Gen.info("BBOStream.cons");
_vsd = vsd;
_md_client = new MarketDataClient(new MDSource(TimeSpan.FromSeconds(0.5)));
// bts.Log.Gen.info("BBOStream.cons");
// _vsd = vsd;
// _md_client = new MarketDataClient(new MDSource(TimeSpan.FromSeconds(0.5)));
FakeBBOStream rs = new FakeBBOStream("Future", "CME", "ES", 5000, 0.25, vsd);
mySetRealSource(rs);
}
protected override void myOnFirstSubscriber() {
@@ -0,0 +1,249 @@
namespace bts.SilverPine {
using BCG;
using System.Runtime.CompilerServices;
using ZstdSharp.Unsafe;
public class DateTimeRange {
public readonly DateTime range_beg;
public readonly DateTime range_end;
public DateTimeRange(DateTime range_beg_, DateTime range_end_) {
range_beg = range_beg_;
range_end = range_end_;
}
public static DateTimeRange parse(string range_beg_, string range_end_) {
DateTime range_beg = String.IsNullOrEmpty(range_beg_) ? DateTime.MinValue : DateTime.Parse(range_beg_);
DateTime range_end = String.IsNullOrEmpty(range_end_) ? DateTime.MaxValue : DateTime.Parse(range_end_);
return new DateTimeRange(range_beg, range_end);
}
}
public class FutureBarsLoader {
public static string get_db_table_name(BarSize bar_size, string symbol, bool is_continous) {
switch (bar_size) {
case BarSize.ONE_DAY: {
if (is_continous) {
return $"bars_future_{symbol}_continous_1day";
} else
return $"bars_future_{symbol}_all_1day";
}
case BarSize.ONE_MIN: {
if (is_continous) {
return $"bars_future_{symbol}_continous_1min";
} else
return $"bars_future_{symbol}_all_1min";
}
}
Log.assert(false);
return null;
}
}
public enum BarSize {
ONE_DAY = 0,
ONE_MIN
}
[DataStreamUserRespresentation("FuturesHistorical1DayBars")]
public class FuturesHistorical1DayBarsStream : ViewStream {
// this gets the raw bars for a symbol (perhaps make it into a raw bars stream?)
public FuturesHistorical1DayBarsStream(string symbol) :
this(symbol, DateTime.MinValue, DateTime.MaxValue) { }
// creates the non raw stream (ie for tradable contract, or continous contract)
public FuturesHistorical1DayBarsStream(string future_contract, DateTime range_beg, DateTime range_end)
: this(new ContractUtils.FutureContract(future_contract), new DateTimeRange(range_beg, range_end)) { }
// this creates a tradable contracts bars streams or continous contracts stream. if get_all_rows is true,
// it gets the raw table with all contracts for that symbol
public FuturesHistorical1DayBarsStream(ContractUtils.FutureContract future_contract,
DateTimeRange dt_range) {
DateTime beg = dt_range.range_beg;//.ToUniversalTime(),
DateTime end = dt_range.range_end;//.ToUniversalTime();
beg = beg.Date;
end = end.Date.Add(new TimeSpan(23, 59, 59));
// this returns rows from beg and end inclusive
Func<string, SQLiteStream> get_sqlite_str = (table_name) => {
string range_beg_iso8601 = beg.ToString("yyyy-MM-ddTHH:mm:ss"),
range_end_iso8601 = end.ToString("yyyy-MM-ddTHH:mm:ss");
string sql_str = $"select * from {table_name} where ts_event >= '{range_beg_iso8601}' AND ts_event <= '{range_end_iso8601}'";
return new SQLiteStream(SQLiteHelper.db_name, sql_str);
};
string symbol = future_contract.symbol();
if (future_contract.wildcard_contract != null) {
string table_name = FutureBarsLoader.get_db_table_name(BarSize.ONE_DAY, future_contract.symbol(), false);
ViewStream rs = get_sqlite_str(table_name);
//rs = new DateStandardizationStream(rs, "ts_event", DateConversion.ChopTime);
mySetRealSource(rs);
return;
}
if (future_contract.wildcard_contract != null) {
string table_name = FutureBarsLoader.get_db_table_name(BarSize.ONE_DAY, future_contract.symbol(), false);
ViewStream rs = get_sqlite_str(table_name);
//rs = new DateStandardizationStream(rs, "ts_event", DateConversion.ChopTime);
mySetRealSource(rs);
return;
}
if (future_contract.is_continous_contract) {
string table_name = FutureBarsLoader.get_db_table_name(BarSize.ONE_DAY, symbol, true);
ViewStream rs = get_sqlite_str(table_name);
//rs = new DateStandardizationStream(rs, "ts_event", DateConversion.ChopTime);
mySetRealSource(rs);
return;
} else {
string table_name = FutureBarsLoader.get_db_table_name(BarSize.ONE_DAY, future_contract.symbol(), false);
ViewStream rs = get_sqlite_str(table_name);
//rs = new DateStandardizationStream(rs, "ts_event", DateConversion.ChopTime);
//TODO: filter it
rs = new FilterStream(rs, $"symbol = '{future_contract.contract_text}'");
mySetRealSource(rs);
}
}
}
[DataStreamUserRespresentation("FuturesHistorical1MinBars")]
public class FuturesHistorical1MinuteBarsStream : ViewStream {
// same as the above but for one min
// this gets the raw bars for a symbol
public FuturesHistorical1MinuteBarsStream(string symbol) :
this(symbol, DateTime.MinValue, DateTime.MaxValue) { }
// creates the non raw stream (ie for tradable contract, or continous contract)
public FuturesHistorical1MinuteBarsStream(string future_contract, DateTime range_beg, DateTime range_end)
: this(new ContractUtils.FutureContract(future_contract), new DateTimeRange(range_beg, range_end)) { }
// this creates a tradable contracts bars streams or continous contracts stream. if get_all_rows is true,
// it gets the raw table with all contracts for that symbol
public FuturesHistorical1MinuteBarsStream(ContractUtils.FutureContract future_contract,
DateTimeRange dt_range) {
Func<string, SQLiteStream> get_sqlite_str = (table_name) => {
string range_beg_iso8601 = dt_range.range_beg.ToString("yyyy-MM-ddTHH:mm:ss"),
range_end_iso8601 = dt_range.range_end.ToString("yyyy-MM-ddTHH:mm:ss");
//string sql_str = $"select * from {table_name} where DATE(ts_event) >= '{range_beg_iso8601}' AND DATE(ts_event) <= '{range_end_iso8601}'";
string sql_str = $"select * from {table_name} where ts_event >= '{range_beg_iso8601}' AND ts_event <= '{range_end_iso8601}'";
return new SQLiteStream(SQLiteHelper.db_name, sql_str);
};
string symbol = future_contract.symbol();
if (future_contract.wildcard_contract != null) {
string table_name = FutureBarsLoader.get_db_table_name(BarSize.ONE_MIN, future_contract.symbol(), false);
ViewStream rs = get_sqlite_str(table_name);
mySetRealSource(rs);
return;
}
if (future_contract.is_continous_contract) {
string table_name = FutureBarsLoader.get_db_table_name(BarSize.ONE_MIN, symbol, true);
ViewStream rs = get_sqlite_str(table_name);
mySetRealSource(rs);
return;
} else {
string table_name = FutureBarsLoader.get_db_table_name(BarSize.ONE_MIN, future_contract.symbol(), false);
ViewStream rs = get_sqlite_str(table_name);
// TODO: filter
rs = new FilterStream(rs, $"symbol = '{future_contract.contract_text}'");
mySetRealSource(rs);
}
}
}
[DataStreamUserRespresentation("FutureHistoricalBars")]
public class FutureHistoricalBarsStream : ViewStream {
public FutureHistoricalBarsStream(string fut_contract, string range_beg, string range_end) {
DateTime dt_range_beg = DateTime.Parse(range_beg);
DateTime dt_range_end = DateTime.Parse(range_end);
TimeSpan diff = dt_range_end - dt_range_beg;
BarSize bar_size = diff < TimeSpan.FromDays(1.0) ? BarSize.ONE_MIN : BarSize.ONE_DAY;
ViewStream rs = new FutureHistoricalBarsStream(fut_contract, bar_size.ToString(), dt_range_beg, dt_range_end);
mySetRealSource(rs);
}
public FutureHistoricalBarsStream(string fut_contract, string bar_size)
: this(fut_contract, bar_size, DateTime.MinValue, DateTime.MaxValue) { }
public FutureHistoricalBarsStream(string fut_contract, string bar_size,
string dt_range_beg, string dt_range_end)
: this(fut_contract, bar_size, DateTime.Parse(dt_range_beg),
DateTime.Parse(dt_range_end)) { }
public FutureHistoricalBarsStream(string fut_contract, string bar_size,
DateTime dt_range_beg, DateTime dt_range_end)
:
this(new ContractUtils.FutureContract(fut_contract), Enum.Parse<BarSize>(bar_size, true),
new DateTimeRange(dt_range_beg, dt_range_end)) { }
public FutureHistoricalBarsStream(ContractUtils.FutureContract fut_contract, BarSize bar_size,
DateTimeRange dt_range) {
switch (bar_size) {
case BarSize.ONE_DAY: {
ViewStream rs = new FuturesHistorical1DayBarsStream(fut_contract, dt_range);
mySetRealSource(rs);
break;
}
case BarSize.ONE_MIN: {
ViewStream rs = new FuturesHistorical1MinuteBarsStream(fut_contract, dt_range);
mySetRealSource(rs);
break;
}
}
}
}
[DataStreamUserRespresentation("FuturePriceDiff")]
public class FutureHistoricalBarsPriceDiffStream : ViewStream {
public FutureHistoricalBarsPriceDiffStream(string future_contract, string dt1, string dt2)
:
this(new ContractUtils.FutureContract(future_contract), DateTime.Parse(dt1), DateTime.Parse(dt2), "close", ViewStreamDatabase.instance) {
}
public FutureHistoricalBarsPriceDiffStream(string future_contract, DateTime dt1, DateTime dt2)
:
this(new ContractUtils.FutureContract(future_contract), dt1, dt2, "close", ViewStreamDatabase.instance) {
}
public FutureHistoricalBarsPriceDiffStream(string future_contract, DateTime dt1, DateTime dt2, string col)
:
this(new ContractUtils.FutureContract(future_contract), dt1, dt2, col, ViewStreamDatabase.instance) {
}
public FutureHistoricalBarsPriceDiffStream(ContractUtils.FutureContract fc, DateTime dt1, DateTime dt2, string col, ViewStreamDatabase vsd) {
BarSize bsize = dt2 - dt1 >= TimeSpan.FromDays(1.0) ? BarSize.ONE_DAY : BarSize.ONE_MIN;
ViewStream rs = new FutureHistoricalBarsStream(fc, BarSize.ONE_DAY, new DateTimeRange(dt1, dt2));
rs = new ColumnDifferenceStream(rs, r => {
DateTime r_dt = r.get_datetime("ts_event");
var rv = dt1.Equals(r_dt);
return rv;
},
//r => dt2.Equals(r.get_datetime("ts_event")), col, col);
r => {
DateTime r_dt = r.get_datetime("ts_event");
var rv = dt2.Equals(r_dt);
return rv;
}, col, col);
rs = new NewProjectStream(rs, "diff !");
mySetRealSource(rs);
}
}
// takes the contract name and expands it into multuiple columns corresponding to the contracts.
// the contract columns are a union, of all properties with some of them empty
public class ContractExpanderStream : ViewStream {
private static IBlueTableSchema map_schema(IBlueTableSchema schema) {
return schema.add_column(TypeCode.String, "contract_type");
}
public ContractExpanderStream(ViewStream parent_str, string contract_col_name) {
ViewStream rs = new MapStream(parent_str, (schema) => map_schema(schema),
r => {
string contract = r.get<string>(contract_col_name);
var ct = ContractUtils.get_type(contract);
MutableBlueRow rv = new MutableBlueRow(map_schema(r.schema), r);
rv.setValue("contract_type", ct.ToString());
return rv;
});
mySetRealSource(rs);
}
}
}
@@ -0,0 +1,46 @@
using BCG;
namespace bts.SilverPine {
/*
----------------------------------------------------------------------
*Expression*
----------------------------------------------------------------------
//r=FutureHistoricalBars("ES*", "02/01/2024 09:30 AM", "02/01/2024 09:30 PM")
r=FutureHistoricalBars("ES*","One_Day", 02/01/2024, 02/01/2025)
r=contractExpander(r,"symbol")
r=filter(r,"contract_type='FutureSpread'")
r=aggregate(r,"symbol","volume:Sum","ts_event")
r=aggregate(r,"volume_sum:Max")
r=newProject(r,"volume_sum_max:volume symbol !")
----------------------------------------------------------------------
*Schema*
----------------------------------------------------------------------
symbol System.String
id System.Int32
volume System.Double
*/
[DataStreamUserRespresentation("VolumeAgg")]
public class VolumeAggregationStream : ViewStream {
public VolumeAggregationStream(string future_contract, string contract_type, DateTime range_beg, DateTime range_end)
: this(future_contract, contract_type, range_beg, range_end, ViewStreamDatabase.instance) { }
public VolumeAggregationStream(string future_contract, string contract_type, string range_beg, string range_end)
:
this(future_contract, contract_type, DateTime.Parse(range_beg), DateTime.Parse(range_end), ViewStreamDatabase.instance) { }
public VolumeAggregationStream(string future_contract, string contract_type, DateTime range_beg, DateTime range_end, ViewStreamDatabase db) {
future_contract = future_contract.EndsWith("*") ? future_contract : future_contract + "*";
ViewStream r;
r = new FutureHistoricalBarsStream(future_contract,
range_beg.ToString(), range_end.ToString());
r = new ContractExpanderStream(r, "symbol");
r = new FilterStream(r, $"contract_type='{contract_type}'");
r = new NonIncGroupByStream(r, "symbol", "volume:Sum", "ts_event");
r = new NonIncGroupByStream(r, "volume_sum:Max");
r = new NewProjectStream(r, "volume_sum_max:volume symbol !");
mySetRealSource(r);
}
}
}
@@ -1,68 +1,117 @@
// namespace bts.SilverPine {
// using bts;
// using BCG;
// using MongoDB.Driver.Linq;
// using System.ComponentModel.DataAnnotations;
// using ZstdSharp.Unsafe;
// [DataStreamUserRespresentation("VWAP")]
// public class VWAPStream : ViewStream {
// // returns the VWAP stream for each contract
// private static ViewStream build_vwap_stream(string symbol, DateTime begin_dt, DateTime end_dt) {
// var ohlcv_params = OHLCVStream.get_params(begin_dt, end_dt);
// ViewStream ohlc_str = new OHLCVStream(symbol, ohlcv_params);
// // stream is grouped per contract and then vwap is calculated per contract
// GlobalMapStream rs = new GlobalMapStream(ohlc_str, "ts_event", "contract",
// (rows, idx) => {
// Log.Gen.info($"VWAPStream.GlobalMapStream delegate n_rows: {rows.Count} idx: {idx}");
// IBlueRow ir = rows.ElementAt((int)idx);
// var sel_rows = rows.Skip((int)idx).Where(r => r.get_datetime("ts_event") >= end_dt && r.get_datetime("ts_event") <= begin_dt);
// var volumeprices = sel_rows.Select(r => Tuple.Create(r.get<int>("volume"), r.get<double>("close"))).ToList();
// double vwap = IndicatorCalc.calculate_VWAP(volumeprices);
// IBlueTableSchema schema = ir.schema.add_columns(Tuple.Create(typeof(double), "vwap"));
// MutableBlueRow nr = new MutableBlueRow(schema, ir);
// //vwap = -313.987;
// nr.setValue("vwap", vwap);
// return nr;
// });
// return rs;
// }
// // the args want dt1 > dt2, if its nto we, swap them
// static void swap_if_necessary(ref DateTime dt1, ref DateTime dt2) {
// if (dt1 < dt2) {
// DateTime aux = dt1;
// dt1 = dt2;
// dt2 = aux;
// }
// }
// // this assumes the ohlc_str has diff contracts
// public VWAPStream(string symbol_or_contract, DateTime begin_dt, DateTime end_dt) {
// swap_if_necessary(ref begin_dt, ref end_dt);
// Tuple<ContractUtils.ContractCategory, string> ccat_sym_contr = ContractUtils.get_symbol_category(symbol_or_contract);
// string symbol = String.Empty;
// symbol = ccat_sym_contr.Item2;
// var ohlcv_params = OHLCVStream.get_params(begin_dt, end_dt);
// ViewStream vwap_str = build_vwap_stream(symbol, begin_dt, end_dt);
// // vwap_str now has vwap for all contracts. if the arg was a symbol, we need to filter it for the active contract,
// // whihc we do with FutureActivesContractStream. If the arg was a contract we'll simply filter it
// ViewStream rs = vwap_str;
// switch (ccat_sym_contr.Item1) {
// case ContractUtils.ContractCategory.FutureString:
// case ContractUtils.ContractCategory.SpreadString:
// rs = new FilterStream(vwap_str, r => r.get<string>("contract").CompareTo(ccat_sym_contr.Item2) == 0);
// break;
// case ContractUtils.ContractCategory.Symbol:
// rs = new FuturesActiveContractStream(vwap_str);
// break;
// }
// rs = new FilterStream(rs, r => r.get_datetime("ts_event").CompareTo(begin_dt) == 0);
// rs = new NewProjectStream(rs, "! vwap");
// mySetRealSource(rs);
// }
// public VWAPStream(string symbol_or_contract, string begin_dt, string end_dt)
// :
// this(symbol_or_contract, DateTime.Parse(begin_dt), DateTime.Parse(end_dt)) { }
// }
// }
using System;
using BCG;
using bts.api;
namespace bts.SilverPine {
using bts;
using BCG;
using MongoDB.Driver.Linq;
using System.ComponentModel.DataAnnotations;
using ZstdSharp.Unsafe;
/*
----------------------------------------------------------------------
*Expression*
----------------------------------------------------------------------
//nl("Find Settlement price of the SP500 future in the first week of 2023.")
r=FutureHistoricalBars("ES", "One_Day", "1/1/2024", "2/1/2024")
r=math(r, "vprice = volume * close")
r=aggregate(r, "vprice:Sum, volume:Sum")
r=math(r, "vwap = vprice_sum / volume_sum")
r=newProject(r, "vwap !")
----------------------------------------------------------------------
*Schema*
----------------------------------------------------------------------
id System.Int32
vwap System.Double
*/
[DataStreamUserRespresentation("VWAP")]
public class VWAPStream : ViewStream {
// returns the VWAP stream for each contract
private static ViewStream build_vwap_stream(string symbol, DateTime begin_dt, DateTime end_dt) {
var ohlcv_params = OHLCVStream.get_params(begin_dt, end_dt);
ViewStream ohlc_str = new OHLCVStream(symbol, ohlcv_params);
// stream is grouped per contract and then vwap is calculated per contract
GlobalMapStream rs = new GlobalMapStream(ohlc_str, "ts_event", "contract",
(rows, idx) => {
Log.Gen.info($"VWAPStream.GlobalMapStream delegate n_rows: {rows.Count} idx: {idx}");
IBlueRow ir = rows.ElementAt((int)idx);
var sel_rows = rows.Skip((int)idx).Where(r => r.get_datetime("ts_event") >= end_dt && r.get_datetime("ts_event") <= begin_dt);
var volumeprices = sel_rows.Select(r => Tuple.Create(r.get<int>("volume"), r.get<double>("close"))).ToList();
double vwap = IndicatorCalc.calculate_VWAP(volumeprices);
IBlueTableSchema schema = ir.schema.add_columns(Tuple.Create(typeof(double), "vwap"));
MutableBlueRow nr = new MutableBlueRow(schema, ir);
//vwap = -313.987;
nr.setValue("vwap", vwap);
return nr;
});
return rs;
}
// the args want dt1 > dt2, if its nto we, swap them
static void swap_if_necessary(ref DateTime dt1, ref DateTime dt2) {
if (dt1 < dt2) {
DateTime aux = dt1;
dt1 = dt2;
dt2 = aux;
}
}
// this assumes the ohlc_str has diff contracts
public VWAPStream(string symbol_or_contract, DateTime begin_dt, DateTime end_dt) {
swap_if_necessary(ref begin_dt, ref end_dt);
Tuple<ContractUtils.ContractCategory, string> ccat_sym_contr = ContractUtils.get_symbol_category(symbol_or_contract);
string symbol = String.Empty;
symbol = ccat_sym_contr.Item2;
var ohlcv_params = OHLCVStream.get_params(begin_dt, end_dt);
ViewStream vwap_str = build_vwap_stream(symbol, begin_dt, end_dt);
// vwap_str now has vwap for all contracts. if the arg was a symbol, we need to filter it for the active contract,
// whihc we do with FutureActivesContractStream. If the arg was a contract we'll simply filter it
ViewStream rs = vwap_str;
switch (ccat_sym_contr.Item1) {
case ContractUtils.ContractCategory.FutureString:
case ContractUtils.ContractCategory.SpreadString:
rs = new FilterStream(vwap_str, r => r.get<string>("contract").CompareTo(ccat_sym_contr.Item2) == 0);
break;
case ContractUtils.ContractCategory.Symbol:
rs = new FuturesActiveContractStream(vwap_str);
break;
}
rs = new FilterStream(rs, r => r.get_datetime("ts_event").CompareTo(begin_dt) == 0);
rs = new NewProjectStream(rs, "! vwap");
mySetRealSource(rs);
}
public VWAPStream(string symbol_or_contract, string begin_dt, string end_dt)
public VWAPStream(string symbol_or_contract, DateTime beg_dt, DateTime end_dt)
:
this(symbol_or_contract, DateTime.Parse(begin_dt), DateTime.Parse(end_dt)) { }
this(symbol_or_contract, beg_dt, end_dt, ViewStreamDatabase.instance) { }
public VWAPStream(string symbol_or_contract, DateTime beg_dt, DateTime end_dt, ViewStreamDatabase db) {
var fc = new ContractUtils.FutureContract(symbol_or_contract);
ViewStream r = new FutureHistoricalBarsStream(fc, BarSize.ONE_DAY, new DateTimeRange(beg_dt, end_dt));
r = new MathStream(r,
"vprice = volume * close");
r = new NonIncGroupByStream(r,
"vprice:Sum, volume:Sum");
r = new MathStream(r,
"vwap = vprice_sum / volume_sum");
r = new NewProjectStream(r,
"vwap !");
mySetRealSource(r);
}
}
}
}
@@ -75,4 +75,36 @@ namespace bts.SilverPine {
mySetRealSource(rs);
}
}
}
namespace bts.SilverPine {
using BCG;
using bts.api;
using Newtonsoft.Json.Serialization;
[DataStreamUserRespresentation("FuturesContinousContract")]
public class FuturesContinousContractStream : ViewStream {
// the argument is symbol, like "ES" etc and not the contract itself
public FuturesContinousContractStream(string symbol) : this(symbol, ViewStreamDatabase.instance) { }
public FuturesContinousContractStream(string symbol, ViewStreamDatabase db) {
ViewStream r = new FuturesHistorical1DayBarsStream(symbol);
// filtering for futures only
r = new FilterStream(r, r => {
string contract = r.get<string>("symbol");
var fc = new ContractUtils.FutureContract(contract);
return fc.future_tradable_contract != null;
});
r = new NonIncGroupByStream(r,
"ts_event",
"volume:Max");
r = new NewProjectStream(r,
"ts_event symbol !");
r = new SortStream(r,
"-ts_event");
mySetRealSource(r);
}
}
}
@@ -2,6 +2,93 @@ namespace bts.SilverPine {
using bts;
using BCG;
using MongoDB.Driver;
// OHLCV request breakdown
// symbol_or_contract (if symbol, get continous, otherwise get raw and filter it)
// bar_size (one min, one day)
// live/historic
// time range
// internal code oranization only
public class OHLCV_1min_stream : ViewStream {
public OHLCV_1min_stream(string symbol_or_contract, string range_beg, string range_end) :
this(symbol_or_contract, DateTime.Parse(range_beg), DateTime.Parse(range_end)) { }
public OHLCV_1min_stream(string symbol_or_contract, DateTime range_beg, DateTime range_end) {
Func<string, bool, SQLiteStream> get_sqlite_str = (symbol, is_continous_contracts) => {
string table_name = is_continous_contracts ? $"ohlcv_1min_{symbol}_continous" : $"{symbol}_ohlcv_1min";
string db_str = $"Data Source={SQLiteHelper.location}{SQLiteHelper.db_name};Version=3";
string range_beg_iso8601 = range_beg.ToString("yyyy-MM-ddTHH:mm:ss"),
range_end_iso8601 = range_end.ToString("yyyy-MM-ddTHH:mm:ss");
string sql_str = $"select * from {table_name} where DATE(ts_event) >= '{range_beg_iso8601}' AND DATE(ts_event) <= '{range_end_iso8601}'";
return new SQLiteStream(db_str, sql_str);
};
Tuple<ContractUtils.ContractCategory, string> tup = ContractUtils.get_symbol_category(symbol_or_contract);
var contract_cat = tup.Item1;
switch (contract_cat) {
case ContractUtils.ContractCategory.Symbol: {
ViewStream res = get_sqlite_str(tup.Item2, true);
mySetRealSource(res);
// this creates the continous stream for 1 min bars, but we store it in the DB, so we just read it from it
// ViewStream ohlcv_1min = get_sqlite_str(tup.Item2, false);
// // the following is whats written to the DB in the _contionous table
// ohlcv_1min = new DateTimeExpanstionStream(ohlcv_1min, "ts_event");
// ViewStream active_contr_str = new FuturesActiveContractStream(tup.Item2);
// ViewStream res = new NonIncJoinStream(ohlcv_1min, "ts_event_date symbol", "ts_event ts_event_date symbol open high low close volume",
// active_contr_str, "ts_event contract", "ts_event:date contract:continous_contract");
// res = new NullFilterStream(res, "continous_contract", true);
// res = new NewProjectStream(res, "ts_event date continous_contract open high low close volume !");
// mySetRealSource(res);
break;
}
default: {
ViewStream sql_str = get_sqlite_str(tup.Item2, false);
ViewStream rs = new FilterStream(sql_str, $"symbol='{symbol_or_contract}'");
// filter here
mySetRealSource(rs);
break;
}
}
}
}
// this is to make bigger bars form smaller bars. Right now the assumption it only works
// with symbols vs contracts, and the 1min <= aggregation_period < 1day
[DataStreamUserRespresentation("OHLCVAggregated")]
public class OHLCVAggregatedStream : ViewStream {
public OHLCVAggregatedStream(string symbol, string agregation_period, string range_beg, string range_end) :
this(symbol, TimeBasedFilter.parse_duration(agregation_period), DateTime.Parse(range_beg), DateTime.Parse(range_end)) {
}
public OHLCVAggregatedStream(string symbol, TimeSpan aggregation_period, DateTime range_beg, DateTime range_end) {
Tuple<ContractUtils.ContractCategory, string> tup = ContractUtils.get_symbol_category(symbol);
Log.assert(tup.Item1 == ContractUtils.ContractCategory.Symbol);
if (aggregation_period < TimeSpan.FromDays(1.0) &&
aggregation_period > TimeSpan.FromMinutes(1.0)) {
ViewStream r = new OHLCV_1min_stream(symbol, range_beg, range_end);
r = new DateTimeExpanstionStream(r,
"ts_event");
double tot_secs = aggregation_period.TotalSeconds;
r = new MathStream(r,
$"bucket = Floor(ts_event_seconds_of_day / {tot_secs})");
r = new NonIncGroupByStream(r,
"bucket",
// column:operation
"open:first, high:max, low:min, close:last, volume:sum",
"ts_event");
r = new SortStream(r,
"-ts_event");
mySetRealSource(r);
} else {
// fix it later
Log.assert(false);
}
}
}
[DataStreamUserRespresentation("OHLCV")]
// This stream retreives the OHLCV table as is, and fires it as-is.
// The only logic here is as follows:
@@ -80,12 +167,13 @@ namespace bts.SilverPine {
switch (ohlcv_params.type) {
case OHLCVType.LIVE:
case OHLCVType.LIVE_AND_HISTORIC:
// TODO
Log.assert(false);
break;
case OHLCVType.HISTORIC:
// TODO: assuming 1 day only for historic
string table_name = get_table_from_barsize(ohlcv_params.bar_size, symbol);// $"{symbol}_OHLCV_1DAY";
//string table_name = $"{symbol}_{ohlcv_params.bar_size}";
//string table_name = $"{symbol}_{ohlcv_params.bar_size}";
string db_str = $"Data Source={_location}{_db_name};Version=3";
string sql_str = $"select * from {table_name}";
// "Data Source=C:\\Users\\m_tah\\development\\data\\stream_data.db;Version=3;"
@@ -111,6 +199,11 @@ namespace bts.SilverPine {
:
this(symbol, Enum.Parse<OHLCVBarSize>(bar_size)) { }
// aggregates the bars
public OHLCVStream(string symbol, TimeSpan agregation_period) {
}
}
@@ -3,49 +3,22 @@ namespace bts.SilverPine {
using bts;
using DnsClient.Protocol;
using MongoDB.Driver;
[DataStreamUserRespresentation("Settlement")]
public class SettlementPriceStream : ViewStream {
public SettlementPriceStream(string future_name, string date_txt)
: this(future_name, date_txt, String.Empty) { }
: this(future_name, date_txt, date_txt) { }
public SettlementPriceStream(string future_name, string begin_dt, string end_dt)
: this(future_name, DateTime.Parse(begin_dt).Date, DateTime.Parse(end_dt).Date) { }
public SettlementPriceStream(string symbol_or_contract, DateTime begin_dt)
: this(symbol_or_contract, begin_dt, DateTime.MinValue) { }
: this(symbol_or_contract, begin_dt, begin_dt) { }
public SettlementPriceStream(string symbol_or_contract, DateTime begin_dt, DateTime end_dt) {
// DateTime begin_dt = DateTime.Parse(begin_dt_txt);
// DateTime end_dt = String.IsNullOrEmpty(end_dt_txt) ? DateTime.MinValue : DateTime.Parse(end_dt_txt);
Tuple<ContractUtils.ContractCategory, string> ccat_sym_contr = ContractUtils.get_symbol_category(symbol_or_contract);
ContractUtils.ContractCategory contr_cat = ccat_sym_contr.Item1;
string symbol = ccat_sym_contr.Item2;
ViewStream settl_str = new OHLCVStream(symbol);
// TODO: close != settlement, get settlement from somewhere
ViewStream rs = new NewProjectStream(settl_str, "close:settlement");
switch (contr_cat) {
case ContractUtils.ContractCategory.FutureString:
case ContractUtils.ContractCategory.SpreadString:
rs = new FilterStream(settl_str, r => r.get<string>("contract").CompareTo(symbol_or_contract) == 0);
break;
case ContractUtils.ContractCategory.Symbol:
rs = new FuturesActiveContractStream(settl_str);
break;
}
if (DateTime.MinValue.Equals(end_dt)) {
rs = new FilterStream(rs, r => r.get_datetime("ts_event").CompareTo(begin_dt) == 0);
}
rs = new FilterStream(rs, r => {
DateTime dt = r.get_datetime("ts_event");
return dt <= begin_dt && dt >= end_dt;
});
rs = new MathStream(rs, "settlement = close * 1.0");
rs = new NewProjectStream(rs, " ! ts_event:date settlement");
ContractUtils.FutureContract fc = new ContractUtils.FutureContract(symbol_or_contract);
ViewStream rs = new FutureHistoricalBarsStream(fc, BarSize.ONE_DAY, new DateTimeRange(begin_dt, end_dt));
rs = new NewProjectStream(rs, "symbol ts_event:date close:settlement !");
mySetRealSource(rs);
}
@@ -41,70 +41,74 @@
},
{
"category": "VWAP",
"description": "This is a few shot learning example for an NL to DSL. The DSL function in question is VWAP (symbol_or_contract, datetime1, datetime2). The first argument is a future (like CL, for crude oil, or ES for SP 500) or a specific future contract like crude oil Dec 24 future (CLZ4) or SP 500 Dec 2024 (ESZ4). The second argument is where the VWAP has to be calculated (also the beginning of the time window), and the third argument is datetime specifying the end of the time window. So, for example, a 20-day VWAP on 9/30/2023, the first argument will be 9/30/2023, and the third argument will be 9/10/2023 (20 days prior). This will also have datetimes expressed as Today, Yesterday, Last Friday, Last Sunday, etc. For the purpose of this few-shot prompts, let's assume today is 10/11/2024.",
"description": "This is a few shot learning example for an NL to DSL. The DSL function in question is VWAP (symbol_or_contract, perio_end_date, period_start_date). The first argument is a future (like CL, for crude oil, or ES for SP 500) or a specific future contract like crude oil Dec 24 future (CLZ4) or SP 500 Dec 2024 (ESZ4). The second and third argument define the end and start of the VWAP period, respectively. So for example, if we are calculating the 10 day VWAP on 10/15/2023, the period will be from 10/15/2023 going back to 10/05/2023. so the second argument will 10/05/2023 and the third argument will be 10/15/2023. The NL prompt will also have datetimes expressed as Today, Yesterday, Last Friday, Last Sunday, First day of quarter, Today etc. You will need to translate these as dates that need to be passed to the VWAP DSL function. ",
"prompt": "What was the VWAP for SP 500 Future over the past 30 days?",
"examples": [
{
"natural_language": "Find the 20 day VWAP of the SP future on 04/22/2023.",
"dsl": "VWAP(\"ES\", 4/22/2023, 4/02/2023)"
"dsl": "VWAP(\"ES\", 4/02/2023, 4/22/2023)"
},
{
"natural_language": "Find Yesterday's 10 day VWAP of the Crude Oil Future.",
"dsl": "VWAP(\"CL\", 10/10/2024, 10/1/2024)"
"dsl": "VWAP(\"CL\", 10/01/2024, 10/10/2024)"
},
{
"natural_language": "Find today's 30-day VWAP of the 10 year treasury future.",
"dsl": "VWAP(\"ZN\", 10/11/2024, 9/11/2024)"
"dsl": "VWAP(\"ZN\", 9/11/2024, 10/11/2024)"
},
{
"natural_language": "Find the 10 hour VWAP of the Natural Gas future from yesterday at 5:00 PM.",
"dsl": "VWAP(\"NG\", 9/27/2024 17:00:00, 9/27/2024 7:00:00)"
"dsl": "VWAP(\"NG\", 9/27/2024 7:00:00, 9/27/2024 17:00:00)"
}
]
},
{
"category": "Price Diff",
"description": "This is a few shot learning example for an NL to DSL. The DSL function in question is OHLCVPriceDiff (symbol_or_contract, datetime1, datetime2). The first argument is a future (like CL, for crude oil) or a specific future contract like crude oil Dec 24 future (CLZ4). The second and third arguments are dates, that the function finds the difference of prices at. Assume today is 10/15/2024",
"prompt": "What is the difference in SP 500 Future from July 10th to July 14th?",
"category": "FuturePriceDiff",
"description": "This is a few-shot learning example for an NL to DSL. The DSL function in question is FuturePriceDiff(symbol_or_contract, datetime1, datetime2). The first argument is a future (like CL, for crude oil, or ES for SP 500 and so on and so forth) or a specific future contract like crude oil Dec 24 future (CLZ4). The second and third arguments are datetimes (usually dates) that the function finds the difference of prices at. If the dates are given like \"first day of quarter\", \"first week of August\" I expect you to convert them into exact dates since the function only takes specific dates. The second argument should be the smaller datetime and the 3rd argument should be the end of the range, ie the higher one. Any prompt asking for price difference or price move should call FuturePriceDiff. If the dates you compute to pass to FuturePriceDiff are dates on which futures exchange might be closed (like 1/1, 12/25, Thanksgiving etc) please adjust the date to the nearest viable trading date, for example if the date falls on christmas day, change it to 12/24 and so on and so on and so forth, or the date happens to be a saturday or sunday, change it to Friday or Monday depending on which is closest",
"prompt": "What is the difference in SP 500 Future from July 10th to July 14th?",
"examples": [
{
"natural_language": "What is the difference in ZN price from July 10th to July 14th?",
"dsl": "OHLCVPriceDiff(\"ZN\", 7/14/2024, 7/10/2024)"
"dsl": "FuturePriceDiff(\"ZN\", 7/10/2024, 7/14/2024)"
},
{
"natural_language": "What is the price difference in SP Future price from Sept 10th and Sept 5th?",
"dsl": "OHLCVPriceDiff(\"ES\", 9/10/2024, 9/5/2024)"
"dsl": "FuturePriceDiff(\"ES\", 9/5/2024, 9/10/2024)"
},
{
"natural_language": "What is the price difference in Natural Gas Dec Future price from Aug 30th and Aug 20th?",
"dsl": "OHLCVPriceDiff(\"NGZ4\", 8/30/2024, 8/20/2024)"
"dsl": "FuturePriceDiff(\"NGZ4\", 8/20/2024, 8/30/2024)"
},
{
"natural_language": "What is the price move in SP 500 Future from Aug 30th 2023 to October 10th 2023",
"dsl": "FuturePriceDiff(\"ES\", 8/30/2023, 10/10/2023)"
},
{
"natural_language": "What is the price move in SP 500 Future during teh first quarter of last year",
"dsl": "FuturePriceDiff(\"ES\", 1/1/2023, 3/31/2023)"
}
]
},
{
"category": "Absolute Volume",
"description": "this is a few shot learning example for an NL to DSL. The DSL function in question is AbsVolume(symbol, ContractType, datetime1, datetime2), which computes the highest volume in the date range, specified by datetime1, and datetime2. The first argument is a future symbol (like CL for crude oil, ES for SP 500 etc). The second argument is the ContractType, which is \"Future\" for futures and \"FutureSpread\" for future spreads. the 3rd argument is the begin date and the 4th argument is the end date, between which the max volume is computed. If no future is specified use \"*\" from the first arugment. For this prompt, assume today is 10/11/2024 ",
"category": "Aggregate Volume",
"description": "this is a few shot learning example for an NL to DSL. The DSL function in question is VolumeAgg(symbol, contract_type, datetime1, datetime2), which computes the highest volume in the date range, specified by datetime1, and datetime2. The first argument is a future symbol (like CL for crude oil, ES for SP 500 etc). The second argument is the ContractType, which is \"Future\" for futures and \"FutureSpread\" for future spreads. the 3rd argument is the begin date and the 4th argument is the end date, between which the max volume is computed. If no future is specified use \"*\" from the first arugment.",
"prompt": "Which SP 500 Future traded the most between 7/14/2024 and 7/10/2024",
"examples": [
{
"natural_language": "Which SP Future traded the most between 7/14/2024 and 7/10/2024",
"dsl": "AbsVolume(\"ES\", \"Future\", 7/14/2024, 7/10/2024)"
"natural_language": "Which SP Future traded the most between 7/10/2024 and 7/14/2024",
"dsl": "VolumeAgg(\"ES\", \"Future\", 1/1/2024, 1/7/2024)"
},
{
"natural_language": "Which Gold Spread traded the most between Today and Sept 20th",
"dsl": "AbsVolume(\"GC\", \"FutureSpread\", 10/1/2024, 9/20/2024)"
"natural_language": "Which Gold Spread traded the most in the first month of this year",
"dsl": "VolumeAgg(\"GC\", \"FutureSpread\", 1/1/2024, 1/31/2024)"
},
{
"natural_language": "Which 10 year Spread traded the highest volume last week?",
"dsl": "AbsVolume(\"ZN\", \"FutureSpread\", 9/27/2024, 9/23/2024)"
"natural_language": "Which 10 year Spread traded the highest volume last week to now?(in this example(only) we are assuming today is 11/26/2024)",
"dsl": "VolumeAgg(\"ZN\", \"FutureSpread\", 11/19/2024, 11/26/2024)"
},
{
"natural_language": "Which Corn Spread traded the most today?",
"dsl": "AbsVolume(\"ZC\", \"FutureSpread\", 10/1/2024, 10/1/2024)"
},
{
"natural_language": "Which Gold Future traded the most today?",
"dsl": "AbsVolume(\"GC\", \"Future\", 9/30/2024, 9/30/2024)"
"natural_language": "Which Corn Spread traded the most today (in this example(only) we are assuming today is 11/26/2024)?",
"dsl": "VolumeAgg(\"ZC\", \"FutureSpread\", 11/26/2024, 11/26/2024)"
}
]
},
@@ -218,6 +222,17 @@
"dsl": "r=standardDeviation(\"ZN\", \"30d\")\nr=math(r, \"abs_daily_change = Abs(close - open) comp = std_dev * 2.0\")\nr=filter(r,\"abs_daily_change >= comp\")"
}
]
},
{
"category": "Price Movement",
"description": "This is a few shot learning example for an NL to DSL. In the NL prompt the user is asking for the average price movement for e-minis (aka ES aka SP Futures aka SP 500 Futures)) on Wednesday between 9:30 am and 9:40 AM? the multiline DSL is as follows (along with the explaination in comments before each line, dont include the comments in the asnwer): //following line gets the 1 min ohlcv bars of ES \n r=OHLCV_1min_stream(\"ES\")\n //this passes it to the filter stream, which filters the rows for Wednesday, and time bw 9:30 and 9:40.\nr=filter(r,\"ts_event_day = 'Wednesday' and ts_event_hour = 9 and ts_event_min >= 30 and ts_event_min < 40\")\n// this aggregates it using the date as the agregation key, it find the max of the column called high, and the low of the column called min, to find the max and the min prices in that time window\nr=aggregate(r, \"ts_event_date\", \"high: max, low: min\")\n/// this then passes the previous result to the math stream operator which calculates the move, which is the difference between high_max and low_min (columns that were added from the previous aggregate)\nr=math(r,\"move = high_max - low_min\")\n// At the end we compute the mean of the move column, notice this is the total mean, so we are not providing an aggregation col to agregate\nr=aggregate(r, \"move:mean\")\n// we project out every col by the ! operator and call rename move_mean as average_vol\nr=newProject(r,\"move_mean:average_move !\")",
"prompt": "What is the average price movement for SP Futures (ES) on Wednesday between 9:30 am and 9:40 AM ?",
"examples": [
{
"natural_language": "What is the average price movement for SP Futures (ES) on Wednesday between 9:30 am and 9:40 AM?",
"dsl": "r=OHLCV_1min_stream(\"ES\")\nr=filter(r,\"ts_event_day = 'Wednesday' and ts_event_hour = 9 and ts_event_min >= 30 and ts_event_min < 40\")\nr=aggregate(r, \"ts_event_date\", \"high: max, low: min\")\nr=math(r,\"move = high_max - low_min\")\nr=aggregate(r, \"move:mean\")\nr=newProject(r,\"move_mean:average_move !\")"
}
]
}
]
}
@@ -1,4 +1,4 @@
namespace sbts.SilverPine {
namespace bts.SilverPine {
using BCG;
using bts;
+141 -62
View File
@@ -1,56 +1,42 @@
cmake_minimum_required(VERSION 3.5)
cmake_minimum_required(VERSION 3.10)
project(Spark.Admin.WebApp)
include(GNUInstallDirs)
include(${CMAKE_SOURCE_DIR}/cmake/safe_dep_glob.cmake)
include(${CMAKE_SOURCE_DIR}/cmake/Protobuf.cmake)
include(${CMAKE_SOURCE_DIR}/cmake/SymLink.cmake)
include(${CMAKE_SOURCE_DIR}/cmake/ProtobufGenerateJs.cmake)
include(${CMAKE_SOURCE_DIR}/cmake/ProtobufGenerateFs.cmake)
# dependencies
set(PROTO_GLOBS
file(
GLOB_RECURSE ${PROJECT_NAME}_PROTO_SOURCES
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
../lib/api/*.proto
../lib/api/services/*.proto
../lib/domain_model/*.proto
../lib/sparkproto/*.proto
../lib/md/protos/*.proto
../../externals/linux/include/google/protobuf/descriptor.proto
../../externals/linux/include/google/protobuf/timestamp.proto)
../../externals/linux/include/google/protobuf/timestamp.proto
)
file(
GLOB_RECURSE _proto_rel_SOURCES
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
${PROTO_GLOBS})
file(
GLOB_RECURSE _proto_abs_SOURCES
${PROTO_GLOBS})
file(
GLOB_RECURSE _ts_SOURCES
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
src/*.ts)
file(
GLOB_RECURSE _fs_SOURCES
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
src/*.fs)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/package-lock.json
COMMAND npm install --include=dev
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/package.json
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
set(_tools ${CMAKE_SOURCE_DIR}/tools)
set(_quiet_exec ${_tools}/quiet_exec.sh)
set(_venv_exec ${_tools}/venv_exec.sh)
set(_tools_py ${CMAKE_SOURCE_DIR}/tools/py)
# The tsconfig file is generated as a function of the cmake build type, and
# binary dir. Depending on the build type, it extends either
# 'tsconfig.debug.json' or 'tsconfig.release.json'.
set(_tsconfig_base "${CMAKE_CURRENT_SOURCE_DIR}/tsconfig.$<IF:$<CONFIG:Debug>,debug,release>.json")
set(WEBPACK_MODE $<IF:$<CONFIG:Debug>,"development","production">)
set(_tsconfig "${CMAKE_CURRENT_BINARY_DIR}/tsconfig.json")
set(_venv_python ${CMAKE_SOURCE_DIR}/.venv/bin/python)
set(_gen_tsconfig_py ${_tools_py}/gen_tsconfig.py)
set(_build_admin_web_app_py ${_tools_py}/build_admin_web_app.py)
set(_gen_tsconfig_deps ${_venv_exec} ${_venv_python} ${_gen_tsconfig_py} ${_tsconfig_base})
set(_build_admin_web_app_deps
${_quiet_exec}
${_venv_exec}
@@ -60,49 +46,142 @@ set(_build_admin_web_app_deps
${CMAKE_CURRENT_SOURCE_DIR}/webpack.config.js
${CMAKE_SOURCE_DIR}/src/lib/api/service_compiler/templates/fs/fable.fs
${CMAKE_CURRENT_SOURCE_DIR}/package-lock.json
${_proto_rel_SOURCES}
${${PROJECT_NAME}_PROTO_SOURCES}
${_api_ts_SOURCES}
${_ui_fs_SOURCES}
${_tsconfig})
update_deps_file("${_build_admin_web_app_deps}")
function(npm_install path)
add_custom_command(
OUTPUT ${path}/package-lock.json
COMMAND "npm" ARGS "install" "--include=dev"
DEPENDS ${path}/package.json
WORKING_DIRECTORY ${path}
COMMENT "Run 'npm install' in ${path}")
endfunction()
npm_install(${CMAKE_CURRENT_SOURCE_DIR})
add_custom_command(
OUTPUT ${_tsconfig}
COMMAND ${_venv_exec}
ARGS
${_venv_python}
${_gen_tsconfig_py}
--config-base ${_tsconfig_base}
--config-out ${_tsconfig}
--build-out ${CMAKE_CURRENT_BINARY_DIR}
DEPENDS ${_gen_tsconfig_deps})
${_venv_python}
${_gen_tsconfig_py}
--config-base ${_tsconfig_base}
--config-out ${_tsconfig}
--build-out ${CMAKE_CURRENT_BINARY_DIR}
DEPENDS
${_venv_exec}
${_venv_python}
${_gen_tsconfig_py}
${_tsconfig_base}
${CMAKE_CURRENT_SOURCE_DIR}/package-lock.json
)
protobuf_generate_js(
TARGET_NAME generate_js_protos
PROTO_INCLUDE_DIRS
..
../../externals/linux/include
PROTO_SOURCES ${${PROJECT_NAME}_PROTO_SOURCES}
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/proto.js
)
protobuf_generate_fs(
TARGET_NAME generate_fs_protos
PROTO_INCLUDE_DIRS
..
../../externals/linux/include
PROTO_SOURCES ${${PROJECT_NAME}_PROTO_SOURCES}
OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/fable
)
# Each input file maps to multiple output files with different extensions
# (js, js.map, d.ts, fs). Transpile ts -> d.ts (and js), then d.ts -> fs.
# Who knew front-end development could be so complicated? `\_(=_=)_/`
file(
GLOB_RECURSE ${PROJECT_NAME}_TS_SOURCES
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
src/*.ts
)
set(${PROJECT_NAME}_JS_PROTOS)
set(${PROJECT_NAME}_TS_PROTOS)
set(${PROJECT_NAME}_FS_PROTOS)
foreach(_ts_source ${${PROJECT_NAME}_TS_SOURCES})
get_filename_component(_ts_source_we ${_ts_source} NAME_WE)
set(_prefix ${CMAKE_CURRENT_BINARY_DIR}/${_ts_source_we})
list(APPEND ${PROJECT_NAME}_JS_PROTOS ${_prefix}.js ${_prefix}.js.map)
list(APPEND ${PROJECT_NAME}_TS_PROTOS ${_prefix}.d.ts ${_prefix}.d.ts.map)
list(APPEND ${PROJECT_NAME}_FS_PROTOS ${_prefix}.fs)
add_custom_command(
OUTPUT ${_prefix}.fs
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND npx --no-install ts2fable ${_prefix}.d.ts ${_prefix}.fs
DEPENDS ${_prefix}.d.ts
)
endforeach()
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/bundle.js
COMMAND ${_quiet_exec}
ARGS
${_venv_exec}
${_venv_python}
${_build_admin_web_app_py}
--protos ${_proto_abs_SOURCES}
--tsconfig ${_tsconfig}
--mode ${WEBPACK_MODE}
--output-dir ${CMAKE_CURRENT_BINARY_DIR}
COMMENT "Building Spark.Admin.WebApp"
DEPENDS ${_build_admin_web_app_deps})
OUTPUT
${${PROJECT_NAME}_JS_PROTOS}
${${PROJECT_NAME}_TS_PROTOS}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND npx
--no-install tsc
-p ${_tsconfig}
DEPENDS
${_tsconfig}
${CMAKE_CURRENT_BINARY_DIR}/proto.js
)
add_custom_target(
transpile_type_script
ALL
DEPENDS
${${PROJECT_NAME}_JS_PROTOS}
${${PROJECT_NAME}_TS_PROTOS}
)
add_custom_target(generate_fsts_stubs ALL DEPENDS ${${PROJECT_NAME}_FS_PROTOS})
add_custom_target(admin_web_app_bundle ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/bundle.js)
sym_link_dir(
TARGET_NAME link_webapp_public
SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/public
DEST ${CMAKE_CURRENT_BINARY_DIR}/public
)
sym_link_dir(
TARGET_NAME link_webapp_root
SOURCE ${CMAKE_CURRENT_BINARY_DIR}
DEST ${CMAKE_SOURCE_DIR}/build/Spark.Admin.WebApp
)
file(
GLOB_RECURSE ${PROJECT_NAME}_FS_SOURCES
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
src/*.fs
)
add_custom_command(
OUTPUT
${CMAKE_CURRENT_BINARY_DIR}/bundle.js
$<$<CONFIG:DEBUG>:${CMAKE_CURRENT_BINARY_DIR}/bundle.js.map>
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src
COMMAND dotnet restore
COMMAND dotnet tool restore
COMMAND export SPARK_ADMIN_WEB_APP_OUTPUT_DIR=${CMAKE_CURRENT_BINARY_DIR}
COMMAND dotnet fable -o ${CMAKE_CURRENT_BINARY_DIR} --verbose
COMMAND npm run build -- --mode $<IF:$<CONFIG:Debug>,"development","production">
DEPENDS
${${PROJECT_NAME}_FS_SOURCES}
${${PROJECT_NAME}_FS_PROTOS}
${CMAKE_CURRENT_SOURCE_DIR}/src/Spark.Admin.WebApp.fsproj
)
add_custom_target(
bundle_spark_admin_webapp ALL
DEPENDS
${CMAKE_CURRENT_BINARY_DIR}/bundle.js
$<$<CONFIG:DEBUG>:${CMAKE_CURRENT_BINARY_DIR}/bundle.js.map>
COMMENT "Bundling Spark.Admin.WebApp"
)
# Exclude 'public' dir because it is a symlink
install(
DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
DESTINATION "${CMAKE_INSTALL_BINDIR}"
PATTERN "public" EXCLUDE
)
# Install actual (non-symlinked) 'public' dir
install(
DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/public"
DESTINATION "${CMAKE_INSTALL_BINDIR}/${PROJECT_NAME}"
)
install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" DESTINATION "${SPARK_INSTALL_PREFIX}/bin")
+2755 -15230
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -42,7 +42,7 @@
"ts-loader": "^9.4.2",
"tsutils": "^3.21.0",
"typescript": "^4.9.5",
"webpack-cli": "^5.0.1",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.11.1",
"ws": "^8.12.1"
}
+6 -1
View File
@@ -17,7 +17,7 @@ type Tab =
| PositionManagementTab
| MarginTab
let venues = [| Venue.BINANCE; Venue.BinanceUs; Venue.BinanceUsdsFutures; Venue.CBOE; Venue.COINBASE; Venue.DERIBIT; Venue.GLBX; Venue.ICE; Venue.TAIFEX; Venue.SIM; |]
let venues = [| Venue.BINANCE; Venue.BinanceUs; Venue.BinanceUsdsFutures; Venue.CBOE; Venue.COINBASE; Venue.DERIBIT; Venue.OKX; Venue.GLBX; Venue.ICE; Venue.TAIFEX; Venue.SIM; |]
let securityTypes = [| SecurityType.SPOT; SecurityType.FUTURE; SecurityType.OPTION; SecurityType.SPREAD; |]
@@ -74,6 +74,11 @@ type TaifexExchangeField =
| Origin of CustomerOrFirm
| ClearingAccount of string
type OkxExchangeField =
| CTI of CTICode
| Origin of CustomerOrFirm
| ClearingAccount of string
type SIMExchangeField =
| CTI of CTICode
| Origin of CustomerOrFirm
@@ -1,6 +1,7 @@
namespace mbts.spark {
public enum MarketViewColumnType {
None,
Venue,
Security,
Volume,
Last,
@@ -1,4 +1,6 @@
namespace bts.shell.market_view {
using bts.api;
using mbts.spark;
using mbts.spark.filters;
@@ -8,6 +10,8 @@ namespace bts.shell.market_view {
[ProtoContract]
class MarketViewFilterStates {
[ProtoMember(37)]
public Venue[] venue { get; set; }
[ProtoMember(1)]
public string security { get; set; }
[ProtoMember(2)]
@@ -123,6 +127,7 @@ namespace bts.shell.market_view {
public Func<MarketViewVM.Row, bool> build_predicate() {
var pb = new ColumnPredicateBuilder<MarketViewVM.Row>();
// security
pb.add_discrete_values(venue, r => r.venue);
pb.add_regex(security, r => r.security.ToString());
pb.add_regex(security_key, r => r.security_key.ToString());
@@ -206,6 +206,7 @@
// security
public SecurityMD security_md { get; }
public SecurityImmutable security => security_md.security;
public bts.api.Venue venue => security_md.security.Exchange;
public uint security_key => security_md.security.Id;
public string currency => security_md.security.Currency;
@@ -92,6 +92,7 @@ namespace bts.shell.market_view {
}
static class DefaultColumns {
public static GridColumnSettings venue => _mk(Columns.Venue, ColHeader.Exchange, ColWidth.Exchange, h_align: HorizontalAlignment.Left);
public static GridColumnSettings security => _mk(Columns.Security, ColHeader.Security, ColWidth.Security, h_align: HorizontalAlignment.Left);
public static GridColumnSettings currency => _mk(Columns.Currency, ColHeader.Currency, ColWidth.Currency, h_align: HorizontalAlignment.Left, is_visible: false);
public static GridColumnSettings volume => _mk(Columns.Volume, ColHeader.Volume, ColWidth.Volume, h_align: HorizontalAlignment.Left);
@@ -122,6 +122,7 @@ namespace mbts.spark {
const double working_orders_col_width = 105;
var price_converter = new TickDisplayConverter();
add_col(ColKey.Venue, r => r.venue.ToString(), nameof(Row.venue), venue_filter, FilterType.DiscreteValues, ColWidth.Exchange);
add_col(ColKey.Security, r => r.security.ToString(), nameof(Row.security), security_filter, FilterType.Regex, ColWidth.Security);
add_col(ColKey.Currency, r => r.currency, nameof(Row.currency), currency_filter, FilterType.Regex, ColWidth.Currency);
add_col(ColKey.Volume, r => r.volume_text, nameof(Row.volume), volume_filter, FilterType.ULongMinMax, ColWidth.Volume);
@@ -388,6 +389,7 @@ namespace mbts.spark {
internal IBus bus => _bus;
internal OrdersModel orders_model => _orders_model;
public IDataGridColumnPersistentFilter<Row, Venue[]> venue_filter { get; } = DiscreteValuesFilterVM.Common.exchange<Row>(r => r.venue, nameof(Row.venue));
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
public IDataGridColumnPersistentFilter<Row, string> currency_filter { get; }
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<Row>(r => r.bid_price, nameof(Row.bid_price));
@@ -535,6 +537,7 @@ namespace mbts.spark {
public MarketViewFilterStates get_filter_states() =>
new() {
venue = venue_filter.get_state(),
security = security_filter.get_state(),
bid_price = bid_price_filter.get_state(),
bid_merged_size = bid_qty_filter.get_state(),
@@ -580,6 +583,7 @@ namespace mbts.spark {
}
using var _ = rows_view.DeferRefresh();
venue_filter.load_state(filters.venue);
security_filter.load_state(filters.security);
bid_price_filter.load_state(filters.bid_price);
bid_qty_filter.load_state(filters.bid_merged_size);
@@ -6,6 +6,7 @@ using mbts.ui.controls;
using mbts.ui.converters;
using mbts.ui.extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
@@ -127,7 +128,11 @@ namespace mbts.ui.spark {
size,
Observable.Return(calc_buttons));
_show_popup(ticket_vm, new OrderTicketHeader { bbo = ticket_vm.bbo }, resources);
_show_popup(
ticket_vm,
new OrderTicketHeader { bbo = ticket_vm.bbo },
resources,
view => Behavior.SetAutoFocusTargetProvider(view, AutoFocusTargetProvider.TypeProvider(typeof(TextBox), view)));
}
void _show_popup(object content, object header) {
@@ -136,7 +141,7 @@ namespace mbts.ui.spark {
_show_popup(content, header, resources);
}
void _show_popup(object content, object header, ResourceDictionary resources) {
void _show_popup(object content, object header, ResourceDictionary resources, Action<HeaderedContentControl> init_view = null) {
var view = new HeaderedContentControl {
DataContext = content,
Content = content,
@@ -146,6 +151,7 @@ namespace mbts.ui.spark {
Resources = resources ?? new AuxiliaryDataResources()
};
view.Style = view.Resources[AuxiliaryDataResources.PopupChildStyleKey] as Style;
init_view?.Invoke(view);
_popup_man.show(view);
}
@@ -10,6 +10,8 @@ using bts.view;
using mbts.persist;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.extensions;
using mbts.ui.settings;
using mbts.ui.spark;
using mbts.ui.utility;
@@ -27,12 +29,18 @@ using System.Collections.Immutable;
using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
namespace mbts.spark {
class MarketViewWindow : SparkWindow<MarketViewSettings>, ISharedWindow, ISlowMeasureContentSize {
const string _window_type = "MarketView", _window_type_display_name = "Market View";
static int _window_id_counter = 0;
readonly string _window_id = $"{_window_type}_{Interlocked.Increment(ref _window_id_counter)}";
readonly OrdersModel _orders_model;
readonly ISecurityMetadataStore _sec_meta_store;
readonly IPositionModel _position_model;
@@ -122,7 +130,7 @@ namespace mbts.spark {
Log.notnull(_state);
_menu_display_vm = new SparkMenuDisplayVM();
_menu_display_vm.load_from(_state.menu_display);
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
var update_securities_cmd = SparkCommands.change_securities_callback(_window_id);
var security_md_map_src = Observable.Create<IReadOnlyDictionary<uint, SecurityMD>>(o => {
IDisposable sub = null;
_security_criteria_changed += on_security_criteria_changed;
@@ -286,7 +294,32 @@ namespace mbts.spark {
}
}
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
protected override void OnParentViewCreated(SparkWindowView parent_view) {
_menu_display_vm.safe_bind_to_view(parent_view);
ui.WindowStyle.AddWinFormsHostChangedHandler(parent_view, OnWinFormsHostChanged);
void OnWinFormsHostChanged(object sender, RoutedPropertyChangedEventArgs<WPFPanel> e) {
// If this window is "activated" by the user clicking on a popup window (such as an
// order ticket), it never receives keyboard focus (seems like a bug in Windows). As a
// workaround we explicitly focus the view when the parent WPFPanel gets focus. If the
// user clicks on the popup a second time, it should get keyboard focus.
if (e.OldValue is not null) {
e.OldValue.GotFocus -= OnGotFocus;
}
if (e.NewValue is not null) {
e.NewValue.GotFocus += OnGotFocus;
}
void OnGotFocus(object sender, EventArgs e) =>
_view.Dispatcher.InvokeAsync(StealFocus, DispatcherPriority.Send);
void StealFocus() {
if (_view.IsVisible) {
Log.Ui.info($"MarketViewWindow. Stealing focus from WPFPanel");
_view.Focus();
}
}
}
}
}
}
@@ -24,6 +24,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using System.Windows;
class TimeAndSalesWindow : SparkWindow<TimeAndSalesSettings>, ISharedWindow {
@@ -35,7 +36,9 @@
const double _VERTICAL_SCROLLBAR_OVERLAP = 9;
static readonly TimeSpan _trade_poll_interval = TimeSpan.FromSeconds(0.4);
static int _window_id_counter = 0;
readonly string _window_id = $"{WINDOW_TYPE_NAME}_{Interlocked.Increment(ref _window_id_counter)}";
[DisallowNull]
readonly MDConsumer _consumer;
[DisallowNull]
@@ -133,7 +136,7 @@
}
return result;
});
var update_securities_cmd = SparkCommands.change_securities_callback(WINDOW_DISPLAY_NAME);
var update_securities_cmd = SparkCommands.change_securities_callback(_window_id);
var security_criteria_changed = new Action<SecurityCriteria>(sc => { });
SparkCommand toggle_is_paused_cmd = null;
@@ -314,6 +314,12 @@
<TextBlock Text="{Binding param.display_value, Converter={StaticResource DoubleTextConverter}}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.DoubleTemplate>
<!--Nullable Double-->
<usp:AlgoParamTemplateSelector.NullableDoubleTemplate>
<DataTemplate>
<TextBlock Text="{Binding param.display_value, Converter={StaticResource DoubleTextConverter}}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.NullableDoubleTemplate>
<!--Enum-->
<usp:AlgoParamTemplateSelector.EnumTemplate>
<DataTemplate>
@@ -435,6 +441,20 @@
</c:TickUpDownDouble>
</DataTemplate>
</usp:AlgoParamTemplateSelector.DoubleTemplate>
<!--Nullable Double-->
<usp:AlgoParamTemplateSelector.NullableDoubleTemplate>
<DataTemplate>
<c:TickUpDownNullableDouble x:Name="tick_up_down" Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.display_value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
<c:TickUpDownNullableDouble.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</c:TickUpDownNullableDouble.CommandBindings>
<c:TickUpDownNullableDouble.InputBindings>
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
</c:TickUpDownNullableDouble.InputBindings>
</c:TickUpDownNullableDouble>
</DataTemplate>
</usp:AlgoParamTemplateSelector.NullableDoubleTemplate>
<!--Enum-->
<usp:AlgoParamTemplateSelector.EnumTemplate>
<DataTemplate>
@@ -33,6 +33,7 @@ namespace mbts.ui.spark {
public DataTemplate UIntTemplate { get; set; }
public DataTemplate NullableUIntTemplate { get; set; }
public DataTemplate DoubleTemplate { get; set; }
public DataTemplate NullableDoubleTemplate { get; set; }
public DataTemplate EnumTemplate { get; set; }
public DataTemplate NullableEnumTemplate { get; set; }
public DataTemplate StringTemplate { get; set; }
@@ -52,6 +53,7 @@ namespace mbts.ui.spark {
IAlgoParamVM pvm = item as IAlgoParamVM ?? (item as AlgoParamRowVM)?.param;
if (pvm != null) {
if (pvm is IAlgoParamVM<double>) return DoubleTemplate;
if (pvm is IAlgoParamVM<double?>) return NullableDoubleTemplate;
if (pvm is IAlgoParamVM<int>) return IntTemplate;
if (pvm is IAlgoParamVM<int?>) return NullableIntTemplate;
if (pvm is IAlgoParamVM<uint>) return UIntTemplate;
File diff suppressed because it is too large. Load diff
@@ -66,8 +66,12 @@ namespace mbts.spark {
_orders = orders;
_real_price = real_price;
_text_lines = backing(nameof(text_lines), () => {
var fmt = MDDisplayHelper.get_value_formatter(_owner.model.security_md.quantity_display);
return BadgeRender.format(side, _quantity, _filled, _current_badge_style, fmt);
var tf = _owner.model.security.tick_factors();
var fmt = MDDisplayHelper.get_value_formatter(
owner.current_settings.size_display,
_owner.model.security_md.quantity_display,
tf.to_api_tick_display_settingsi());
return BadgeRender.format(side, _quantity, _filled, price, _current_badge_style, fmt);
});
_clear_phantom_price = new(() => {
_phantom_price = double.NaN;
@@ -100,6 +100,9 @@ namespace mbts.spark {
readonly OrderEntryPanelItemVisibilityVM _cancel_all_visibility = new("Cancel All");
readonly OrderEntryPanelItemVisibilityVM _status_message_visibility = new("Status Message");
readonly PanelPricePointVM _bid_price = new("Bid");
readonly PanelPricePointVM _ask_price = new("Ask");
readonly PanelPricePointVM _mid_price = new("Mid");
readonly PanelRelativePricePointVM _day_change = new("Day Change");
readonly PanelPricePointVM _session_high = new("High");
readonly PanelPricePointVM _session_low = new("Low");
@@ -139,9 +142,6 @@ namespace mbts.spark {
accounts_view = new ListCollectionView<AccountTuple?>(_accounts) {
CustomSort = NullCheckComparer<AccountTuple>.Default
};
// cmds
tick_up_ticks_per_row_cmd = new DelegateCommand(increment_ticks_per_row);
tick_down_ticks_per_row_cmd = new DelegateCommand(decrement_ticks_per_row);
// update
_update = new DeferrableAction(() => result_changed?.Invoke());
mini_ob.result_changed += _update.Invoke;
@@ -191,26 +191,6 @@ namespace mbts.spark {
}
AlgoDefinitionVM algo_factory(AlgoData data) => AlgoDefinitionVM.create(_model, data);
void increment_ticks_per_row() {
var tpr = ticks_per_row;
for (uint i = 1; i < 100; i++) {
var new_tpr = TickUtils.fit_ticks_per_row_to_dollars(security, ticks_per_row + i);
if (new_tpr != tpr) {
ticks_per_row = new_tpr;
break;
}
}
}
void decrement_ticks_per_row() {
var tpr = ticks_per_row;
for (uint i = 1; i < tpr; i++) {
var new_tpr = TickUtils.fit_ticks_per_row_to_dollars(security, tpr - i);
if (new_tpr != tpr) {
ticks_per_row = new_tpr;
break;
}
}
}
}
public event Action result_changed;
@@ -235,15 +215,8 @@ namespace mbts.spark {
}
public uint ticks_per_row {
get => _ticks_per_row;
set => Set(ref _ticks_per_row, value, dependent_props: nameof(rounded_ticks_per_row));
set => Set(ref _ticks_per_row, value);
}
public ICommand tick_up_ticks_per_row_cmd { get; }
public ICommand tick_down_ticks_per_row_cmd { get; }
// since settings profiles may be shared among ladders
// of different securities/tick sizes, the ticks per row setting
// may need to be rounded to a number that is a factor of $1.00
public uint? rounded_ticks_per_row =>
TickUtils.fit_ticks_per_row_to_dollars(_security, ticks_per_row);
public MarketSizeDisplayVM size_display { get; } = new MarketSizeDisplayVM();
public SimpleGridSettingsVM mini_ob { get; } = new SimpleGridSettingsVM(OrderBookGridSettingsManager.hard_column_flags);
@@ -327,7 +300,12 @@ namespace mbts.spark {
public IEnumerable order_entry_panel_items {
get {
// Order these items according to their display order in the actual ladder (from top to
// bottom). There are a lot of them, so this is a simple convenience for the user.
yield return _zoom_slider_visbility;
yield return _ask_price;
yield return _mid_price;
yield return _bid_price;
yield return _day_change;
yield return _session_high;
yield return _session_low;
@@ -450,6 +428,12 @@ namespace mbts.spark {
zoom_factor = settings.zoom_factor;
// left-panel (show/hide)
_zoom_slider_visbility.show = settings.zoom_slider_visible;
_bid_price.show = settings.bid_price_visible;
_bid_price.load_from(settings.panel_price_point_settings.bid_price);
_ask_price.show = settings.ask_price_visible;
_ask_price.load_from(settings.panel_price_point_settings.ask_price);
_mid_price.show = settings.mid_price_visible;
_mid_price.load_from(settings.panel_price_point_settings.mid_price);
_day_change.show = settings.day_visible;
_day_change.load_from(settings.panel_price_point_settings.day_change);
_session_high.show = settings.high_visible;
@@ -537,6 +521,9 @@ namespace mbts.spark {
.ToList();
// left-panel price items
clone.panel_price_point_settings = new LadderPanelPricePointSettings {
bid_price = _bid_price.create(),
ask_price = _ask_price.create(),
mid_price = _mid_price.create(),
day_change = _day_change.create(),
open = _open.create(),
session_low = _session_low.create(),
@@ -561,6 +548,9 @@ namespace mbts.spark {
clone.order_entry_panel_initial_width = _order_entry_panel_width;
clone.zoom_factor = zoom_factor;
// left-panel (components)
clone.bid_price_visible = _bid_price.show;
clone.ask_price_visible = _ask_price.show;
clone.mid_price_visible = _mid_price.show;
clone.day_visible = _day_change.show;
clone.high_visible = _session_high.show;
clone.low_visible = _session_low.show;
@@ -715,6 +705,7 @@ namespace mbts.spark {
public uint sample_quantity { get; } = 50;
public uint sample_filled { get; } = 12;
public double sample_price => 2175.50;
public Side sample_side { get; } = Side.Buy;
public Brush badge_border { get; } = new SolidColorBrush(BTSColors.MarketData.ask);
public Brush badge_background { get; } = Brushes.White;
File diff suppressed because it is too large. Load diff
@@ -14,6 +14,7 @@ using mbts.ui.utility.text;
using mbts.utils;
using Spark.Core.Disposables;
using Spark.Core.Math;
using System;
using System.Collections;
@@ -62,6 +63,7 @@ namespace mbts.spark {
readonly RefValueContainer<IEnumerable<VolumeAtPriceLevelImmutable>> _volume_container = new();
IDisposable _settings_sub, _volume_sub, _tick_sub, _fair_value_lines_sub, _vm_sub;
readonly ThrottledAction _scroll_to_tick_log_throttle = new(TimeSpan.FromSeconds(5));
readonly SingleValuePump<ScrollViewer> _scroll_viewer_source;
ScrollViewer _scroll_viewer {
@@ -92,8 +94,8 @@ namespace mbts.spark {
readonly MarketBreakLines _mkt_break_lines;
NativePriceSize _last_trade;
Func<NativePriceSize, string> _fmt_mkt_depth_item_impl = MDDisplayHelper.get_formatter_nps(
MarketSizeDisplay.get_default(), Security.Defaults.quantity_display_settingsi);
Func<double, string> _fmt_mkt_quantity_impl = v => v.ToString();
MarketSizeDisplay.get_default(), Security.Defaults.quantity_display_settingsi, Security.Defaults.price_display_settingsi);
Func<double, double, string> _fmt_mkt_quantity_impl = (v, p) => v.ToString();
int _old_tooltip_row = int.MaxValue;
NativePriceSize? _old_last_trade;
string _direction_arrow = string.Empty;
@@ -452,8 +454,8 @@ namespace mbts.spark {
public bool scroll_to_tick(long tick) {
if (tick > int.MaxValue) {
Log.Ui.error(
$"Unable to scroll to tick {tick} because it exceeds int.MaxValue");
_scroll_to_tick_log_throttle.Execute(() =>
Log.Ui.error($"Unable to scroll to tick {tick} because it exceeds int.MaxValue"));
return false;
}
return scroll_to_tick((int)tick);
@@ -735,8 +737,8 @@ namespace mbts.spark {
};
view.RequestClose += (o, _) => popup.IsOpen = false;
}
string fmt_mbo_qty(int value) =>
value < 0 ? $"-{_fmt_mkt_quantity((uint)value)}" : _fmt_mkt_quantity((uint)value);
string fmt_mbo_qty(double value, double price) =>
value < 0 ? $"-{_fmt_mkt_quantity(value, price)}" : _fmt_mkt_quantity(value, price);
}
protected override void OnMouseDoubleClick(MouseButtonEventArgs e) {
@@ -873,26 +875,7 @@ namespace mbts.spark {
render_market_col(_bids, _bid_market_cell_background);
break;
case ColumnType.Price:
if (_settings.net_change != LadderSettings.NetChangeTerms.None) {
LadderRender.render_prices(
column.visual_tup.visual,
column.visual_tup.glyph_context,
_price_list,
_settings.row_height,
column.settings,
_spec,
_vm.md.settlement_price,
_settings.net_change == LadderSettings.NetChangeTerms.Ticks);
} else {
LadderRender.render_prices(
column.visual_tup.visual,
column.visual_tup.glyph_context,
_price_list,
_settings.row_height,
column.settings,
_spec,
_settings.price_display_format);
}
_refresh_prices(column);
break;
case ColumnType.Ask:
render_market_col(_asks, _ask_market_cell_background);
@@ -1014,8 +997,10 @@ namespace mbts.spark {
//System.Diagnostics.Debug.WriteLine(" Tooltip - Previous tooltip is visible, but we've changed rows");
_hide_tool_tip();
}
_tooltip_view.Content = _fmt_mkt_quantity(volume);
double price = row < 0 || row >= (_price_list?.Count ?? 0)
? double.NaN
: _price_for_row(row);
_tooltip_view.Content = _fmt_mkt_quantity(volume, price);
_tooltip_view.IsOpen = true;
ToolTip = _tooltip_view;
} else {
@@ -1102,6 +1087,23 @@ namespace mbts.spark {
return Disposable.Create(() => _can_user_send_orders_changed -= o.OnNext);
});
LadderPriceList _create_price_list() {
if (_settings is not null) {
switch (_settings.net_change) {
case LadderSettings.NetChangeTerms.BasisPoints:
case LadderSettings.NetChangeTerms.NetBasisPoints:
return new() {
length = _initial_row_count,
regional_tick = new RegionalTickFactors(new user.settings.TickFactors(10000, 1)),
};
}
}
return new() {
length = _initial_row_count,
regional_tick = _spec
};
}
#region Timer.Tick, Refresh Methods
void _tick() {
if (_settings != null) {
@@ -1111,10 +1113,7 @@ namespace mbts.spark {
_mkt_break_lines.refresh();
if (_price_list is null) {
Log.assert(_price_dirty);
_price_list = new LadderPriceList {
length = _initial_row_count,
security = security
};
_price_list = _create_price_list();
var mid_tick = security.tickdiff(_mkt_break_lines.origin, 0) ?? 0;
if (mid_tick > int.MaxValue) {
Log.Ui.error(
@@ -1151,10 +1150,8 @@ namespace mbts.spark {
_scroll_viewer.ScrollToVerticalOffset(new_vertical_offset);
}
}
_refresh_prices();
} else {
_refresh_prices();
}
_refresh_prices();
scroll_to_pending_target();
_refresh_all_visuals();
} else {
@@ -1163,8 +1160,17 @@ namespace mbts.spark {
_refresh_bid_mkt_break();
if (_mkt_break_lines.ask_dirty)
_refresh_ask_mkt_break();
if (_mkt_break_lines.mid_dirty)
if (_mkt_break_lines.mid_dirty) {
switch (_settings.net_change) {
case LadderSettings.NetChangeTerms.BasisPoints:
case LadderSettings.NetChangeTerms.NetBasisPoints:
_refresh_prices();
break;
default:
break;
}
_refresh_mid_mkt_break();
}
_refresh_fair_value_lines(false);
if (_mkt_break_lines.session_low_dirty)
_refresh_session_low_line();
@@ -1174,8 +1180,16 @@ namespace mbts.spark {
_refresh_session_range();
if (last_trade_changed) {
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
if (last_index > 0)
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column, _fmt_mkt_quantity);
if (last_index > 0) {
LadderRender.render_last(
_last_vis_tup.visual,
_last_vis_tup.glyph_context,
_last_trade,
last_index,
_settings.row_height,
_settings.last_column,
_fmt_mkt_depth_item);
}
}
if (depth_changed)
_refresh_sizes();
@@ -1190,25 +1204,27 @@ namespace mbts.spark {
}
}
void _refresh_prices() {
void _refresh_prices(Column column = null) {
if (_settings is null) {
return;
}
if (_settings.net_change != LadderSettings.NetChangeTerms.None) {
LadderRender.render_prices(
_price_vis_tup.visual, _price_vis_tup.glyph_context,
_price_list, _vm.md.settlement_price,
_settings.net_change == LadderSettings.NetChangeTerms.Ticks,
_settings.row_height, _settings.price_column,
_spec);
} else {
LadderRender.render_prices(
_price_vis_tup.visual, _price_vis_tup.glyph_context,
_price_list, _settings.row_height,
_settings.price_column, _spec,
_settings.price_display_format);
}
column ??= _price_column;
var ref_price = _settings.net_change switch {
LadderSettings.NetChangeTerms.BasisPoints or
LadderSettings.NetChangeTerms.NetBasisPoints or
LadderSettings.NetChangeTerms.Ticks => _get_mid_market_break(),
_ => 0.0
};
LadderRender.render_prices(
column.visual_tup.visual,
column.visual_tup.glyph_context,
_price_list,
ref_price,
_settings.net_change,
_settings.row_height,
column.settings,
_spec,
_settings.price_display_format);
}
void _refresh_all_visuals() {
@@ -1264,26 +1280,43 @@ namespace mbts.spark {
}
void _refresh_session_low_line() {
if (_settings.session_low_line.show && _mkt_break_lines.low_render_index.HasValue)
_render_session_line(_session_low_vis, _mkt_break_lines.low_render_index.Value, _settings.session_low_line.color);
if (_settings.session_low_line.show) {
_render_session_line(_session_low_vis, _mkt_break_lines.low_render_index ?? -1, _settings.session_low_line.color);
}
}
void _refresh_session_high_line() {
if (_settings.session_high_line.show && _mkt_break_lines.high_render_index.HasValue)
_render_session_line(_session_high_vis, _mkt_break_lines.high_render_index.Value, _settings.session_high_line.color);
if (_settings.session_high_line.show) {
_render_session_line(_session_high_vis, _mkt_break_lines.high_render_index ?? -1, _settings.session_high_line.color);
}
}
void _refresh_last_trade() {
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
if (last_index > 0)
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column, _fmt_mkt_quantity);
LadderRender.render_last(
_last_vis_tup.visual,
_last_vis_tup.glyph_context,
_last_trade,
last_index,
_settings.row_height,
_settings.last_column,
_fmt_mkt_depth_item);
}
void _refresh_piq() {
if (!_settings.piq_column.show)
return;
int origin_index = LadderRender.index_of(_price_list, _mkt_break_lines.origin, _price_comparer);
LadderRender.render_piqs(_piq_vis_tup.visual, _last_vis_tup.glyph_context, _price_list, _vm.price_vs_piq, _settings.row_height, _spec, _settings.piq_column, _fmt_mkt_quantity);
LadderRender.render_piqs(
_piq_vis_tup.visual,
_last_vis_tup.glyph_context,
_price_list,
_vm.price_vs_piq,
_settings.row_height,
_spec,
_settings.piq_column,
_fmt_mkt_quantity);
}
void _refresh_sizes() {
@@ -1345,7 +1378,7 @@ namespace mbts.spark {
}
// regardless of if we've arrived here via a new Last (and new direction arrow)
// or we're reusing the old one, this format bit is the same
return $"{_direction_arrow} {_fmt_mkt_quantity(_last_trade.size)} ";
return $"{_direction_arrow} {_fmt_mkt_quantity(_last_trade.size, _last_trade.price)} ";
}
}
@@ -1361,7 +1394,7 @@ namespace mbts.spark {
return;
if (_price_list is null) {
_price_list = new LadderPriceList {
security = security
regional_tick = _spec
};
}
_price_list.reset(null, null, _initial_row_count);
@@ -1398,7 +1431,10 @@ namespace mbts.spark {
double _get_bid_market_break() {
double best_bid = _get_top_price(_bids);
double best_ask = _get_top_price(_asks);
return LadderPriceLineViewAdapter.bid_line(best_bid, best_ask, _vm.settlement, _spec);
var v = best_bid
.if_nan(_spec.tickdown(best_ask))
.if_nan(_vm.settlement);
return _spec.round_to_tick(v, RoundingMode.AbsDown);
}
double _normalize_price(double price) =>
@@ -1407,13 +1443,20 @@ namespace mbts.spark {
double _get_ask_market_break() {
double best_bid = _get_top_price(_bids);
double best_ask = _get_top_price(_asks);
return LadderPriceLineViewAdapter.ask_line(best_bid, best_ask, _vm.settlement, _spec);
var v = _spec.tickdown(best_ask)
.if_nan(best_bid)
.if_nan(_vm.settlement);
return _spec.round_to_tick(v, RoundingMode.AbsDown);
}
double _get_mid_market_break() {
double best_bid = _get_top_price(_bids);
double best_ask = _get_top_price(_asks);
return LadderPriceLineViewAdapter.mid_line(best_bid, best_ask, _vm.settlement, _spec);
var v = TickUtils.mid_tick(_spec, best_bid, best_ask)
.if_nan(best_bid)
.if_nan(_spec?.tickdown(best_ask) ?? double.NaN)
.if_nan(_vm.settlement);
return _spec.round_to_tick(v, RoundingMode.AbsDown);
}
double _get_session_low() => _normalize_price(_get_session_low_raw());
@@ -1445,7 +1488,13 @@ namespace mbts.spark {
_pending_scroll_to_origin |= _settings == null;
_invalidate_spec();
if (_price_list != null) {
_price_list.security = security;
_pending_price_list = null;
if (_price_list.Count > 0) {
var high_price = _price_list.First();
var new_tickdiff = _spec.count_ticks(high_price);
_price_list.high_tick = (long)new_tickdiff;
}
_price_list.regional_tick = _spec;
}
_bid_vis_tup.glyph_context = glyph_context(s.bid_column);
_ask_vis_tup.glyph_context = glyph_context(s.ask_column);
@@ -1457,9 +1506,11 @@ namespace mbts.spark {
static GlyphContext glyph_context(LadderColumnSettings cs) =>
GlyphContext.create(cs.font_family, cs.font_weight);
var price_display = _spec.tick_factors(0).to_api_tick_display_settingsi();
_fmt_mkt_depth_item_impl = MDDisplayHelper.get_formatter_nps(
_settings.size_display, _qty_display);
_fmt_mkt_quantity_impl = MDDisplayHelper.get_value_formatter(_qty_display);
_settings.size_display, _qty_display, price_display);
_fmt_mkt_quantity_impl = MDDisplayHelper.get_value_formatter(
_settings.size_display, _qty_display, price_display);
_set_columns();
_apply_column_visual_offset();
if (prev_row_height != _settings.row_height)
@@ -1530,6 +1581,7 @@ namespace mbts.spark {
// }
scroll_to_pending_target();
visible_columns_changed?.Invoke();
_refresh_all_visuals();
}
// This is designed to be a redundant null check for _fmt_mkt_depth_item_impl.
@@ -1537,16 +1589,17 @@ namespace mbts.spark {
// Assign the impl delegate to a local variable, just in case it is reassigned on another thread.
// This shouldn't happen, but better to avoid the potential null ref just in case it does. Expect the unexpected.
var impl = _fmt_mkt_depth_item_impl;
impl ??= MDDisplayHelper.get_formatter_nps(MarketSizeDisplay.get_default(), _qty_display);
impl ??= MDDisplayHelper.get_formatter_nps(
MarketSizeDisplay.get_default(), _qty_display, _spec.tick_factors(0).to_api_tick_display_settingsi());
return impl(value);
}
// This is designed to be a redundant null check for _fmt_mkt_quantity_impl.
string _fmt_mkt_quantity(double value) {
string _fmt_mkt_quantity(double value, double price) {
// Assign the impl delegate to a local variable, just in case it is reassigned on another thread.
// This shouldn't happen, but better to avoid the potential null ref just in case it does. Expect the unexpected.
var impl = _fmt_mkt_quantity_impl;
return impl is null ? value.ToString() : impl(value);
return impl is null ? value.ToString() : impl(value, price);
}
void _init_tactile_adorner() {
@@ -1,62 +0,0 @@
using bts.api;
namespace mbts.spark {
using RegionalTickFactors = user.settings.RegionalTickFactors;
class LadderPriceLineViewAdapter {
public static double bid_line(
double bid, double ask, double settlement, RegionalTickFactors spec) {
return TickUtils.normalize_price(spec, impl());
double impl() {
if (!double.IsNaN(bid)) {
return bid;
}
if (!double.IsNaN(ask)) {
return _try_tick_down(spec, ask);
}
return settlement;
}
}
public static double ask_line(
double bid, double ask, double settlement, RegionalTickFactors spec) {
return TickUtils.normalize_price(spec, impl());
double impl() {
if (!double.IsNaN(ask)) {
return _try_tick_down(spec, ask);
}
if (!double.IsNaN(bid)) {
return bid;
}
return settlement;
}
}
public static double mid_line(
double bid, double ask, double settlement, RegionalTickFactors spec) {
return TickUtils.normalize_price(spec, impl());
double impl() {
if (double.IsNaN(bid)) {
if (double.IsNaN(ask)) {
return settlement;
}
return _try_tick_down(spec, ask);
}
if (double.IsNaN(ask)) {
return bid;
}
if (spec is not null) {
return TickUtils.mid_tick(spec, bid, ask);
}
return bid;
}
}
static double _try_tick_down(RegionalTickFactors spec, double value) {
if (spec is not null) {
return spec.tickdown(value);
}
return value;
}
}
}
@@ -16,33 +16,34 @@ namespace mbts.spark {
long _length;
long _high_tick;
SecurityImmutable? _security;
RegionalTickFactors _regional_tick_backing;
RegionalTickFactors _regional_tick =>
_regional_tick_backing ??= TickUtils.get_regional_ticks(_security);
public LadderPriceList() { }
public LadderPriceList()
: this(RegionalTickFactors.Default) { }
public LadderPriceList(RegionalTickFactors regional_tick) =>
this.regional_tick = regional_tick;
public LadderPriceList(LadderPriceList other) {
_length = other._length;
_high_tick = other._high_tick;
_security = other._security;
regional_tick = other.regional_tick;
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
public SecurityImmutable? security {
get => _security;
RegionalTickFactors _regional_tick;
public RegionalTickFactors regional_tick {
get => _regional_tick;
set {
if (value == _security) {
return;
if (value is null) {
_regional_tick = RegionalTickFactors.Default;
} else {
_regional_tick = value;
}
_security = value;
_regional_tick_backing = null;
}
}
public ContractSpecificationImmutable spec { get; set; }
public long high_tick {
get => _high_tick;
set => _high_tick = value;
@@ -54,8 +55,8 @@ namespace mbts.spark {
}
public double start {
get => _regional_tick.tickup<double>(0, (uint)high_tick);
set => high_tick = _regional_tick.tickdiff(value, 0) ?? high_tick;
get => regional_tick.tickup<double>(0, (uint)high_tick);
set => high_tick = regional_tick.tickdiff(value, 0) ?? high_tick;
}
public long length {
@@ -80,7 +81,7 @@ namespace mbts.spark {
public bool IsReadOnly => false;
public double this[int idx] {
get => _regional_tick.tick<double>(0, high_tick - idx);
get => regional_tick.tick<double>(0, high_tick - idx);
set => throw new NotSupportedException();
}
@@ -94,7 +95,7 @@ namespace mbts.spark {
GetEnumerator();
public int IndexOf(double item) {
var idx = _regional_tick.tickdiff(start, item) ?? -1;
var idx = regional_tick.tickdiff(start, item) ?? -1;
if (idx < 0 || idx >= length) {
return -1;
}
@@ -209,7 +210,7 @@ namespace mbts.spark {
other != null &&
_high_tick == other._high_tick &&
_length == other._length &&
_regional_tick.safe_equals(other._regional_tick);
regional_tick.safe_equals(other.regional_tick);
public override bool Equals(object obj) =>
Equals(obj as LadderPriceList);
@@ -218,7 +219,7 @@ namespace mbts.spark {
HashCode.Combine(_high_tick, _length);
double _tick_to_price(long tick) =>
_regional_tick.tick<double>(0, tick);
regional_tick.tick<double>(0, tick);
void _add_tick(long tick) {
int idx;
@@ -31,19 +31,19 @@ namespace mbts.spark {
Side side,
double quantity,
double filled,
double price,
LadderSettings.BadgeStyle style,
Func<double, string> fmt_value = null
Func<double, double, string> fmt_value = null
) {
if (fmt_value is null)
fmt_value = FormattingDelegates.default_string<double>();
fmt_value ??= (v, p) => v.ToString();
switch (style) {
case LadderSettings.BadgeStyle.Alternate:
var working_qty = quantity > filled ? quantity - filled : 0;
return new[] {
$"{side.abbreviate()}:{fmt_value(filled)}",
$"W:{fmt_value(working_qty)}"
$"{side.abbreviate()}:{fmt_value(filled, price)}",
$"W:{fmt_value(working_qty, price)}"
};
default: return new[] { $"{fmt_value(filled)}/{fmt_value(quantity)}" };
default: return new[] { $"{fmt_value(filled, price)}/{fmt_value(quantity, price)}" };
}
}
@@ -79,13 +79,6 @@ namespace mbts.spark {
public static int index_of(IList<double> prices, double price, IComparer<double> comparer, bool relative_idx) {
for (int i = 1; i < prices.Count; i++) {
Log.assert(prices[i - 1] > prices[i]);
var c = comparer.Compare(prices[i - 1], prices[i]);
if (c >= 0) {
c = comparer.Compare(prices[i - 1], prices[i]);
} else {
c = comparer.Compare(prices[i - 1], prices[i]);
}
Log.assert(c < 0);
}
var idx = CollectionHelper.binary_search(prices, price, comparer);
if (relative_idx && idx < 0) {
@@ -109,17 +102,64 @@ namespace mbts.spark {
settings.font_size, settings.width, row_height, HorizontalAlignment.Right,
-4, VerticalAlignment.Center, row * row_height, true);
public static GlyphRun[] format_prices(IList<double> prices, double reference_price, bool format_in_tick_terms, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, RegionalTickFactors spec) {
public static GlyphRun[] format_prices(
IList<double> prices,
double reference_price,
LadderSettings.NetChangeTerms terms,
double row_height,
LadderColumnSettings settings,
GlyphContext glyph_context,
RegionalTickFactors spec,
LadderSettings.PriceDisplayFormat price_display_format = LadderSettings.PriceDisplayFormat.Default
) {
var result = new GlyphRun[prices.Count];
string basis_pts_fmt = terms switch {
LadderSettings.NetChangeTerms.BasisPoints or
LadderSettings.NetChangeTerms.NetBasisPoints => get_basis_pts_fmt(),
_ => null
};
Parallel.For(0, prices.Count, i =>
result[i] = format_text(spec.format(prices[i] - reference_price, format_in_tick_terms), i, row_height, settings, glyph_context));
return result;
}
public static GlyphRun[] format_prices(IList<double> prices, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, RegionalTickFactors spec, LadderSettings.PriceDisplayFormat price_display_format) {
var result = new GlyphRun[prices.Count];
Parallel.For(0, prices.Count, i =>
result[i] = format_text(spec.format(prices[i]), i, row_height, settings, glyph_context, price_display_format));
result[i] = format_text(fmt(i), i, row_height, settings, glyph_context, price_display_format));
return result;
string fmt(int i) {
switch (terms) {
case LadderSettings.NetChangeTerms.Dollars:
return prices[i].ToString("0.00");
case LadderSettings.NetChangeTerms.BasisPoints: {
if (!reference_price.is_real_non_zero()) {
return spec.format(double.NaN);
}
var basis_pts = prices[i] * 10000.0 / reference_price;
return basis_pts.ToString(basis_pts_fmt);
}
case LadderSettings.NetChangeTerms.NetBasisPoints: {
if (!reference_price.is_real_non_zero()) {
return spec.format(double.NaN);
}
var net_basis_pts = prices[i] * 10000.0 / reference_price - 10000.0;
return net_basis_pts.ToString(basis_pts_fmt);
}
default:
return spec.format(prices[i]);
}
}
string get_basis_pts_fmt() {
if (!reference_price.is_real_non_zero()) {
return "0.000";
}
var tick_sz = spec.ticksize(reference_price);
var basis_pt = reference_price.abs() / 10000.0;
var precision_log10 = Math.Log10(tick_sz / basis_pt);
int n = 0;
if (precision_log10 < 0) {
n = (int)Math.Ceiling(-precision_log10);
}
return n < 1
? "0"
: "0." + new string('0', n);
}
}
public static void clear_prices(DrawingVisual price, int row_count, double row_height, LadderColumnSettings settings, GlyphContext glyph_context) {
@@ -135,51 +175,20 @@ namespace mbts.spark {
public static void render_prices(
DrawingVisual price,
GlyphContext glyph_context,
IList<double> price_list,
IList<double> dprices,
double wrt_price,
bool format_in_tick_terms,
LadderSettings.NetChangeTerms terms,
double row_height,
LadderColumnSettings settings,
RegionalTickFactors tick) {
if (price_list.IsNullOrEmpty()) {
RegionalTickFactors tick,
LadderSettings.PriceDisplayFormat price_display_format = LadderSettings.PriceDisplayFormat.Default) {
if (dprices.IsNullOrEmpty()) {
return;
}
render_prices(
price, glyph_context, price_list, row_height, settings, tick,
wrt_price, format_in_tick_terms);
}
public static void render_prices(
DrawingVisual price,
GlyphContext glyph_context,
IList<double> dprices,
double row_height,
LadderColumnSettings settings,
RegionalTickFactors tick,
double wrt_price,
bool format_in_tick_terms) {
var foreground = settings.foreground.rgb_brush();
//var background = new SolidColorBrush(settings.background.rgb_color());
var fprices = format_prices(dprices, wrt_price, format_in_tick_terms, row_height, settings, glyph_context, tick);
using (var dc = price.RenderOpen())
using (_draw_with_clip(dc, settings, row_height, dprices.Count)) {
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, row_count * row_height));
for (int x = 0; x < dprices.Count; ++x)
dc.DrawGlyphRun(foreground, fprices[x]);
}
}
public static void render_prices(
DrawingVisual price,
GlyphContext glyph_context,
IList<double> dprices,
double row_height,
LadderColumnSettings settings,
RegionalTickFactors tick,
LadderSettings.PriceDisplayFormat price_display_format) {
var foreground = settings.foreground.rgb_brush();
//var background = new SolidColorBrush(settings.background.rgb_color());
var fprices = format_prices(dprices, row_height, settings, glyph_context, tick, price_display_format);
var fprices = format_prices(
dprices, wrt_price, terms, row_height, settings, glyph_context, tick, price_display_format);
using (var dc = price.RenderOpen())
using (_draw_with_clip(dc, settings, row_height, dprices.Count)) {
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, row_count * row_height));
@@ -232,8 +241,9 @@ namespace mbts.spark {
using (var dc = dv.RenderOpen())
using (_draw_with_clip(dc, settings, row_height, prices.Count)) {
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, prices.Count * row_height));
if (!prices.within_range(origin_index) || rungs.is_null_or_empty())
if (rungs.is_null_or_empty()) {
return;
}
var foreground = settings.foreground.rgb_brush();
int curr_index = origin_index;
double curr_price = prices[curr_index];
@@ -254,8 +264,8 @@ namespace mbts.spark {
var price = ticks.HasValue ? spec.tickup(0, ticks.Value) : rung.price;
// prices are in descending order, so get diff from curr_price - price
var tick_diff = spec.tickdiff(curr_price, price).assert_value();
checked {
curr_index += (int)tick_diff;
if (!MathUtils.try_increment(ref curr_index, tick_diff)) {
continue;
}
curr_price = price;
if (curr_index >= 0 && curr_index < prices.Count) {
@@ -280,7 +290,7 @@ namespace mbts.spark {
VolumeColumnSettings settings,
int last_index,
string last_size_and_direction,
Func<double, string> fmt_value) {
Func<double, double, string> fmt_value) {
//
if (!data.Any()) {
_clear_visual(dv);
@@ -303,12 +313,12 @@ namespace mbts.spark {
_clear_visual(dv);
return;
}
fmt_value ??= FormattingDelegates.default_string<double>();
fmt_value ??= (v, p) => v.ToString();
// inflate the max so that the graph never fills the available space
double max = data.Max(v => v.Volume) * 1.1;
_render_cells(dv, row_height, data.Select(d => (d.Price, d)), settings.general, prices, spec, render_cell);
/// local methods
void render_cell(DrawingContext dc, int r, VolumeAtPriceLevelImmutable vap) {
void render_cell(DrawingContext dc, int r, double price, VolumeAtPriceLevelImmutable vap) {
Brush text_brush = settings.general.foreground.rgb_brush();
double y = row_height * r;
var last_trade_here = r == last_index;
@@ -335,11 +345,11 @@ namespace mbts.spark {
}
x_bar += width;
if (show_numbers)
numbers_text.Add(fmt_value(v));
numbers_text.Add(fmt_value(v, vap.Price));
}
if (settings.total.show_numbers) {
numbers_text.Add(fmt_value(vap.Volume));
numbers_text.Add(fmt_value(vap.Volume, vap.Price));
}
if (settings.show_ltq && last_trade_here) {
numbers_text.Clear();
@@ -366,9 +376,17 @@ namespace mbts.spark {
}
}
public delegate void RenderCell<T>(DrawingContext dc, int row, T data_item);
public delegate void RenderCell<T>(DrawingContext dc, int row, double price, T data_item);
static void _render_cells<T>(DrawingVisual dv, double row_height, IEnumerable<(double price, T value)> cells, LadderColumnSettings settings, IList<double> prices, RegionalTickFactors spec, RenderCell<T> render_cell) {
static void _render_cells<T>(
DrawingVisual dv,
double row_height,
IEnumerable<(double price, T value)> cells,
LadderColumnSettings settings,
IList<double> prices,
RegionalTickFactors spec,
RenderCell<T> render_cell
) {
using (var dc = dv.RenderOpen())
using (_draw_with_clip(dc, settings, row_height, prices.Count)) {
if (prices.is_null_or_empty() || cells.is_null_or_empty()) {
@@ -388,7 +406,7 @@ namespace mbts.spark {
var check_price = spec.tickdown(max_price, idx);
Log.assert(spec.tick_equals(price, check_price));
Log.assert(idx >= 0 && idx < prices.Count);
render_cell(dc, idx, value);
render_cell(dc, idx, price, value);
}
}
}
@@ -400,10 +418,10 @@ namespace mbts.spark {
int index,
double row_height,
LadderColumnSettings settings,
Func<double, string> fmt_value) {
fmt_value ??= FormattingDelegates.default_string<double>();
Func<NativePriceSize, string> fmt_value) {
fmt_value ??= nps => nps.size.ToString();
using (var dc = dv.RenderOpen()) {
var gr = format_text(fmt_value(last.size), index, row_height, settings, glyph_context);
var gr = format_text(fmt_value(last), index, row_height, settings, glyph_context);
dc.DrawGlyphRun(settings.foreground.rgb_brush(), gr);
}
}
@@ -417,13 +435,13 @@ namespace mbts.spark {
double row_height,
RegionalTickFactors spec,
LadderColumnSettings settings,
Func<double, string> fmt_value
Func<double, double, string> fmt_value
) {
fmt_value ??= FormattingDelegates.default_string<double>();
fmt_value ??= (v, p) => v.ToString();
var fg = settings.foreground.rgb_brush();
_render_cells(dv, row_height, pvp, settings, prices, spec, (dc, row, piq) =>
_render_cells(dv, row_height, pvp, settings, prices, spec, (dc, row, price, piq) =>
dc.DrawGlyphRun(
fg, format_text(fmt_value(piq), row, row_height, settings, glyph_context)));
fg, format_text(fmt_value(piq, price), row, row_height, settings, glyph_context)));
}
public static void render_mkt_break(DrawingVisual dv, int index, double row_height, uint color) =>
File diff suppressed because it is too large. Load diff
@@ -39,6 +39,9 @@
<cv:EnumCharacterCasingConverter x:Key="casing_converter"/>
<cv:GridLengthConverter x:Key="GridLengthPixelConverter" UnitType="Pixel" />
<cv:ResponsiveTextConverter x:Key="BidPriceTextConverter" LongText="Bid:" ShortText="B:" Threshold="99" />
<cv:ResponsiveTextConverter x:Key="AskPriceTextConverter" LongText="Ask:" ShortText="A:" Threshold="99" />
<cv:ResponsiveTextConverter x:Key="MidPriceTextConverter" LongText="Mid:" ShortText="M:" Threshold="99" />
<cv:ResponsiveTextConverter x:Key="DayTextConverter" LongText="Day:" ShortText="D:" Threshold="99" />
<cv:ResponsiveTextConverter x:Key="HighTextConverter" LongText="High:" ShortText="H:" Threshold="99" />
<cv:ResponsiveTextConverter x:Key="LowTextConverter" LongText="Low:" ShortText="L:" Threshold="99" />
@@ -363,13 +366,103 @@
<Setter Property="Grid.Column" Value="2"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Margin" Value="0,0,0,2"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="Padding" Value="4,1"/>
</Style>
<Style x:Key="LeftPanelPricePointText" TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.default_text}"/>
</Style>
<Style x:Key="LeftPanelPricePointButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Grid.Column" Value="2" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Right" />
<Setter Property="Margin" Value="0,0,0,2" />
<Setter Property="Padding" Value="4,1" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="0"
Height="Auto"
Padding="0"
SnapsToDevicePixels="True">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="CommonStates">
<VisualState Name="Normal" />
<VisualState Name="MouseOver">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="HoverRect" Storyboard.TargetProperty="Fill.Opacity" BeginTime="0" Duration="00:00:00.15" To="0.20">
<DoubleAnimation.EasingFunction>
<QuadraticEase />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.mouseover_border}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.pressed_background}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.pressed_border}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.disabled_background}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.disabled_border}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup Name="FocusStates">
<VisualState Name="Unfocused" />
<VisualState Name="Focused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.focused_border}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid>
<Rectangle Name="HoverRect" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" IsHitTestVisible="False">
<Rectangle.Fill>
<SolidColorBrush Color="White" Opacity="0" />
</Rectangle.Fill>
</Rectangle>
<ContentPresenter
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
RecognizesAccessKey="True" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<sys:Double x:Key="MaxZoomFactor">1.5</sys:Double>
<sys:Double x:Key="MinZoomFactor">0.5</sys:Double>
</ResourceDictionary>
@@ -434,6 +527,9 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
@@ -441,9 +537,123 @@
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Day Change (label) -->
<!-- Ask Price (label) -->
<TextBlock
Grid.Row="0"
Text="{Binding left_panel_width, Converter={StaticResource AskPriceTextConverter}}"
Visibility="{Binding
ask_price_style.actual_show_label,
Converter={StaticResource BooleanToVisibilityConverter}}"
Style="{StaticResource LeftPanelPricePointLabel}" />
<!-- Ask Price (value) -->
<Button
Grid.Row="0"
Visibility="{Binding
ask_price_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
ToolTip="Ask"
Command="{Binding go_to_price_cmd}"
CommandParameter="{Binding ask_price}"
Style="{StaticResource LeftPanelPricePointButton}">
<Button.Background>
<SolidColorBrush Color="{Binding ask_price_style.background_color}" Opacity="1" />
</Button.Background>
<TextBlock
FontFamily="{Binding ask_price_style.font_family}"
FontSize="{Binding ask_price_style.font_size}"
FontWeight="{Binding ask_price_style.font_weight}"
Foreground="{Binding
ask_price_style.font_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Style="{StaticResource LeftPanelPricePointText}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="ask_price"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Button>
<!-- Mid Price (label) -->
<TextBlock
Grid.Row="1"
Text="{Binding left_panel_width, Converter={StaticResource MidPriceTextConverter}}"
Visibility="{Binding
mid_price_style.actual_show_label,
Converter={StaticResource BooleanToVisibilityConverter}}"
Style="{StaticResource LeftPanelPricePointLabel}" />
<!-- Mid Price (value) -->
<Button
Grid.Row="1"
Command="{Binding go_to_price_cmd}"
CommandParameter="{Binding mid_price}"
Visibility="{Binding
mid_price_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
ToolTip="Mid"
Style="{StaticResource LeftPanelPricePointButton}">
<Button.Background>
<SolidColorBrush Color="{Binding mid_price_style.background_color}" Opacity="1" />
</Button.Background>
<TextBlock
FontFamily="{Binding mid_price_style.font_family}"
FontSize="{Binding mid_price_style.font_size}"
FontWeight="{Binding mid_price_style.font_weight}"
Foreground="{Binding
mid_price_style.font_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Style="{StaticResource LeftPanelPricePointText}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="mid_price"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Button>
<!-- Bid Price (label) -->
<TextBlock
Grid.Row="2"
Text="{Binding left_panel_width, Converter={StaticResource BidPriceTextConverter}}"
Visibility="{Binding
bid_price_style.actual_show_label,
Converter={StaticResource BooleanToVisibilityConverter}}"
Style="{StaticResource LeftPanelPricePointLabel}" />
<!-- Bid Price (value) -->
<Button
Grid.Row="2"
Command="{Binding go_to_price_cmd}"
CommandParameter="{Binding bid_price}"
Visibility="{Binding
bid_price_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
ToolTip="Bid"
Style="{StaticResource LeftPanelPricePointButton}">
<Button.Background>
<SolidColorBrush Color="{Binding bid_price_style.background_color}" Opacity="1" />
</Button.Background>
<TextBlock
FontFamily="{Binding bid_price_style.font_family}"
FontSize="{Binding bid_price_style.font_size}"
FontWeight="{Binding bid_price_style.font_weight}"
Foreground="{Binding
bid_price_style.font_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Style="{StaticResource LeftPanelPricePointText}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="bid_price"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Button>
<!-- Day Change (label) -->
<TextBlock
Grid.Row="3"
Text="{Binding left_panel_width, Converter={StaticResource DayTextConverter}}"
Visibility="{Binding
day_change_style.actual_show_label,
@@ -452,7 +662,7 @@
<!-- Day Change (value) -->
<Border
Grid.Row="0"
Grid.Row="3"
Background="{Binding
day_change_background_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
@@ -479,7 +689,7 @@
<!-- High (label) -->
<TextBlock
Grid.Row="1"
Grid.Row="4"
Text="{Binding left_panel_width, Converter={StaticResource HighTextConverter}}"
Visibility="{Binding
session_high_style.actual_show_label,
@@ -487,15 +697,17 @@
Style="{StaticResource LeftPanelPricePointLabel}" />
<!-- High (value) -->
<Border
Grid.Row="1"
<Button
Grid.Row="4"
Background="{Binding
session_high_style.background_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Command="{Binding go_to_price_cmd}"
CommandParameter="{Binding session_high}"
Visibility="{Binding
session_high_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
ToolTip="Session High"
Style="{StaticResource LeftPanelPricePointBorder}">
Style="{StaticResource LeftPanelPricePointButton}">
<TextBlock
FontFamily="{Binding session_high_style.font_family}"
FontSize="{Binding session_high_style.font_size}"
@@ -504,24 +716,19 @@
session_high_style.font_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Style="{StaticResource LeftPanelPricePointText}">
<Hyperlink
Command="{Binding go_to_price_cmd}" CommandParameter="{Binding session_high}">
<TextBlock>
<TextBlock.Text>
<MultiBinding
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="session_high"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Hyperlink>
<TextBlock.Text>
<MultiBinding
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="session_high"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Border>
</Button>
<!-- Low (label) -->
<TextBlock
Grid.Row="2"
Grid.Row="5"
Text="{Binding left_panel_width, Converter={StaticResource LowTextConverter}}"
Visibility="{Binding
session_low_style.actual_show_label,
@@ -529,15 +736,17 @@
Style="{StaticResource LeftPanelPricePointLabel}" />
<!-- Low (value) -->
<Border
Grid.Row="2"
<Button
Grid.Row="5"
Background="{Binding
session_low_style.background_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Command="{Binding go_to_price_cmd}"
CommandParameter="{Binding session_low}"
Visibility="{Binding
session_low_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
ToolTip="Session Low"
Style="{StaticResource LeftPanelPricePointBorder}">
Style="{StaticResource LeftPanelPricePointButton}">
<TextBlock
FontFamily="{Binding session_low_style.font_family}"
FontSize="{Binding session_low_style.font_size}"
@@ -546,24 +755,19 @@
session_low_style.font_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Style="{StaticResource LeftPanelPricePointText}">
<Hyperlink
Command="{Binding go_to_price_cmd}" CommandParameter="{Binding session_low}">
<TextBlock>
<TextBlock.Text>
<MultiBinding
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="session_low"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Hyperlink>
<TextBlock.Text>
<MultiBinding
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="session_low"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Border>
</Button>
<!-- Open (label) -->
<TextBlock
Grid.Row="3"
Grid.Row="6"
Text="{Binding left_panel_width, Converter={StaticResource OpenTextConverter}}"
Visibility="{Binding
open_style.actual_show_label,
@@ -571,15 +775,17 @@
Style="{StaticResource LeftPanelPricePointLabel}" />
<!-- Open (value) -->
<Border
Grid.Row="3"
<Button
Grid.Row="6"
Background="{Binding
open_style.background_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Command="{Binding go_to_price_cmd}"
CommandParameter="{Binding open}"
Visibility="{Binding
open_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
ToolTip="Open"
Style="{StaticResource LeftPanelPricePointBorder}">
Style="{StaticResource LeftPanelPricePointButton}">
<TextBlock
FontFamily="{Binding open_style.font_family}"
FontSize="{Binding open_style.font_size}"
@@ -587,23 +793,19 @@
Foreground="{Binding
open_style.font_color, Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Style="{StaticResource LeftPanelPricePointText}">
<Hyperlink Command="{Binding go_to_price_cmd}" CommandParameter="{Binding open}">
<TextBlock>
<TextBlock.Text>
<MultiBinding
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="open"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Hyperlink>
<TextBlock.Text>
<MultiBinding
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="open"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Border>
</Button>
<!-- Settlement (label) -->
<TextBlock
Grid.Row="4"
Grid.Row="7"
Text="{Binding
left_panel_width, Converter={StaticResource SettlementTextConverter}}"
Visibility="{Binding
@@ -612,15 +814,17 @@
Style="{StaticResource LeftPanelPricePointLabel}" />
<!-- Settlement (value) -->
<Border
Grid.Row="4"
<Button
Grid.Row="7"
Background="{Binding
settlement_style.background_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Command="{Binding go_to_price_cmd}"
CommandParameter="{Binding settlement}"
Visibility="{Binding
settlement_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
ToolTip="Settlement"
Style="{StaticResource LeftPanelPricePointBorder}">
Style="{StaticResource LeftPanelPricePointButton}">
<TextBlock
FontFamily="{Binding settlement_style.font_family}"
FontSize="{Binding settlement_style.font_size}"
@@ -629,23 +833,18 @@
settlement_style.font_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Style="{StaticResource LeftPanelPricePointText}">
<Hyperlink
Command="{Binding go_to_price_cmd}" CommandParameter="{Binding settlement}">
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="settlement"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Hyperlink>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="settlement"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Border>
</Button>
<!-- Theo (label) -->
<TextBlock
Grid.Row="5"
Grid.Row="8"
Text="{Binding left_panel_width, Converter={StaticResource TheoTextConverter}}"
Visibility="{Binding
theo_style.actual_show_label,
@@ -653,15 +852,17 @@
Style="{StaticResource LeftPanelPricePointLabel}" />
<!-- Theo (value) -->
<Border
Grid.Row="5"
<Button
Grid.Row="8"
Background="{Binding
theo_style.background_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Command="{Binding go_to_price_cmd}"
CommandParameter="{Binding theo}"
Visibility="{Binding
theo_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
ToolTip="Theo"
Style="{StaticResource LeftPanelPricePointBorder}">
Style="{StaticResource LeftPanelPricePointButton}">
<TextBlock
FontFamily="{Binding theo_style.font_family}"
FontSize="{Binding theo_style.font_size}"
@@ -669,23 +870,19 @@
Foreground="{Binding
theo_style.font_color, Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Style="{StaticResource LeftPanelPricePointText}">
<Hyperlink Command="{Binding go_to_price_cmd}" CommandParameter="{Binding theo}">
<TextBlock>
<TextBlock.Text>
<MultiBinding
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="theo"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Hyperlink>
<TextBlock.Text>
<MultiBinding
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="theo"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Border>
</Button>
<!-- Delta -->
<TextBlock
Grid.Row="6"
Grid.Row="9"
Text="{Binding left_panel_width, Converter={StaticResource DeltaTextConverter}}"
Visibility="{Binding
delta_style.actual_show_label,
@@ -693,7 +890,7 @@
Style="{StaticResource LeftPanelPricePointLabel}" />
<Border
Grid.Row="6"
Grid.Row="9"
Background="{Binding
delta_style.background_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
@@ -714,7 +911,7 @@
<!-- Vega -->
<TextBlock
Grid.Row="7"
Grid.Row="10"
Text="{Binding left_panel_width, Converter={StaticResource VegaTextConverter}}"
Visibility="{Binding
vega_style.actual_show_label,
@@ -722,7 +919,7 @@
Style="{StaticResource LeftPanelPricePointLabel}" />
<Border
Grid.Row="7"
Grid.Row="10"
Background="{Binding
vega_style.background_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
@@ -743,7 +940,7 @@
<!-- Volume (label) -->
<TextBlock
Grid.Column="0"
Grid.Row="8"
Grid.Row="11"
Text="{Binding left_panel_width, Converter={StaticResource VolumeTextConverter}}"
Visibility="{Binding
volume_style.actual_show_label,
@@ -752,7 +949,7 @@
<!-- Volume (price) -->
<Border
Grid.Row="8"
Grid.Row="11"
Background="{Binding
volume_style.background_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
@@ -793,6 +990,7 @@
Margin="2,10,2,2"
ItemsSource="{Binding accounts}"
SelectedItem="{Binding selected_account}"
ToolTip="Account"
Visibility="{Binding
DataContext.show_account,
RelativeSource={RelativeSource AncestorType=UserControl},
@@ -802,6 +1000,7 @@
<ComboBox
DataContext="{Binding order_properties_vm}" ItemsSource="{Binding time_in_forces}"
SelectedItem="{Binding selected_time_in_force}" Margin="2"
ToolTip="Time In Force"
Visibility="{Binding
DataContext.show_tif,
RelativeSource={RelativeSource AncestorType=UserControl},
@@ -824,6 +1023,7 @@
IsOpen="{Binding is_expanded}"
ItemsSource="{Binding algo_definitions_view}"
SelectedItem="{Binding selected_algo_definition}"
ToolTip="Order/Algo Type"
Visibility="{Binding
DataContext.show_order_type,
RelativeSource={RelativeSource AncestorType=UserControl},
@@ -879,6 +1079,7 @@
Increment="{Binding size_calculator.unit_quantity}"
Minimum="0"
TickDisplaySettings="{Binding size_calculator.display_settings}"
ToolTip="Size"
Value="{Binding size_calculator.display_value, Mode=TwoWay}" />
<!-- Qty quick buttons -->
@@ -901,7 +1102,8 @@
RelativeSource={RelativeSource AncestorType=ItemsControl}}"
Content="{Binding}"
Focusable="False"
Foreground="{u:BTSColor UI.Button.normal_foreground}">
Foreground="{u:BTSColor UI.Button.normal_foreground}"
ToolTip="Add to Size">
<Button.CommandParameter>
<MultiBinding
Converter="{x:Static cv:DynamicDoubleMultiplierConverter.Instance}">
@@ -921,6 +1123,7 @@
Margin="2"
HorizontalAlignment="Stretch"
Command="{Binding size_calculator.clear}"
ToolTip="Clear Size"
Visibility="{Binding
show_clear, Converter={StaticResource BooleanToVisibilityConverter}}"
Style="{StaticResource CommonButton}">
@@ -931,6 +1134,7 @@
<CheckBox
Margin="2"
IsChecked="{Binding auto_clear}"
ToolTip="Clear Size after order"
Visibility="{Binding
show_auto_clear, Converter={StaticResource BooleanToVisibilityConverter}}">
Auto Clear
@@ -76,14 +76,8 @@ namespace mbts.spark {
if (show_total_pos) add_part(total_pos_text, " (", ")");
//
if (part_count > 1) {
// This is a complex expression (showing more than 1 value) - wrap it in single quotes.
// Explicitly state which value will be copied to the size input.
sb.Insert(0, "'");
sb.Add("'");
sb.Add(" position\n(click to copy ", primary_pos, " to size)");
} else {
// Only showing 1 value, no need for single quotes,
// nor an explicit statement that indicates which value will be copied to the size input.
sb.Add(" position\n(click to copy to size)");
}
return sb.ToString();
@@ -389,7 +389,7 @@ namespace mbts.spark {
}
void refresh_quantity_display() {
foreach (var row in _row_collection.all_rows) {
foreach (var row in _row_collection.GetAllRows()) {
if (_cached_securities.TryGetValue(row.security_key, out var smd)) {
row.quantity_display_settings = smd.quantity_display;
}
@@ -289,7 +289,8 @@ namespace bts.shell.pnl {
public AccountTuple account => new(account_id, account_name);
public string symbol => security?.pnl_symbol() ?? string.Empty;
public DateTime expiration => security?.earliest_expiration() ?? DateTime.MinValue;
public DateTime expiration =>
security?.normal_expirations()?.MinOrDefault(DateTime.MinValue) ?? DateTime.MinValue;
public double effective_cpp => override_cpp ?? default_cpp;
/// Market/Calc
@@ -18,7 +18,7 @@
<sys:Double x:Key="VerticalSpacing">4.0</sys:Double>
<sys:Double x:Key="ButtonHeight">22.0</sys:Double>
<cv:NullableDoubleConverter x:Key="TickDefaultPriceConverter" DefaultValue="0"/>
<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Style TargetType="Button" BasedOn="{StaticResource DefaultButton}">
<Setter Property="Height" Value="{StaticResource ButtonHeight}"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
</Style>
@@ -53,8 +53,6 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="140"/>
@@ -77,6 +75,7 @@
RegionalTick="{Binding price_tick}"
Focusable="false"
TickDefaultValue="{Binding current_price, Converter={StaticResource TickDefaultPriceConverter}, FallbackValue={u:Double 0}}"
ToolTip="Price"
Value="{Binding price}"
/>
<c:TickUpDownNullableDouble
@@ -88,6 +87,7 @@
Increment="{Binding size_calculator.unit_quantity}"
Minimum="0"
TickDisplaySettings="{Binding size_calculator.display_settings}"
ToolTip="Size"
Value="{Binding size_calculator.display_value}"
/>
</Grid>
@@ -97,13 +97,24 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalSpacing}}"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Command="{Binding set_price_command}" CommandParameter="{Binding bbo.bid}" Foreground="{u:BTSColor MarketData.bid}" FontWeight="Bold">
<Button
Command="{Binding set_price_command}"
CommandParameter="{Binding bbo.bid}"
Foreground="{u:BTSColor MarketData.bid}"
FontWeight="Bold"
ToolTip="Bid Price">
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
<Binding Path="bbo.bid"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</Button>
<Button Grid.Column="2" Command="{Binding set_price_command}" CommandParameter="{Binding bbo.ask}" Foreground="{u:BTSColor MarketData.ask}" FontWeight="Bold">
<Button
Grid.Column="2"
Command="{Binding set_price_command}"
CommandParameter="{Binding bbo.ask}"
Foreground="{u:BTSColor MarketData.ask}"
FontWeight="Bold"
ToolTip="Ask Price">
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
<Binding Path="bbo.ask"/>
<Binding Path="security.ContractSpec"/>
@@ -120,7 +131,7 @@
Command="{Binding size_calculator.set_actual_value_command}"
CommandParameter="{Binding bbo.bid_size}"
Foreground="{u:BTSColor MarketData.bid}"
>
ToolTip="Bid Size">
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}">
<Binding Path="bbo.bid_size" />
<Binding Path="qty_display" />
@@ -131,20 +142,20 @@
Command="{Binding size_calculator.set_actual_value_command}"
CommandParameter="{Binding bbo.ask_size}"
Foreground="{u:BTSColor MarketData.ask}"
>
ToolTip="Ask Size">
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}">
<Binding Path="bbo.ask_size" />
<Binding Path="qty_display" />
</MultiBinding>
</Button>
</Grid>
<Grid Grid.Row="4" Grid.RowSpan="3">
<Grid Grid.Row="4">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ItemsControl Margin="-2,-2,-2,0" DataContext="{Binding size_calculator}" ItemsSource="{Binding buttons}" Focusable="False">
<ItemsControl Margin="0,0,-4,-4" DataContext="{Binding size_calculator}" ItemsSource="{Binding buttons}" Focusable="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3" Focusable="False"/>
@@ -153,11 +164,11 @@
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
Margin="2,4,2,0"
Margin="0,0,4,4"
Content="{Binding}"
Focusable="False"
Command="{Binding Path=DataContext.click, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
>
ToolTip="Add to Size">
<Button.CommandParameter>
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}">
<Binding />
@@ -170,35 +181,35 @@
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Grid.Row="2" Command="{Binding size_calculator.clear}">Clear</Button>
<Button
Grid.Row="2"
Command="{Binding size_calculator.clear}"
Content="Clear"
ToolTip="Clear Size" />
</Grid>
<Grid Grid.Row="4" Grid.Column="2" Grid.RowSpan="3">
<Grid Grid.Row="4" Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ComboBox
Grid.ColumnSpan="2"
usp:OrderModificationControl.IsDirty="{Binding account_dirty, FallbackValue={u:False}}"
IsEnabled="{Binding can_change_account, FallbackValue={u:True}}"
ItemsSource="{Binding properties_vm.accounts}"
Margin="0,2"
SelectedItem="{Binding properties_vm.selected_account}"
/>
ToolTip="Account" />
<ComboBox
Grid.Row="1"
Grid.ColumnSpan="2"
Grid.Row="2"
usp:OrderModificationControl.IsDirty="{Binding time_in_force_dirty, FallbackValue={u:False}}"
IsEnabled="{Binding can_change_time_in_force, FallbackValue={u:True}}"
ItemsSource="{Binding properties_vm.time_in_forces}"
Margin="0,2"
SelectedItem="{Binding properties_vm.selected_time_in_force}"
/>
ToolTip="Time In Force" />
<uag:OrderDetailsPopupControl
Grid.Row="2"
Margin="0,2"
Grid.Row="4"
DataContext="{Binding properties_vm}"
usp:OrderModificationControl.IsDirty="{Binding DataContext.order_type_dirty, RelativeSource={RelativeSource AncestorType=usp:OrderModificationControl}, FallbackValue={u:False}}"
HasEditableProperties="{Binding can_expand, FallbackValue={u:False}}"
@@ -207,7 +218,7 @@
IsOpen="{Binding is_expanded, FallbackValue={u:False}}"
ItemsSource="{Binding algo_definitions_view}"
SelectedItem="{Binding selected_algo_definition}"
>
ToolTip="Order/Algo Type">
<uag:OrderDetailsPopupControl.Style>
<Style TargetType="uag:OrderDetailsPopupControl" BasedOn="{StaticResource {x:Type uag:OrderDetailsPopupControl}}">
<Style.Triggers>
@@ -128,7 +128,7 @@ namespace mbts.spark {
new PropertyValidation<AccountTuple?>(
a => (a.HasValue && !string.IsNullOrWhiteSpace(a.Value.name) && accounts.Contains(a.Value), "Please select an account"))
});
attach(_accounts, accounts_src.Select(accs => accs.Values.ToList()), inline_account_src);
attach(_accounts, accounts_src.Select(accs => accs.Values.ToListSorted()), inline_account_src);
// time in force
_time_in_force = new SelectorProperty<TimeInForce>(
+1 -1
View File
@@ -398,7 +398,7 @@
$"Expected rounded qty to be real, non-negative. " +
$"qty: {qty}; rounded_qty: {rounded_qty}; display: {display})");
var iqty = Math.Floor(rounded_qty);
return ((uint)iqty, new() { Fraction = Math.Round(rounded_qty - iqty, 2) });
return ((uint)iqty, new() { Fraction = Math.Round(rounded_qty - iqty, 6) });
}
private CustomOrderDefinitionImmutable? _create_custom_def_for_stop_and_stop_limit(OrderRecordImmutable order, ref double stop_price) {
@@ -1,41 +0,0 @@
// using System;
// using System.Collections.Generic;
// using System.Linq;
// using System.Text;
// namespace BCG
// {
// public class DateTimeFormatter
// {
// private delegate string MyFormatDateTime (DateTime v);
// public enum ExpirationShortForms
// {
// DayMonthYearDiff2000,
// MonthYearDiff2000
// }
// public enum DateTimeFormats { Regular, ShortDate, DayMonthYearDiff2000, MonthYearDiff2000 }
// private static Dictionary<string, MyFormatDateTime> _local_formats = null;
// private static void _init()
// {
// if (_local_formats == null)
// {
// _local_formats = new Dictionary<string, MyFormatDateTime>();
// _local_formats.Add(DateTimeFormats.Regular.ToString(), v => v.ToString());
// _local_formats.Add(DateTimeFormats.ShortDate.ToString(), v => v.ToShortDateString());
// _local_formats.Add(DateTimeFormats.DayMonthYearDiff2000.ToString(),
// (DateTime v) => { return String.Format("{0} {1}{2}", v.Day, BCG.Utilities.Dates.DateCode.getMonthSymbol(v.Month), v.Year - 2000); });
// _local_formats.Add(DateTimeFormats.MonthYearDiff2000.ToString(),
// (DateTime v) => { return String.Format("{0}{1}", BCG.Utilities.Dates.DateCode.getMonthSymbol(v.Month), v.Year - 2000); });
// }
// }
// public static string format(DateTime val, string format)
// {
// _init ();
// MyFormatDateTime f = null;
// if (_local_formats.TryGetValue(format, out f))
// return f(val);
// else
// return val.ToString(format);
// }
// }
// }
@@ -227,7 +227,7 @@ namespace mbts.ui {
public static BTSColor atm_background => BTSCoreColors.gray_dark;
public static BTSColor session_low => 0xff59596b;
public static BTSColor session_low => 0xff8f5924;
public static BTSColor session_high => 0xff239b00;
public static BTSColor session_range_fill => 0x1a000000;
@@ -984,7 +984,7 @@ namespace mbts.ui {
public static BTSColor alternate_column_foreground => UI.foreground_standout;
public static BTSColor bid_line => BTSCoreColors.blue_light_dark_saturated;
public static BTSColor ask_line => BTSCoreColors.red_light_dark_saturated;
public static BTSColor mid_line => 0x40000000;
public static BTSColor mid_line => 0xffa4a4a4;
public static BTSColor default_column_background => 0xff141420;//0xff28283b;
public static BTSColor alternate_column_background => 0xff141420;// 0xff494957;
@@ -5,7 +5,8 @@ using mbts.ui.utility.text;
using ProtoBuf;
using System;
using Spark.Core.Protobuf;
using System.Windows;
using System.Windows.Media;
@@ -13,7 +14,7 @@ using FontFamily = System.Windows.Media.FontFamily;
namespace bts.proto {
[ProtoContract]
class FontParamsProto : IEquatable<FontParamsProto> {
class FontParamsProto : IProtobuf<FontParamsProto> {
[ProtoMember(1)]
public TypefaceProto typeface { get; set; }
[ProtoMember(2)]
@@ -24,6 +25,27 @@ namespace bts.proto {
public string font_family => typeface.font_family;
public int font_weight => typeface.font_weight;
public FontParamsProto With(TypefaceProto? typeface = null, double? font_size = null, uint? font_color = null, bool in_place = false) {
var rv = in_place ? this : Clone();
if (typeface.HasValue) {
rv.typeface = typeface.Value;
}
if (font_size.HasValue) {
rv.font_size = font_size.Value;
}
if (font_color.HasValue) {
rv.font_color = font_color.Value;
}
return rv;
}
public FontParamsProto Clone() =>
new() {
typeface = typeface.Clone(),
font_size = font_size,
font_color = font_color,
};
public bool Equals(FontParamsProto other) =>
other != null &&
typeface == other.typeface &&
@@ -4,14 +4,15 @@ using mbts.ui.utility.text;
using ProtoBuf;
using System;
using Spark.Core.Protobuf;
using System.Windows;
using FontFamily = System.Windows.Media.FontFamily;
namespace bts.proto {
[ProtoContract]
readonly struct TypefaceProto : IEquatable<TypefaceProto> {
readonly struct TypefaceProto : IProtobuf<TypefaceProto> {
static readonly int _type_hash = typeof(TypefaceProto).GetHashCode();
[ProtoMember(1)]
@@ -30,6 +31,9 @@ namespace bts.proto {
font_family ?? this.font_family,
font_weight ?? this.font_weight);
public TypefaceProto Clone() =>
new(font_family.ToString(), font_weight);
public bool Equals(TypefaceProto other) =>
font_family == other.font_family &&
font_weight == other.font_weight;
+9 -2
View File
@@ -379,8 +379,15 @@ namespace mbts.ui.controls {
class BadgeTextLinesConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
if (ConvertUtils.try_convert_args(values, out Side side, out uint quantity, out uint filled, out mbts.spark.LadderSettings.BadgeStyle style))
return BadgeRender.format(side, quantity, filled, style);
if (ConvertUtils.TryConvertArgs(
values,
out Side side,
out uint quantity,
out uint filled,
out double price,
out LadderSettings.BadgeStyle style)) {
return BadgeRender.format(side, quantity, filled, price, style);
}
else return new string[0];
}
@@ -20,13 +20,23 @@ namespace mbts.ui {
}
public static class AutoFocusTargetProvider {
public static IAutoFocusTargetProvider<T> TypeProvider<T>(FrameworkElement root, bool focus, bool focus_keyboard, bool release_keyboard, bool capture_mouse, bool release_mouse) where T : FrameworkElement =>
new AnonymousProvider<T>(() => root.descendants_of_type<T>().FirstOrDefault(), focus, focus_keyboard, release_keyboard, capture_mouse, release_mouse);
public static IAutoFocusTargetProvider TypeProvider(Type type, FrameworkElement root, bool focus, bool focus_keyboard, bool release_keybobard, bool capture_mouse, bool release_mouse) =>
public static IAutoFocusTargetProvider TypeProvider(
Type type,
FrameworkElement root,
bool focus = true,
bool focus_keyboard = false,
bool release_keyboard = true,
bool capture_mouse = false,
bool release_mouse = true) =>
new AnonymousProvider<FrameworkElement>(
() => root.visual_children_where(el => type.IsAssignableFrom(el.GetType())).Cast<FrameworkElement>().FirstOrDefault(),
focus, focus_keyboard, release_keybobard, capture_mouse, release_mouse);
() => root.visual_children_where(el => type.IsAssignableFrom(el.GetType()))
.Cast<FrameworkElement>()
.FirstOrDefault(),
focus,
focus_keyboard,
release_keyboard,
capture_mouse,
release_mouse);
private class AnonymousProvider<T> : IAutoFocusTargetProvider<T> where T : FrameworkElement {
readonly Func<T> _get_target;
@@ -473,7 +473,7 @@ namespace mbts.spark.filters {
// Exchange
public static IDataGridColumnPersistentFilter<TRow, Venue[]> exchange<TRow>(
Func<TRow, Venue> get_row_exchange, string? prop_name = null) =>
Create(EnumHelper.get_values_except(Venue.Unset).ToList(), get_row_exchange, prop_name);
Create(EnumHelper.get_values_except(Venue.Unset).ToListSorted(e => e.ToString()), get_row_exchange, prop_name);
// Symbol
public static IDataGridColumnPersistentFilter<TRow, string[]> symbol<TRow>(
@@ -12,7 +12,10 @@ namespace mbts.spark.filters {
str = str.Trim();
if (str == string.Empty)
return null;
return new Regex(str, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
try {
return new Regex(str, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
} catch (RegexParseException) { }
return null;
}
}
class RegexFilterVM<TRow> : FilterVMBase<TRow, string, string> {
@@ -3,6 +3,7 @@
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
>
<UserControl.Resources>
@@ -21,7 +22,7 @@
<GroupBox Header="Font Family" Height="{Binding ActualHeight, ElementName=_panel_right}">
<ListBox
x:Name="_font_family_list_box"
Background="White"
Background="{u:BTSBrush UI.ListBox.normal_background}"
ItemsSource="{Binding FontFamilies, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
SelectedItem="{Binding SelectedFontFamily, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
>
@@ -40,7 +41,7 @@
</Grid.RowDefinitions>
<GroupBox Header="Font Weight">
<ListBox
Background="White"
Background="{u:BTSBrush UI.ListBox.normal_background}"
ItemsSource="{Binding FontWeights, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
SelectedItem="{Binding SelectedFontWeight, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
>
@@ -73,7 +74,7 @@
FontFamily="{Binding SelectedFontFamily, Mode=OneWay, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
FontSize="{Binding SelectedFontSize, Mode=OneWay, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
FontWeight="{Binding SelectedFontWeight, Mode=OneWay, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
Foreground="{Binding SelectedFontColor, Mode=OneWay, RelativeSource={RelativeSource AncestorType=c:FontPicker}, Converter={StaticResource ColorToBrushConverter}}"
Foreground="{u:BTSBrush UI.default_text}"
TextWrapping="Wrap"
>
<TextBlock.Inlines>
@@ -12,8 +12,12 @@
x:Name="order_type"
Text="{Binding algo_root.order_type, Converter={StaticResource OrderTypeDisplayTextConverter}, StringFormat='{}{0}: ', Mode=OneWay}"
FontWeight="Bold"
/>
<TextBlock x:Name="display_name" Text="{Binding name, Mode=OneWay}" FontWeight="Normal"/>
Foreground="{u:BTSBrush UI.default_text}" />
<TextBlock
x:Name="display_name"
Text="{Binding name, Mode=OneWay}"
FontWeight="Normal"
Foreground="{u:BTSBrush UI.default_text}" />
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Value="{u:True}">
@@ -87,10 +87,10 @@
</c:TickUpDown>
</ControlTemplate>
<Style TargetType="usp:PriceEntryControl">
<Setter Property="Background" Value="{u:BTSColor UI.input_background}" />
<Setter Property="Background" Value="{u:BTSBrush UI.Button.normal_background}" />
<Setter Property="Foreground" Value="{u:BTSColor UI.input_text}" />
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Template" Value="{StaticResource PriceEntryTemplate}"/>
</Style>
@@ -14,7 +14,7 @@
<Style TargetType="{x:Type c:TickUpDown}">
<Setter Property="Background" Value="{StaticResource TextBox.Static.Background.Brush}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Height" Value="20" />
@@ -75,7 +75,8 @@
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource TextBox.MouseOver.Border.Brush}"/>
<Setter TargetName="Bd" Property="Background" Value="{u:BTSBrush UI.Button.mouseover_background}"/>
<Setter TargetName="Bd" Property="BorderBrush" Value="{u:BTSBrush UI.Button.mouseover_border}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
@@ -133,7 +134,8 @@
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource TextBox.MouseOver.Border.Brush}"/>
<Setter TargetName="Bd" Property="Background" Value="{u:BTSBrush UI.Button.mouseover_background}"/>
<Setter TargetName="Bd" Property="BorderBrush" Value="{u:BTSBrush UI.Button.mouseover_border}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
@@ -145,7 +147,7 @@
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="IsTabStop" Value="False"/>
@@ -188,7 +190,7 @@
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="IsTabStop" Value="False"/>
@@ -230,7 +232,7 @@
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="IsTabStop" Value="False"/>
@@ -272,7 +274,7 @@
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="IsTabStop" Value="False"/>
@@ -310,7 +312,7 @@
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="IsTabStop" Value="False"/>
@@ -348,7 +350,7 @@
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="IsTabStop" Value="False"/>
@@ -384,7 +386,7 @@
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="IsTabStop" Value="False"/>
@@ -428,7 +430,7 @@
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="IsTabStop" Value="False"/>
@@ -466,7 +468,7 @@
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="IsTabStop" Value="False"/>
@@ -548,7 +550,7 @@
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="IsTabStop" Value="False"/>
@@ -586,7 +588,7 @@
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="IsTabStop" Value="False"/>
@@ -1,248 +1,264 @@
using bts.utils;
using mbts.utils;
using Spark.Core.Disposables;
using Spark.ViewModel.Commands;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
namespace mbts.ui {
static class Behavior {
#region CloseWindow
public static readonly DependencyProperty CloseWindowProperty = DependencyProperty.RegisterAttached("CloseWindow", typeof(bool), typeof(Behavior),
new PropertyMetadata(false, (d, dpc_args) => {
var button = d as Button;
if (button != null && (bool)dpc_args.NewValue) {
RoutedEventHandler close_window = (sender, e) => {
var window = button.find_ancestor_of_type<Window>();
if (window != null)
window.Close();
};
button.Click += close_window;
IDisposable dispose = Disposable.Create(() => {
button.Click -= close_window;
close_window = null;
});
IDisposable early_dispose = button.ObserveProperty(CloseWindowProperty, (sender, e) => {
if (!GetCloseWindow(button))
Disposable.dispose(ref dispose);
});
DisposeExtensions.attach_to_disposer(button, dispose);
DisposeExtensions.attach_to_disposer(button, early_dispose);
}
}));
public static bool GetCloseWindow(Button obj) { return (bool)obj.GetValue(CloseWindowProperty); }
public static void SetCloseWindow(Button obj, bool value) { obj.SetValue(CloseWindowProperty, value); }
#endregion
#region AnimatedScrollViewerVerticalOffset
static double GetAnimatedScrollViewerVerticalOffset(DependencyObject obj) =>
(double)obj.GetValue(AnimatedScrollViewerVerticalOffsetProperty);
public static void SetAnimatedScrollViewerVerticalOffset(DependencyObject obj, double value) =>
obj.SetValue(AnimatedScrollViewerVerticalOffsetProperty, value);
public static readonly DependencyProperty AnimatedScrollViewerVerticalOffsetProperty =
DependencyProperty.RegisterAttached("AnimatedScrollViewerVerticalOffset", typeof(double), typeof(Behavior), new PropertyMetadata(0.0, scroll_changed));
static void scroll_changed(DependencyObject d, DependencyPropertyChangedEventArgs e) {
ScrollViewer target = d as ScrollViewer;
if (target != null) {
target.ScrollToVerticalOffset((double)e.NewValue);
}
}
#endregion
#region AutoFocusTargetProvider
public static readonly DependencyProperty AutoFocusTargetProviderProperty = DependencyProperty.RegisterAttached("AutoFocusTargetProvider", typeof(bool),
typeof(Behavior), new PropertyMetadata(OnAutoFocusTargetProviderChanged));
static void OnAutoFocusTargetProviderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (e.NewValue is IAutoFocusTargetProvider aftp) {
var el = ExceptionAssert.Argument.Is<FrameworkElement>(d, nameof(d));
d.SetValue(AutoFocusTargetProviderLifeDependencyPropertyKey, new AutoFocusTargetProviderLifeDependency(el, aftp));
} else
d.SetValue(AutoFocusTargetProviderLifeDependencyPropertyKey, null);
}
public static bool GetAutoFocusTargetProvider(DependencyObject d) => (bool)d.GetValue(AutoFocusTargetProviderProperty);
public static void SetAutoFocusTargetProvider(DependencyObject d, bool value) => d.SetValue(AutoFocusTargetProviderProperty, value);
#endregion
#region AutoFocusTargetProviderLifeDependency (ReadOnly)
static readonly DependencyPropertyKey AutoFocusTargetProviderLifeDependencyPropertyKey = DependencyProperty.RegisterAttachedReadOnly("AutoFocusTargetProviderLifeDependency",
typeof(FrameworkElementLifeDependency), typeof(Behavior), new PropertyMetadata(FrameworkElementLifeDependencyHelper.DisposeOldInitializeNew));
public static readonly DependencyProperty AutoFocusTargetProviderLifeDependencyProperty = AutoFocusTargetProviderLifeDependencyPropertyKey.DependencyProperty;
public static FrameworkElementLifeDependency GetAutoFocusTargetProviderLifeDependency(DependencyObject d) => d.GetValue(AutoFocusTargetProviderLifeDependencyProperty) as FrameworkElementLifeDependency;
public static void SetAutoFocusTargetProviderLifeDependency(DependencyObject d, FrameworkElementLifeDependency value) => d.SetValue(AutoFocusTargetProviderLifeDependencyPropertyKey, value);
class AutoFocusTargetProviderLifeDependency : FrameworkElementLifeDependency {
readonly IAutoFocusTargetProvider _focus_target_provider;
public AutoFocusTargetProviderLifeDependency(FrameworkElement root, IAutoFocusTargetProvider focus_target_provider) : base(root) =>
_focus_target_provider = focus_target_provider;
protected override void OnLoaded() {
var el = _focus_target_provider.Target;
if (el != null) {
if (_focus_target_provider.Focus)
el.Focus();
if (_focus_target_provider.FocusKeyboard)
Keyboard.Focus(el);
if (_focus_target_provider.CaptureMouse)
el.CaptureMouse();
}
}
protected override void OnUnloaded() {
var el = _focus_target_provider.Target;
if (el != null) {
if (_focus_target_provider.ReleaseKeyboard) {
ReferenceEquals(Keyboard.FocusedElement, el);
Keyboard.ClearFocus();
}
if (_focus_target_provider.ReleaseMouse)
el.ReleaseMouseCapture();
}
}
}
#endregion
#region EatMouseDown
public static readonly DependencyProperty EatMouseDownProperty =
DependencyProperty.RegisterAttached("EatMouseDown", typeof(bool), typeof(Behavior), new PropertyMetadata(false, OnEatMouseDownChanged));
static void OnEatMouseDownChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var el = (FrameworkElement)d;
if ((bool)e.NewValue) el.MouseDown += _eat_mouse_down;
else el.MouseDown -= _eat_mouse_down;
}
static void _eat_mouse_down(object sender, MouseButtonEventArgs e) =>
e.Handled = true;
public static bool GetEatMouseDown(DependencyObject d) => (bool)d.GetValue(EatMouseDownProperty);
public static void SetEatMouseDown(DependencyObject d, bool value) => d.SetValue(EatMouseDownProperty, value);
#endregion
#region DisablePaste
public static readonly DependencyProperty DisablePasteProperty = DependencyProperty.RegisterAttached(
"DisablePaste", typeof(bool), typeof(Behavior), new PropertyMetadata(false, OnDisablePasteChanged));
static void OnDisablePasteChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if ((bool)e.NewValue)
DataObject.AddPastingHandler(d, _disable_paste_pasting_handler);
else DataObject.RemovePastingHandler(d, _disable_paste_pasting_handler);
}
static void _disable_paste_pasting_handler(object sender, DataObjectPastingEventArgs e) => e.CancelCommand();
public static bool GetDisablePaste(DependencyObject d) => (bool)d.GetValue(DisablePasteProperty);
public static void SetDisablePaste(DependencyObject d, bool value) => d.SetValue(DisablePasteProperty, value);
#endregion
public static readonly DependencyProperty BindingUpdateGroupsProperty =
DependencyProperty.RegisterAttached("BindingUpdateGroups", typeof(BindingUpdateGroupCollection), typeof(Behavior), new PropertyMetadata(null));
public static BindingUpdateGroupCollection GetBindingUpdateGroups(DependencyObject d) => d.GetValue(BindingUpdateGroupsProperty) as BindingUpdateGroupCollection;
public static void SetBindingUpdateGroups(DependencyObject d, BindingUpdateGroupCollection value) => d.SetValue(BindingUpdateGroupsProperty, value);
#region AutoRaiseCanExecuteChanged
public static readonly DependencyProperty AutoRaiseCanExecuteChangedProperty =
DependencyProperty.RegisterAttached("AutoRaiseCanExecuteChanged", typeof(bool), typeof(Behavior),
new PropertyMetadata(false, (d, e) => {
if ((bool)e.NewValue) {
BindingOperations.SetBinding(d, AutoCommandProperty, new Binding(nameof(ButtonBase.Command)) { Source = d, Mode = BindingMode.OneWay });
BindingOperations.SetBinding(d, AutoCommandParameterProperty, new Binding(nameof(ButtonBase.CommandParameter)) { Source = d, Mode = BindingMode.OneWay });
} else {
BindingOperations.ClearBinding(d, AutoCommandProperty);
BindingOperations.ClearBinding(d, AutoCommandParameterProperty);
}
}));
public static bool GetAutoRaiseCanExecuteChanged(DependencyObject d) => (bool)d.GetValue(AutoRaiseCanExecuteChangedProperty);
public static void SetAutoRaiseCanExecuteChanged(DependencyObject d, bool value) => d.SetValue(AutoRaiseCanExecuteChangedProperty, value);
/// Do not bind directly to <see cref="AutoCommandProperty"/> (used internally by <see cref="AutoRaiseCanExecuteChangedProperty"/>
public static readonly DependencyProperty AutoCommandProperty =
DependencyProperty.RegisterAttached("AutoCommand", typeof(ICommand), typeof(Behavior), new PropertyMetadata(null, _try_raise_can_execute));
public static ICommand GetAutoCommand(DependencyObject d) => d.GetValue(AutoCommandProperty) as ICommand;
public static void SetAutoCommand(DependencyObject d, ICommand value) => d.SetValue(AutoCommandProperty, value);
/// Do not bind directly to <see cref="AutoCommandParameterProperty"/> (used internally by <see cref="AutoRaiseCanExecuteChangedProperty"/>
public static readonly DependencyProperty AutoCommandParameterProperty =
DependencyProperty.RegisterAttached("AutoCommandParameter", typeof(object), typeof(Behavior), new PropertyMetadata(null, _try_raise_can_execute));
public static object GetAutoCommandParameter(DependencyObject d) => d.GetValue(AutoCommandParameterProperty);
public static void SetAutoCommandParameter(DependencyObject d, object value) => d.SetValue(AutoCommandParameterProperty, value);
static void _try_raise_can_execute(DependencyObject d, DependencyPropertyChangedEventArgs e) =>
(d.GetValue(ButtonBase.CommandProperty) as IRaiseCanExecuteChangedCommand)?.RaiseCanExecuteChanged();
#endregion
#region FocusOnLoaded
public static readonly DependencyProperty FocusOnLoadedProperty =
DependencyProperty.RegisterAttached("FocusOnLoaded", typeof(bool), typeof(Behavior), new PropertyMetadata(false, OnFocusOnLoadedChanged));
static void OnFocusOnLoadedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var target = (FrameworkElement)d;
if ((bool)e.NewValue) target.Loaded += _target_Loaded;
else target.Loaded -= _target_Loaded;
}
static void _target_Loaded(object sender, RoutedEventArgs e) {
var target = (FrameworkElement)sender;
Keyboard.Focus(target);
}
public static bool GetFocusOnLoaded(DependencyObject d) => (bool)d.GetValue(FocusOnLoadedProperty);
public static void SetFocusOnLoaded(DependencyObject d, bool value) => d.SetValue(FocusOnLoadedProperty, value);
#endregion
#region MirrorLayout
static readonly DependencyProperty[] _layout_props = new[] {
FrameworkElement.HeightProperty,
FrameworkElement.WidthProperty,
FrameworkElement.MarginProperty,
Control.PaddingProperty,
FrameworkElement.HorizontalAlignmentProperty,
Control.HorizontalContentAlignmentProperty,
FrameworkElement.VerticalAlignmentProperty,
Control.VerticalContentAlignmentProperty
};
public static readonly DependencyProperty MirrorLayoutForProperty =
DependencyProperty.RegisterAttached("MirrorLayoutFor", typeof(FrameworkElement), typeof(Behavior),
new PropertyMetadata(OnMirrorLayoutForChanged));
static void OnMirrorLayoutForChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var target = new LazyCast<FrameworkElement, DependencyObject>(d);
if (e.OldValue is FrameworkElement old_src) {
target.value.clear_bindings(_layout_props);
}
if (e.NewValue is FrameworkElement new_src) {
target.value.bind(FrameworkElement.WidthProperty, new_src, FrameworkElement.ActualWidthProperty);
target.value.bind(FrameworkElement.HeightProperty, new_src, FrameworkElement.ActualHeightProperty);
target.value.bind(FrameworkElement.MarginProperty, new_src, FrameworkElement.MarginProperty);
target.value.bind(FrameworkElement.HorizontalAlignmentProperty, new_src, FrameworkElement.HorizontalAlignmentProperty);
target.value.bind(FrameworkElement.VerticalAlignmentProperty, new_src, FrameworkElement.VerticalAlignmentProperty);
if (target.value is Control && new_src is Control) {
target.value.bind(Control.PaddingProperty, new_src, Control.PaddingProperty);
target.value.bind(Control.HorizontalContentAlignmentProperty, new_src, Control.HorizontalContentAlignmentProperty);
target.value.bind(Control.VerticalContentAlignmentProperty, new_src, Control.VerticalContentAlignmentProperty);
}
}
}
public static FrameworkElement GetMirrorLayoutFor(DependencyObject d) => d.GetValue(MirrorLayoutForProperty) as FrameworkElement;
public static void SetMirrorLayoutFor(DependencyObject d, FrameworkElement value) => d.SetValue(MirrorLayoutForProperty, value);
#endregion
}
}
using bts.utils;
using mbts.utils;
using Spark.Core.Disposables;
using Spark.ViewModel.Commands;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
namespace mbts.ui {
static class Behavior {
#region CloseWindow
public static readonly DependencyProperty CloseWindowProperty = DependencyProperty.RegisterAttached("CloseWindow", typeof(bool), typeof(Behavior),
new PropertyMetadata(false, (d, dpc_args) => {
var button = d as Button;
if (button != null && (bool)dpc_args.NewValue) {
RoutedEventHandler close_window = (sender, e) => {
var window = button.find_ancestor_of_type<Window>();
if (window != null)
window.Close();
};
button.Click += close_window;
IDisposable dispose = Disposable.Create(() => {
button.Click -= close_window;
close_window = null;
});
IDisposable early_dispose = button.ObserveProperty(CloseWindowProperty, (sender, e) => {
if (!GetCloseWindow(button))
Disposable.dispose(ref dispose);
});
DisposeExtensions.attach_to_disposer(button, dispose);
DisposeExtensions.attach_to_disposer(button, early_dispose);
}
}));
public static bool GetCloseWindow(Button obj) { return (bool)obj.GetValue(CloseWindowProperty); }
public static void SetCloseWindow(Button obj, bool value) { obj.SetValue(CloseWindowProperty, value); }
#endregion
#region AnimatedScrollViewerVerticalOffset
static double GetAnimatedScrollViewerVerticalOffset(DependencyObject obj) =>
(double)obj.GetValue(AnimatedScrollViewerVerticalOffsetProperty);
public static void SetAnimatedScrollViewerVerticalOffset(DependencyObject obj, double value) =>
obj.SetValue(AnimatedScrollViewerVerticalOffsetProperty, value);
public static readonly DependencyProperty AnimatedScrollViewerVerticalOffsetProperty =
DependencyProperty.RegisterAttached("AnimatedScrollViewerVerticalOffset", typeof(double), typeof(Behavior), new PropertyMetadata(0.0, scroll_changed));
static void scroll_changed(DependencyObject d, DependencyPropertyChangedEventArgs e) {
ScrollViewer target = d as ScrollViewer;
if (target != null) {
target.ScrollToVerticalOffset((double)e.NewValue);
}
}
#endregion
#region AutoFocusTargetProvider
public static readonly DependencyProperty AutoFocusTargetProviderProperty = DependencyProperty.RegisterAttached(
"AutoFocusTargetProvider", typeof(IAutoFocusTargetProvider), typeof(Behavior), new PropertyMetadata(OnAutoFocusTargetProviderChanged));
static void OnAutoFocusTargetProviderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (e.NewValue is IAutoFocusTargetProvider aftp) {
var el = ExceptionAssert.Argument.Is<FrameworkElement>(d, nameof(d));
d.SetValue(AutoFocusTargetProviderLifeDependencyPropertyKey, new AutoFocusTargetProviderLifeDependency(el, aftp));
} else
d.SetValue(AutoFocusTargetProviderLifeDependencyPropertyKey, null);
}
public static IAutoFocusTargetProvider GetAutoFocusTargetProvider(DependencyObject d) =>
d.GetValue(AutoFocusTargetProviderProperty) as IAutoFocusTargetProvider;
public static void SetAutoFocusTargetProvider(DependencyObject d, IAutoFocusTargetProvider value) =>
d.SetValue(AutoFocusTargetProviderProperty, value);
#endregion
#region AutoFocusTargetProviderLifeDependency (ReadOnly)
static readonly DependencyPropertyKey AutoFocusTargetProviderLifeDependencyPropertyKey = DependencyProperty.RegisterAttachedReadOnly("AutoFocusTargetProviderLifeDependency",
typeof(FrameworkElementLifeDependency), typeof(Behavior), new PropertyMetadata(FrameworkElementLifeDependencyHelper.DisposeOldInitializeNew));
public static readonly DependencyProperty AutoFocusTargetProviderLifeDependencyProperty = AutoFocusTargetProviderLifeDependencyPropertyKey.DependencyProperty;
public static FrameworkElementLifeDependency GetAutoFocusTargetProviderLifeDependency(DependencyObject d) => d.GetValue(AutoFocusTargetProviderLifeDependencyProperty) as FrameworkElementLifeDependency;
public static void SetAutoFocusTargetProviderLifeDependency(DependencyObject d, FrameworkElementLifeDependency value) => d.SetValue(AutoFocusTargetProviderLifeDependencyPropertyKey, value);
class AutoFocusTargetProviderLifeDependency : FrameworkElementLifeDependency {
readonly IAutoFocusTargetProvider _focus_target_provider;
public AutoFocusTargetProviderLifeDependency(FrameworkElement root, IAutoFocusTargetProvider focus_target_provider) : base(root) =>
_focus_target_provider = focus_target_provider;
protected override void OnLoaded() {
var el = _focus_target_provider.Target;
if (el != null) {
if (_focus_target_provider.Focus) {
var fs = FocusManager.GetFocusScope(el);
if (fs is not null) {
FocusManager.SetFocusedElement(fs, el);
if (fs is UIElement fsUiEl) {
fsUiEl.Focus();
} else if (fs is IInputElement fsInputEl) {
Keyboard.Focus(fsInputEl);
} else {
el.Focus();
}
} else {
el.Focus();
}
}
if (_focus_target_provider.FocusKeyboard)
Keyboard.Focus(el);
if (_focus_target_provider.CaptureMouse)
el.CaptureMouse();
}
}
protected override void OnUnloaded() {
var el = _focus_target_provider.Target;
if (el != null) {
if (_focus_target_provider.ReleaseKeyboard) {
ReferenceEquals(Keyboard.FocusedElement, el);
Keyboard.ClearFocus();
}
if (_focus_target_provider.ReleaseMouse)
el.ReleaseMouseCapture();
}
}
}
#endregion
#region EatMouseDown
public static readonly DependencyProperty EatMouseDownProperty =
DependencyProperty.RegisterAttached("EatMouseDown", typeof(bool), typeof(Behavior), new PropertyMetadata(false, OnEatMouseDownChanged));
static void OnEatMouseDownChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var el = (FrameworkElement)d;
if ((bool)e.NewValue) el.MouseDown += _eat_mouse_down;
else el.MouseDown -= _eat_mouse_down;
}
static void _eat_mouse_down(object sender, MouseButtonEventArgs e) =>
e.Handled = true;
public static bool GetEatMouseDown(DependencyObject d) => (bool)d.GetValue(EatMouseDownProperty);
public static void SetEatMouseDown(DependencyObject d, bool value) => d.SetValue(EatMouseDownProperty, value);
#endregion
#region DisablePaste
public static readonly DependencyProperty DisablePasteProperty = DependencyProperty.RegisterAttached(
"DisablePaste", typeof(bool), typeof(Behavior), new PropertyMetadata(false, OnDisablePasteChanged));
static void OnDisablePasteChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if ((bool)e.NewValue)
DataObject.AddPastingHandler(d, _disable_paste_pasting_handler);
else DataObject.RemovePastingHandler(d, _disable_paste_pasting_handler);
}
static void _disable_paste_pasting_handler(object sender, DataObjectPastingEventArgs e) => e.CancelCommand();
public static bool GetDisablePaste(DependencyObject d) => (bool)d.GetValue(DisablePasteProperty);
public static void SetDisablePaste(DependencyObject d, bool value) => d.SetValue(DisablePasteProperty, value);
#endregion
public static readonly DependencyProperty BindingUpdateGroupsProperty =
DependencyProperty.RegisterAttached("BindingUpdateGroups", typeof(BindingUpdateGroupCollection), typeof(Behavior), new PropertyMetadata(null));
public static BindingUpdateGroupCollection GetBindingUpdateGroups(DependencyObject d) => d.GetValue(BindingUpdateGroupsProperty) as BindingUpdateGroupCollection;
public static void SetBindingUpdateGroups(DependencyObject d, BindingUpdateGroupCollection value) => d.SetValue(BindingUpdateGroupsProperty, value);
#region AutoRaiseCanExecuteChanged
public static readonly DependencyProperty AutoRaiseCanExecuteChangedProperty =
DependencyProperty.RegisterAttached("AutoRaiseCanExecuteChanged", typeof(bool), typeof(Behavior),
new PropertyMetadata(false, (d, e) => {
if ((bool)e.NewValue) {
BindingOperations.SetBinding(d, AutoCommandProperty, new Binding(nameof(ButtonBase.Command)) { Source = d, Mode = BindingMode.OneWay });
BindingOperations.SetBinding(d, AutoCommandParameterProperty, new Binding(nameof(ButtonBase.CommandParameter)) { Source = d, Mode = BindingMode.OneWay });
} else {
BindingOperations.ClearBinding(d, AutoCommandProperty);
BindingOperations.ClearBinding(d, AutoCommandParameterProperty);
}
}));
public static bool GetAutoRaiseCanExecuteChanged(DependencyObject d) => (bool)d.GetValue(AutoRaiseCanExecuteChangedProperty);
public static void SetAutoRaiseCanExecuteChanged(DependencyObject d, bool value) => d.SetValue(AutoRaiseCanExecuteChangedProperty, value);
/// Do not bind directly to <see cref="AutoCommandProperty"/> (used internally by <see cref="AutoRaiseCanExecuteChangedProperty"/>
public static readonly DependencyProperty AutoCommandProperty =
DependencyProperty.RegisterAttached("AutoCommand", typeof(ICommand), typeof(Behavior), new PropertyMetadata(null, _try_raise_can_execute));
public static ICommand GetAutoCommand(DependencyObject d) => d.GetValue(AutoCommandProperty) as ICommand;
public static void SetAutoCommand(DependencyObject d, ICommand value) => d.SetValue(AutoCommandProperty, value);
/// Do not bind directly to <see cref="AutoCommandParameterProperty"/> (used internally by <see cref="AutoRaiseCanExecuteChangedProperty"/>
public static readonly DependencyProperty AutoCommandParameterProperty =
DependencyProperty.RegisterAttached("AutoCommandParameter", typeof(object), typeof(Behavior), new PropertyMetadata(null, _try_raise_can_execute));
public static object GetAutoCommandParameter(DependencyObject d) => d.GetValue(AutoCommandParameterProperty);
public static void SetAutoCommandParameter(DependencyObject d, object value) => d.SetValue(AutoCommandParameterProperty, value);
static void _try_raise_can_execute(DependencyObject d, DependencyPropertyChangedEventArgs e) =>
(d.GetValue(ButtonBase.CommandProperty) as IRaiseCanExecuteChangedCommand)?.RaiseCanExecuteChanged();
#endregion
#region FocusOnLoaded
public static readonly DependencyProperty FocusOnLoadedProperty =
DependencyProperty.RegisterAttached("FocusOnLoaded", typeof(bool), typeof(Behavior), new PropertyMetadata(false, OnFocusOnLoadedChanged));
static void OnFocusOnLoadedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var target = (FrameworkElement)d;
if ((bool)e.NewValue) target.Loaded += _target_Loaded;
else target.Loaded -= _target_Loaded;
}
static void _target_Loaded(object sender, RoutedEventArgs e) {
var target = (FrameworkElement)sender;
Keyboard.Focus(target);
}
public static bool GetFocusOnLoaded(DependencyObject d) => (bool)d.GetValue(FocusOnLoadedProperty);
public static void SetFocusOnLoaded(DependencyObject d, bool value) => d.SetValue(FocusOnLoadedProperty, value);
#endregion
#region MirrorLayout
static readonly DependencyProperty[] _layout_props = new[] {
FrameworkElement.HeightProperty,
FrameworkElement.WidthProperty,
FrameworkElement.MarginProperty,
Control.PaddingProperty,
FrameworkElement.HorizontalAlignmentProperty,
Control.HorizontalContentAlignmentProperty,
FrameworkElement.VerticalAlignmentProperty,
Control.VerticalContentAlignmentProperty
};
public static readonly DependencyProperty MirrorLayoutForProperty =
DependencyProperty.RegisterAttached("MirrorLayoutFor", typeof(FrameworkElement), typeof(Behavior),
new PropertyMetadata(OnMirrorLayoutForChanged));
static void OnMirrorLayoutForChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var target = new LazyCast<FrameworkElement, DependencyObject>(d);
if (e.OldValue is FrameworkElement old_src) {
target.value.clear_bindings(_layout_props);
}
if (e.NewValue is FrameworkElement new_src) {
target.value.bind(FrameworkElement.WidthProperty, new_src, FrameworkElement.ActualWidthProperty);
target.value.bind(FrameworkElement.HeightProperty, new_src, FrameworkElement.ActualHeightProperty);
target.value.bind(FrameworkElement.MarginProperty, new_src, FrameworkElement.MarginProperty);
target.value.bind(FrameworkElement.HorizontalAlignmentProperty, new_src, FrameworkElement.HorizontalAlignmentProperty);
target.value.bind(FrameworkElement.VerticalAlignmentProperty, new_src, FrameworkElement.VerticalAlignmentProperty);
if (target.value is Control && new_src is Control) {
target.value.bind(Control.PaddingProperty, new_src, Control.PaddingProperty);
target.value.bind(Control.HorizontalContentAlignmentProperty, new_src, Control.HorizontalContentAlignmentProperty);
target.value.bind(Control.VerticalContentAlignmentProperty, new_src, Control.VerticalContentAlignmentProperty);
}
}
}
public static FrameworkElement GetMirrorLayoutFor(DependencyObject d) => d.GetValue(MirrorLayoutForProperty) as FrameworkElement;
public static void SetMirrorLayoutFor(DependencyObject d, FrameworkElement value) => d.SetValue(MirrorLayoutForProperty, value);
#endregion
}
}
@@ -557,6 +557,8 @@
</Style.Triggers>
</Style>
<Style x:Key="DefaultButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}" />
<Style TargetType="RepeatButton">
<Setter Property="Foreground" Value="{u:BTSBrush UI.Button.normal_foreground}" />
<Setter Property="Background" Value="{u:BTSBrush UI.Button.normal_background}" />
@@ -1,348 +1,371 @@
using bts;
using mbts.ui.native;
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
namespace mbts.ui {
public partial class WindowStyle : ResourceDictionary {
// 'Top' is 0 because the window's TitleContent overlaps the top-resize-border.
public static readonly Thickness NegativeResizeBorderThickness = new Thickness(-5, 0, -5, -5);
public static readonly ExecutedRoutedEventHandler
CloseWindowExecutedHandler = WindowCallbackHelper.safe_command_executed_handler(w => w.Close(), "closing"),
HideWindowExecutedHandler = WindowCallbackHelper.safe_command_executed_handler(w => w.Visibility = Visibility.Hidden, "hiding"),
MinimizeWindowExecutedHandler = WindowCallbackHelper.command_executed_handler(w => w.WindowState = WindowState.Minimized),
MaximizeWindowExecutedHandler = WindowCallbackHelper.command_executed_handler(w => w.WindowState = w.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized);
#region UnsafeWindow
// UnsafeWindow is described as 'Unsafe' because the window may be on a different dispatcher than the DependencyObject target.
public static readonly DependencyProperty UnsafeWindowProperty =
DependencyProperty.RegisterAttached("UnsafeWindow", typeof(Window), typeof(WindowStyle), new PropertyMetadata(OnUnsafeWindowChanged));
static void OnUnsafeWindowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (d is UIElement el)
el.RaiseEvent(new RoutedPropertyChangedEventArgs<Window>(e.OldValue as Window, e.NewValue as Window, UnsafeWindowChangedEvent));
}
public static Window GetUnsafeWindow(DependencyObject d) => d.GetValue(UnsafeWindowProperty) as Window;
public static void SetUnsafeWindow(DependencyObject d, Window value) => d.SetValue(UnsafeWindowProperty, value);
#endregion
#region UnsafeWindowChanged (Event)
internal static readonly RoutedEvent UnsafeWindowChangedEvent = EventManager.RegisterRoutedEvent("UnsafeWindowChanged", RoutingStrategy.Direct, typeof(RoutedPropertyChangedEventHandler<Window>), typeof(WindowStyle));
internal static void AddUnsafeWindowChangedHandler(UIElement el, RoutedPropertyChangedEventHandler<Window> handler) =>
el.AddHandler(UnsafeWindowChangedEvent, handler);
internal static void RemoveUnsafeWindowChangedHandler(UIElement el, RoutedPropertyChangedEventHandler<Window> handler) =>
el.RemoveHandler(UnsafeWindowChangedEvent, handler);
#endregion
static class WindowCallbackHelper {
public static ExecutedRoutedEventHandler safe_command_executed_handler(Action<Window> action, string action_name) =>
(s, e) => _safe_execute_on_window(s, action, action_name);
public static ExecutedRoutedEventHandler command_executed_handler(Action<Window> action) =>
(s, e) => _execute_on_window(s, action);
public static RoutedEventHandler routed_event_handler(Action<Window> action) =>
(s, e) => _execute_on_window(s, action);
static void _execute_on_window(object sender, Action<Window> action) {
if (_get_host_window(sender) is Window window)
action(window);
}
static void _safe_execute_on_window(object sender, Action<Window> action, string action_name) {
try {
_execute_on_window(sender, action);
} catch (Exception err) {
Log.Ui.warn($"Exception {action_name} window : {err}");
}
}
static Window _get_host_window(object sender) =>
sender is Window w ? w : sender is DependencyObject d ? Window.GetWindow(d) : null;
}
#region Attached properties to control the style b/c the default Window properties are lame
#region Close button
public static bool GetShowCloseButton(DependencyObject obj) {
return (bool)obj.GetValue(ShowCloseButtonProperty);
}
public static void SetShowCloseButton(DependencyObject obj, bool value) {
obj.SetValue(ShowCloseButtonProperty, value);
}
public static readonly DependencyProperty ShowCloseButtonProperty =
DependencyProperty.RegisterAttached("ShowCloseButton", typeof(bool), typeof(WindowStyle), new PropertyMetadata(true));
#endregion
#region Minimize Button
public static bool GetShowMinimizeButton(DependencyObject obj) {
return (bool)obj.GetValue(ShowMinimizeButtonProperty);
}
public static void SetShowMinimizeButton(DependencyObject obj, bool value) {
obj.SetValue(ShowMinimizeButtonProperty, value);
}
public static readonly DependencyProperty ShowMinimizeButtonProperty =
DependencyProperty.RegisterAttached("ShowMinimizeButton", typeof(bool), typeof(WindowStyle), new PropertyMetadata(true));
#endregion
#region Maximize Button
public static bool GetShowMaximizeButton(DependencyObject obj) {
return (bool)obj.GetValue(ShowMaximizeButtonProperty);
}
public static void SetShowMaximizeButton(DependencyObject obj, bool value) {
obj.SetValue(ShowMaximizeButtonProperty, value);
}
public static readonly DependencyProperty ShowMaximizeButtonProperty =
DependencyProperty.RegisterAttached("ShowMaximizeButton", typeof(bool), typeof(WindowStyle), new PropertyMetadata(false));
#endregion
#region AllowResize
public static bool GetAllowResize(DependencyObject obj) {
return (bool)obj.GetValue(AllowResizeProperty);
}
public static void SetAllowResize(DependencyObject obj, bool value) {
obj.SetValue(AllowResizeProperty, value);
}
public static readonly DependencyProperty AllowResizeProperty =
DependencyProperty.RegisterAttached("AllowResize", typeof(bool), typeof(WindowStyle), new PropertyMetadata(false, (s, e) => {
var wnd = s as Window;
if (wnd != null) {
if ((bool)e.NewValue == true) {
wnd.ResizeMode = ResizeMode.CanResize;
} else {
wnd.ResizeMode = ResizeMode.NoResize;
}
}
}));
#endregion
#region IsNonClientArea Inherited property
public static bool GetIsNonClientArea(DependencyObject obj) {
return (bool)obj.GetValue(IsNonClientAreaProperty);
}
public static void SetIsNonClientArea(DependencyObject obj, bool value) {
obj.SetValue(IsNonClientAreaProperty, value);
}
public static readonly DependencyProperty IsNonClientAreaProperty =
DependencyProperty.RegisterAttached("IsNonClientArea", typeof(bool), typeof(WindowStyle), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits));
#endregion
#region [Custom]SizeToContent property (some additional logic is needed to avoid ugly black border margins on the edge of the window)
public static readonly DependencyProperty SizeToContentProperty = DependencyProperty.RegisterAttached("SizeToContent", typeof(SizeToContent), typeof(WindowStyle), new PropertyMetadata(SizeToContent.Manual, OnSizeToContentChanged));
private static void OnSizeToContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
SizeToContent old_val = (SizeToContent)e.OldValue;
SizeToContent new_val = (SizeToContent)e.NewValue;
Window w = (Window)d;
if (old_val != SizeToContent.Manual) {
if (new_val == SizeToContent.Manual) w.ContentRendered -= InvalidateVisualOnContentRendered;
} else w.ContentRendered += InvalidateVisualOnContentRendered;
w.SizeToContent = new_val;
}
private static void InvalidateVisualOnContentRendered(object sender, EventArgs e) { ((Window)sender).InvalidateVisual(); }
public static SizeToContent GetSizeToContent(Window w) { return (SizeToContent)w.GetValue(SizeToContentProperty); }
public static void SetSizeToContent(Window w, SizeToContent value) { w.SetValue(SizeToContentProperty, value); }
#endregion
#region TitleContent
public static readonly DependencyProperty TitleContentProperty = DependencyProperty.RegisterAttached("TitleContent", typeof(object), typeof(WindowStyle), new PropertyMetadata(null));
public static object GetTitleContent(DependencyObject d) { return d.GetValue(TitleContentProperty); }
public static void SetTitleContent(DependencyObject d, object value) { d.SetValue(TitleContentProperty, value); }
#endregion
#region TitleContentTemplate
public static readonly DependencyProperty TitleContentTemplateProperty = DependencyProperty.RegisterAttached("TitleContentTemplate", typeof(DataTemplate), typeof(WindowStyle), new PropertyMetadata(null));
public static DataTemplate GetTitleContentTemplate(DependencyObject d) { return d.GetValue(TitleContentTemplateProperty) as DataTemplate; }
public static void SetTitleContentTemplate(DependencyObject d, DataTemplate value) { d.SetValue(TitleContentTemplateProperty, value); }
#endregion
#region DisposeVMOnClose
public static readonly DependencyProperty DisposeVMOnCloseProperty = DependencyProperty.RegisterAttached("DisposeVMOnClose", typeof(bool), typeof(WindowStyle), new PropertyMetadata(false, OnDisposeVMOnCloseChanged));
private static void OnDisposeVMOnCloseChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var target = d as Window;
target?.SetValue(DisposeVMOnCloseHandlerWrapperPropertyKey, (bool)e.NewValue ? new DisposeVMOnCloseHandlerWrapper(target) : null);
}
public static bool GetDisposeVMOnClose(DependencyObject d) { return (bool)d.GetValue(DisposeVMOnCloseProperty); }
public static void SetDisposeVMOnClose(DependencyObject d, bool value) { d.SetValue(DisposeVMOnCloseProperty, value); }
private static readonly DependencyPropertyKey DisposeVMOnCloseHandlerWrapperPropertyKey = DependencyProperty.RegisterAttachedReadOnly("DisposeVMOnCloseHandlerWrapper", typeof(IDisposable), typeof(WindowStyle), new PropertyMetadata(null, FrameworkElementLifeDependencyHelper.DisposeOld));
internal static readonly DependencyProperty DisposeVMOnCloseHandlerWrapperProperty = DisposeVMOnCloseHandlerWrapperPropertyKey.DependencyProperty;
internal static IDisposable GetDisposeVMOnCloseHandlerWrapper(Window w) { return w.GetValue(DisposeVMOnCloseHandlerWrapperProperty) as IDisposable; }
private static void SetDisposeVMOnCloseHandlerWrapper(Window w, IDisposable value) { w.SetValue(DisposeVMOnCloseHandlerWrapperProperty, value); }
private class DisposeVMOnCloseHandlerWrapper : IDisposable {
public DisposeVMOnCloseHandlerWrapper(Window target) { (_target = target).Closed += _target_Closed; }
private readonly Window _target;
private void _target_Closed(object sender, EventArgs e) { (_target.DataContext as IDisposable)?.Dispose(); }
public void Dispose() { _target.Closed -= _target_Closed; }
}
#endregion
#endregion
private void style_loaded(object sender, RoutedEventArgs _) {
if (sender is DependencyObject) {
var window = Window.GetWindow(((DependencyObject)sender));
if (window != null) {
if (WindowStyle.GetAllowResize(window)) {
set_resize_window_events(sender as FrameworkElement, window);
} else {
// Simpler dragmove interaction
var border = (FrameworkElement)sender;
border.MouseLeftButtonDown += (s, e) => {
if (Mouse.LeftButton == MouseButtonState.Pressed) {
window.DragMove();
}
};
}
// Handle Close command automagically
if (window.CommandBindings.Count == 0) {
window.CommandBindings.Add(new CommandBinding(SystemCommands.CloseWindowCommand, CloseWindowExecutedHandler));
window.CommandBindings.Add(new CommandBinding(SystemCommands.MinimizeWindowCommand, MinimizeWindowExecutedHandler));
window.CommandBindings.Add(new CommandBinding(SystemCommands.MaximizeWindowCommand, MaximizeWindowExecutedHandler));
}
}
}
}
private static void set_resize_window_events(FrameworkElement border, Window window) {
HookWndProc(window);
Log.assert(border != null);
var restore_on_move = false;
border.MouseLeftButtonDown += (s, e) => {
FrameworkElement fe = e.OriginalSource as FrameworkElement;
if (fe != null && (bool)fe.GetValue(WindowStyle.IsNonClientAreaProperty)) {
if (e.ClickCount == 2) {
toggle_max(window);
} else {
if (window.WindowState == WindowState.Maximized) { restore_on_move = true; }
if (Mouse.LeftButton == MouseButtonState.Pressed) {
window.DragMove();
}
}
}
};
border.MouseLeftButtonUp += (s, e) => restore_on_move = false;
border.MouseMove += (s, e) => {
if (restore_on_move && window.WindowState == WindowState.Maximized && Mouse.LeftButton == MouseButtonState.Pressed) {
window.WindowState = WindowState.Normal;
window.Top = 0;
if (Mouse.LeftButton == MouseButtonState.Pressed) {
window.DragMove();
}
}
};
}
private static void toggle_max(Window window) {
if (window.WindowState == WindowState.Maximized) {
window.WindowState = WindowState.Normal;
} else {
window.WindowState = WindowState.Maximized;
}
}
private static void HookWndProc(Window window) {
HwndSource source = PresentationSource.FromVisual(window) as HwndSource;
if (source != null) {
source.AddHook(new HwndSourceHook(WndProc));
}
}
private static void UnhookWndProc(Window window) {
HwndSource source = PresentationSource.FromVisual(window) as HwndSource;
if (source != null) {
source.RemoveHook(new HwndSourceHook(WndProc));
}
}
private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled) {
WM wmsg = (WM)msg;
if (WM.NCHITTEST == wmsg /*|| WM.MOUSEMOVE == wmsg*/) {
Thickness t = new Thickness(6);
// Get the mouse coordinates in screen space
short x = (short)lparam.ToInt32();
short y = (short)(lparam.ToInt32() >> 16);
// Get the window bounds in screen coords
Win32Rect w;
NativeMethods.GetWindowRect(hwnd, out w);
// Assume that mouse position is inside the window
bool left = x < (w.Left + t.Left);
bool right = x > (w.Right - t.Right);
bool top = y < (w.Top + t.Top);
bool bottom = y > (w.Bottom - t.Bottom);
// A B C
// D E
// F G H
HT ret = HT.CLIENT;
if (top) {
if (left) ret = HT.TOPLEFT; // A
else if (right) ret = HT.TOPRIGHT; // C
else ret = HT.TOP; // B
} else if (bottom) {
if (left) ret = HT.BOTTOMLEFT; // F
else if (right) ret = HT.BOTTOMRIGHT; // H
else ret = HT.BOTTOM; // G
} else {
if (left) ret = HT.LEFT; // D
if (right) ret = HT.RIGHT; // E
}
handled = true;
return new IntPtr((int)ret);
} else if (wmsg == WM.NCCALCSIZE) {
handled = true;
} else if (wmsg == WM.NCPAINT) {
handled = true;
} else if (wmsg == WM.NCACTIVATE) {
handled = true;
return NativeMethods.DefWindowProc(hwnd, msg, wparam, new IntPtr(-1));
} else if (wmsg == WM.ERASEBKGND) {
handled = true;
} else if (wmsg == WM.GETMINMAXINFO) {
SetMinMaxInfo(hwnd, lparam);
handled = true;
}
return IntPtr.Zero;
}
private static void SetMinMaxInfo(IntPtr hwnd, IntPtr lparam) {
var mminfo = (MINMAXINFO)Marshal.PtrToStructure(lparam, typeof(MINMAXINFO));
var hmonitor = NativeMethods.MonitorFromWindow(hwnd, NativeMethods.MONITOR_DEFAULTONNEAREST);
if (hmonitor != IntPtr.Zero) {
MONITORINFO minfo = new MONITORINFO();
minfo.cbSize = Marshal.SizeOf(minfo);
NativeMethods.GetMonitorInfo(hmonitor, ref minfo);
var rc_monitor = minfo.rcMonitor;
var rc_work = minfo.rcWork;
mminfo.ptMaxPosition.x = Math.Abs(rc_work.Left - rc_monitor.Left);
mminfo.ptMaxPosition.y = Math.Abs(rc_work.Top - rc_monitor.Top);
mminfo.ptMaxSize.x = Math.Abs(rc_work.Right - rc_work.Left);
mminfo.ptMaxSize.y = Math.Abs(rc_work.Bottom - rc_work.Top);
Marshal.StructureToPtr(mminfo, lparam, true);
}
}
}
}
using bts;
using mbts.ui.native;
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
namespace mbts.ui {
public partial class WindowStyle : ResourceDictionary {
// 'Top' is 0 because the window's TitleContent overlaps the top-resize-border.
public static readonly Thickness NegativeResizeBorderThickness = new Thickness(-5, 0, -5, -5);
public static readonly ExecutedRoutedEventHandler
CloseWindowExecutedHandler = WindowCallbackHelper.safe_command_executed_handler(w => w.Close(), "closing"),
HideWindowExecutedHandler = WindowCallbackHelper.safe_command_executed_handler(w => w.Visibility = Visibility.Hidden, "hiding"),
MinimizeWindowExecutedHandler = WindowCallbackHelper.command_executed_handler(w => w.WindowState = WindowState.Minimized),
MaximizeWindowExecutedHandler = WindowCallbackHelper.command_executed_handler(w => w.WindowState = w.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized);
#region UnsafeWindow
// UnsafeWindow is described as 'Unsafe' because the window may be on a different dispatcher than the DependencyObject target.
public static readonly DependencyProperty UnsafeWindowProperty =
DependencyProperty.RegisterAttached("UnsafeWindow", typeof(Window), typeof(WindowStyle), new PropertyMetadata(OnUnsafeWindowChanged));
static void OnUnsafeWindowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (d is UIElement el)
el.RaiseEvent(new RoutedPropertyChangedEventArgs<Window>(e.OldValue as Window, e.NewValue as Window, UnsafeWindowChangedEvent));
}
public static Window GetUnsafeWindow(DependencyObject d) => d.GetValue(UnsafeWindowProperty) as Window;
public static void SetUnsafeWindow(DependencyObject d, Window value) => d.SetValue(UnsafeWindowProperty, value);
#endregion
#region UnsafeWindowChanged (Event)
internal static readonly RoutedEvent UnsafeWindowChangedEvent = EventManager.RegisterRoutedEvent("UnsafeWindowChanged", RoutingStrategy.Direct, typeof(RoutedPropertyChangedEventHandler<Window>), typeof(WindowStyle));
internal static void AddUnsafeWindowChangedHandler(UIElement el, RoutedPropertyChangedEventHandler<Window> handler) =>
el.AddHandler(UnsafeWindowChangedEvent, handler);
internal static void RemoveUnsafeWindowChangedHandler(UIElement el, RoutedPropertyChangedEventHandler<Window> handler) =>
el.RemoveHandler(UnsafeWindowChangedEvent, handler);
#endregion
#region WinFormsHost
public static readonly DependencyProperty WinFormsHostProperty =
DependencyProperty.RegisterAttached("WinFormsHost", typeof(WPFPanel), typeof(WindowStyle), new PropertyMetadata(OnWinFormsHostChanged));
static void OnWinFormsHostChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (d is UIElement el)
el.RaiseEvent(new RoutedPropertyChangedEventArgs<WPFPanel>(e.OldValue as WPFPanel, e.NewValue as WPFPanel, WinFormsHostChangedEvent));
}
public static WPFPanel GetWinFormsHost(DependencyObject d) => d.GetValue(WinFormsHostProperty) as WPFPanel;
public static void SetWinFormsHost(DependencyObject d, WPFPanel value) => d.SetValue(WinFormsHostProperty, value);
#endregion
#region WinFormsHostChanged (Event)
internal static readonly RoutedEvent WinFormsHostChangedEvent = EventManager.RegisterRoutedEvent("WinFormsHostChanged", RoutingStrategy.Direct, typeof(RoutedPropertyChangedEventHandler<WPFPanel>), typeof(WindowStyle));
internal static void AddWinFormsHostChangedHandler(UIElement el, RoutedPropertyChangedEventHandler<WPFPanel> handler) =>
el.AddHandler(WinFormsHostChangedEvent, handler);
internal static void RemoveWinFormsHostChangedHandler(UIElement el, RoutedPropertyChangedEventHandler<WPFPanel> handler) =>
el.RemoveHandler(WinFormsHostChangedEvent, handler);
#endregion
static class WindowCallbackHelper {
public static ExecutedRoutedEventHandler safe_command_executed_handler(Action<Window> action, string action_name) =>
(s, e) => _safe_execute_on_window(s, action, action_name);
public static ExecutedRoutedEventHandler command_executed_handler(Action<Window> action) =>
(s, e) => _execute_on_window(s, action);
public static RoutedEventHandler routed_event_handler(Action<Window> action) =>
(s, e) => _execute_on_window(s, action);
static void _execute_on_window(object sender, Action<Window> action) {
if (_get_host_window(sender) is Window window)
action(window);
}
static void _safe_execute_on_window(object sender, Action<Window> action, string action_name) {
try {
_execute_on_window(sender, action);
} catch (Exception err) {
Log.Ui.warn($"Exception {action_name} window : {err}");
}
}
static Window _get_host_window(object sender) =>
sender is Window w ? w : sender is DependencyObject d ? Window.GetWindow(d) : null;
}
#region Attached properties to control the style b/c the default Window properties are lame
#region Close button
public static bool GetShowCloseButton(DependencyObject obj) {
return (bool)obj.GetValue(ShowCloseButtonProperty);
}
public static void SetShowCloseButton(DependencyObject obj, bool value) {
obj.SetValue(ShowCloseButtonProperty, value);
}
public static readonly DependencyProperty ShowCloseButtonProperty =
DependencyProperty.RegisterAttached("ShowCloseButton", typeof(bool), typeof(WindowStyle), new PropertyMetadata(true));
#endregion
#region Minimize Button
public static bool GetShowMinimizeButton(DependencyObject obj) {
return (bool)obj.GetValue(ShowMinimizeButtonProperty);
}
public static void SetShowMinimizeButton(DependencyObject obj, bool value) {
obj.SetValue(ShowMinimizeButtonProperty, value);
}
public static readonly DependencyProperty ShowMinimizeButtonProperty =
DependencyProperty.RegisterAttached("ShowMinimizeButton", typeof(bool), typeof(WindowStyle), new PropertyMetadata(true));
#endregion
#region Maximize Button
public static bool GetShowMaximizeButton(DependencyObject obj) {
return (bool)obj.GetValue(ShowMaximizeButtonProperty);
}
public static void SetShowMaximizeButton(DependencyObject obj, bool value) {
obj.SetValue(ShowMaximizeButtonProperty, value);
}
public static readonly DependencyProperty ShowMaximizeButtonProperty =
DependencyProperty.RegisterAttached("ShowMaximizeButton", typeof(bool), typeof(WindowStyle), new PropertyMetadata(false));
#endregion
#region AllowResize
public static bool GetAllowResize(DependencyObject obj) {
return (bool)obj.GetValue(AllowResizeProperty);
}
public static void SetAllowResize(DependencyObject obj, bool value) {
obj.SetValue(AllowResizeProperty, value);
}
public static readonly DependencyProperty AllowResizeProperty =
DependencyProperty.RegisterAttached("AllowResize", typeof(bool), typeof(WindowStyle), new PropertyMetadata(false, (s, e) => {
var wnd = s as Window;
if (wnd != null) {
if ((bool)e.NewValue == true) {
wnd.ResizeMode = ResizeMode.CanResize;
} else {
wnd.ResizeMode = ResizeMode.NoResize;
}
}
}));
#endregion
#region IsNonClientArea Inherited property
public static bool GetIsNonClientArea(DependencyObject obj) {
return (bool)obj.GetValue(IsNonClientAreaProperty);
}
public static void SetIsNonClientArea(DependencyObject obj, bool value) {
obj.SetValue(IsNonClientAreaProperty, value);
}
public static readonly DependencyProperty IsNonClientAreaProperty =
DependencyProperty.RegisterAttached("IsNonClientArea", typeof(bool), typeof(WindowStyle), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits));
#endregion
#region [Custom]SizeToContent property (some additional logic is needed to avoid ugly black border margins on the edge of the window)
public static readonly DependencyProperty SizeToContentProperty = DependencyProperty.RegisterAttached("SizeToContent", typeof(SizeToContent), typeof(WindowStyle), new PropertyMetadata(SizeToContent.Manual, OnSizeToContentChanged));
private static void OnSizeToContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
SizeToContent old_val = (SizeToContent)e.OldValue;
SizeToContent new_val = (SizeToContent)e.NewValue;
Window w = (Window)d;
if (old_val != SizeToContent.Manual) {
if (new_val == SizeToContent.Manual) w.ContentRendered -= InvalidateVisualOnContentRendered;
} else w.ContentRendered += InvalidateVisualOnContentRendered;
w.SizeToContent = new_val;
}
private static void InvalidateVisualOnContentRendered(object sender, EventArgs e) { ((Window)sender).InvalidateVisual(); }
public static SizeToContent GetSizeToContent(Window w) { return (SizeToContent)w.GetValue(SizeToContentProperty); }
public static void SetSizeToContent(Window w, SizeToContent value) { w.SetValue(SizeToContentProperty, value); }
#endregion
#region TitleContent
public static readonly DependencyProperty TitleContentProperty = DependencyProperty.RegisterAttached("TitleContent", typeof(object), typeof(WindowStyle), new PropertyMetadata(null));
public static object GetTitleContent(DependencyObject d) { return d.GetValue(TitleContentProperty); }
public static void SetTitleContent(DependencyObject d, object value) { d.SetValue(TitleContentProperty, value); }
#endregion
#region TitleContentTemplate
public static readonly DependencyProperty TitleContentTemplateProperty = DependencyProperty.RegisterAttached("TitleContentTemplate", typeof(DataTemplate), typeof(WindowStyle), new PropertyMetadata(null));
public static DataTemplate GetTitleContentTemplate(DependencyObject d) { return d.GetValue(TitleContentTemplateProperty) as DataTemplate; }
public static void SetTitleContentTemplate(DependencyObject d, DataTemplate value) { d.SetValue(TitleContentTemplateProperty, value); }
#endregion
#region DisposeVMOnClose
public static readonly DependencyProperty DisposeVMOnCloseProperty = DependencyProperty.RegisterAttached("DisposeVMOnClose", typeof(bool), typeof(WindowStyle), new PropertyMetadata(false, OnDisposeVMOnCloseChanged));
private static void OnDisposeVMOnCloseChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var target = d as Window;
target?.SetValue(DisposeVMOnCloseHandlerWrapperPropertyKey, (bool)e.NewValue ? new DisposeVMOnCloseHandlerWrapper(target) : null);
}
public static bool GetDisposeVMOnClose(DependencyObject d) { return (bool)d.GetValue(DisposeVMOnCloseProperty); }
public static void SetDisposeVMOnClose(DependencyObject d, bool value) { d.SetValue(DisposeVMOnCloseProperty, value); }
private static readonly DependencyPropertyKey DisposeVMOnCloseHandlerWrapperPropertyKey = DependencyProperty.RegisterAttachedReadOnly("DisposeVMOnCloseHandlerWrapper", typeof(IDisposable), typeof(WindowStyle), new PropertyMetadata(null, FrameworkElementLifeDependencyHelper.DisposeOld));
internal static readonly DependencyProperty DisposeVMOnCloseHandlerWrapperProperty = DisposeVMOnCloseHandlerWrapperPropertyKey.DependencyProperty;
internal static IDisposable GetDisposeVMOnCloseHandlerWrapper(Window w) { return w.GetValue(DisposeVMOnCloseHandlerWrapperProperty) as IDisposable; }
private static void SetDisposeVMOnCloseHandlerWrapper(Window w, IDisposable value) { w.SetValue(DisposeVMOnCloseHandlerWrapperProperty, value); }
private class DisposeVMOnCloseHandlerWrapper : IDisposable {
public DisposeVMOnCloseHandlerWrapper(Window target) { (_target = target).Closed += _target_Closed; }
private readonly Window _target;
private void _target_Closed(object sender, EventArgs e) { (_target.DataContext as IDisposable)?.Dispose(); }
public void Dispose() { _target.Closed -= _target_Closed; }
}
#endregion
#endregion
private void style_loaded(object sender, RoutedEventArgs _) {
if (sender is DependencyObject) {
var window = Window.GetWindow(((DependencyObject)sender));
if (window != null) {
if (WindowStyle.GetAllowResize(window)) {
set_resize_window_events(sender as FrameworkElement, window);
} else {
// Simpler dragmove interaction
var border = (FrameworkElement)sender;
border.MouseLeftButtonDown += (s, e) => {
if (Mouse.LeftButton == MouseButtonState.Pressed) {
window.DragMove();
}
};
}
// Handle Close command automagically
if (window.CommandBindings.Count == 0) {
window.CommandBindings.Add(new CommandBinding(SystemCommands.CloseWindowCommand, CloseWindowExecutedHandler));
window.CommandBindings.Add(new CommandBinding(SystemCommands.MinimizeWindowCommand, MinimizeWindowExecutedHandler));
window.CommandBindings.Add(new CommandBinding(SystemCommands.MaximizeWindowCommand, MaximizeWindowExecutedHandler));
}
}
}
}
private static void set_resize_window_events(FrameworkElement border, Window window) {
HookWndProc(window);
Log.assert(border != null);
var restore_on_move = false;
border.MouseLeftButtonDown += (s, e) => {
FrameworkElement fe = e.OriginalSource as FrameworkElement;
if (fe != null && (bool)fe.GetValue(WindowStyle.IsNonClientAreaProperty)) {
if (e.ClickCount == 2) {
toggle_max(window);
} else {
if (window.WindowState == WindowState.Maximized) { restore_on_move = true; }
if (Mouse.LeftButton == MouseButtonState.Pressed) {
window.DragMove();
}
}
}
};
border.MouseLeftButtonUp += (s, e) => restore_on_move = false;
border.MouseMove += (s, e) => {
if (restore_on_move && window.WindowState == WindowState.Maximized && Mouse.LeftButton == MouseButtonState.Pressed) {
window.WindowState = WindowState.Normal;
window.Top = 0;
if (Mouse.LeftButton == MouseButtonState.Pressed) {
window.DragMove();
}
}
};
}
private static void toggle_max(Window window) {
if (window.WindowState == WindowState.Maximized) {
window.WindowState = WindowState.Normal;
} else {
window.WindowState = WindowState.Maximized;
}
}
private static void HookWndProc(Window window) {
HwndSource source = PresentationSource.FromVisual(window) as HwndSource;
if (source != null) {
source.AddHook(new HwndSourceHook(WndProc));
}
}
private static void UnhookWndProc(Window window) {
HwndSource source = PresentationSource.FromVisual(window) as HwndSource;
if (source != null) {
source.RemoveHook(new HwndSourceHook(WndProc));
}
}
private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled) {
WM wmsg = (WM)msg;
if (WM.NCHITTEST == wmsg /*|| WM.MOUSEMOVE == wmsg*/) {
Thickness t = new Thickness(6);
// Get the mouse coordinates in screen space
short x = (short)lparam.ToInt32();
short y = (short)(lparam.ToInt32() >> 16);
// Get the window bounds in screen coords
Win32Rect w;
NativeMethods.GetWindowRect(hwnd, out w);
// Assume that mouse position is inside the window
bool left = x < (w.Left + t.Left);
bool right = x > (w.Right - t.Right);
bool top = y < (w.Top + t.Top);
bool bottom = y > (w.Bottom - t.Bottom);
// A B C
// D E
// F G H
HT ret = HT.CLIENT;
if (top) {
if (left) ret = HT.TOPLEFT; // A
else if (right) ret = HT.TOPRIGHT; // C
else ret = HT.TOP; // B
} else if (bottom) {
if (left) ret = HT.BOTTOMLEFT; // F
else if (right) ret = HT.BOTTOMRIGHT; // H
else ret = HT.BOTTOM; // G
} else {
if (left) ret = HT.LEFT; // D
if (right) ret = HT.RIGHT; // E
}
handled = true;
return new IntPtr((int)ret);
} else if (wmsg == WM.NCCALCSIZE) {
handled = true;
} else if (wmsg == WM.NCPAINT) {
handled = true;
} else if (wmsg == WM.NCACTIVATE) {
handled = true;
return NativeMethods.DefWindowProc(hwnd, msg, wparam, new IntPtr(-1));
} else if (wmsg == WM.ERASEBKGND) {
handled = true;
} else if (wmsg == WM.GETMINMAXINFO) {
SetMinMaxInfo(hwnd, lparam);
handled = true;
}
return IntPtr.Zero;
}
private static void SetMinMaxInfo(IntPtr hwnd, IntPtr lparam) {
var mminfo = (MINMAXINFO)Marshal.PtrToStructure(lparam, typeof(MINMAXINFO));
var hmonitor = NativeMethods.MonitorFromWindow(hwnd, NativeMethods.MONITOR_DEFAULTONNEAREST);
if (hmonitor != IntPtr.Zero) {
MONITORINFO minfo = new MONITORINFO();
minfo.cbSize = Marshal.SizeOf(minfo);
NativeMethods.GetMonitorInfo(hmonitor, ref minfo);
var rc_monitor = minfo.rcMonitor;
var rc_work = minfo.rcWork;
mminfo.ptMaxPosition.x = Math.Abs(rc_work.Left - rc_monitor.Left);
mminfo.ptMaxPosition.y = Math.Abs(rc_work.Top - rc_monitor.Top);
mminfo.ptMaxSize.x = Math.Abs(rc_work.Right - rc_work.Left);
mminfo.ptMaxSize.y = Math.Abs(rc_work.Bottom - rc_work.Top);
Marshal.StructureToPtr(mminfo, lparam, true);
}
}
}
}
+1 -1
View File
@@ -100,7 +100,7 @@
}
void PerformMEFComposition(string config_source) {
Log.Ui.info($"Generating MEF dependencies for the shell " +
Log.Ui.info($"this is v2.2.1 Generating MEF dependencies for the shell" +
$"({typeof(IInitializable)}s, {typeof(IShellModule)}s, " +
$"{typeof(IShellDaemon)}s, etc.)");
try {
@@ -1,241 +1,242 @@
namespace mbts.ui.shell {
using bts;
using Spark.Core;
using Spark.Core.Async;
using Spark.Core.Disposables;
using System;
using System.Reactive.Linq;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
public class ShellWindowVM : DisposableViewModelBase {
readonly IWindowManager _window_manager;
readonly Property<bool> _edit_mode;
readonly Property<string> _title;
readonly Property<string> _title_override;
readonly Property<bool> _has_custom_title;
IShellWindow _model;
UIElement _ui;
ShellWindowUI _document;
HwndContentHost _hwnd_host;
FloatingWindow _owner;
ImageSource _icon;
ICommand _close_command;
ICommand _rename_command;
ICommand _unrename_command;
public ShellWindowVM(IShellWindow model, IWindowManager wm, IShellThreadFactory threads, string host = null) {
Log.assert(model != null);
_model = model;
_model.IconChanged += on_icon_changed;
_window_manager = wm;
host_id = (model as ITabAffineWindow)?.tab_group ?? host ?? string.Empty;
// init Dispatcher
var stf = (ShellThreadFactory)threads;
if (IsShared) {
Dispatcher = stf.get_dispatcher((ISharedWindow)model);
} else if (IsSlow) {
Dispatcher = stf.create_dispatcher((ISlowWindow)model);
} else {
Dispatcher = Dispatcher.CurrentDispatcher;
}
_edit_mode = backing(nameof(EditMode), false);
_title_override = backing<string>(nameof(TitleOverride));
_title = backing(
nameof(Title),
_model.observe_title()
.CombineLatest(
_title_override.observe_current(true),
(title, title_override) => (title, title_override))
.Select(t => t.title_override ?? t.title)
.OnDispatcher());
_has_custom_title = backing(
nameof(HasCustomTitle),
_title_override.observe_current(true).Select(t => !t.is_null_or_white_space()).OnDispatcher());
model.on_shell_window_vm(this);
_dispose = Disposable.Create(() => {
_model.IconChanged -= on_icon_changed;
if (IsShared) {
// Shared threads continue running
Dispatcher.Invoke(() => Disposable.dispose(ref _model));
} else if (IsSlow) {
// Terminating the background thread will dispose the model on that thread
Log.Ui.info("Beginning asyncronous dispose of slow window {0} .".fmt(_model.GetType().ToString()));
Dispatcher.BeginInvokeShutdown(DispatcherPriority.Background);
} else {
Disposable.dispose(ref _model);
}
_ui = null;
Disposable.dispose(ref _hwnd_host);
});
void on_icon_changed() {
_icon = null;
RaisePropertyChanged(nameof(Icon));
}
}
public AsyncEvent<(object sender, ShellWindowVM e)> Closing { get; } = new();
public string host_id { get; set; }
public string OwnerName => _model.Owner?.name ?? string.Empty;
public IShellWindow Model => _model;
public Dispatcher Dispatcher { get; }
// title
public string Title => _title.Value;
public string TitleOverride {
get => _title_override.Value;
set {
_title_override.Value = value;
_edit_mode.set(false);
}
}
public bool HasCustomTitle => _has_custom_title.Value;
public ImageSource Icon =>
_icon ??= _model.Icon ?? _model.Owner?.icon;
public bool EditMode => _edit_mode.Value;
public double InitialWidth { get; private set; }
public double InitialHeight { get; private set; }
public UIElement AppContent =>
_ui ??= _create_contents();
public bool IsSlow => _model is ISlowWindow;
public bool IsShared => _model is ISharedWindow;
public bool HasTabAffinity => _model is ITabAffineWindow;
public ICommand rename_command =>
_rename_command ??= new DelegateCommand(_begin_rename);
public ICommand unrename_command =>
_unrename_command ??= new DelegateCommand(() => TitleOverride = string.Empty);
public ICommand CloseCommand =>
_close_command ??= new DelegateCommand(_begin_close);
internal FloatingWindow owner {
get => _owner;
set {
if (Set(ref _owner, value)) {
_try_set_wpf_bus();
}
}
}
public IObservable<string> observe_title() =>
_title.observe_current(true);
void _begin_rename() {
if (string.IsNullOrEmpty(TitleOverride)) {
_title_override.Value = Title;
}
_edit_mode.set(true);
}
async void _begin_close(object sender) {
if (_window_manager is null) {
return;
}
if (!_window_manager.contains(_model)) {
return;
}
await Closing?.InvokeAsync((sender, this));
// Removing the window will cause this object to be Disposed
// Which will terminate the background thread
// Which will dispose the model (on that thread)
await _window_manager.remove(_model);
}
UIElement _create_contents() {
string error_text = null;
Dispatcher.Invoke(() => {
// Executes possibly on background thread
try {
_document = _model.GetContent();
} catch (Exception ex) {
error_text = ex.Message;
Log.Ui.error($"Error getting window contents: {ex}");
}
}, DispatcherPriority.Send);
if (_document == null && string.IsNullOrEmpty(error_text)) {
error_text = $"No content for window {_model.GetType()}";
}
// If there's an error draw a big red X
if (!error_text.is_null_or_empty()) {
_document = new ShellWindowUI(new TextBlock() {
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Foreground = Brushes.Red,
Text = error_text
});
_model = new ErrorShellWindow {
Content = _document,
Icon = _model.Icon,
Title = $"Error: {_model.GetType()}"
};
RaisePropertyChanged("Title");
}
Log.assert(_document != null);
return _document.content.Match(on_wpf_element, on_handle_ref);
UIElement on_wpf_element(UIElement element) {
if (IsSlow || IsShared) {
// Create a WPFPanel (a Winforms window), then attach it with the HwndContentHost.
// This gives the document content a separate thread affinity from the shell window,
// and lets the win32 layer handle the thread boundary.
var retval = Dispatcher.Invoke(() => {
var interop_window = new WPFPanel(element);
return new HandleRef(interop_window, interop_window.Handle);
}, DispatcherPriority.Send);
_hwnd_host = new HwndContentHost(retval);
InitialHeight = _hwnd_host.InitialHeight;
InitialWidth = _hwnd_host.InitialWidth;
return _hwnd_host;
}
if (element is FrameworkElement fe) {
InitialHeight = fe.Height;
InitialWidth = fe.Width;
fe.Height = fe.Width = double.NaN;
}
return element;
}
UIElement on_handle_ref(HandleRef handle_ref) {
_hwnd_host = new HwndContentHost(handle_ref);
InitialHeight = _hwnd_host.Height;
InitialWidth = _hwnd_host.Width;
return _hwnd_host;
}
}
void _try_set_wpf_bus() {
if (_owner is null) {
return;
}
_document.content.try_match(on_wpf_element);
void on_wpf_element(UIElement element) =>
element.Dispatcher.InvokeAsync(() =>
ui.WindowStyle.SetUnsafeWindow(element, _owner));
}
}
}
namespace mbts.ui.shell {
using bts;
using Spark.Core;
using Spark.Core.Async;
using Spark.Core.Disposables;
using System;
using System.Reactive.Linq;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
public class ShellWindowVM : DisposableViewModelBase {
readonly IWindowManager _window_manager;
readonly Property<bool> _edit_mode;
readonly Property<string> _title;
readonly Property<string> _title_override;
readonly Property<bool> _has_custom_title;
IShellWindow _model;
UIElement _ui;
ShellWindowUI _document;
HwndContentHost _hwnd_host;
FloatingWindow _owner;
ImageSource _icon;
ICommand _close_command;
ICommand _rename_command;
ICommand _unrename_command;
public ShellWindowVM(IShellWindow model, IWindowManager wm, IShellThreadFactory threads, string host = null) {
Log.assert(model != null);
_model = model;
_model.IconChanged += on_icon_changed;
_window_manager = wm;
host_id = (model as ITabAffineWindow)?.tab_group ?? host ?? string.Empty;
// init Dispatcher
var stf = (ShellThreadFactory)threads;
if (IsShared) {
Dispatcher = stf.get_dispatcher((ISharedWindow)model);
} else if (IsSlow) {
Dispatcher = stf.create_dispatcher((ISlowWindow)model);
} else {
Dispatcher = Dispatcher.CurrentDispatcher;
}
_edit_mode = backing(nameof(EditMode), false);
_title_override = backing<string>(nameof(TitleOverride));
_title = backing(
nameof(Title),
_model.observe_title()
.CombineLatest(
_title_override.observe_current(true),
(title, title_override) => (title, title_override))
.Select(t => t.title_override ?? t.title)
.OnDispatcher());
_has_custom_title = backing(
nameof(HasCustomTitle),
_title_override.observe_current(true).Select(t => !t.is_null_or_white_space()).OnDispatcher());
model.on_shell_window_vm(this);
_dispose = Disposable.Create(() => {
_model.IconChanged -= on_icon_changed;
if (IsShared) {
// Shared threads continue running
Dispatcher.Invoke(() => Disposable.dispose(ref _model));
} else if (IsSlow) {
// Terminating the background thread will dispose the model on that thread
Log.Ui.info("Beginning asyncronous dispose of slow window {0} .".fmt(_model.GetType().ToString()));
Dispatcher.BeginInvokeShutdown(DispatcherPriority.Background);
} else {
Disposable.dispose(ref _model);
}
_ui = null;
Disposable.dispose(ref _hwnd_host);
});
void on_icon_changed() {
_icon = null;
RaisePropertyChanged(nameof(Icon));
}
}
public AsyncEvent<(object sender, ShellWindowVM e)> Closing { get; } = new();
public string host_id { get; set; }
public string OwnerName => _model.Owner?.name ?? string.Empty;
public IShellWindow Model => _model;
public Dispatcher Dispatcher { get; }
// title
public string Title => _title.Value;
public string TitleOverride {
get => _title_override.Value;
set {
_title_override.Value = value;
_edit_mode.set(false);
}
}
public bool HasCustomTitle => _has_custom_title.Value;
public ImageSource Icon =>
_icon ??= _model.Icon ?? _model.Owner?.icon;
public bool EditMode => _edit_mode.Value;
public double InitialWidth { get; private set; }
public double InitialHeight { get; private set; }
public UIElement AppContent =>
_ui ??= _create_contents();
public bool IsSlow => _model is ISlowWindow;
public bool IsShared => _model is ISharedWindow;
public bool HasTabAffinity => _model is ITabAffineWindow;
public ICommand rename_command =>
_rename_command ??= new DelegateCommand(_begin_rename);
public ICommand unrename_command =>
_unrename_command ??= new DelegateCommand(() => TitleOverride = string.Empty);
public ICommand CloseCommand =>
_close_command ??= new DelegateCommand(_begin_close);
internal FloatingWindow owner {
get => _owner;
set {
if (Set(ref _owner, value)) {
_try_set_wpf_bus();
}
}
}
public IObservable<string> observe_title() =>
_title.observe_current(true);
void _begin_rename() {
if (string.IsNullOrEmpty(TitleOverride)) {
_title_override.Value = Title;
}
_edit_mode.set(true);
}
async void _begin_close(object sender) {
if (_window_manager is null) {
return;
}
if (!_window_manager.contains(_model)) {
return;
}
await Closing?.InvokeAsync((sender, this));
// Removing the window will cause this object to be Disposed
// Which will terminate the background thread
// Which will dispose the model (on that thread)
await _window_manager.remove(_model);
}
UIElement _create_contents() {
string error_text = null;
Dispatcher.Invoke(() => {
// Executes possibly on background thread
try {
_document = _model.GetContent();
} catch (Exception ex) {
error_text = ex.Message;
Log.Ui.error($"Error getting window contents: {ex}");
}
}, DispatcherPriority.Send);
if (_document == null && string.IsNullOrEmpty(error_text)) {
error_text = $"No content for window {_model.GetType()}";
}
// If there's an error draw a big red X
if (!error_text.is_null_or_empty()) {
_document = new ShellWindowUI(new TextBlock() {
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Foreground = Brushes.Red,
Text = error_text
});
_model = new ErrorShellWindow {
Content = _document,
Icon = _model.Icon,
Title = $"Error: {_model.GetType()}"
};
RaisePropertyChanged("Title");
}
Log.assert(_document != null);
return _document.content.Match(on_wpf_element, on_handle_ref);
UIElement on_wpf_element(UIElement element) {
if (IsSlow || IsShared) {
// Create a WPFPanel (a Winforms window), then attach it with the HwndContentHost.
// This gives the document content a separate thread affinity from the shell window,
// and lets the win32 layer handle the thread boundary.
var retval = Dispatcher.Invoke(() => {
var interop_window = new WPFPanel(element);
element.SetCurrentValue(ui.WindowStyle.WinFormsHostProperty, interop_window);
return new HandleRef(interop_window, interop_window.Handle);
}, DispatcherPriority.Send);
_hwnd_host = new HwndContentHost(retval);
InitialHeight = _hwnd_host.InitialHeight;
InitialWidth = _hwnd_host.InitialWidth;
return _hwnd_host;
}
if (element is FrameworkElement fe) {
InitialHeight = fe.Height;
InitialWidth = fe.Width;
fe.Height = fe.Width = double.NaN;
}
return element;
}
UIElement on_handle_ref(HandleRef handle_ref) {
_hwnd_host = new HwndContentHost(handle_ref);
InitialHeight = _hwnd_host.Height;
InitialWidth = _hwnd_host.Width;
return _hwnd_host;
}
}
void _try_set_wpf_bus() {
if (_owner is null) {
return;
}
_document.content.try_match(on_wpf_element);
void on_wpf_element(UIElement element) =>
element.Dispatcher.InvokeAsync(() =>
ui.WindowStyle.SetUnsafeWindow(element, _owner));
}
}
}
@@ -47,16 +47,16 @@ namespace mbts.spark {
readonly SecurityImmutable _security;
readonly MBOSecurityQueryImmutable _query;
readonly Dictionary<(string, bool), FormattedText> _cache;
readonly Func<int, string> _fmt_mbo_qty;
readonly Func<double, double, string> _fmt_mbo_qty;
int _max_order_size = 1;
double _height = 0;
double _number_width = 0;
public MBOPopupModel(MBOService mbo, SecurityImmutable security, MBOSecurityQueryImmutable query, Func<int, string> fmt_mbo_qty) {
public MBOPopupModel(MBOService mbo, SecurityImmutable security, MBOSecurityQueryImmutable query, Func<double, double, string> fmt_mbo_qty) {
_mbo = mbo;
_security = security;
_query = query;
_fmt_mbo_qty = fmt_mbo_qty ?? new Func<int, string>(v => v.ToString());
_fmt_mbo_qty = fmt_mbo_qty ?? new Func<double, double, string>((v, p) => v.ToString());
// prime the cache with numbers on the left hand side
_cache =
@@ -112,7 +112,7 @@ namespace mbts.spark {
pdp.row_numbers = p.orders.Select((o, i) => format_text((i + 1).ToString(), ismine(o))).ToList();
pdp.numbers_width = _number_width = Math.Max(_number_width, (int)pdp.row_numbers.Max(r => r.Width) + text_margin);
pdp.row_labels = p.orders.Select(o => format_text(_fmt_mbo_qty(o.Size) + (ismine(o) ? "*" : ""), ismine(o))).ToList();
pdp.row_labels = p.orders.Select(o => format_text(_fmt_mbo_qty(o.Size, o.Price) + (ismine(o) ? "*" : ""), ismine(o))).ToList();
var max_label_width = pdp.row_labels.Max(l => l.Width) + text_margin;
var available_width = pdp.width - pdp.numbers_width - max_label_width;
+389 -335
View File
@@ -1,335 +1,389 @@
using bts.api;
using bts.api.domain;
using bts.utils;
using mbts.co2;
using mbts.spark;
using mbts.ui.shell;
using mbts.utils;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace mbts.ui.spark {
[ProtoContract]
class MarketSizeDisplay : IEquatable<MarketSizeDisplay> {
[ProtoMember(1)]
public readonly SizeFormattingElement[] normal;
[ProtoMember(2)]
public readonly SizeFormattingElement[] implied_present;
public MarketSizeDisplay() { }
public MarketSizeDisplay(SizeFormattingElement[] normal, SizeFormattingElement[] implied_present) {
this.normal = normal;
this.implied_present = implied_present;
}
public enum SizeFormattingElementType {
Merged,
Direct,
Implied,
Const
}
public static MarketSizeDisplay get_default() =>
new MarketSizeDisplay(SizeFormattingElement.merged, SizeFormattingElement.merged_asterisk);
public MarketSizeDisplay with(SizeFormattingElement[] normal = null, SizeFormattingElement[] implied_present = null) =>
new MarketSizeDisplay(normal ?? this.normal, implied_present ?? this.implied_present);
public bool Equals(MarketSizeDisplay other) =>
other != null &&
EqualityComparer.safe_sequence_equals(normal, other.normal) &&
EqualityComparer.safe_sequence_equals(implied_present, other.implied_present);
public override bool Equals(object obj) =>
Equals(obj as MarketSizeDisplay);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(implied_present.GetHashCode(), normal.GetHashCode());
[ProtoContract]
public struct SizeFormattingElement : IEquatable<SizeFormattingElement> {
[ProtoMember(1)]
public readonly SizeFormattingElementType type;
[ProtoMember(2)]
public readonly string data;
SizeFormattingElement(SizeFormattingElementType type, string data = null) {
this.type = type;
this.data = data;
}
public bool Equals(SizeFormattingElement other) => other.type == type && string.Equals(data, other.data);
public override bool Equals(object obj) => obj is SizeFormattingElement other && Equals(other);
public override int GetHashCode() => EqualityComparer.aggregate_hash(type.GetHashCode(), EqualityComparer.safe_get_hash_code(data));
public override string ToString() => type == SizeFormattingElementType.Const ? data ?? string.Empty : type.ToString();
public static bool operator ==(SizeFormattingElement a, SizeFormattingElement b) => a.Equals(b);
public static bool operator !=(SizeFormattingElement a, SizeFormattingElement b) => !a.Equals(b);
public static SizeFormattingElement[] direct => new[] { new SizeFormattingElement(SizeFormattingElementType.Direct) };
public static SizeFormattingElement[] implied => new[] { new SizeFormattingElement(SizeFormattingElementType.Implied) };
public static SizeFormattingElement[] direct_implied => new[] {
new SizeFormattingElement(SizeFormattingElementType.Direct),
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
new SizeFormattingElement(SizeFormattingElementType.Implied),
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
};
public static SizeFormattingElement[] merged => new[] { new SizeFormattingElement(SizeFormattingElementType.Merged) };
public static SizeFormattingElement[] merged_asterisk => new[] { new SizeFormattingElement(SizeFormattingElementType.Merged), new SizeFormattingElement(SizeFormattingElementType.Const, "*") };
public static SizeFormattingElement[] merged_implied => new[] {
new SizeFormattingElement(SizeFormattingElementType.Merged),
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
new SizeFormattingElement(SizeFormattingElementType.Implied),
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
};
public static SizeFormattingElement[] merged_direct => new[] {
new SizeFormattingElement(SizeFormattingElementType.Merged),
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
new SizeFormattingElement(SizeFormattingElementType.Direct),
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
};
public static SizeFormattingElement[] merged_direct_implied => new[] {
new SizeFormattingElement(SizeFormattingElementType.Merged),
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
new SizeFormattingElement(SizeFormattingElementType.Direct),
new SizeFormattingElement(SizeFormattingElementType.Const, ":"),
new SizeFormattingElement(SizeFormattingElementType.Implied),
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
};
}
}
static class MDDisplayHelper {
/// This delegate should be created by calling <see cref="get_formatter(MarketSizeDisplay)"/>
public delegate string SizeFormatter(double merged, double implied);
/// The private delegates below use refs bc the <see cref="SizeFormatter"/> returned by <see cref="get_formatter(MarketSizeDisplay)"/>
/// internally uses several <see cref="_SizeFormatter"/>s to add different text components to the resultant string.
/// The refs, therefor, avoid making extra copies of the variables.
/// These are private members, so any code-readability cost is confined to a limited scope.
delegate void _SizeFormatter(ref double merged, ref double implied, ref StringBuilder sb);
delegate void _SizeFormatterNPS(ref NativePriceSize nps, ref StringBuilder sb);
public static Func<double, string> get_value_formatter(double divisor) =>
value => (value / divisor).ToString();
public static Func<double, string> get_value_formatter(
TickDisplaySettingsImmutable display) =>
v => display.format(v);
public static Func<NativePriceSize, string> get_formatter_nps(
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display) {
var raw_fmt_size = get_formatter(size_display, display);
return nps => raw_fmt_size(
nps.implied_size + nps.direct_size,
nps.implied_size);
}
public static SizeFormatter get_formatter(MarketSizeDisplay size_display, TickDisplaySettingsImmutable display) {
_SizeFormatter implied_absent = _get_formatter(size_display.normal, display);
_SizeFormatter implied_present = _get_formatter(
size_display.implied_present ?? size_display.normal, display);
return (merged, implied) => {
StringBuilder sb = new StringBuilder();
(implied > 0 ? implied_present : implied_absent)(ref merged, ref implied, ref sb);
return sb.ToString();
};
}
static _SizeFormatterNPS _get_formatter_nps(
MarketSizeDisplay.SizeFormattingElement[] fmt_elements,
TickDisplaySettingsImmutable display) {
var formatters = fmt_elements.is_null_or_empty() ?
Array.Empty<_SizeFormatterNPS>() :
CollectionHelper.convert_array(fmt_elements, sfe => _get_formatter_nps(sfe, display));
return new _SizeFormatterNPS((ref NativePriceSize nps, ref StringBuilder sb) => {
foreach (var f in formatters)
f(ref nps, ref sb);
});
}
static _SizeFormatter _get_formatter(
MarketSizeDisplay.SizeFormattingElement[] fmt_elements,
TickDisplaySettingsImmutable display) {
_SizeFormatter[] formatters = fmt_elements.is_null_or_empty() ?
Array.Empty<_SizeFormatter>() :
CollectionHelper.convert_array(fmt_elements, sfe => _get_formatter(sfe, display));
return new _SizeFormatter((ref double m, ref double i, ref StringBuilder sb) => {
foreach (var f in formatters)
f(ref m, ref i, ref sb);
});
}
static _SizeFormatterNPS _get_formatter_nps(
MarketSizeDisplay.SizeFormattingElement fmt_element,
TickDisplaySettingsImmutable display) =>
fmt_element.type switch {
MarketSizeDisplay.SizeFormattingElementType.Const =>
SizeTokenFormatters.char_formatter_nps(fmt_element.data ?? string.Empty),
MarketSizeDisplay.SizeFormattingElementType.Direct =>
SizeTokenFormatters.direct_formatter_nps(display),
MarketSizeDisplay.SizeFormattingElementType.Implied =>
SizeTokenFormatters.implied_formatter_nps(display),
MarketSizeDisplay.SizeFormattingElementType.Merged =>
SizeTokenFormatters.merged_formatter_nps(display),
_ => throw ExceptionBuilder.Argument.unexpected_case(
fmt_element.type, nameof(fmt_element.type)),
};
static _SizeFormatter _get_formatter(
MarketSizeDisplay.SizeFormattingElement fmt_element,
TickDisplaySettingsImmutable display) =>
fmt_element.type switch {
MarketSizeDisplay.SizeFormattingElementType.Const =>
SizeTokenFormatters.char_formatter(fmt_element.data ?? string.Empty),
MarketSizeDisplay.SizeFormattingElementType.Direct =>
SizeTokenFormatters.direct_formatter(display),
MarketSizeDisplay.SizeFormattingElementType.Implied =>
SizeTokenFormatters.implied_formatter(display),
MarketSizeDisplay.SizeFormattingElementType.Merged =>
SizeTokenFormatters.merged_formatter(display),
_ => throw ExceptionBuilder.Argument.unexpected_case(
fmt_element.type, nameof(fmt_element.type)),
};
static class SizeTokenFormatters {
public static _SizeFormatter char_formatter(string data) {
string local = data.ToString();
return new _SizeFormatter((ref double m, ref double i, ref StringBuilder sb) => sb.Append(local));
}
public static _SizeFormatter merged_formatter(TickDisplaySettingsImmutable display) =>
(ref double m, ref double i, ref StringBuilder sb) => sb.Append(display.format(m));
public static _SizeFormatter direct_formatter(TickDisplaySettingsImmutable display) =>
(ref double m, ref double i, ref StringBuilder sb) => sb.Append(display.format(m - i));
public static _SizeFormatter implied_formatter(TickDisplaySettingsImmutable display) =>
(ref double m, ref double i, ref StringBuilder sb) => sb.Append(display.format(i));
public static _SizeFormatterNPS char_formatter_nps(string data) {
string local = data.ToString();
return new _SizeFormatterNPS((ref NativePriceSize _, ref StringBuilder sb) => sb.Append(local));
}
public static _SizeFormatterNPS merged_formatter_nps(TickDisplaySettingsImmutable display) =>
(ref NativePriceSize nps, ref StringBuilder sb) =>
sb.Append(display.format((double)nps.size));
public static _SizeFormatterNPS direct_formatter_nps(TickDisplaySettingsImmutable display) =>
(ref NativePriceSize nps, ref StringBuilder sb) =>
sb.Append(display.format((double)nps.direct_size));
public static _SizeFormatterNPS implied_formatter_nps(TickDisplaySettingsImmutable display) =>
(ref NativePriceSize nps, ref StringBuilder sb) =>
sb.Append(display.format((double)nps.implied_size));
}
}
class MarketSizeDisplayVM : ViewModelBase, IValueFactory<MarketSizeDisplay> {
readonly IEqualityComparer<MarketSizeDisplay.SizeFormattingElement[]> _comp = EqualityComparer.Create<MarketSizeDisplay.SizeFormattingElement[]>((a, b) => EqualityComparer.safe_sequence_equals(a, b));
readonly Property<FormattingVM> _selected_format, _selected_alternate_implied_format;
readonly DelegateProperty<bool> _can_choose_alternate_implied_format;
public MarketSizeDisplayVM() {
_selected_format = backing<FormattingVM>(nameof(selected_format));
_selected_alternate_implied_format = backing<FormattingVM>(nameof(selected_alternate_implied_format));
_can_choose_alternate_implied_format = backing(nameof(can_choose_alternate_implied_format), () =>
_comp.Equals(MarketSizeDisplay.SizeFormattingElement.merged, selected_format?.value));
sample_items = new[] {
new SampleItem(90, 0, this),
new SampleItem(75, 25, this)
};
_selected_format.Changed += refresh_sample_items;
_selected_alternate_implied_format.Changed += refresh_sample_items;
void refresh_sample_items(FormattingVM oldval, FormattingVM newval) {
_can_choose_alternate_implied_format.refresh();
foreach (var si in sample_items)
si.refresh();
}
}
public IEnumerable<FormattingVM> format_options { get; } = new[] {
MarketSizeDisplay.SizeFormattingElement.direct,
MarketSizeDisplay.SizeFormattingElement.implied,
MarketSizeDisplay.SizeFormattingElement.direct_implied,
MarketSizeDisplay.SizeFormattingElement.merged_implied,
MarketSizeDisplay.SizeFormattingElement.merged_direct_implied,
MarketSizeDisplay.SizeFormattingElement.merged
}.Select(ts => new FormattingVM(ts)).ToArray();
public IEnumerable<FormattingVM> alternate_implied_format_options { get; } = new[] {
MarketSizeDisplay.SizeFormattingElement.merged,
MarketSizeDisplay.SizeFormattingElement.merged_asterisk,
MarketSizeDisplay.SizeFormattingElement.merged_implied,
MarketSizeDisplay.SizeFormattingElement.merged_direct,
MarketSizeDisplay.SizeFormattingElement.merged_direct_implied
}.Select(ts => new FormattingVM(ts)).ToArray();
public FormattingVM selected_format {
get => _selected_format.Value;
set => _selected_format.Value = value;
}
public FormattingVM selected_alternate_implied_format {
get => _selected_alternate_implied_format.Value;
set => _selected_alternate_implied_format.Value = value;
}
public bool can_choose_alternate_implied_format => _can_choose_alternate_implied_format.Value;
public IEnumerable<SampleItem> sample_items { get; }
public MarketSizeDisplay create() {
var normal_fmt = selected_format?.value;
var alt_implied_fmt = can_choose_alternate_implied_format ? selected_alternate_implied_format?.value : normal_fmt;
return new MarketSizeDisplay(normal_fmt, alt_implied_fmt);
}
public void load_from(MarketSizeDisplay msd) {
selected_format = format_options.FirstOrDefault(f => _comp.Equals(f.value, msd.normal));
if (can_choose_alternate_implied_format)
selected_alternate_implied_format = alternate_implied_format_options.FirstOrDefault(f => _comp.Equals(f.value, msd.implied_present));
}
public class FormattingVM : ViewModelBase, IEquatable<FormattingVM> {
public FormattingVM(MarketSizeDisplay.SizeFormattingElement[] tokens) =>
value = tokens;
public MarketSizeDisplay.SizeFormattingElement[] value { get; }
public bool Equals(FormattingVM other) =>
other != null &&
EqualityComparer.safe_sequence_equals(value, other.value);
public override bool Equals(object obj) => Equals(obj as FormattingVM);
public override int GetHashCode() => EqualityComparer.safe_aggregate_hash(value);
public override string ToString() => string.Join(string.Empty, value);
}
public class SampleItem : ViewModelBase {
readonly DelegateProperty<string> _display;
public SampleItem(uint direct, uint implied, MarketSizeDisplayVM owner) {
this.direct = direct;
this.implied = implied;
_display = backing(nameof(display), fmt);
string fmt() {
var impl = MDDisplayHelper.get_formatter(
owner.create(), Security.Defaults.quantity_display_settingsi);
return impl(merged, implied);
}
}
public uint direct { get; }
public uint implied { get; }
public uint merged => direct + implied;
public string display => _display.Value;
public void refresh() => _display.refresh();
}
}
}
using bts.api;
using bts.api.domain;
using bts.utils;
using mbts.co2;
using mbts.spark;
using mbts.ui.shell;
using mbts.utils;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace mbts.ui.spark {
[ProtoContract]
class MarketSizeDisplay : IEquatable<MarketSizeDisplay> {
[ProtoMember(1)]
public readonly SizeFormattingElement[] normal;
[ProtoMember(2)]
public readonly SizeFormattingElement[] implied_present;
public MarketSizeDisplay() { }
public MarketSizeDisplay(SizeFormattingElement[] normal, SizeFormattingElement[] implied_present) {
this.normal = normal;
this.implied_present = implied_present;
}
public enum SizeFormattingElementType {
Merged,
Direct,
Implied,
Const,
MergedNotional,
}
public static MarketSizeDisplay get_default() =>
new MarketSizeDisplay(SizeFormattingElement.merged, SizeFormattingElement.merged_asterisk);
public static bool TryParse(string expression, out List<SizeFormattingElement> elements) {
elements = new List<SizeFormattingElement>();
if (expression is null) {
return false;
}
int tokenStart = 0;
for (int i = 0; i < expression.Length - 3; i++) {
if (expression[i] != '$' || expression[i + 1] != '{') {
continue;
}
if (i > tokenStart) {
elements.Add(SizeFormattingElement.Create(expression.Substring(tokenStart, i - tokenStart)));
}
i += 2;
tokenStart = i;
while (i < expression.Length && expression[i] != '}') {
i++;
}
if (i == expression.Length
|| !Enum.TryParse(expression.Substring(tokenStart, i - tokenStart), out SizeFormattingElementType typ)
|| typ == SizeFormattingElementType.Const) {
elements = null;
return false;
}
elements.Add(SizeFormattingElement.Create(typ));
tokenStart = i + 1;
}
if (tokenStart < expression.Length) {
elements.Add(SizeFormattingElement.Create(expression.Substring(tokenStart)));
}
return true;
}
public MarketSizeDisplay with(SizeFormattingElement[] normal = null, SizeFormattingElement[] implied_present = null) =>
new MarketSizeDisplay(normal ?? this.normal, implied_present ?? this.implied_present);
public bool Equals(MarketSizeDisplay other) =>
other != null &&
EqualityComparer.safe_sequence_equals(normal, other.normal) &&
EqualityComparer.safe_sequence_equals(implied_present, other.implied_present);
public override bool Equals(object obj) =>
Equals(obj as MarketSizeDisplay);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(implied_present.GetHashCode(), normal.GetHashCode());
[ProtoContract]
public struct SizeFormattingElement : IEquatable<SizeFormattingElement> {
[ProtoMember(1)]
public readonly SizeFormattingElementType type;
[ProtoMember(2)]
public readonly string data;
SizeFormattingElement(SizeFormattingElementType type, string data = null) {
this.type = type;
this.data = data;
}
internal static SizeFormattingElement Create(string str) =>
new(SizeFormattingElementType.Const, str);
internal static SizeFormattingElement Create(SizeFormattingElementType type) =>
new(type);
public bool Equals(SizeFormattingElement other) => other.type == type && string.Equals(data, other.data);
public override bool Equals(object obj) => obj is SizeFormattingElement other && Equals(other);
public override int GetHashCode() => EqualityComparer.aggregate_hash(type.GetHashCode(), EqualityComparer.safe_get_hash_code(data));
public override string ToString() =>
type == SizeFormattingElementType.Const
? data ?? string.Empty
: "${" + type.ToString() + "}";
public static bool operator ==(SizeFormattingElement a, SizeFormattingElement b) => a.Equals(b);
public static bool operator !=(SizeFormattingElement a, SizeFormattingElement b) => !a.Equals(b);
public static SizeFormattingElement[] direct => new[] { new SizeFormattingElement(SizeFormattingElementType.Direct) };
public static SizeFormattingElement[] implied => new[] { new SizeFormattingElement(SizeFormattingElementType.Implied) };
public static SizeFormattingElement[] direct_implied => new[] {
new SizeFormattingElement(SizeFormattingElementType.Direct),
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
new SizeFormattingElement(SizeFormattingElementType.Implied),
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
};
public static SizeFormattingElement[] merged => new[] { new SizeFormattingElement(SizeFormattingElementType.Merged) };
public static SizeFormattingElement[] merged_asterisk => new[] { new SizeFormattingElement(SizeFormattingElementType.Merged), new SizeFormattingElement(SizeFormattingElementType.Const, "*") };
public static SizeFormattingElement[] merged_implied => new[] {
new SizeFormattingElement(SizeFormattingElementType.Merged),
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
new SizeFormattingElement(SizeFormattingElementType.Implied),
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
};
public static SizeFormattingElement[] merged_direct => new[] {
new SizeFormattingElement(SizeFormattingElementType.Merged),
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
new SizeFormattingElement(SizeFormattingElementType.Direct),
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
};
public static SizeFormattingElement[] merged_direct_implied => new[] {
new SizeFormattingElement(SizeFormattingElementType.Merged),
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
new SizeFormattingElement(SizeFormattingElementType.Direct),
new SizeFormattingElement(SizeFormattingElementType.Const, ":"),
new SizeFormattingElement(SizeFormattingElementType.Implied),
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
};
public static SizeFormattingElement[] merged_notional => new SizeFormattingElement[] {
new(SizeFormattingElementType.MergedNotional)
};
}
}
static class MDDisplayHelper {
/// This delegate should be created by calling <see cref="get_formatter(MarketSizeDisplay)"/>
public delegate string SizeFormatter(double merged, double implied, double price);
/// The private delegates below use refs bc the <see cref="SizeFormatter"/> returned by <see cref="get_formatter(MarketSizeDisplay)"/>
/// internally uses several <see cref="_SizeFormatter"/>s to add different text components to the resultant string.
/// The refs, therefor, avoid making extra copies of the variables.
/// These are private members, so any code-readability cost is confined to a limited scope.
delegate void _SizeFormatter(ref double merged, ref double implied, ref double price, ref StringBuilder sb);
delegate void _SizeFormatterNPS(ref NativePriceSize nps, ref StringBuilder sb);
public static Func<double, double, string> get_value_formatter(
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display, TickDisplaySettingsImmutable price_display) {
var raw_fmt_size = get_formatter(size_display, display, price_display);
return (q, p) => raw_fmt_size(q, 0, p);
}
public static Func<NativePriceSize, string> get_formatter_nps(
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display, TickDisplaySettingsImmutable price_display) {
var raw_fmt_size = get_formatter(size_display, display, price_display);
return nps => raw_fmt_size(
nps.implied_size + nps.direct_size,
nps.implied_size,
nps.price);
}
public static SizeFormatter get_formatter(
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display, TickDisplaySettingsImmutable price_display) {
_SizeFormatter implied_absent = _get_formatter(size_display.normal, display, price_display);
_SizeFormatter implied_present = _get_formatter(
size_display.implied_present ?? size_display.normal, display, price_display);
return (merged, implied, price) => {
StringBuilder sb = new StringBuilder();
(implied > 0 ? implied_present : implied_absent)(ref merged, ref implied, ref price, ref sb);
return sb.ToString();
};
}
static _SizeFormatterNPS _get_formatter_nps(
MarketSizeDisplay.SizeFormattingElement[] fmt_elements,
TickDisplaySettingsImmutable display,
TickDisplaySettingsImmutable price_display) {
var formatters = fmt_elements.is_null_or_empty() ?
Array.Empty<_SizeFormatterNPS>() :
CollectionHelper.convert_array(fmt_elements, sfe => _get_formatter_nps(sfe, display, price_display));
return new _SizeFormatterNPS((ref NativePriceSize nps, ref StringBuilder sb) => {
foreach (var f in formatters)
f(ref nps, ref sb);
});
}
static _SizeFormatter _get_formatter(
MarketSizeDisplay.SizeFormattingElement[] fmt_elements,
TickDisplaySettingsImmutable display,
TickDisplaySettingsImmutable price_display) {
_SizeFormatter[] formatters = fmt_elements.is_null_or_empty() ?
Array.Empty<_SizeFormatter>() :
CollectionHelper.convert_array(fmt_elements, sfe => _get_formatter(sfe, display, price_display));
return new _SizeFormatter((ref double m, ref double i, ref double p, ref StringBuilder sb) => {
foreach (var f in formatters)
f(ref m, ref i, ref p, ref sb);
});
}
static _SizeFormatterNPS _get_formatter_nps(
MarketSizeDisplay.SizeFormattingElement fmt_element,
TickDisplaySettingsImmutable display,
TickDisplaySettingsImmutable price_display) =>
fmt_element.type switch {
MarketSizeDisplay.SizeFormattingElementType.Const =>
SizeTokenFormatters.char_formatter_nps(fmt_element.data ?? string.Empty),
MarketSizeDisplay.SizeFormattingElementType.Direct =>
SizeTokenFormatters.direct_formatter_nps(display),
MarketSizeDisplay.SizeFormattingElementType.Implied =>
SizeTokenFormatters.implied_formatter_nps(display),
MarketSizeDisplay.SizeFormattingElementType.Merged =>
SizeTokenFormatters.merged_formatter_nps(display),
MarketSizeDisplay.SizeFormattingElementType.MergedNotional =>
SizeTokenFormatters.merged_notional_formatter_nps(price_display),
_ => throw ExceptionBuilder.Argument.unexpected_case(
fmt_element.type, nameof(fmt_element.type)),
};
static _SizeFormatter _get_formatter(
MarketSizeDisplay.SizeFormattingElement fmt_element,
TickDisplaySettingsImmutable display,
TickDisplaySettingsImmutable price_display) =>
fmt_element.type switch {
MarketSizeDisplay.SizeFormattingElementType.Const =>
SizeTokenFormatters.char_formatter(fmt_element.data ?? string.Empty),
MarketSizeDisplay.SizeFormattingElementType.Direct =>
SizeTokenFormatters.direct_formatter(display),
MarketSizeDisplay.SizeFormattingElementType.Implied =>
SizeTokenFormatters.implied_formatter(display),
MarketSizeDisplay.SizeFormattingElementType.Merged =>
SizeTokenFormatters.merged_formatter(display),
MarketSizeDisplay.SizeFormattingElementType.MergedNotional =>
SizeTokenFormatters.merged_notional_formatter(price_display),
_ => throw ExceptionBuilder.Argument.unexpected_case(
fmt_element.type, nameof(fmt_element.type)),
};
static class SizeTokenFormatters {
public static _SizeFormatter char_formatter(string data) {
string local = data.ToString();
return (ref double m, ref double i, ref double p, ref StringBuilder sb) =>
sb.Append(local);
}
public static _SizeFormatter merged_formatter(TickDisplaySettingsImmutable display) =>
(ref double m, ref double i, ref double p, ref StringBuilder sb) =>
sb.Append(display.format(m));
public static _SizeFormatter direct_formatter(TickDisplaySettingsImmutable display) =>
(ref double m, ref double i, ref double p, ref StringBuilder sb) =>
sb.Append(display.format(m - i));
public static _SizeFormatter implied_formatter(TickDisplaySettingsImmutable display) =>
(ref double m, ref double i, ref double p, ref StringBuilder sb) =>
sb.Append(display.format(i));
public static _SizeFormatter merged_notional_formatter(TickDisplaySettingsImmutable display) =>
(ref double m, ref double i, ref double p, ref StringBuilder sb) =>
sb.Append(display.format(m * p));
public static _SizeFormatterNPS char_formatter_nps(string data) {
string local = data.ToString();
return new _SizeFormatterNPS((ref NativePriceSize _, ref StringBuilder sb) => sb.Append(local));
}
public static _SizeFormatterNPS merged_formatter_nps(TickDisplaySettingsImmutable display) =>
(ref NativePriceSize nps, ref StringBuilder sb) =>
sb.Append(display.format((double)nps.size));
public static _SizeFormatterNPS direct_formatter_nps(TickDisplaySettingsImmutable display) =>
(ref NativePriceSize nps, ref StringBuilder sb) =>
sb.Append(display.format((double)nps.direct_size));
public static _SizeFormatterNPS implied_formatter_nps(TickDisplaySettingsImmutable display) =>
(ref NativePriceSize nps, ref StringBuilder sb) =>
sb.Append(display.format((double)nps.implied_size));
public static _SizeFormatterNPS merged_notional_formatter_nps(TickDisplaySettingsImmutable display) =>
(ref NativePriceSize nps, ref StringBuilder sb) =>
sb.Append(display.format(nps.notional_size));
}
}
class MarketSizeDisplayVM : ViewModelBase, IValueFactory<MarketSizeDisplay> {
readonly IEqualityComparer<MarketSizeDisplay.SizeFormattingElement[]> _comp = EqualityComparer.Create<MarketSizeDisplay.SizeFormattingElement[]>((a, b) => EqualityComparer.safe_sequence_equals(a, b));
readonly Property<string> _selected_format, _selected_alternate_implied_format;
public MarketSizeDisplayVM() {
_selected_format = backing<string>(nameof(selected_format));
_selected_alternate_implied_format = backing<string>(nameof(selected_alternate_implied_format));
sample_items = new[] {
new SampleItem(90, 0, this),
new SampleItem(75, 25, this)
};
_selected_format.Changed += refresh_sample_items;
_selected_alternate_implied_format.Changed += refresh_sample_items;
void refresh_sample_items(string oldval, string newval) {
foreach (var si in sample_items)
si.refresh();
}
}
public IEnumerable<string> format_options { get; } = new[] {
string.Concat(MarketSizeDisplay.SizeFormattingElement.direct),
string.Concat(MarketSizeDisplay.SizeFormattingElement.implied),
string.Concat(MarketSizeDisplay.SizeFormattingElement.direct_implied),
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_implied),
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_direct_implied),
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged),
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_notional),
};
public IEnumerable<string> alternate_implied_format_options { get; } = new[] {
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged),
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_asterisk),
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_implied),
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_direct),
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_direct_implied),
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_notional),
};
public string selected_format {
get => _selected_format.Value;
set => _selected_format.Value = value;
}
public string selected_alternate_implied_format {
get => _selected_alternate_implied_format.Value;
set => _selected_alternate_implied_format.Value = value;
}
public IEnumerable<SampleItem> sample_items { get; }
public MarketSizeDisplay create() {
var normal_fmt = selected_format;
var alt_implied_fmt = selected_alternate_implied_format;
if (!MarketSizeDisplay.TryParse(normal_fmt, out var normal_els)) {
normal_els = new(MarketSizeDisplay.SizeFormattingElement.merged);
}
if (!MarketSizeDisplay.TryParse(alt_implied_fmt, out var alt_implied_els)) {
alt_implied_els = new(normal_els);
}
return new MarketSizeDisplay(normal_els.ToArray(), alt_implied_els.ToArray());
}
public void load_from(MarketSizeDisplay msd) {
selected_format = string.Concat(msd.normal.empty_if_null());
selected_alternate_implied_format = string.Concat(msd.implied_present.empty_if_null());
}
public class SampleItem : ViewModelBase {
readonly DelegateProperty<string> _display;
public SampleItem(uint direct, uint implied, MarketSizeDisplayVM owner) {
this.direct = direct;
this.implied = implied;
_display = backing(nameof(display), fmt);
string fmt() {
var impl = MDDisplayHelper.get_formatter(
owner.create(), Security.Defaults.quantity_display_settingsi, Security.Defaults.price_display_settingsi);
return impl(merged, implied, 1);
}
}
public uint direct { get; }
public uint implied { get; }
public uint merged => direct + implied;
public string display => _display.Value;
public void refresh() => _display.refresh();
}
}
}
@@ -41,6 +41,7 @@ namespace mbts.co2 {
public Size size => _size;
public Size direct_size => _direct_size;
public Size implied_size => _size - _direct_size;
public double notional_size => price * size;
public double price => _price.price();
public bool is_nan => _price.is_nan;
@@ -10,58 +10,64 @@ using System.IO;
using System.Reactive.Disposables;
using mbts;
using System.Threading;
using System.Runtime.InteropServices;
namespace bts.api.core {
internal class QuickbusClientChannel : ClientChannel {
IntPtr _client;
internal class QuickbusClientChannel : ClientChannel {
IntPtr _client;
public QuickbusClientChannel(ServiceContext context, string type_name, string qualifier) : base(context) {
_client = ApiNativeMethods.api_client_create(context.native_context, type_name, qualifier);
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero);
}
public QuickbusClientChannel(ServiceContext context, string type_name, string qualifier) : base(context) {
Log.QBus.diag($"Created QuickbusClientChannel");
_client = ApiNativeMethods.api_client_create(context.native_context, type_name, qualifier);
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero);
}
public override void Dispose() {
ApiNativeMethods.api_client_destroy(_client);
_client = IntPtr.Zero;
}
public override void Dispose() {
Log.QBus.diag($"Disposed QuickbusClientChannel");
ApiNativeMethods.api_client_destroy(_client);
_client = IntPtr.Zero;
}
int _req_id = 0;
Dictionary<int, PBCallbackWrapper> _keepalive_cache = new();
unsafe public override Task<Result<Response>> send<Request, Response>(int method_id, Request request) {
var id = Interlocked.Increment(ref _req_id);
var header = new RequestHeader() {
Method = method_id,
RequestId = id,
Token = _context.token.HasValue ? new AuthToken() { Token = _context.token?.Token } : null
};
var buffer = header.serialize(request);
var tcs = new TaskCompletionSource<Result<Response>>();
//ApiNativeMethods.api_client_log_error($"Call {id}");
ApiNativeMethods.raw_pb_callback callback = (rbuffer, rlength, _) => {
//ApiNativeMethods.api_client_log_error($"Reply {id}");
var res = ApiUtils.deserialize<ResponseHeader, Response>(rbuffer, (long)rlength);
if (res.header.Code == ApiError.Success) {
tcs.TrySetResult(new Result<Response>(res.body));
}
else {
tcs.TrySetResult(new Result<Response>(res.header.Code));
}
var id = Interlocked.Increment(ref _req_id);
var header = new RequestHeader() {
Method = method_id,
RequestId = id,
Token = _context.token.HasValue ? new AuthToken() { Token = _context.token?.Token } : null
};
var buffer = header.serialize(request);
var tcs = new TaskCompletionSource<Result<Response>>();
ApiNativeMethods.raw_pb_callback callback = (rbuffer, rlength, _) => {
var res = ApiUtils.deserialize<ResponseHeader, Response>(rbuffer, (long)rlength);
if (res.header.Code == ApiError.Success) {
tcs.TrySetResult(new Result<Response>(res.body));
}
else {
tcs.TrySetResult(new Result<Response>(res.header.Code));
}
lock (_keepalive_cache) {
_keepalive_cache.Remove(id);
}
};
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero, "Channel may have been disposed");
var cb_wrapper = new PBCallbackWrapper(callback);
lock (_keepalive_cache) {
_keepalive_cache.Remove(id);
_keepalive_cache.Add(id, cb_wrapper);
}
};
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero, "Channel may have been disposed");
var cb_wrapper = new PBCallbackWrapper(callback);
lock (_keepalive_cache) {
_keepalive_cache.Add(id, cb_wrapper);
}
ApiNativeMethods.api_client_send(
_client, buffer, buffer.Length, cb_wrapper.callback, IntPtr.Zero);
return tcs.Task;
}
ApiNativeMethods.api_client_send(
_client, buffer, buffer.Length, cb_wrapper.Callback, IntPtr.Zero);
// Return a task that will free the handle once the task is completed
return tcs.Task.ContinueWith(task => {
cb_wrapper.Free(); // Ensure the GCHandle is freed when the callback is done
return task.Result;
});
}
public override IObservable<RawUpdate<TOut>> subscribe<TParse,TOut>(int method_id, string method, string qualifier, MessageParser<TParse> parser, Func<TParse,TOut> converter) {
var result = Observable.Create<RawUpdate<TOut>>(async observer => {
@@ -206,11 +212,29 @@ namespace bts.api.core {
/// garbage-collector race may yield the infamous error, "A callback was made on a garbage
/// collected delegate of type...").
/// </remarks>
class PBCallbackWrapper {
public readonly ApiNativeMethods.raw_pb_callback callback;
class PBCallbackWrapper
{
public readonly ApiNativeMethods.raw_pb_callback Callback;
private GCHandle _handle;
public PBCallbackWrapper(ApiNativeMethods.raw_pb_callback callback) =>
this.callback = callback;
}
public PBCallbackWrapper(ApiNativeMethods.raw_pb_callback callback)
{
// Pin the callback to prevent garbage collection
Log.QBus.diag($"Created PBCallbackWrapper");
_handle = GCHandle.Alloc(callback);
Callback = callback;
}
public void Free()
{
// Free the GCHandle when the delegate is no longer needed
Log.QBus.diag($"Deleting PBCallbackWrapper");
if (_handle.IsAllocated)
{
_handle.Free();
}
}
}
}
}
@@ -16,6 +16,9 @@ namespace mbts.user.settings {
public RegionalTickFactors()
: this(new TickFactors(), new TickFactors(), double.MinValue) { }
public RegionalTickFactors(TickFactors tick)
: this(tick, tick, double.PositiveInfinity) { }
public RegionalTickFactors(TickFactors low_tick, TickFactors high_tick, double low_tick_inclusive_end) {
_low_tick = low_tick;
_high_tick = high_tick;
@@ -78,14 +78,16 @@ namespace mbts.user.settings {
bool? show_commas = null,
string? nan_text = null,
bool? show_decimal_part = null,
bool? show_zero_integer = null) =>
bool? show_zero_integer = null,
TickFactors? tick_factors = null) =>
new() {
pad_with_leading_zeros = pad_with_leading_zeros ?? settings.pad_with_leading_zeros,
display_multiplier = display_multiplier ?? settings.display_multiplier,
show_commas = show_commas ?? settings.show_commas,
nan_text = nan_text ?? settings.nan_text,
show_decimal_part = show_decimal_part ?? settings.show_decimal_part,
show_zero_integer = show_zero_integer ?? settings.show_zero_integer
show_zero_integer = show_zero_integer ?? settings.show_zero_integer,
tick_factors = tick_factors ?? settings.tick_factors,
};
public static double to_display_value(this TickDisplaySettings settings, double value) =>
@@ -7,6 +7,7 @@ namespace mbts {
using mbts.user.settings;
using Spark.Core;
using Spark.Core.Math;
using System;
using System.Collections.Generic;
@@ -63,7 +64,7 @@ namespace mbts {
if (ticks_per_base.is_int()) {
return true;
}
return tick.tick_unit > tick.tick_base && tick.tick_base == 1;
return tick.tick_unit > tick.tick_base && tick.tick_unit_frac().is_int();
}
public static bool is_valid(this RegionalTickFactors regional_tick) =>
@@ -95,7 +96,7 @@ namespace mbts {
public static double reduced_base(this TickFactors tick) => MathUtils.factor_out(tick.tick_base, 10);
public static bool tick_equals(this RegionalTickFactors regional_tick, double x, double y) {
public static bool tick_equals(this RegionalTickFactors regional_tick, double x, double y, RoundingMode rounding_mode = RoundingMode.Closest) {
if (x.is_nan()) {
return y.is_nan();
}
@@ -107,15 +108,15 @@ namespace mbts {
return xtick is not null
&& ytick is not null
&& xtick.Equals(ytick)
&& xtick.tick_equals(x, y);
&& xtick.tick_equals(x, y, rounding_mode);
}
public static bool tick_equals(this TickFactors tick, double x, double y) {
public static bool tick_equals(this TickFactors tick, double x, double y, RoundingMode rounding_mode = RoundingMode.Closest) {
if (x.is_nan()) {
return y.is_nan();
}
var x_units = MathUtils.round_fractional_units(x, tick.tick_unit_frac());
var y_units = MathUtils.round_fractional_units(y, tick.tick_unit_frac());
var x_units = MathUtils.round_fractional_units(x, tick.tick_unit_frac(), rounding_mode, TickUtils.precision_const<double>());
var y_units = MathUtils.round_fractional_units(y, tick.tick_unit_frac(), rounding_mode, TickUtils.precision_const<double>());
return x_units == y_units;
}
@@ -137,10 +138,10 @@ namespace mbts {
x => NumericCastConverter<T, int>.convert(
MathUtils.round_fractional_units(x, tick.tick_unit_frac<T>())));
public static IEqualityComparer<double> tick_equality_comparer(this TickFactors tick) =>
public static IEqualityComparer<double> tick_equality_comparer(this TickFactors tick, RoundingMode rounding_mode = RoundingMode.Closest) =>
EqualityComparer.Create<double>(
(a, b) => tick_equals(tick, a, b),
x => (int)MathUtils.round_fractional_units(x, tick.tick_unit_frac()));
(a, b) => tick_equals(tick, a, b, rounding_mode),
x => (int)MathUtils.round_fractional_units(x, tick.tick_unit_frac(), rounding_mode));
public static TickFactors get_tick_for(this RegionalTickFactors rtick, double value) =>
value > rtick.low_tick_inclusive_end ? rtick.high_tick : rtick.low_tick;
@@ -179,10 +180,7 @@ namespace mbts {
if (T.IsInfinity(value)) {
return value.ToString();
}
return tick?.is_valid() ?? false ?
_format_tick(tick, value, settings) :
_format_no_tick(
value, settings.display_multiplier.as_type<double, T>(), settings.show_commas);
return _format_tick(tick, value, settings);
}
public static string format<T>(
+60 -93
View File
@@ -6,6 +6,7 @@ using bts.utils;
using mbts.user.settings;
using Spark.Core;
using Spark.Core.Math;
using System;
using System.Collections.Generic;
@@ -189,52 +190,58 @@ namespace mbts.spark {
public static int compare_tick_fallback_to_double(this ContractSpecificationImmutable contract, double x, double y) =>
tick_equals(contract, x, y) ? 0 : x.CompareTo(y);
public static double round_to_tick(this ContractSpecificationImmutable spec, double value) =>
round_to_tick(spec.ticksize(value), value);
public static double round_to_tick(this ContractSpecificationImmutable spec, double value, RoundingMode rounding_mode = RoundingMode.Closest) =>
round_to_tick(spec.ticksize(value), value, rounding_mode);
public static decimal round_to_tick(this ContractSpecificationImmutable spec, decimal value) =>
round_to_tick((decimal)spec.ticksize((double)value), value);
public static decimal round_to_tick(this ContractSpecificationImmutable spec, decimal value, RoundingMode rounding_mode = RoundingMode.Closest) =>
round_to_tick((decimal)spec.ticksize((double)value), value, rounding_mode);
public static double round_to_tick(this RegionalTickFactors rtick, double value) =>
round_to_tick(rtick.ticksize(value), value);
public static double round_to_tick(this RegionalTickFactors rtick, double value, RoundingMode rounding_mode = RoundingMode.Closest) =>
round_to_tick(rtick.ticksize(value), value, rounding_mode);
public static decimal round_to_tick(this RegionalTickFactors rtick, decimal value) =>
round_to_tick((decimal)rtick.ticksize((double)value), value);
public static decimal round_to_tick(this RegionalTickFactors rtick, decimal value, RoundingMode rounding_mode = RoundingMode.Closest) =>
round_to_tick((decimal)rtick.ticksize((double)value), value, rounding_mode);
public static double round_to_tick(double tick_size, double value) {
double intpart = (long)value;
double floatpart = value % 1;
floatpart = ((int)Math.Round(floatpart / tick_size)) * tick_size;
return intpart + floatpart;
public static double round_to_tick(double tick_size, double value, RoundingMode rounding_mode = RoundingMode.Closest) {
double n = rounding_mode switch {
RoundingMode.Closest => Math.Round(value / tick_size),
RoundingMode.Up => Math.Ceiling((value - _precision_const) / tick_size),
RoundingMode.Down => Math.Floor((value + _precision_const) / tick_size),
RoundingMode.AbsUp => value < 0
? Math.Floor((value + _precision_const) / tick_size)
: Math.Ceiling((value - _precision_const) / tick_size),
RoundingMode.AbsDown => value < 0
? Math.Ceiling((value - _precision_const) / tick_size)
: Math.Floor((value + _precision_const) / tick_size),
_ => double.NaN,
};
return n * tick_size;
}
public static decimal round_to_tick(decimal tick_size, decimal value) {
decimal intpart = (long)value;
decimal floatpart = value % 1;
floatpart = ((int)(floatpart / tick_size)) * tick_size;
return intpart + floatpart;
public static decimal round_to_tick(decimal tick_size, decimal value, RoundingMode rounding_mode = RoundingMode.Closest) {
decimal n = rounding_mode switch {
RoundingMode.Closest => Math.Round(value / tick_size),
RoundingMode.Up => Math.Ceiling(value / tick_size),
RoundingMode.Down => Math.Floor(value / tick_size),
RoundingMode.AbsUp => value < 0
? Math.Floor(value / tick_size)
: Math.Ceiling(value / tick_size),
RoundingMode.AbsDown => value < 0
? Math.Ceiling(value / tick_size)
: Math.Floor(value / tick_size),
_ => throw new ArgumentException("Invalid rounding mode", nameof(rounding_mode)),
};
return n * tick_size;
}
public static long? count_ticks(this RegionalTickFactors rtick, double value) {
if (!rtick.is_valid()) {
return null;
}
public static long? count_ticks(this RegionalTickFactors rtick, double value, RoundingMode rounding_mode = RoundingMode.AbsDown) {
var rounded_value = rtick.round_to_tick(value, rounding_mode);
var n = _count_ticks(
rtick.low_tick.tick_unit_frac(),
rtick.low_tick_inclusive_end,
rtick.high_tick.tick_unit_frac(),
0,
value);
var orig_n = n;
// Enforce proper rounding
var rounded_val = rtick.round_to_tick(value);
var half_tick = rtick.get_tick_for(value).tick_unit_frac() / 2;
if (n < 0 && (value - rounded_val) > half_tick) {
++n;
} else if (n > 0 && (rounded_val - value) > half_tick) {
--n;
}
Log.assert(n == orig_n);
rounded_value);
return n;
}
@@ -253,71 +260,19 @@ namespace mbts.spark {
return tick;
}
public static RegionalTickFactors multiply_tick_size(RegionalTickFactors spec, uint factor) {
factor = _fit_ticks_per_row_to_dollars(spec, factor) ?? 1;
return new(
public static RegionalTickFactors multiply_tick_size(RegionalTickFactors spec, uint factor) =>
new(
new(spec.low_tick.tick_base, spec.low_tick.tick_unit * factor),
new(spec.high_tick.tick_base, spec.high_tick.tick_unit * factor),
spec.low_tick_inclusive_end);
}
public static double normalize_price(RegionalTickFactors rtick, double price) {
if (!price.is_real()) {
var maybe_ticks = rtick.count_ticks(price);
if (!maybe_ticks.HasValue) {
return price;
}
var ticks = rtick.count_ticks(price);
if (!ticks.HasValue) {
return price;
}
var normalized = rtick.tick(0, ticks.Value);
var tick = price > rtick.low_tick_inclusive_end ? rtick.high_tick : rtick.low_tick;
// Diff should always be < 1/2 tick
Log.assert((normalized - price).abs() < tick.tick_unit_frac() * 0.5);
return normalized;
}
public static uint fit_ticks_per_row_to_dollars(SecurityImmutable sec, uint ticks_per_row) =>
_fit_ticks_per_row_to_dollars(sec, ticks_per_row) ?? 1;
public static uint fit_ticks_per_row_to_dollars(RegionalTickFactors rtick, uint ticks_per_row) =>
_fit_ticks_per_row_to_dollars(rtick, ticks_per_row) ?? 1;
static uint? _fit_ticks_per_row_to_dollars(SecurityImmutable sec, uint ticks_per_row) =>
_fit_ticks_per_row_to_dollars(sec.regional_ticks(), ticks_per_row);
static uint? _fit_ticks_per_row_to_dollars(RegionalTickFactors tf, uint ticks_per_row) {
if (ticks_per_row == 0)
return null;
// it is assumed that low tick will be the same as high tick;
// however, best to double check - return null if this isn't the case (let the caller decide what to do).
if (!tf.low_tick.Equals(tf.high_tick) || !tf.is_valid() || tf.low_tick.is_integer())
return null;
var ticks_per_dollar = (uint)(tf.low_tick.tick_base / tf.low_tick.tick_unit);
// if ticks_per_row is greater than ticks_per_dollar, round to a multiple of ticks_per_dollar
if (ticks_per_row > ticks_per_dollar) {
if (ticks_per_dollar == 0) {
return null;
}
return (uint)(ticks_per_dollar * Math.Floor((double)(ticks_per_row / ticks_per_dollar)));
} else if (ticks_per_row == ticks_per_dollar) {
return ticks_per_row;
} else {
uint max_decrease_adj = ticks_per_row;
uint max_increase_adj = ticks_per_dollar - ticks_per_row - 1;
for (uint adj = 0; adj < Math.Max(max_decrease_adj, max_increase_adj); adj++) {
if (adj < max_decrease_adj) {
var result = ticks_per_row - adj;
if (ticks_per_dollar % result == 0)
return result;
}
if (adj < max_increase_adj) {
var result = ticks_per_row + adj;
if (ticks_per_dollar % result == 0)
return result;
}
}
}
return null;
var ticks = maybe_ticks.Value;
return rtick.tick(0, (long)ticks);
}
static long? _count_ticks(
@@ -367,8 +322,20 @@ namespace mbts.spark {
static long count_single_tick_sz(
double tick_sz, double from, double to) {
var from_n = (long)MathUtils.round_fractional_units(from, tick_sz);
var to_n = (long)MathUtils.round_fractional_units(to, tick_sz);
from = round_to_tick(tick_sz, from, RoundingMode.AbsDown);
to = round_to_tick(tick_sz, to, RoundingMode.AbsDown);
if (from < 0) {
from -= _precision_const;
} else {
from += _precision_const;
}
if (to < 0) {
to -= _precision_const;
} else {
to += _precision_const;
}
var from_n = (long)(from / tick_sz);
var to_n = (long)(to / tick_sz);
return to_n - from_n;
}
}
@@ -39,7 +39,6 @@
static IEnumerable<ContractLegImmutable> _get_root_legs(
ContractLegImmutable leg, double root_quantity) {
_validate_root_quantity(root_quantity);
if (leg.HasSecurity) {
return _get_root_legs(leg.Security, leg.signed_quantity() * root_quantity);
}
@@ -54,10 +53,8 @@
return new[] { new ContractLegImmutable(mleg) };
}
static IEnumerable<ContractLegImmutable> _get_root_legs(
SecurityImmutable security, double root_quantity) {
_validate_root_quantity(root_quantity);
return security.ContractCase switch {
static IEnumerable<ContractLegImmutable> _get_root_legs(SecurityImmutable security, double root_quantity) =>
security.ContractCase switch {
Security.ContractOneofCase.Spread => security.Spread.Legs.SelectMany(l => _get_root_legs(l.Security, root_quantity * l.signed_quantity())).ToArray(),
Security.ContractOneofCase.Synthetic => security.Synthetic.Legs.SelectMany(l => _get_root_legs(l.Security, root_quantity * l.signed_quantity())).ToArray(),
_ => new[] {
@@ -69,13 +66,5 @@
})
},
};
}
static void _validate_root_quantity(double root_quantity) {
if (!root_quantity.is_real_non_zero()) {
throw new ArgumentException(
$"Expected real non-zero value (actually {root_quantity})", nameof(root_quantity));
}
}
}
}
@@ -1,5 +1,6 @@
namespace bts.api {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
@@ -36,6 +37,15 @@ namespace bts.api {
};
}
public static IEnumerable<DateTime> normal_expirations(this SecurityImmutable security) =>
security.ContractCase switch {
Security.ContractOneofCase.Future => new[] { security.Future.expiration_date() },
Security.ContractOneofCase.Option => new[] { security.Option.expiration_date() },
Security.ContractOneofCase.Spread => security.Spread.Legs.SelectMany(l => l.Security.normal_expirations()),
Security.ContractOneofCase.Synthetic => security.Synthetic.Legs.SelectMany(l => l.Security.normal_expirations()),
_ => Enumerable.Empty<DateTime>()
};
public static ProductImmutable product(this SecurityImmutable security) =>
new(new() {
Exchange = security.Exchange,
@@ -59,13 +69,7 @@ namespace bts.api {
new((int)future.ExpirationYear, (int)future.ExpirationMonth, (int)future.ExpirationDay);
internal static DateTime earliest_expiration(this SecurityImmutable security) =>
security.ContractCase switch {
Security.ContractOneofCase.Synthetic => security.Synthetic.Legs.First().Security.earliest_expiration(),
Security.ContractOneofCase.Spread => security.Spread.Legs.First().Security.earliest_expiration(),
Security.ContractOneofCase.Future => security.Future.expiration_date(),
Security.ContractOneofCase.Option => security.Option.expiration_date(),
_ => throw new NotImplementedException(),
};
security.normal_expirations().Min();
public static double strike(this SecurityImmutable security) =>
security.HasOption ? security.Option.Strike : double.NaN;
@@ -105,6 +109,7 @@ namespace bts.api {
Security.ExchangeDataOneofCase.Deribit => security.Deribit.SecurityDesc,
Security.ExchangeDataOneofCase.Ftx => security.Ftx.SecurityDesc,
Security.ExchangeDataOneofCase.Binance => security.Binance.SecurityDesc,
Security.ExchangeDataOneofCase.BinanceCoinFutures => security.BinanceCoinFutures.SecurityDesc,
Security.ExchangeDataOneofCase.BinanceUs => security.BinanceUs.SecurityDesc,
Security.ExchangeDataOneofCase.BinanceUsdsFutures => security.BinanceUsdsFutures.SecurityDesc,
Security.ExchangeDataOneofCase.Coinbase => security.Coinbase.SecurityDesc,
@@ -151,12 +156,13 @@ namespace bts.api {
domain.Venue.Deribit => Venue.Deribit,
domain.Venue.Ftx => Venue.Ftx,
domain.Venue.Binance => Venue.Binance,
domain.Venue.BinanceCoinFutures => Venue.BinanceCoinFutures,
domain.Venue.BinanceUs => Venue.BinanceUs,
domain.Venue.BinanceUsdsFutures => Venue.BinanceUsdsFutures,
domain.Venue.Glbx => Venue.Glbx,
domain.Venue.Coinbase => Venue.Coinbase,
domain.Venue.Okx => Venue.Okx,
domain.Venue.Cboe => Venue.Cboe,
domain.Venue.Cboe => Venue.Cboe,
domain.Venue.Taifex => Venue.Taifex,
@@ -171,6 +177,7 @@ namespace bts.api {
Venue.Deribit => domain.Venue.Deribit,
Venue.Ftx => domain.Venue.Ftx,
Venue.Binance => domain.Venue.Binance,
Venue.BinanceCoinFutures => domain.Venue.BinanceCoinFutures,
Venue.BinanceUs => domain.Venue.BinanceUs,
Venue.BinanceUsdsFutures => domain.Venue.BinanceUsdsFutures,
Venue.Coinbase => domain.Venue.Coinbase,
@@ -162,7 +162,7 @@
return leg.signed_quantity() switch {
1.0 => sec_str,
-1.0 => $"-{sec_str}",
_ => ""
_ => $"{leg.signed_quantity()}{sec_str}",
};
}
@@ -172,7 +172,7 @@
return leg.signed_quantity() switch {
1.0 => $"+{sec_str}",
-1.0 => $"-{sec_str}",
_ => $"{leg.signed_quantity():+0;-0}{sec_str}",
_ => $"{leg.signed_quantity():+0.######;-0.######}{sec_str}",
};
}
}
@@ -0,0 +1,61 @@
{
"user": "bts",
"pubsub": {
"milliseconds_between_sub_info_updates": 1000,
"milliseconds_between_time_slice_updates": 5000,
"milliseconds_between_no_buffer_space_retries": 100,
"retries_on_no_buffer_space": 1,
"do_it_all_seconds_between_logs": 0,
"heartbeat_interval": 15000,
"missing_heartbeats_before_gc": 50,
"use_managed_sockets": true,
"state_update_period": 5000,
"statistics_update_period": 5000,
"disposed_connection_check_period": 1000,
"message_processing_warning_threshold": 1000,
"server_port": 12345,
"publisher_routes": [
{
"name": "default",
"server_address": "bts-cloud://localhost:12345",
"tag": ".*"
}
],
"subscriber_routes": [
{
"name": "default",
"server_address": "bts-cloud://localhost:12345",
"tag": ".*"
}
],
"max_packet_size": 500000000,
"blue_comm_must_die": true,
"crushable_tags": [],
"tag_type_to_assembly_map": [
{
"handler_class": "BCG.PubSubServerTagHandler",
"assembly": "DataStreamCore.dll"
}
],
"server_queue": "PubSubQueue",
"port_range": {
"first": 30400,
"last": 31700
},
"bind_to_random_port": false,
"debug_tags": "^NOTAGS$",
"latency_report_threshold_ms": 1500,
"use_substations": false,
"use_dynamic_clients": false,
"is_substation": false,
"internally_publish_sub_info": true,
"use_managed_clients_in_native_code": false,
"compressed_tags": "^NOTAGS$",
"tags_to_report_compression_ratio": "^NOTAGS$",
"disable_msmq": false,
"use_prepublished_server_state": false,
"ip_translation": null,
"gc_activation_threshold": 0,
"wait_period_after_gc": 1000
}
}
+18 -1
View File
@@ -1,4 +1,7 @@
namespace spark_api_server {
using BCG;
using Bts.PubSub;
using Bts.Websocket;
using bts;
@@ -29,8 +32,11 @@
static async Task<int> _run(RunOptions opts) {
Console.WriteLine("Starting spark_api_server... ");
opts.MergeWithConfigFile();
opts.MergeWith(RunOptions.Default());
Log.notnull(opts.user);
Log.notnull(opts.password);
PubSubConfig.SetGlobalConfig(Log.notnull(opts.PubSub));
var service_ctx = ServiceContext.create(opts.sparkd);
await service_ctx.login(opts.user, opts.password);
@@ -67,6 +73,14 @@
}
});
var vsdTask = Task.Run(() => {
Log.Gen.info("Starting ViewStreamDatabase work loop");
var cancellationToken = cancellationTokenSource.Token;
while (!cancellationToken.IsCancellationRequested) {
ViewStreamDatabase.instance.doWork();
Thread.Sleep(100);
}
}, cancellationTokenSource.Token);
// var task = Task.Run(() => {
// while (!cancellationTokenSource.IsCancellationRequested) {
// // Do something that can be interrupted
@@ -76,8 +90,11 @@
// Console.WriteLine("Press any key to stop the server...");
// Console.ReadKey();
// cancellationTokenSource.Cancel();
await Task.WhenAny(listen);
await Task.WhenAny(listen, vsdTask);
//UPTILL HERE
// These tasks aren't designed to finish naturally. If the code reaches here, it means one
// of them threw an exception. Cancel the other task to exit (somewhat) gracefully.
cancellationTokenSource.Cancel();
return (int)errors;
}
}
+46
View File
@@ -1,6 +1,13 @@
namespace Spark.Api.Server {
using Bts.PubSub;
using CommandLine;
using System;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
[Verb("run")]
public class RunOptions {
[Option("user", Default = CommonOptionMetadata.DEFAULT_USER)]
@@ -14,5 +21,44 @@ namespace Spark.Api.Server {
MetaValue = CommonOptionMetadata.SPARKD_META_VALUE,
HelpText = CommonOptionMetadata.SPARKD_HELP_TEXT)]
public string? sparkd { get; set; }
[JsonPropertyName("pubsub")]
public PubSubConfig? PubSub { get; set; }
[JsonIgnore]
[Option(
"config",
MetaValue = "PATH",
HelpText = "Path to json file representing these options (RunOptions)")]
public string? ConfigFile { get; set; }
public static RunOptions Default() =>
new() {
PubSub = PubSubConfig.Default(),
};
public void MergeWith(RunOptions other) {
sparkd ??= other.sparkd;
PubSub ??= other.PubSub;
ConfigFile ??= other.ConfigFile;
}
/// <summary>
/// Merges options with those defined in `ConfigFile` (does not overwrite
/// non-null existing values).
/// </summary>
/// <exception cref="InvalidOperationException">
/// JsonSerializer.Deserialize returns null
/// </exception>
public void MergeWithConfigFile() {
if (ConfigFile is null) {
return;
}
var cfgString = File.ReadAllText(ConfigFile);
var parsedCfg = JsonSerializer.Deserialize<RunOptions>(cfgString) ??
throw new InvalidOperationException($"Failed to parse json from {ConfigFile}");
MergeWith(parsedCfg);
ConfigFile = null;
}
}
}
@@ -640,6 +640,9 @@ namespace System.Collections.Generic {
public static List<T> ToListSorted<T, TKey>(this IEnumerable<T> enumerable, Func<T, TKey> key_selector, IComparer<TKey> key_comparer) =>
enumerable.ToListSorted(key_comparer.wrap(key_selector));
public static List<T> ToListSorted<T>(this IEnumerable<T> enumerable) where T : IComparable<T> =>
enumerable.ToListSorted(Comparer<T>.Default);
public static List<T> ToListSorted<T>(this IEnumerable<T> enumerable, IComparer<T> comparer) {
var list = new List<T>(enumerable);
list.Sort(comparer);
@@ -73,7 +73,8 @@ namespace bts.collections {
public IEnumerable<TRow> all_rows => _tree.all_rows;
public int Count => _rows.Count;
public int total_row_count => _tree.total_row_count;
public IEnumerable<TRow> GetAllRows(bool excludeFiltered = false) =>
_tree.GetAllRows(excludeFiltered);
public IDisposable defer_refresh() =>
_refresh.defer();
@@ -61,6 +61,8 @@ namespace bts.collections {
IEnumerable<INode<TRow>> _root_nodes =>
_filter == null ? _tree.RootNodes : _cached_filtered ?? (_cached_filtered = _tree.RootNodes.filtered(_filter).sorted_hard(_comparer));
public IEnumerable<TRow> GetAllRows(bool excludeFiltered = false) =>
excludeFiltered ? all_rows : _tree;
// collapse
public void collapse_row(TKey key) => _toggle_is_expanded(key, false);
public void collapse_all() => _toggle_is_expanded(false);
Loaded 100 of 335 files, more files were not shown because too many files have changed in this diff. Show more