Compare commits

..
Author SHA1 Message Date
Taha Afzal 66ad51280f Reversehedging fixed 2025-04-08 22:13:33 +00:00
arham 029edd8a86 Format: ran clang format on file 2025-04-07 15:39:57 +00:00
arham dfdcbab2a0 Add: config changes to make server controller work and set up hk zen server 2025-04-07 15:33:26 +00:00
Taha Afzal 6d7c0702f0 Fixing build: 2025-04-05 23:38:05 +00:00
Taha Afzal 86eaddea93 Fixing a bug, where the spreader was isidentifying the quoting leg 2025-04-05 15:38:30 +00:00
John Colvin 57f8f3ff58 ui: Add Sniper and Reverse Hedge controls 2025-04-04 00:19:38 -04:00
John Colvin 775df2850d Add Sniper and Reverse Hedge params to spreader proto 2025-04-04 00:19:38 -04:00
Taha Afzal cb2b15f10b Adding IOC to SimExecution 2025-04-03 23:24:30 +00:00
Taha Afzal 60362a4c95 Adding reverse-hedge to spreader 2025-04-03 23:24:30 +00:00
John Colvin c6d7a8f38c ui (pnl): Use dark theme fg in "Selected" and "Total" labels
Previously, it was black and hard to read since the background is dark.
2025-03-26 14:46:44 -04:00
John Colvin 7931c62b46 ui (pnl): Hide "Override Cpp" menu item
The overrides aren't respected in (new) pnl calcs.
2025-03-26 14:35:23 -04:00
John Colvin 58518451c8 ui (pnl): Show realized/unrealized pnl in row group totals 2025-03-26 14:31:04 -04:00
John Colvin e3bad78aef fix (pnl ui): Get cash per point from security
Previously, it was hardcoded to 1.
2025-03-24 16:54:37 -04:00
John Colvin 8e9ab8f2eb fix: Don't apply cme cpp overrides to non-cme securities
Bugs (fixed):
  * The cash per point for many securities is wrong. For example, it is
    5 for all BTC securities (which is wrong).

    Cause: cpp override config that should be specific to cme is applied
    to ALL securities.
2025-03-24 20:20:53 +00:00
John Colvin 8512f414d6 fix: Don't duplicate animations when migrating pnl window settings 2025-03-24 14:53:09 -04:00
John Colvin 513fc353cf ui (pnl): Increase default width for a few columns
I noticed some data was truncated when connected to Alphaworx prod
server.
2025-03-24 14:39:40 -04:00
John Colvin 4a04beafd2 cleanup: Consoildate order extension methods 2025-03-24 11:19:51 -04:00
John Colvin 79a3251f26 fix: Make security comparison deterministic again 2025-03-24 11:19:51 -04:00
John Colvin 8785e0fecd fix: Bootstrap all observers w/ snapshot on first update
Previously, the Subscribe impl only bootstrapped the first observer
(i.e., only in first call to Subscribe).

Bugs (fixed):
  * The ladder crashes if there are working orders when the
    ticks-per-row setting is changed.

    Cause: The ladder viewmodel resubscribes to order updates each time
    the ticks per row setting changes (as this determines the size of
    the price buckets into which orders are grouped). The IObservable
    "Bootstrap" extension fn doesn't properly clean up state leftover
    from the previous subcsription when this happens.
2025-03-24 11:17:04 -04:00
John Colvin 04251c96b6 ui (pnl): Compute pnl as sum of realized + unrealized
This addresses a subtle inconsistency in the pnl window.

The old "start_pnl" and "day_pnl" values (from IPNLUnit) are computed
with respect to the mid market for both long and short positions, while
the new "unrealized" pnl values are computed with respect to the side of
the market needed to close the position (i.e., bid market for long pos,
ask for short). Consequently, the combined pnl columns weren't equal to
the sum of realized and unrealized pnl.
2025-03-24 10:13:24 -04:00
John Colvin c2d400af1a fix (pnl ui): Don't overwrite menu display settings w/ defaults 2025-03-24 10:13:24 -04:00
John Colvin 133cc16628 ui: Add menu item to open mview from pnl
Populated with the same securities as the pnl window
2025-03-24 10:13:24 -04:00
John Colvin 16f7aede93 fix: Add case for Synthetic sec type in proto conversions 2025-03-24 10:13:24 -04:00
John Colvin 2b9488faff ui: Make pnl default settings pretty
* Make profit (green) and loss (red) easier to read against dark bg
* Hide 'SOD' and 'Day' columns (just show totals)
2025-03-24 10:13:24 -04:00
John Colvin 51b6d592d3 fix (pnl ui): Negate short avg cost when translating to avg price 2025-03-23 15:17:41 -04:00
John Colvin 1eb4ceba0a ui: Add columns for long/short avg pnl
When looking at avg cost, you often really want to know the difference
between it and the current market. These columns provide that information
so you don't need to do the arithmetic in your head.
2025-03-23 15:17:41 -04:00
John Colvin dbf5cadb44 ui: Add columns for realized/unrealized pnl 2025-03-23 15:17:41 -04:00
John Colvin 90f1bb0851 refactor: Move PNL data types to spark_api csproj 2025-03-23 13:08:46 -04:00
John Colvin 547d83e1b8 cleanup: Remove unused pnl enum WaterfallPriceFlags 2025-03-23 13:08:41 -04:00
John Colvin e888149c41 Add order msg throttle to sparkd config profiles 2025-03-20 17:29:43 +00:00
John Colvin b944c3980f Show 6 decimals in spreader order log
So we can better track those rascally iceberg orders w/ small fractional
quantities
2025-03-20 14:30:35 +00:00
Taha Afzal c2a8266106 merging 2025-03-20 01:23:05 +00:00
Mohammad Arham 4277ba61b9 Fix: Okx execution only sends 2025-03-19 13:26:57 -04:00
Mohammad Arham 35cfe9533b Add filter for okx er we that were caused by orders from outside the system 2025-03-19 13:26:39 -04:00
John Colvin d49efef93d Increase ram to 8gb in alphaworx_prod config
The previous value of 4gb wasn't enough, as the container would
occasionally crash with a SIGKILL interrupt.
2025-03-19 15:26:14 +00:00
John Colvin 502ed13456 fix: Round IcebergAlgo remaining quantity
Bugs (fixed, 2nd attempt):
  * Iceberg orders w/ fractional quantities can get into a state where
    they send an infinite stream of orders w/ 0 qty
2025-03-19 14:43:24 +00:00
John Colvin 716c4dd674 tool: Add script to dump sparkd logs to sqlite db 2025-03-19 14:41:23 +00:00
John Colvin 4a2b189a07 diag: Format Price and Quantity w/ up to 6 decimal places 2025-03-17 13:00:15 +00:00
John Colvin a42edaf9fa diag: Trivial fix for log msgs in SpreaderOrder
* Format qty with fractional part
* Remove redundant log
2025-03-17 13:00:15 +00:00
John Colvin cbc142ab15 fix: Account for rounding error in IcebergAlgo fills
Bugs (fixed):
  * Iceberg orders w/ fractional quantities can get into a state where
    they send an infinite stream of orders w/ 0 qty

    Note: I also changed the number of decimal places used when rounding
    order quantities from 6 to 7 (the level of precision guarenteed by
    IEEE 754).
2025-03-17 13:00:15 +00:00
John Colvin 3980f2d030 Add order msg throttle to alphaworx_prod config 2025-03-17 13:00:15 +00:00
John Colvin 00f6d269c9 fix: Set binance routing record "operator_id" to username (not account)
Must have been a typo
2025-03-17 00:51:54 +00:00
John Colvin 10b2d38de8 tool: Add cmd to tail sparkd log file using docker compose 2025-03-17 00:51:54 +00:00
John Colvin 6d4d8f6ac1 fix: Disable ccapi order book cross check for Binance md
When enabled, the checks fail regularly (usually more than once per
hour). Sometimes many of them happen in rapid succession before sparkd
crashes. I'm disabling them in the hopes that the crosses are benign,
and this will stop the crashes.
2025-03-17 00:51:54 +00:00
John Colvin 5db89e1e8d diag: Tone down noisy order management logs
* EMNativeOrder
* OrderRouter - only log routing record result if there are errors
* OrderServiceServer
* OrderStore - also avoid calling pb::to_string twice per change (it's
  expensive)
* ServiceBridge
2025-03-17 00:51:47 +00:00
John Colvin 379a46afa2 Add order msg throttle to sim_dev config 2025-03-15 20:07:14 +00:00
John Colvin d9eff37184 fix: Don't let rounding error accumulate in mview order summarizer
Bugs (fixed):
  * Sometimes mview shows that a security has working orders when it
    doesn't. It appears to be rounding-related as the price and qty of
    the phony working orders is NaN and 0, respectively.

  Cause: Rounding error accumulates in the order summarizer. If it gets
  bad enough, the working quantity (which is computed incrementally as
  orders transition to/from a working state) may still be non-zero after
  all orders become non-working.
2025-03-15 15:21:56 -04:00
John Colvin 3d055de139 cleanup: Rename _tick_factors -> _qty_tick in mview order summarizer
It is specifically for quantity (not price).
2025-03-15 15:21:56 -04:00
John Colvin 648be7dd97 improvement: Don't resort orderbook on every order update
This causes performace issues when the orderbook is sufficiently large
(noticeable when around 20k orders).
2025-03-15 15:21:56 -04:00
John Colvin 2cac9affe4 fix: Use consistent price comp in ladder order stream
Bugs (fixed):
  * The ladder occasionally crashes when orders are changing.

    Cause: Subtle differences between the ordinal and equality comparers
    made it possible for 2 prices to be considered equal by the former,
    and not equal by the latter. Since the ladder indexes orders by
    price, a "Key not found" exception could be thrown if the equality
    comparer detected a price change that the ordinal comparer did not.
2025-03-15 15:21:55 -04:00
John Colvin 30ff3ba707 Add some null checks in LadderGridHost 2025-03-15 15:21:55 -04:00
John Colvin 9d3afd68aa ui: Format Perp and Spot exps as "Perp" and "None" 2025-03-15 15:21:55 -04:00
John Colvin 8c3de1789b fix: Apply leg price mults when computing synth book
This is necessary to support synths with BTC-USDC-SWAP legs at Okx
(because 1 "contract" == 0.01 BTC).

Bugs (fixed):
  * Price multiplier has no effect in synthetic book
2025-03-14 19:32:04 +00:00
John Colvin b251e1d47e build: Generate semantic version header from git
Dump it to console and log file when sparkd runs
2025-03-14 16:00:06 +00:00
arham 6ccd48a8ba Fix, 1e-5 orders working okx 2025-03-13 17:49:14 +00:00
John Colvin 41d84f7f73 Change externals submodule url from ssh to https 2025-03-13 17:08:04 +00:00
John Colvin 795062864d fix: Fix typo in permission filter comparison (change == to !=)
Bugs (fixed):
  * The orderbook and ladder blink/flicker when orders change if logged
    in as non-bts user.

    Cause: The permission filter acted as if the predicate itself
    changed everytime an order changed. As a result, it would fire one
    update to cleared all values, then fire another to add items that
    pass the "new" predicate.
2025-03-13 10:39:33 -04:00
John Colvin 1ff72e9444 fix (ui): Show fractional order qty in activity log
Also fix formatting - change "price @ qty" to "qty @ price"
2025-03-13 10:39:33 -04:00
John Colvin e13b5066a3 fix: Preserve fractional qty when modifying existing order in ticket 2025-03-13 10:39:33 -04:00
Mohammad Arham 5ba7901258 Fix: undo special case for btc swap and add ccp for swaps 2025-03-12 20:47:44 +00:00
Mohammad Arham bc6eb59110 Fix: Change cttype from optional to string 2025-03-12 20:45:27 +00:00
John Colvin 5ae5a5dcbf fix: Add switch case for PositionSide::BOTH in binance futs gw
In this case, the position is signed (unlike LONG and SHORT).
2025-03-12 14:55:56 +00:00
John Colvin 7966bcce77 fix: Change trade id from uint32 to int64 2025-03-12 04:27:54 +00:00
Mohammad Arham ff86b10895 Fix: temp fix to move okx btc swap to config for min tick 2025-03-11 20:35:24 +00:00
John Colvin ea3c0717d8 fix: Remove erroneous assert from binance usds futs gateway 2025-03-11 19:48:11 +00:00
John Colvin d0abca3fd9 Fix symbols and account alphaworx_prod config 2025-03-11 19:47:34 +00:00
John Colvin 7895fe7357 Remove taifex and glbx from NotSimExecution in conf profiles 2025-03-11 15:24:08 +00:00
Mohammad Arham 6f5f4f5cc2 Fix: okx excution working now 2025-03-11 14:25:42 +00:00
John Colvin 066a0ba693 ui: Use dark theme foreground in mini orderbook, fills popups
It was black in a couple places before, making it hard to read.
2025-03-10 14:47:58 -04:00
John Colvin c2b67b4813 fix: Check for null in orderbook root algo type filter 2025-03-10 14:47:58 -04:00
John Colvin ecad456a2d fix: Apply exp filter to popup grids in pnl window 2025-03-10 14:47:58 -04:00
Taha Afzal 2cb3cfdcdd Spreader's orders were hardcoded to be DAY, now it gets it from the parent 2025-03-10 17:22:08 +00:00
Taha Afzal 0ebc8e5b4b Orders in DB are deleted on server bounce 2025-03-10 15:42:12 +00:00
John Colvin 2072105c0f cleanup: Add using aliases to make code more concise 2025-03-10 14:53:07 +00:00
John Colvin d663c86aa0 improvement: Only load synth metadata from disk once in md dumper
Instead of every time a synth is dumped (i.e., do it lazily, outside the
loop body)
2025-03-10 14:53:07 +00:00
John Colvin 4a2dfa46a1 improvement: Print delimiter line when dumping secs to console 2025-03-10 14:53:07 +00:00
John Colvin 6e35db6a24 improvement: Apply filter to shared mem secs in md dumper
Previously, filter only applied to db securities (i.e., when --db flag
was used)
2025-03-10 14:52:39 +00:00
John Colvin b45dc3bc2d fix: Preserve create time when order is updated
Bugs (fixed):
  * Order create time mirrors last update time (it should never change).
2025-03-10 05:24:49 +00:00
John Colvin a1912907b8 Add brief readmes for sparkd config profiles 2025-03-10 05:24:49 +00:00
John Colvin b1b3dcdeb8 Add sim_dev config profile for sparkd 2025-03-10 05:24:49 +00:00
John Colvin 03dc8ef767 Add boto3 to py venv, so we can retrieve secrets from aws 2025-03-10 05:24:49 +00:00
John Colvin 975bf56c2a fix: Don't spam binance w/ listen key requests
Sleep for a few seconds before trying again on failure.
2025-03-10 05:24:49 +00:00
John Colvin 3f58c2ce84 Add script to install aws cli
The aws cli can be used to configure credentials in the shell in order
to retrieve secrets (e.g., production api keys for alphaworx, etc.).
2025-03-10 05:24:49 +00:00
John Colvin 96f28acf91 Add dev config profile for sparkd 2025-03-10 05:24:49 +00:00
John Colvin c2c2658e21 Add alphaworx_prod config profile for sparkd 2025-03-10 05:24:49 +00:00
John Colvin d6b8e359cd Expand environment vars in binance gw config 2025-03-10 03:05:59 +00:00
John Colvin 84a0850d95 Tweak binance_dev config profile
* Factor out repeated symbol list into SymbolLists entry
* Change "binance_dev" account to "BINANCE_DEV" in exe gw json
* Adjust log level
* Remove unused config:
  - ExchangeSubAccounts
  - JetFIX_Connection.max_order_msgs_per_window
2025-03-10 03:05:59 +00:00
John Colvin 4effdc4160 diag: Log when http buffers are resized in binance exe gws 2025-03-10 03:05:59 +00:00
John Colvin b9fe39f6e3 refactor: Make MDRecorder join the run thread in destructor
This wasn't broken, but detached threads bother me.
2025-03-10 03:05:59 +00:00
John Colvin b555850015 build: Package & deploy sparkd with docker compose 2025-03-10 03:05:59 +00:00
John Colvin 23794fa30d devcontainer: Add docker in docker
Also remove default port bindings in devcontainer (so the "docker in
docker" sparkd can use them). It's easy enough to manually bind ports
in vs code.
2025-03-08 05:20:08 +00:00
John Colvin 90b5a309a3 cleanup: Make a couple fns const
Probably a typo, since these fns don't mutate anything.
2025-03-08 05:20:08 +00:00
John Colvin 07beda3fc6 fix: Support fractional show qty in iceberg orders 2025-03-07 20:42:24 +00:00
Taha Afzal e3c1ea70f2 Spreader orders can now be entered in fractional qty 2025-03-07 05:24:39 +00:00
Mohammad Arham 93b93969f5 Add: config to control databento md better 2025-03-06 02:44:40 +00:00
Mohammad Arham 0a10111846 Small changes to make cboe md work 2025-03-06 02:44:07 +00:00
John Colvin eee00cc407 diag: Integrate websocketpp with our logging system
* Unifies log message structure for improved readability
* Grants us runtime control over log levels via config
2025-03-05 20:48:14 +00:00
John Colvin 0eb58eaba8 fix (preemptive): Pass websocket io_context to PImpl ctr
The 'io_context' member var is accessed in the constructor (it must be
initialized before that happens).
2025-03-05 20:42:50 +00:00
John Colvin 12f6733d1e cleanup: Remove unnecessary account mapping in binance_dev conf 2025-03-05 18:04:04 +00:00
John Colvin 93a0984ed1 Filter out non usdc/usdt spots in binance_dev conf 2025-03-05 18:04:04 +00:00
John Colvin b7e9c393a0 fix: Don't sign binance listen key requests
The spots and usds futures apis ignore the unnecessary signature params,
but the cross margin api responds with an error.
2025-03-05 18:04:04 +00:00
John Colvin aa602b3fd6 tool: Add cross margin reqs to binance.env 2025-03-05 18:04:04 +00:00
John Colvin b4b8cabdae tool: Add listen key reqs to binance.env 2025-03-05 18:04:04 +00:00
John Colvin cfe06869b3 tool: Support empty query params in binance_orders_client.sh 2025-03-05 18:04:04 +00:00
John Colvin b177ed1cc0 fix: Include listenKey param in listen key refresh req (binance)
The cross margin api requires this. Spots and futures apis don't, but
including it for them is harmless. I also changed the response json impl
to treat the 'listenKey' field as optional (it's omitted in the cross
margin api).
2025-03-05 18:04:04 +00:00
John Colvin ff17a64933 cleanup: Remove unnecessary includes 2025-03-05 18:04:04 +00:00
John Colvin 81a1fe11f6 feat: Add binance gw for cross margin orders
Spots only
2025-03-05 18:04:04 +00:00
John Colvin ad5bf88fcf refactor: Rename user_data_streams dir, ns without plural 's'
There is one user data stream for Binance Spots, and another for Usds
Futures. We model both of these user data streams in separate folders,
so each "user_data_stream" dir represents a single stream.
2025-03-05 18:04:04 +00:00
John Colvin e15f155ac9 refactor: Abstract out common binance gw code
For all sub-venues to use
2025-03-05 18:04:04 +00:00
John Colvin 6d6822cd65 refactor: Rename binance exe gw filenames so they are unique
The debugger gets confused when a particular lib contains multiple
source files with the same name (in different dirs).
2025-03-05 18:04:04 +00:00
John Colvin ace0c70516 fix: Trim account name string when converting to Fill proto 2025-03-05 18:04:04 +00:00
John Colvin e9e5059bc2 fix: Disable CURLOPT_FAILONERROR to always get response body
Even when the http error code indicates failure. The payload may contain useful
information about the error.
2025-03-05 18:04:04 +00:00
John Colvin cde72f51a0 Improve logging/error reporting in binance execution gw
In the process, consolidate binance exe report stores into single class.
2025-03-05 18:04:04 +00:00
John Colvin e1fd49ae32 cleanup: Specify bts account directly in binance gw config
This eliminates the need for an ExchangeSubAccountResolver.
2025-03-05 16:00:43 +00:00
John Colvin 274f3f98e4 cleanup: Replace includes w/ fwd declares in header 2025-03-05 16:00:43 +00:00
John Colvin a4b7d84318 fix: Reject orders w/ invalid TimeInForce at binance
Do this internally (before sending them to the exchange)
2025-03-05 16:00:43 +00:00
John Colvin be17205094 fix: Report internal reject msgs to ExecutionReport
For Binance execution gateways (spots and usds futures)
2025-03-05 16:00:43 +00:00
John Colvin 6ef9bee8e4 Add reconnect behavior to WebsocketClient 2025-03-05 16:00:43 +00:00
John Colvin 2c8b3563f1 cleanup: Perform basic init in WebsocketClient ctr 2025-03-05 16:00:43 +00:00
John Colvin 71f7e64ccb Move config profiles dir to server/sparkd 2025-03-05 16:00:43 +00:00
John Colvin c31b44ecfb diag: Log when config server starts and show url 2025-03-03 19:32:16 +00:00
John Colvin 19efb77e07 tool: Add some usds futures requests to binance.env 2025-02-24 15:12:45 +00:00
John Colvin 76de640421 diag: Show Binance sub-venue in log message prefix
Otherwise it's hard to tell which Binance venue the log is about (e.g.,
spots, usds futures, etc.).
2025-02-24 06:16:22 +00:00
John Colvin fb030742d3 Clean up binance_orders_client.sh a bit, add docs 2025-02-24 06:06:15 +00:00
John Colvin 73608f1b45 perf: Run each ccapi md session on its own thread 2025-02-24 06:04:52 +00:00
John Colvin 58aebe7002 diag: Reduce log noise in CCApiMarketDepthAdaptor 2025-02-24 06:04:52 +00:00
John Colvin 299cb0b29e diag: Log when curl_global_init/cleanup is called
It should only happen once for the whole app (so it shouldn't be noisy).
2025-02-23 20:08:52 +00:00
John Colvin 06663f711e improvement: Only call curl_global_init/cleanup once
Avoids unnecessary overhead (plus their docs say it only needs to be
done once for the whole app). These fns have "global" in their name for
a reason.

https://curl.se/libcurl/c/curl_global_init.html
2025-02-23 20:08:52 +00:00
John Colvin 51479f4885 fix: Disable signal handling in curl
This avoids a strange stack corruption bug related to set/longjmp.

https://stackoverflow.com/questions/9191668/error-longjmp-causes-uninitialized-stack-frame
2025-02-23 20:08:50 +00:00
John Colvin 5b520ba241 cleanup: Get rid of custom fmt::formatter for security proto
Security proto objects are large, so we shouldn't pass them by value to
the log system. The custom formatter's presence makes this easy to do by
accident.
2025-02-23 19:05:42 +00:00
John Colvin f0651a5341 tool: Add flag to roll logs without running sparkd 2025-02-23 15:36:10 +00:00
John Colvin 897e0990a2 diag: Improve logging in SecurityCache 2025-02-23 15:36:10 +00:00
John Colvin 30e1ae5c3c Add Binance execution to config profile 2025-02-23 15:36:10 +00:00
John Colvin efe83f81ec fix: Index live securities (as well as db ones) in SecurityCache 2025-02-23 15:36:10 +00:00
John Colvin 5fb21ada97 diag: Add sec id to PositionMap log 2025-02-23 15:36:10 +00:00
John Colvin 58edd07f6b fix: Check for empty string in strn fns 2025-02-23 15:36:10 +00:00
John Colvin 9d81354c3a Add binance_orders_client.sh for quick/easy troubleshooting 2025-02-23 15:36:10 +00:00
John Colvin 0f37be89a4 Fix typos in binance.env 2025-02-23 15:36:10 +00:00
John Colvin f6586fe636 Activate ccapi exchanges according to md config at runtime
Previously, the list of exchanges ccapi would activate was determined at
build time.
2025-02-23 15:36:08 +00:00
John Colvin 7827ccc0e7 Update externals to get ccapi v6.12.0 2025-02-23 15:35:35 +00:00
John Colvin af6a2acc0a perf: Send Binance md rest reqs on background thread 2025-02-23 06:10:36 +00:00
John Colvin 84abf59a55 feat: Add binance spots execution 2025-02-23 06:10:36 +00:00
John Colvin 2fc1d30bf1 Add binance_usds_futures_testnet config profile
To activate this config profile, run:

tools/sparkd_profile.sh binance_usds_futures_testnet
2025-02-23 06:10:36 +00:00
John Colvin 4dcd5d63a1 tool: Add script to easily switch between spark.conf profiles 2025-02-23 06:10:36 +00:00
John Colvin 63e5697b20 Unify conf and conf_json dirs
Put all files (.conf and .json) in sparkdata/conf. This allows us to
implement config profiles via symlink.
2025-02-23 06:10:36 +00:00
John Colvin 8e5cb100a7 fix: Assign BTS account to ExecutionReport
Not external exch account
2025-02-23 06:10:36 +00:00
John Colvin 938bee1817 Validate ExecutionReport before invoking callback 2025-02-23 06:10:36 +00:00
John Colvin d48af15480 diag: Log warning if ExecutionReport acct not same as order def 2025-02-23 06:10:36 +00:00
John Colvin e3b2e98cd9 fix build: Add JsonReader.cpp to client & api server builds 2025-02-20 00:55:48 -05:00
John Colvin ee53fb854c ui: Make buy/sell button bg darker in order ticket
The buttons looked washed out; too bright, yet pale.
2025-02-20 00:49:59 -05:00
Taha d36e2d61f6 Fixing PNL for both crypto and non crypto 2025-02-19 19:06:12 -05:00
John Colvin bfff3ea5fe fix (preemptive): Check for null sec in ExecutionReport::to_string
I don't think it's ever a nullptr in practice, but if that changes, this
fn shouldn't seg fault.
2025-02-19 23:14:18 +00:00
John Colvin 059c03d289 cleanup: Removing docker submodules again
...they're back. It's like Night of the Living Dead submodules!
2025-02-19 22:50:29 +00:00
John Colvin 8c561f7886 improvement: Pass account/order updates by move (not value) 2025-02-19 22:50:29 +00:00
John Colvin 288567d1eb cleanup: Pass JetFIX cfg directly to binance usds futs exe gw 2025-02-19 22:50:29 +00:00
John Colvin ecfc5405fe refactor: Move macro defined json fns to cpp file
Some fns are big (shouldn't be inlined); plus its best to avoid exposing
macros in headers.
2025-02-19 22:50:29 +00:00
John Colvin 8db34a2a11 Add json & fmt formatters for order & sec protos 2025-02-19 22:50:29 +00:00
John Colvin 91094c4d84 refactor: Make parse_uint support generic char iterator 2025-02-19 22:50:29 +00:00
John Colvin dae8c85d73 fix: Check strlen when making order/fill keys
Although the string data is stored in a fixed size array, it isn't
necessarily the full length.
2025-02-19 22:50:29 +00:00
John Colvin abdd98082f Move BrokerProxy do_work fn to cpp
Doesn't make sense to inline; it's too big.
2025-02-19 22:50:29 +00:00
John Colvin 2f6aa785ab Use object_pool wrapper to count allocations and assert if too many 2025-02-19 22:50:29 +00:00
John Colvin 7acf1f7043 diag: Improve logging in PositionMap 2025-02-19 22:49:30 +00:00
John Colvin 1c3c7a078a diag: Improve logging in binance usds futures exe gateway
Also propagate error msgs through the execution report (so we can see
them in the front end).
2025-02-19 22:49:30 +00:00
John Colvin 93ace52bfa diag: Periodically log ActorThread work cycle speed
So we can better detect slowness, blockage, etc.
2025-02-19 22:49:30 +00:00
John Colvin b5d97c0b1b refactor: Consolidate ExecutionGatewayProvider/Factory code
It's easier to read and manage code when you're not constantly jumping
to and fro across a bunch of files.
2025-02-19 22:49:30 +00:00
John Colvin 284aa53ad4 Add script to run sparkd and roll over log w/ timestamp suffix
So each run gets its own log file
2025-02-19 22:49:30 +00:00
John Colvin 8a9952f52d diag: Improve logging in ServiceBridge 2025-02-19 22:49:30 +00:00
John Colvin 748aafaab3 cleanup: Remove unnecessary includes 2025-02-19 22:49:30 +00:00
John Colvin 3ba30c70de cleanup: Normalize indentation in orderman_cfg.conf
It was all over the place before; unreadable
2025-02-19 22:49:30 +00:00
John Colvin e72f465999 diag: Improve logging in OrderManager 2025-02-19 22:49:30 +00:00
John Colvin f3d93f7749 fix leak: Replace object pool raw ptrs w/ shared ptrs 2025-02-19 22:49:30 +00:00
Mohammad Arham 8d142b8a74 Fix: forgot to push a file 2025-02-19 03:36:14 +00:00
Mohammad Arham 8210b9f35f Fix: change onix exe so it uses dispatch fix msgs 2025-02-19 03:33:00 +00:00
Mohammad Arham 12b20b05ce Fix: OKX executions sends er only when dispatch called 2025-02-18 16:02:10 +00:00
Mohammad Arham 72efbccdbd Fix: USDC swaps md working for okx 2025-02-14 23:02:49 +00:00
Mohammad Arham 33643e9eab Fix: make log resize work again 2025-02-14 22:59:35 +00:00
Mohammad Arham 0f6dc6f40b Fix: Typo in Okx execution 2025-02-13 23:38:27 +00:00
Mohammad Arham 8c778f5e41 Fix: Added tdmode to config for okx execution 2025-02-13 23:32:53 +00:00
Mohammad Arham f308654200 Fix: okx execution fix large spreader orderd 2025-02-13 23:27:32 +00:00
Mohammad Arham bdf5b980fc Add: config for sim trading 2025-02-13 22:06:23 +00:00
Mohammad Arham 914ac597cd Vesrsion: Update to v2.2.5 deploying to jame prod 2025-02-13 14:40:27 +00:00
Mohammad Arham 4aa6a77680 Fix: OKX execution works as long as spreader not to big 2025-02-13 06:09:43 +00:00
Mohammad Arham 6b59dd84ee Fix: Ordermanager will not try to remake orders from er after restart 2025-02-13 06:09:01 +00:00
Mohammad Arham 4e2c5f2d17 Fix: Okx USDT perps fix re added 2025-02-13 06:08:27 +00:00
Mohammad Arham 9a1b94a1b0 Add: fill id to okx execution report 2025-02-13 01:13:50 +00:00
Mohammad Arham 13b3c55771 Change: lst msg only entered if error 2025-02-12 23:13:59 +00:00
Mohammad Arham d0a6fcdb9c Add: Stricter Assert if websocket goes down okx execution 2025-02-12 22:23:53 +00:00
John Colvin 1eb13028c1 ui: Get qty display settings from md instead of config 2025-02-12 15:59:51 -05:00
John Colvin 424e5ff16b Get Spark version at runtime via reflection
So it doesn't need to be hardcoded per release
2025-02-12 15:59:51 -05:00
John Colvin bed389885b Support qty tick size 10e-9
Some spots at OKX need this (e.g., BTC-AED)
2025-02-12 20:42:28 +00:00
John Colvin 85cf25a57a fix: Get min qty and min increment from correct fields in okx json
"lotSz" represents min qty, and "minSz" represents min qty increment. We
had them backwards before.
2025-02-12 20:36:40 +00:00
John Colvin 61471d4675 diag: Show qty info fields in FixedSecurity json 2025-02-12 20:34:48 +00:00
John Colvin d83435d49d Move FixedSecurity to_json fn to cpp
Doesn't make sense to inline; it's too big.
2025-02-12 20:34:47 +00:00
John Colvin 582fc10dce Add get_binance_md.sh for quick/easy troubleshooting 2025-02-12 20:34:47 +00:00
John Colvin 5969985d24 cleanup: Remove unnecessary includes 2025-02-12 20:34:47 +00:00
John Colvin fabe7d665c devcontainer: Install clang-format extension 2025-02-12 20:34:47 +00:00
John Colvin 7ce75bb8e7 devcontainer: Increase ram and shared mem
In order to support large symbol sets for Alphaworx
2025-02-12 20:34:47 +00:00
Mohammad Arham b8e1c3cd60 Add: Fix build Issue 2025-02-12 19:48:52 +00:00
Taha 4909944694 Fixing PNL for perps (its base don mid market now) 2025-02-12 11:20:55 -05:00
Mohammad Arham 7258af09b4 Add: Basic Okx execution working 2025-02-12 11:55:50 +00:00
Mohammad Arham 7f2ce6c1a9 Add: Okx execution cfg 2025-02-12 11:06:45 +00:00
Mohammad Arham 4835725e1c Add: diag logging to gttp client 2025-02-12 11:06:09 +00:00
Mohammad Arham 6a9e8de475 Deployment: Updating version numbers 2025-02-11 05:06:21 +00:00
Taha Afzal 4639951acc Fixing Sprader's reportin of its filled qty 2025-02-11 03:04:30 +00:00
Taha d8de211aac Front end does NOT filter nay CBOE or CME expirations 2025-02-10 17:49:14 -05:00
John Colvin 87394bc60b ui: Increase Currency col width so filter icon isn't clipped 2025-02-10 15:31:31 -05:00
John Colvin beba30f65c fix (ui): Remove temp hack that assumed synth qty always notional 2025-02-10 15:31:31 -05:00
John Colvin 2d58424d8f ui (mview): Hook up currency filter in vm
Make it a discrete values filter (listbox) while we're at it. This seems
more practical than regex.
2025-02-10 15:31:31 -05:00
John Colvin d1bbfa3791 Revert batch subscribe change in Binance 2025-02-10 19:59:42 +00:00
John Colvin aab17f460f cleanup: Remove docker submodules again
They just keep coming back.
2025-02-10 19:59:32 +00:00
John Colvin 461ef53322 devcontainer: Clean up and simplify
* Remove mounted dirs (the repo is automatically persisted in a volume)
* Remove user specific shell env initialization scripts (they're more
  trouble than they're worth, often break the devcontainer build)
* Remove redundant settings already defined in settings.json
2025-02-10 16:55:53 +00:00
Taha Afzal 2486bc90b2 Spreader is hooked to the priceCal dropdown while creating the spreader 2025-02-10 03:10:11 +00:00
John Colvin 1c5cbaf60b Change: Update version to 2.2.4-1 2025-02-08 13:19:41 -05:00
John Colvin b8de447cfb fix (pnl ui): Assume rows w/ exp DateTime.MinValue are Spots
Not the most elegant fix, but it'll do for now.

Bugs (fixed):
  * The Pnl window groups perpetual futures with spots
2025-02-08 13:18:27 -05:00
Mohammad Arham 8bfc2d40f9 Update: V2.2.4 pnl working now 2025-02-08 12:43:13 -05:00
Taha c09fd71c94 Fixing PNL for spots 2025-02-08 06:55:07 -05:00
Mohammad Arham f3b037553e Add: Batch subs in binance sub 2025-02-08 06:44:10 +00:00
Taha Afzal d386d93166 SpreaderChildOrder::on_order_udpate, changing an asserrt to an error lgo to monitor condition 2025-02-07 15:06:16 +00:00
Mohammad Arham 5217742c71 Add: SendingTime tag to cboe execution 2025-02-07 12:26:11 +00:00
Taha c59ec95460 Sheets and spreadbox theos can now optionally be set to market mid 2025-02-06 17:42:12 -05:00
Mohammad Arham 70f01583ec ADD: CBOE TIME and SALES working 2025-02-06 20:42:58 +00:00
Mohammad Arham 66d2f9eb63 Add: Quote algo updates, theos removed 2025-02-06 03:08:46 +00:00
John Colvin 4625bed8b8 Change: Update version to v2.2.3-1 2025-02-05 17:01:11 -05:00
John Colvin f18fb332b5 fix (orderbook): Make ColumnKeys enum 64bit
We rely on bit flags to indicate "dirty" columns in the vm. Need to do
this now that we have > 31 columns.
2025-02-05 17:01:11 -05:00
John Colvin 8746a6f5ca fix (orderbook): Refresh Notional Value view when it changes in vm 2025-02-05 16:30:13 -05:00
John Colvin 702d47abb5 fix (orderbook): Fix typos in default settings for Notional Value 2025-02-05 16:29:29 -05:00
John Colvin bf2a54717c ui (orderbook): Add Filled Notional Value column 2025-02-05 16:13:34 -05:00
John Colvin 9db779fb25 fix (hack): Assume synth quantities are always notional values 2025-02-05 16:07:07 -05:00
Mohammad Arham f55ddec708 Add: Vesrion logs to bothserver and client 2025-02-05 09:50:33 -05:00
Mohammad Arham 6e942dd2d4 Change: Updated version to v2.2.3 2025-02-05 08:38:31 -05:00
Mohammad Arham 142310a17a Add: SPXW to default for cboe 2025-02-05 12:16:47 +00:00
John Colvin 7f99a89a95 ui (fills): Add notional value column 2025-02-05 00:00:41 -05:00
John Colvin bfce663b4b fix (orderbook): Hook up notional val filter in vm, persist settings 2025-02-04 23:42:58 -05:00
John Colvin 33c743643d fix (orderbook): Include fractional qty in notional val 2025-02-04 23:34:47 -05:00
Taha Afzal 858e0a389b Fixed the issue where a notioanal quoter would send a 0 qty order 2025-02-05 04:16:53 +00:00
Taha Afzal 9c3bb85674 Notional qty handling in spreader 2025-02-05 04:16:53 +00:00
Taha Afzal f4930e8cd4 Added notional to PriceCalc type for the sprader, added code to compute notional books in SpreaderUtils 2025-02-05 04:16:53 +00:00
John Colvin f21e7f3602 ui (orderbook): Show synth notional value in terms of first leg 2025-02-04 16:54:37 -05:00
John Colvin b56c35aca4 ui (ladder): Show synth notional value in terms of first leg 2025-02-04 16:54:37 -05:00
John Colvin e6ac83196e fix (benign): Actually dispose _vm_sub in LadderGridHost 2025-02-04 16:54:37 -05:00
John Colvin 7b6f74fc4a cleanup: Get rid of 'mimic tt' ladder config
This config was leftover from custom DJ work. We no longer need it.
2025-02-04 16:54:37 -05:00
John Colvin f754953fac cleanup: Remove duplicate equality check 2025-02-04 16:54:37 -05:00
Taha 2dd1143d9f Fixed spx spreadbox theos 2025-02-04 16:31:17 -05:00
Mohammad Arham e1ee3275d8 Add: Filter secs upto n months databento 2025-01-31 21:46:11 +00:00
John Colvin 2549e406f3 fix: Compute notional value correctly when ticks per row > 1
It's the sum of price * qty for each level in the row. Previously, we
were multiplying the total qty by the same price (smallest in the row).
2025-01-29 16:28:28 -05:00
John Colvin d5024f1858 fix build: Don't check if unsigned int < 0 2025-01-28 20:20:06 +00:00
John Colvin 3623ac96db fix: Multiply array size by sizeof(T) 2025-01-28 20:19:51 +00:00
John Colvin 19834625f9 fix: Don't fall through switch case 2025-01-28 19:59:32 +00:00
John Colvin c8fc649b6b fix build: Pass config arg to cmake --build
For Ninja Multi-Config builds (i.e., api server and data streams)
2025-01-28 19:59:09 +00:00
John Colvin 6b1fc1c413 cleanup: Remove unused code 2025-01-28 19:59:09 +00:00
John Colvin 6f086532ac cleanup: Convert co2::TradeMessage in DomainSecurityUtils
Instead of domain::TradeRecord constructor. The domain types should have
minimal dependency on protos.
2025-01-28 19:59:09 +00:00
John Colvin dc66282225 cleanup: Rename UINT32 -> VALUE in qty macro names
Should have done this when we changed qty primitive from uint32 to float
2025-01-28 19:59:09 +00:00
John Colvin e2c74c0bb3 cleanup: Don't redefine CONTAINS macro 2025-01-28 19:59:09 +00:00
John Colvin e89d50eec1 cleanup: Move large fns from header to cpp 2025-01-28 19:59:08 +00:00
John Colvin 8bebbc278d cleanup: Get rid of 'using namespaces'
In headers, this is an objectively bad practice, since the type aliases
leak into dependent code and can cause unexpected naming conflicts. In
source files, it is debatable, but I prefer explicit aliases of the form
'using Foo = Bar'. The intentions are clear, and the resulting code is
naturally resistent to naming conflicts.
2025-01-28 19:59:08 +00:00
John Colvin 2e6d12c9c1 cleanup: Remove unnecessary includes 2025-01-28 19:59:08 +00:00
John Colvin 102a5e23ea vscode: Add Tests Sparkd entries to tasks.json 2025-01-28 19:59:08 +00:00
John Colvin 582dc791db vscode: Add Run Sparkd (Skip Build) entry to tasks.json 2025-01-28 19:59:08 +00:00
John Colvin afb35d8c25 Compute synth book qty in terms of first leg 2025-01-28 19:59:08 +00:00
John Colvin 3a32ed5fe6 Fix BookSide equality operators 2025-01-27 16:35:22 +00:00
John Colvin 4e01697327 Add equality and output operators to BookLevel 2025-01-27 16:25:32 +00:00
John Colvin 22032c1742 Add fmt::formatter for domain::Side
So we can pass it to LOG macros, etc.
2025-01-27 16:25:15 +00:00
John Colvin 0a9bce317c Add contract qty info to security proto (and FixedSecurity)
We should get this kind of security metadata from the exchange rather
than config.
2025-01-25 20:41:47 +00:00
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
705 changed files with 40360 additions and 31693 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 @@
]
}
}
}
}
+6
View File
@@ -90,6 +90,12 @@ RUN apt-get update && apt-get install -y libpcap0.8-dev
# Databento (deps)
RUN apt-get update && apt-get -y install libzstd-dev
# Docker in Docker
RUN apt-get update && apt-get install -y \
docker.io \
docker-compose \
docker-compose-v2
# Set up default executables (update-alternatives)
RUN \
update-alternatives --install /usr/bin/clang clang /usr/bin/clang-$clang_major 100; \
+7 -26
View File
@@ -1,15 +1,10 @@
{
"dockerFile": "Dockerfile",
// Creates local dir to mount to HOME in docker
"initializeCommand": "mkdir -p ${localEnv:HOME}/.bts/devcontainer-mounts/${devcontainerId}",
"hostRequirements": {
"memory": "4gb",
"storage": "6gb"
"memory": "8gb",
"storage": "10gb"
},
"postCreateCommand": "/home/bts/spark/docker/sparkd/dev/devcontainer_post_create.sh",
"containerUser": "bts",
"workspaceMount": "source=${localWorkspaceFolder},target=/home/bts/spark,type=bind",
"workspaceFolder": "/home/bts/spark",
"updateRemoteUserUID": true,
"shutdownAction": "none",
// Note: If running multiple containers on the same host, each must use a
@@ -17,34 +12,20 @@
// example, if 8000-8003 is taken, go with 9000-9003, then 10000-10003, etc.
"runArgs": [
"--shm-size",
"2gb",
"-p",
"8000:8000",
"-p",
"8001:8001",
"-p",
"8002:8002",
"-p",
"8003:8003",
"-p",
"12345:12345"
"8gb"
],
"mounts": [
"source=${localEnv:HOME}/.bts/devcontainer-mounts/${devcontainerId},target=/home/bts,type=bind"
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"
],
"customizations": {
"vscode": {
"settings": {
"clangd.arguments": [
"--compile-commands-dir=${workspaceFolder}/build/clang/debug"
]
},
"extensions": [
"eamodio.gitlens",
"llvm-vs-code-extensions.vscode-clangd",
"ms-vscode.cmake-tools",
"ms-vscode.cpptools"
"ms-vscode.cpptools",
"xaver.clang-format"
]
}
}
}
}
+48
View File
@@ -0,0 +1,48 @@
# This file defines the global default configuration for docker deployments.
# It is tracked in source control. For temporary modifications, please create
# a separate file called ".env.local" (it's in .gitignore, so won't be tracked).
# If a variable is defined in both files, the value in .env.local will be used.
#
# About the deployment procedure:
# We use Docker Compose to build images and launch containers for various services
# that make up our software suite:
# - sparkd
# - TODO: pubsub
# - TODO: md-publisher
# - TODO: webapp (DSQA on the web)
# - TODO: api-server (websocket based)
# From inside a devcontainer, you can launch all services in separate containers
# on your machine by running:
#
# tools/deploy.sh
#
# This script builds release versions for each service, packaged (generally) in
# self-contained directories. Next, it runs a few "docker compose" commands to
# build docker images loaded with the self-contained binaries from the previous
# step, and start containers with those images to run each service. To view the
# commands that deploy.sh runs internally (without actually running them), run:
#
# tools/deploy.sh --dry-run
#
# For additional information/help, run:
#
# tools/deploy.sh --help
#
### Sparkd ###
# The internal image for sparkd to use. For more info, view:
# src/server/sparkd/Dockerfile
SPARKD__IMAGE=sparkd
# The config dir for this service to use
SPARKD__CONFIG_DIR=src/server/sparkd/configurations/dev
# Mount a specific volume or dir to "~/sparkdata/data" inside the docker.
# Defaults to a volume named "sparkd_<config_port>" (e.g., sparkd_8000).
# SPARKD__DATA_DIR=/home/bts/sparkdata/data
SPARKD__SOURCE_DIR=install/sparkd-release-gcc
SPARKD__RUNTIME_CONF_DIR=/home/ubuntu/8000/sparkdata/conf/
SPARKD__CONFIG_PORT=8000
SPARKD__QUICKBUS_PORT=8001
SPARKD__SIM_FIX_PORT=8002
SPARKD__SHM_SIZE=4gb
# TODO: add config for other services (pubsub, md-publisher, webapp, api-server, etc.)
+1
View File
@@ -59,3 +59,4 @@ trace_data/
.devcontainer/
*.Generated.cs
*.lst
.env.local
+1 -1
View File
@@ -1,6 +1,6 @@
[submodule "externals"]
path = externals
url = git@bitbucket.org:bluetradesys/externals.git
url = https://bitbucket.org/bluetradesys/externals.git
[submodule "vcpkg"]
path = vcpkg
url = https://github.com/microsoft/vcpkg.git
+181 -10
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",
},
]
}
+103 -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",
@@ -684,6 +699,24 @@
"color": "terminal.ansiGreen",
},
},
{
"label": "Run Sparkd (Skip Build)",
"type": "process",
"command": "",
"linux": {
"command": "sparkd",
},
"args": [
"run"
],
"options": {
"cwd": "${workspaceFolder}/build/${input:sparkdCmakePreset}/server"
},
"icon": {
"id": "run",
"color": "terminal.ansiGreen",
},
},
{
"label": "Run Sparkd Config Query",
"dependsOn": "Build Sparkd",
@@ -705,6 +738,65 @@
"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": "Test Sparkd",
"dependsOn": "Build Sparkd",
"type": "process",
"command": "",
"linux": {
"command": "sparkd",
},
"args": [
"test",
"${input:sparkdTest}",
],
"options": {
"cwd": "${workspaceFolder}/build/${input:sparkdCmakePreset}/server"
},
"icon": {
"id": "coverage",
"color": "terminal.ansiYellow",
},
},
{
"label": "Test Sparkd (Skip Build)",
"type": "process",
"command": "",
"linux": {
"command": "sparkd",
},
"args": [
"test",
"${input:sparkdTest}",
],
"options": {
"cwd": "${workspaceFolder}/build/${input:sparkdCmakePreset}/server"
},
"icon": {
"id": "coverage",
"color": "terminal.ansiYellow",
},
},
{
"label": "View DataStreamQueryAnalyzer Logs (Last Run)",
"type": "process",
@@ -823,6 +915,16 @@
"id": "sparkdConfigQuery",
"type": "promptString",
"description": "Config Type(s)"
}
},
{
"id": "sparkTest",
"type": "promptString",
"description": "Test to run",
},
{
"id": "sparkdTest",
"type": "promptString",
"description": "Test to run",
},
]
}
+12 -3
View File
@@ -1,8 +1,10 @@
cmake_minimum_required(VERSION 3.12)
project(spark)
include(GNUInstallDirs)
include(${CMAKE_SOURCE_DIR}/cmake/GitVersion.cmake)
set(CMAKE_CXX_STANDARD 20)
get_version_from_git()
if(WIN32)
set(platform "windows")
else()
@@ -88,9 +90,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")
@@ -110,6 +118,7 @@ else()
# subdirs
if (BUILD_SPARKD)
add_subdirectory(src/lib_binance_execution)
add_subdirectory(src/server/sparkd)
add_subdirectory(src/data_files)
endif()
+2 -10
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",
@@ -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
},
{
+48
View File
@@ -0,0 +1,48 @@
include_guard(GLOBAL)
function(get_version_from_git)
find_package(Git QUIET)
if(NOT Git_FOUND)
message(WARNING "Git not found")
return()
endif()
execute_process(
COMMAND ${GIT_EXECUTABLE} describe --tags --always
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_TAG
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE GIT_RESULT
)
if(NOT GIT_RESULT EQUAL 0)
message(WARNING "Failed to get git tag")
return()
endif()
execute_process(
COMMAND ${GIT_EXECUTABLE} rev-parse --short=7 HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_COMMIT_SHORT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
)
set(PROJECT_VERSION_BUILD_METADATA ${GIT_COMMIT_SHORT_HASH})
set(PROJECT_VERSION_BUILD_METADATA ${GIT_COMMIT_SHORT_HASH} PARENT_SCOPE)
string(REGEX REPLACE "^v" "" CLEAN_TAG "${GIT_TAG}")
if(CLEAN_TAG MATCHES "^([0-9]+)\\.([0-9]+)\\.([0-9]+)(-.*)?$")
set(PROJECT_VERSION_MAJOR ${CMAKE_MATCH_1})
set(PROJECT_VERSION_MAJOR ${CMAKE_MATCH_1} PARENT_SCOPE)
set(PROJECT_VERSION_MINOR ${CMAKE_MATCH_2})
set(PROJECT_VERSION_MINOR ${CMAKE_MATCH_2} PARENT_SCOPE)
set(PROJECT_VERSION_PATCH ${CMAKE_MATCH_3})
set(PROJECT_VERSION_PATCH ${CMAKE_MATCH_3} PARENT_SCOPE)
set(PROJECT_VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}")
set(PROJECT_VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}" PARENT_SCOPE)
set(PROJECT_VERSION_FULL "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}+${GIT_COMMIT_SHORT_HASH}")
set(PROJECT_VERSION_FULL "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}+${GIT_COMMIT_SHORT_HASH}" PARENT_SCOPE)
else()
message(WARNING "Tag '${CLEAN_TAG}' does not match semver format")
endif()
endfunction()
+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()
+11
View File
@@ -0,0 +1,11 @@
#ifndef BTS_SPARK_VERSION_H
#define BTS_SPARK_VERSION_H
#define BTS_SPARK_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define BTS_SPARK_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define BTS_SPARK_VERSION_PATCH @PROJECT_VERSION_PATCH@
#define BTS_SPARK_VERSION_BUILD_METADATA @PROJECT_VERSION_BUILD_METADATA@
#define BTS_SPARK_VERSION "@PROJECT_VERSION@"
#define BTS_SPARK_VERSION_FULL "@PROJECT_VERSION_FULL@"
#endif
+100
View File
@@ -0,0 +1,100 @@
services:
sparkd:
volumes:
- ${SPARKD__DATA_DIR:-sparkd_data}:/home/bts/sparkdata/data
build:
dockerfile: src/server/sparkd/Dockerfile
args:
- IMAGE=${SPARKD__IMAGE}
- SOURCE_DIR=${SPARKD__SOURCE_DIR}
- CONFIG_DIR=${SPARKD__CONFIG_DIR}
shm_size: "${SPARKD__SHM_SIZE:-512mb}"
command: >
sh -c "
echo bts | sudo -S chown -R bts:bts /home/bts/sparkdata/data
if [ -e "/home/bts/sparkdata/data/logging.sparkd/log" ]; then
mv "/home/bts/sparkdata/data/logging.sparkd/log" "/home/bts/sparkdata/data/logging.sparkd/log_$(date '+%Y%m%d-%H%M')"
fi
if [ -e "/home/bts/sparkdata/conf/.env.local" ]; then
echo 'Sourcing /home/bts/sparkdata/conf/.env.local'
set -a
. /home/bts/sparkdata/conf/.env.local
set +a
fi
./sparkd run"
env_file:
- .env
- ${SPARKD__CONFIG_DIR}/.env
ports:
- "${SPARKD__CONFIG_PORT}:${SPARKD__CONFIG_PORT}"
- "${SPARKD__QUICKBUS_PORT}:${SPARKD__QUICKBUS_PORT}"
- "${SPARKD__SIM_FIX_PORT}:${SPARKD__SIM_FIX_PORT}"
deploy:
resources:
limits:
memory: "${SPARKD__MEMORY:-4gb}"
sparkd-symbols:
volumes:
- ${SPARKD__DATA_DIR:-sparkd_data}:/home/bts/sparkdata/data
- ${SPARKD__RUNTIME_CONF_DIR:-sparkd_conf}:/home/bts/sparkdata/conf
build:
dockerfile: src/server/sparkd/Dockerfile
args:
- IMAGE=${SPARKD__IMAGE}
- SOURCE_DIR=${SPARKD__SOURCE_DIR}
- CONFIG_DIR=${SPARKD__CONFIG_DIR}
shm_size: "${SPARKD__SHM_SIZE:-512mb}"
command: >
sh -c "
echo bts | sudo -S chown -R bts:bts /home/bts/sparkdata/data
if [ -e "/home/bts/sparkdata/data/logging.sparkd/log" ]; then
mv "/home/bts/sparkdata/data/logging.sparkd/log" "/home/bts/sparkdata/data/logging.sparkd/log_$(date '+%Y%m%d-%H%M')"
fi
ls -alh /home/bts/sparkdata/conf/
cat /home/bts/sparkdata/conf/.env.local
if [ -e "/home/bts/sparkdata/conf/.env.local" ]; then
echo 'Sourcing /home/bts/sparkdata/conf/.env.local'
set -a
. /home/bts/sparkdata/conf/.env.local
set +a
fi
./sparkd run"
env_file:
- .env
- ${SPARKD__CONFIG_DIR}/.env
ports:
- "${SPARKD__CONFIG_PORT}:${SPARKD__CONFIG_PORT}"
- "${SPARKD__QUICKBUS_PORT}:${SPARKD__QUICKBUS_PORT}"
- "${SPARKD__SIM_FIX_PORT}:${SPARKD__SIM_FIX_PORT}"
deploy:
resources:
limits:
memory: "${SPARKD__MEMORY:-4gb}"
sparkd-tail:
image: bluetradingsystems/linux_dotnet7
volumes:
- type: volume
read_only: true
source: sparkd_data
target: /home/bts/sparkdata/data/logging.sparkd
volume:
subpath: logging.sparkd
command: ["tail", "-f", "/home/bts/sparkdata/data/logging.sparkd/log"]
sparkd-ping-test:
depends_on:
- sparkd
profiles:
- ping-test
build:
context: .
dockerfile_inline: |
FROM bluetradingsystems/linux_dotnet7
USER "root"
RUN apt-get update && apt-get -y install iputils-ping
command: [ "ping", "sparkd" ]
volumes:
sparkd_data:
name: "sparkd_${SPARKD__CONFIG_PORT:-8000}"
@@ -0,0 +1,38 @@
FROM ubuntu:22.04
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y pkg-config
RUN apt-get update && apt-get install -y locales
RUN apt-get update && apt-get install -y ca-certificates
RUN apt-get update && apt-get install -y libssh2-1-dev
RUN apt-get update && apt-get install -y software-properties-common
RUN apt-get update && apt-get install -y sudo
# GCC
ARG gcc_major=13
RUN add-apt-repository ppa:ubuntu-toolchain-r/test \
&& apt-get update \
&& apt-get install -y g++-$gcc_major
RUN apt-get update \
&& apt-get install -y dotnet7
RUN locale-gen en_US.UTF-8
ARG docker_user="bts"
ARG docker_uid="1000"
ARG docker_pass="bts"
RUN \
groupadd -g "$docker_uid" "$docker_user" \
&& useradd \
-m \
-u "$docker_uid" \
-g "$docker_uid" \
-G sudo \
--password "$(openssl passwd -1 "$docker_pass")" \
"$docker_user"
USER bts
WORKDIR /home/bts
Submodule docker/linux deleted from 56c61231f3.
Submodule docker/windows deleted from de7d40c09d.
+14
View File
@@ -1,11 +1,20 @@
astroid==2.7.3
autopep8==1.5.7
blinker==1.9.0
boto3==1.37.9
botocore==1.37.9
certifi==2023.5.7
charset-normalizer==3.1.0
click==8.1.8
colorama==0.4.4
Flask==3.1.0
idna==3.4
isort==5.9.3
itsdangerous==2.2.0
Jinja2==3.1.6
jmespath==1.0.1
lazy-object-proxy==1.6.0
MarkupSafe==3.0.2
mccabe==0.6.1
platformdirs==2.3.0
protobuf==4.21.12
@@ -14,9 +23,14 @@ pycodestyle==2.7.0
pycparser==2.20
pylint==2.10.2
PyMeta3==0.5.1
python-dateutil==2.9.0.post0
python-dotenv==1.0.1
requests==2.31.0
s3transfer==0.11.4
six==1.17.0
termcolor==1.1.0
toml==0.10.2
urllib3==2.0.3
Werkzeug==3.1.3
wrapt==1.12.1
yapf==0.31.0
@@ -23,7 +23,6 @@
<PackageReference Include="MongoDB.Driver" Version="2.23.1" />
<PackageReference Include="Parquet.Net" Version="5.0.1" />
<PackageReference Include="System.Data.SQLite" Version="1.0.118" />
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
</ItemGroup>
</Project>
@@ -4,46 +4,42 @@ namespace bts.SilverPine {
using System;
using System.Collections.Generic;
using System.Linq;
//using MathNet.Numerics.Statistics;
using ZstdSharp.Unsafe;
public enum MultiRowOp {
public enum GroupByBundleOp {
Mean,
Median,
Mode,
Range,
StdDev,
Max,
Min,
First,
Last,
Sum,
EdgeDiff,
EdgeDiffPercentage,
//IsEdgeDiff,
Sum
}
public class MultiRowOpHelper {
public readonly List<Tuple<string, MultiRowOp>> operations;
public class GroupByBundle {
public readonly List<Tuple<string, GroupByBundleOp>> operations;
public static MultiRowOpHelper parse(string text) {
public static GroupByBundle parse(string text) {
// text is inp_col: op, inp_col: op, inp_col:op
List<Tuple<string, MultiRowOp>> rv = new List<Tuple<string, MultiRowOp>>();
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();
MultiRowOp op = Enum.Parse<MultiRowOp>(op_txt, true);
GroupByBundleOp op = Enum.Parse<GroupByBundleOp>(op_txt, true);
rv.Add(Tuple.Create(inp_col, op));
}
return new MultiRowOpHelper(rv);
return new GroupByBundle(rv);
}
public MultiRowOpHelper(List<Tuple<string, MultiRowOp>> operations_) {
public GroupByBundle(List<Tuple<string, GroupByBundleOp>> operations_) {
operations = operations_;
}
public static IBlueRow group_by(List<IBlueRow> rows, MultiRowOpHelper parameters) {
public static IBlueRow group_by(List<IBlueRow> rows, GroupByBundle parameters) {
if (rows.Count() == 0) {
return null;
}
@@ -51,8 +47,8 @@ namespace bts.SilverPine {
IBlueRow ir = rows.ElementAt(0);
IBlueTableSchema in_schema = ir.schema;
IBlueTableSchema out_schema = in_schema;
IDictionary<string, Tuple<string, MultiRowOp>> out_col_map =
new Dictionary<string, Tuple<string, MultiRowOp>>();
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);
@@ -62,7 +58,7 @@ namespace bts.SilverPine {
foreach (var kv in out_col_map) {
string out_col_name = kv.Key;
string in_col_name = kv.Value.Item1;
MultiRowOp op = kv.Value.Item2;
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);
@@ -70,51 +66,20 @@ namespace bts.SilverPine {
return or;
}
// rolling_rows are the rolling window of rows. the 0th row is the one thats gettin
// calculated. rows 1 - n-1 are its window rows. the schema is based on the operations.
public IBlueRow rolling_calc(List<IBlueRow> rolling_rows) {
if (rolling_rows.Count() == 0) {
return null;
}
IBlueRow ir = rolling_rows.ElementAt(0);
IBlueTableSchema in_schema = ir.schema;
IBlueTableSchema out_schema = in_schema;
IDictionary<string, Tuple<string, MultiRowOp>> out_col_map =
new Dictionary<string, Tuple<string, MultiRowOp>>();
foreach (var t in operations) {
string out_col_name = $"{t.Item1}_{t.Item2}".ToLower();
out_schema = out_schema.add_column(TypeCode.Double, out_col_name);
out_col_map[out_col_name] = t;
}
MutableBlueRow or = new MutableBlueRow(out_schema, ir);
foreach (var kv in out_col_map) {
string out_col_name = kv.Key;
string in_col_name = kv.Value.Item1;
MultiRowOp op = kv.Value.Item2;
IEnumerable<double> col_vals = BlueDataUtility.get_colvalues<double>(rolling_rows, in_col_name);
double comp_res = compute_op(col_vals, op);
or.setValue(out_col_name, comp_res);
}
return or;
}
public static double compute_op(IEnumerable<double> col_vals, MultiRowOp op) {
public static double compute_op(IEnumerable<double> col_vals, GroupByBundleOp op) {
switch (op) {
case MultiRowOp.Max:
case GroupByBundleOp.Max:
return col_vals.Max<double>();
case MultiRowOp.Min:
case GroupByBundleOp.Min:
return col_vals.Min<double>();
case MultiRowOp.Mean:
case GroupByBundleOp.Mean:
return col_vals.Sum() / col_vals.Count();
case MultiRowOp.Sum:
case GroupByBundleOp.Sum:
return col_vals.Sum();
case MultiRowOp.First:
case GroupByBundleOp.First:
return col_vals.First();
case MultiRowOp.Last:
case GroupByBundleOp.Last:
return col_vals.Last();
case MultiRowOp.StdDev:
return MathNet.Numerics.Statistics.Statistics.StandardDeviation(col_vals);
default:
// TODO
Log.assert(false);
@@ -1,20 +0,0 @@
namespace bts.SilverPine {
using BCG;
using System;
[DataStreamUserRespresentation("AggregationStream")]
public class AgregationStream : ViewStream {
public AgregationStream(ViewStream parent_str, string operation_txt) {
ViewStream rs = new NonIncGroupByStream(parent_str, String.Empty, operation_txt);
mySetRealSource(rs);
}
public AgregationStream(ViewStream parent_str, string groupby_col, string operation_txt) {
ViewStream rs = new NonIncGroupByStream(parent_str, groupby_col, operation_txt);
mySetRealSource(rs);
}
}
}
@@ -8,7 +8,7 @@ using bts;
using bts.SilverPine;
namespace BCG {
[DataStreamUserRespresentation("AggregateStream")]
[DataStreamUserRespresentation("aggregate")]
public class NonIncGroupByStream : ViewStream {
private class DelNonIncGroupByEvaluator : INonIncGroupByEvaluator {
Func<string[]> _groupby_cols_func;
@@ -53,18 +53,15 @@ namespace BCG {
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)
: this(parent_str, groupby_col, standard_group_by_text, String.Empty) { }
public NonIncGroupByStream(ViewStream parent_str, string groupby_col, string standard_group_by_text, string optional_sort_col) {
MultiRowOpHelper std_group_by = MultiRowOpHelper.parse(standard_group_by_text);
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 = MultiRowOpHelper.group_by(group_rows, std_group_by);
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);
@@ -69,6 +69,4 @@ namespace bts {
myFireEvent(dp_out);
}
}
}
@@ -13,8 +13,9 @@ namespace BCG {
/// desired row ie newRow = f(row), where f is the mapping fucntion. Note that the id
/// is preserved across the rows, ie row["id"] = f(row)["id"]
/// </summary>
[DataStreamUserRespresentation(false)]
public class MapStream : MethodDrivenStream {
[DataStreamUserRespresentation (false)]
public class MapStream : MethodDrivenStream
{
protected IStreamMapper myMapper;
protected IChangeableStreamMapper myChangeableMapper = null;
private bool myUseNewMap = Configuration.get<DataStreamConfig>().UseNewMap;
@@ -1,14 +0,0 @@
namespace bts.SilverPine
{
using BCG;
[DataStreamUserRespresentation("RollingComputationStream")]
public class RollingComputationStream : ViewStream
{
private ViewStream _parent_str;
public RollingComputationStream(ViewStream parent_str, string operation_txt, string roll_column, uint window_sz)
{
}
}
}
@@ -1,10 +0,0 @@
namespace bts.SilverPine {
using BCG;
[DataStreamUserRespresentation("RollingComputationStream")]
public class RollingComputationStream : ViewStream {
public RollingComputationStream(ViewStream parent_str, string operation_txt, string roll_column, uint window_sz) {
}
}
}
@@ -108,7 +108,6 @@ namespace bts {
}
[DataStreamUserRespresentation("Project")]
public class NewProjectStream : ViewStream, IStreamMapper {
private ViewStream _stream;
@@ -4,7 +4,7 @@ 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) => {
@@ -24,11 +24,11 @@ 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}_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;
});
@@ -1,15 +1,18 @@
using System;
using System.Collections.Generic;
using mbts;
namespace BCG {
internal class MutableAppendTable : IBlueRowCollection {
private class MyRow : IBlueRow {
namespace BCG
{
internal class MutableAppendTable : IBlueRowCollection
{
private class MyRow : IBlueRow
{
private IBlueRow myRow;
private int myID;
public MyRow(IBlueRow r, int id) {
public MyRow(IBlueRow r, int id)
{
myRow = r;
myID = id;
}
@@ -18,8 +21,10 @@ namespace BCG {
public IBlueTableSchema schema { get { return myRow.schema; } }
public object this[int index] {
get {
public object this[int index]
{
get
{
if (myRow.schema[index].colName == "id")
return myID;
else
@@ -47,17 +52,20 @@ namespace BCG {
public System.Collections.ICollection Keys { get { return myRow.Keys; } }
public void Remove(object key) { throw new Exception("The method or operation is not implemented."); }
public void Remove(object key) {throw new Exception("The method or operation is not implemented.");}
public System.Collections.ICollection Values {
get {
public System.Collections.ICollection Values
{
get
{
object[] vals = new object[myRow.Count];
CopyTo(vals, 0);
return vals;
}
}
public object this[object key] {
public object this[object key]
{
get { return key is string ? this[key as string] : this[(int)key]; }
set { throw new Exception("The method or operation is not implemented."); }
}
@@ -66,7 +74,8 @@ namespace BCG {
#region ICollection Members
public void CopyTo(Array array, int index) {
public void CopyTo(Array array, int index)
{
myRow.CopyTo(array, index);
array.SetValue(myID, index + myRow.schema["id"].colNo);
}
@@ -81,7 +90,8 @@ namespace BCG {
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return new BlueRowEnumerator(this); }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{ return new BlueRowEnumerator(this); }
#endregion
}
@@ -96,7 +106,8 @@ namespace BCG {
public MutableAppendTable(IBlueTableSchema schema) { myRows = new BlueTable(schema); }
public IBlueRow addRow(IBlueRow r, int tableNo) {
public IBlueRow addRow(IBlueRow r, int tableNo)
{
if (!myIDMap.ContainsKey(tableNo))
myIDMap.Add(tableNo, new Dictionary<int, IBlueRow>());
IBlueRow nr = new MyRow(r, myID++); // BlueDataUtility.getNewReplacedRow(r, myID++, "id");
@@ -105,17 +116,20 @@ namespace BCG {
return nr;
}
public IBlueRow removeRow(IBlueRow r, int tableNo) {
public IBlueRow removeRow(IBlueRow r, int tableNo)
{
IBlueRow dr = myIDMap[tableNo][(int)r["id"]];
myRows.deleteRow(dr);
myIDMap[tableNo].Remove((int)r["id"]);
return dr;
}
public IBlueRowCollection removeTable(int tableNo) {
public IBlueRowCollection removeTable(int tableNo)
{
if (!myIDMap.ContainsKey(tableNo))
return null;
else {
else
{
BlueTable rv = new BlueTable(myRows.schema);
foreach (IBlueRow r in myIDMap[tableNo].Values)
rv.addRow(r);
@@ -125,7 +139,8 @@ namespace BCG {
}
}
public void clear() {
public void clear()
{
myRows.clear();
myIDMap.Clear();
}
@@ -136,7 +151,7 @@ namespace BCG {
public IBlueTableSchema schema { get { return myRows.schema; } }
public bool containsID(int id) { return myRows.containsID(id); }
public bool containsID(int id) {return myRows.containsID(id);}
#endregion
@@ -154,7 +169,7 @@ namespace BCG {
#region IEnumerable Members
public System.Collections.IEnumerator GetEnumerator() { return myRows.GetEnumerator(); }
public System.Collections.IEnumerator GetEnumerator() {return myRows.GetEnumerator();}
#endregion
}
@@ -164,8 +179,8 @@ namespace BCG {
/// to append (or remove) more streams at
/// runtime
/// </summary>
[DataStreamUserRespresentation("UnionStream")]
public class MutableAppendStream : ViewStream {
public class MutableAppendStream : ViewStream
{
private bool mySubscriptionActive = false;
private MutableAppendTable myTable;
private int myMovingIndex;
@@ -173,17 +188,22 @@ namespace BCG {
private bool _allign_col_orders = true;
public MutableAppendStream(params ViewStream[] streams)
: this(new List<ViewStream>(streams)) { }
: this(new List<ViewStream>(streams))
{ }
public MutableAppendStream(bool allign_col_order, params ViewStream[] streams)
: this(allign_col_order, new List<ViewStream>(streams)) { }
: this(allign_col_order, new List<ViewStream>(streams))
{ }
public MutableAppendStream(ICollection<ViewStream> streams)
: this(true, streams) { }
: this(true, streams)
{ }
public MutableAppendStream(bool allign_col_orders, ICollection<ViewStream> streams) {
public MutableAppendStream(bool allign_col_orders, ICollection<ViewStream> streams)
{
_allign_col_orders = allign_col_orders;
foreach (ViewStream vs in streams) {
foreach (ViewStream vs in streams)
{
myMovingIndex++;
ViewStream pj = new ChangeTableNameStream(vs, myMovingIndex.ToString());
if (_allign_col_orders)
@@ -192,7 +212,8 @@ namespace BCG {
}
}
public void addStream(ViewStream vs) {
public void addStream(ViewStream vs)
{
myMovingIndex++;
///the change name is done to identify the callbacks, and the project to
///make all the schemas col orders the same
@@ -204,14 +225,19 @@ namespace BCG {
pj.onChange += new ChangeDelegate(myHandleStreamChanges);
}
public void removeStream(ViewStream vs) {
public void removeStream(ViewStream vs)
{
int index = -1;
foreach (KeyValuePair<int, Pair<ViewStream, ViewStream>> p in myStreamIndex) {
if (p.Value.first == vs) {
foreach (KeyValuePair<int, Pair<ViewStream, ViewStream>> p in myStreamIndex)
{
if (p.Value.first == vs)
{
index = p.Key;
if (mySubscriptionActive) {
if (mySubscriptionActive)
{
p.Value.second.onChange -= new ChangeDelegate(myHandleStreamChanges);
if (myTable != null) {
if (myTable != null)
{
IBlueRowCollection delRows = myTable.removeTable(p.Key);
if (delRows != null)
myFireEvent(new DataPacket(myTable, new BlueTable(myTable.schema), delRows));
@@ -224,13 +250,15 @@ namespace BCG {
}
}
protected override void myOnFirstSubscriber() {
protected override void myOnFirstSubscriber()
{
mySubscriptionActive = true;
foreach (KeyValuePair<int, Pair<ViewStream, ViewStream>> p in myStreamIndex)
p.Value.second.onChange += new ChangeDelegate(myHandleStreamChanges);
}
protected override void myOnLastSubscriber() {
protected override void myOnLastSubscriber()
{
mySubscriptionActive = false;
foreach (KeyValuePair<int, Pair<ViewStream, ViewStream>> p in myStreamIndex)
p.Value.second.onChange -= new ChangeDelegate(myHandleStreamChanges);
@@ -238,17 +266,20 @@ namespace BCG {
myTable.clear();
}
private void myHandleStreamChanges(object newValue) {
private void myHandleStreamChanges(object newValue)
{
DataPacket dp = newValue as DataPacket;
myTable = myTable ?? new MutableAppendTable(new BlueTableSchema("MutableAppend", dp.schema.columnInfo));
BlueTable adds = new BlueTable(myTable.schema),
dels = new BlueTable(myTable.schema);
int index = Int32.Parse(dp.schema.tableName);
foreach (IBlueRow r in dp.deletedRows) {
foreach (IBlueRow r in dp.deletedRows)
{
IBlueRow dr = myTable.removeRow(r, index);
dels.addRow(dr);
}
foreach (IBlueRow r in dp.addedRows) {
foreach (IBlueRow r in dp.addedRows)
{
IBlueRow nr = myTable.addRow(r, index);
adds.addRow(nr);
}
+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
@@ -148,27 +148,22 @@ namespace bts.SilverPine {
[DataStreamUserRespresentation("FutureHistoricalBars")]
public class FutureHistoricalBarsStream : ViewStream {
public FutureHistoricalBarsStream(string fut_contract, string bar_size_txt, 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 = Enum.Parse<BarSize>(bar_size_txt, true);
ViewStream rs = new FutureHistoricalBarsStream(fut_contract, bar_size.ToString(), dt_range_beg, dt_range_end);
mySetRealSource(rs);
}
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_DAY : BarSize.ONE_MIN;
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)
:
@@ -43,11 +43,4 @@ namespace bts.SilverPine {
mySetRealSource(r);
}
}
[DataStreamUserRespresentation("PeriodicVolumeAgg")]
public class PeriodicVolumeAggregationStream : ViewStream {
public PeriodicVolumeAggregationStream(string future_contract, string contract_type, DateTime range_beg, DateTime range_end,
TimeSpan period, ViewStreamDatabase db) {
}
}
}
@@ -100,9 +100,9 @@ namespace bts.SilverPine {
this(symbol_or_contract, beg_dt, end_dt, ViewStreamDatabase.instance) { }
public VWAPStream(string symbol_or_contract, DateTime beg_dt, DateTime end_dt, ViewStreamDatabase db) {
//var fc = new ContractUtils.FutureContract(symbol_or_contract);
ViewStream r = new FutureHistoricalBarsStream(symbol_or_contract, beg_dt.ToString(), end_dt.ToString());
// since this is already filtered, we can groupBy iit without a groupBy column
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,
@@ -90,15 +90,20 @@ namespace bts.SilverPine {
// the argument is symbol, like "ES" etc and not the contract itself
public FuturesContinousContractStream(string symbol) : this(symbol, ViewStreamDatabase.instance) { }
public FuturesContinousContractStream(string symbol, ViewStreamDatabase db) {
symbol = symbol.EndsWith("*") ? symbol : symbol + "*";
ViewStream r = new FuturesHistorical1DayBarsStream(symbol);
r = new ContractExpanderStream(r, "symbol");
r = new FilterStream(r, "contract_type='Future'");
// finding the max volume across symbols, if we need to do this for open_interest, then join here
// with the open interest stream on symbol/contract
r = new NonIncGroupByStream(r, "ts_event", "volume:Max");
r = new NewProjectStream(r, "ts_event symbol !");
r = new SortStream(r, "-ts_event");
// 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,14 +2,15 @@ namespace bts.SilverPine {
using bts;
using BCG;
// it uses the volume (better to use open interest perhap) to detect the rollover, ie when a new
// future exp becomes the highest volume one
// it uses the volume - better to use open interst perhap - to detect the rollover, is when a new
// futuere exp becomes the highest volume one
[DataStreamUserRespresentation("FuturesRoll")]
public class FuturesRollStream : ViewStream {
public FuturesRollStream(string future_symbol) {
ViewStream rs = new FuturesContinousContractStream(future_symbol);
rs = new ColumnEvolutionStream(rs, "ts_event", "symbol");
rs = new NewProjectStream(rs, "symbol ts_event:roll_date !");
//ViewStream ohlcv_str = new OHLCVStream(future_symbol);
ViewStream rs = new FuturesActiveContractStream(future_symbol);
rs = new ColumnEvolutionStream(rs, "ts_event", "contract");
rs = new NewProjectStream(rs, "ts_event:roll_date !");
mySetRealSource(rs);
}
}
@@ -2,21 +2,21 @@ namespace bts.SilverPine {
using BCG;
using bts;
// used directly in blueorb
[DataStreamUserRespresentation("Settlement")]
public class SettlementPriceStream : ViewStream {
// public SettlementPriceStream(string future_name, string date_txt)
// : this(future_name, date_txt, date_txt) { }
public SettlementPriceStream(string future_name, string date_txt)
: this(future_name, date_txt, date_txt) { }
// public SettlementPriceStream(string future_name, string begin_dt, string end_dt)
// : this(future_name, DateTime.Parse(begin_dt).Date, DateTime.Parse(end_dt).Date) { }
public SettlementPriceStream(string future_name, string begin_dt, string end_dt)
: this(future_name, DateTime.Parse(begin_dt).Date, DateTime.Parse(end_dt).Date) { }
public SettlementPriceStream(string future_contract, DateTime begin_dt)
: this(future_contract, begin_dt, begin_dt) { }
public SettlementPriceStream(string symbol_or_contract, DateTime begin_dt)
: this(symbol_or_contract, begin_dt, begin_dt) { }
public SettlementPriceStream(string future_contract, DateTime begin_dt, DateTime end_dt) {
ContractUtils.FutureContract fc = new ContractUtils.FutureContract(future_contract);
public SettlementPriceStream(string symbol_or_contract, DateTime begin_dt, DateTime end_dt) {
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);
@@ -1,276 +0,0 @@
# **DSL Master Learning Prompt**
This prompt is designed to teach the foundational concepts of the DSL for financial data streaming and transformation. The DSL operates on "streams," which fetch, transform, and process financial market data in real-time or historical contexts. Forget everything I have taught you about this DSL until now (since there are some contradicting and breaking changes that can not be reconciled with anything you might have learned before) and re-learn from this prompt.
# **Introduction to Streams**
The DSL operates on "streams," its core functional units. Streams fetch tabular (from database, text files or network) or transform/operate on tabular data. They either act as **Datasources** (e.g., FutureHistoricalBars), that bootstrap the data or **Operators** (e.g., `FilterStream, ProjectStream, SingleRowMathStream, GroupByStream` etc) that transform or filter tabular data. Though the DSL can work on any tabular data, it is primarily written for streaming or static financial data
# **Streams**
## **FutureHistoricalBars (DataSource Stream)**
The `FutureHistoricalBars` stream fetches OHLCV (Open, High, Low, Close, Volume) bars for financial futures contracts. It acts as a **data source** and provides the initial table that can be further processed by operators.
#### **Stream arguments (parameters):**
1. **`Future_contract` (string):** Specifies the contract to fetch data for. This can be one of the following 3 types
1. **Continuous contracts:** Use the base symbol (e.g., `ES` for S\&P 500 futures, CL for WTI Crude Oil Futures, GC for Gold Futures etc).
2. **Specific contracts:** Use a detailed symbol (e.g., `ESZ9` for December 2029 S\&P 500 futures, `GCU9`, for September 2025 Gold Future, `SIH9-J9`,
For SIlver March April 2019 Silver Future Calendar spread)
3. **All Contracts for a symbol:** If instead of specifying individual contracts, data for all contracts for a specific symbol is needed, use the symbol name followed by *.* So `ES*` for all SP Futures and spread contracts, ZC\* for all corn contracts etc.
2. **`Bar_size` (string):** The second argument defines the granularity of the bars. Accepted values:
* `ONE_DAY` – Daily bars.
* `ONE_MIN` – Minute bars.
3. **Optional:**
* **`range_begin` (datetime):** Start of the desired time range.
* **`range_end` (datetime):** End of the desired time range.
#### **Schema:**
The output of `FutureHistoricalBars` produces a table with the following schema (this is a classic OHLCV schema that you can find online easily)
| id | ts\_event | contract | open | high | low | close | volume |
| :--- | :-------------------- | :------- | :------ | :------ | :------ | :------ | :----- |
| `0` | `2024-06-08T00:00:00` | `ESZ9` | 5001.50 | 5052.25 | 4990.75 | 5025.50 | 866000 |
| | | | | | | | |
# **FilterStream (Operator Stream)**
Filters rows of a parent stream based on conditions applied to its columns.
#### **Stream arguments (parameters):**
1. `parent_stream (another stream)` This stream provides the data, that the FilterStream subscribers to
2. `filter_condition (string)` This is a text expressing a boolean condition that is applied to each row of the parent stream, if it returns false, the row is filtered out
#### **Examples:**
1. **NL prompt:** Show me all the days when the open price for SP 500 Futures was greater than or equal to 5000:
**DSL:** `r = FilterStream(FutureHistoricalBars("ES", "ONE_DAY"), "open >= 5000")`
2. **NL prompt:** Show me all the days when the close price for SP 500 Futures was less than 3000 and volume was greater than half a million
**DSL:** `r = FilterStream(FutureHistoricalBars("ES", "ONE_MIN"), "close < 3000 and volume > 500000)`
3. **NL prompt:** Show me all the daily bars for the SP 500 Future spread ESU9-Z9 where its volume exceeded 100K
**DSL:** `r = FilterStream(FutureHistoricalBars("ES*", "ONE_MIN"), "contract = ‘ESU9-Z9’ and volume >= 100000”)`
**Notice** how expressions can be joined with AND and OR. These expressions can be nested with parenthesis.Also string needs to be in single quotes.
# **ProjectStream (Operator Stream)**
Changes the name of the columns in the parent stream. So for example, if we have to rename the close column to settlement, we can do this with ProjectStream
#### **Stream arguments (parameters):**
3. `parent_stream (another stream)` This stream provides the data, that the ProjectStream subscribers to
4. `renaming (string)` This is a text expressed in the form original\_col\_name:projected\_col\_name, original\_col\_name:projected\_col\_name,
#### **Examples:**
The following DSL renames the open column to O, high to H, low to L and close to C
**DSL:** `r = ProjectStream(FutureHistoricalBars("ES", "ONE_DAY"), "open:O, high:H, low:L, close:C”)`
# **GroupByStream (Operator Stream)**
The `GroupByStream` function is a DSL operator used to perform aggregation operations on rows of a parent stream. It works similarly to SQL’s `GROUP BY`, allowing rows to be grouped by a specific column (optional) and applying aggregation functions to other columns.
---
#### **Stream arguments (parameters):**
1. **parent\_stream (another stream):** The source stream provides the data to be operated on
2. **Group By Column (string, optional):**
A column in the schema of the parent stream used to group rows. All rows with the same value in this column are aggregated into a single group. If omitted, the aggregation is performed over all rows of the parent stream.
3. **Operation Text (string):** A string defining the column(s) to be aggregated and the operation(s) to apply, in the format:
`<column_name: operation>, <column_name: operation>, …` Each `<column_name: operation>` pair produces a new column in the output stream. The new column name is formatted as `<column_name>_<operation>` in lowercase.
---
#### **Valid Aggregation Operations:**
* `Mean`: Arithmetic mean of the values.
* `Median`: Median of the values.
* `Mode`: Most frequently occurring value.
* `Range`: Difference between the max and min.
* `StdDev`: Standard deviation of the values.
* `Max`: Maximum value.
* `Min`: Minimum value.
* `First`: First value in the group.
* `Last`: Last value in the group.
* `Sum`: Sum of the values.
* `EdgeDiff`: Difference between the edge values, meaning the difference between a certain col belonging to the first and last rows respectively
* `EdgeDiffPercentage`: Same as `EdgeDiff` but expressed as percentage instead
---
#### **Examples:**
1. **NL:**
Find the total SP 500 volume executed in the first week of 2021\.
**DSL:**
`bars = FutureHistoricalBars("ES", “ONE_DAY”, “2021-01-03T00:00:00”,“2021-01-11T00:00:00”)`
`result = GroupByStream(bars,”volume_Sum”)`
2. **NL:**
**Find the highest price and lowest price of the WTI Crude Oil futures over the first half of 2024**
**DSL:**
`bars = FutureHistoricalBars("CL", “ONE_DAY”, “2021-01-03T00:00:00”,“2021-01-06T00:00:00”)`
`result = GroupByStream(bars,”high:Max low:Min”)`
`result = ProjectStream(result,”high_max:highest_price min_low: lowest_price”)`
3. **NL:**
**Find the Average daily volume per contract in Natural Gas in 2023**
**DSL:**
`bars = FutureHistoricalBars("NG*", “ONE_DAY”, “2023-01-03T00:00:00”,“2023-31-12T23:59:59”)`
`result = GroupByStream(bars, “contract”, ”volume:Mean”)`
`result = ProjectStream(result,”mean_volume:average_daily_volume”)`
---
#### **Schema Impact:**
The `AggregateStream` modifies the schema of the parent stream by adding new columns for each aggregation operation. Existing columns are unaffected.
# **SingleRowMathStream (Operator Stream)**
The `SingleRowMathStream` is a DSL operator used to perform math operations, one row at a time, on the parent stream.
---
#### **Stream arguments (parameters):**
1. **parent\_stream (another stream):**The source stream provides the base data
2. **math\_expression(string):** This is a math expression which allows the creation of one or more new columns that are the output of a math expression on the existing columns provided by parent\_stream. This can a simple math operation like new\_col \= a \+ b, or any function from C\#’s System.Math namespace, like new\_column \= Math.Log(Math.Pow(volume, 1.1))
#### **Examples:**
4. **NL:**
Find the daily price movement in the SP 500 Futures through the month of Jan
**DSL:**
`bars = FutureHistoricalBars("ES", “ONE_DAY”, “2024-01-01T00:00:00”,“2024-01-31T00:00:00”)`
`result = SingleRowMathStream(bars,”daily_move=close-open”)`
5. **NL:**
Find the daily typical price of the WTI Crude Oil futures over the first half of 2024
**DSL:**
`bars = FutureHistoricalBars("CL", “ONE_DAY”, “2021-01-03T00:00:00”,“2021-01-06T00:00:00”)`
`result = SingleRowMathStream(bars, “typical_price = (high+low+close)/3.0”)`
#### **Input Space (Scope):** The scope of this operation is a **single row.** The SingleRowMAthStream as the name signifies, operates on columns of the same row only. **No multi row operations are allowed.** For multi row operations using what follows.
**Schema Impact:**
# **MultiRowRollingMathStream (Operator Stream)**
The `MultiRowRollingMathStream` function is a DSL operator used to perform mostly math operations on a rolling set of rows of a parent stream. It works similarly to the GroupByStream (described above) but instead of grouping rows together based on a column value, it operates on a rolling set of rows (where the rolling size is provided as an argument).
---
#### **Stream arguments (parameters):**
1. **Parent Stream (another stream):** The source stream provides the data to be operated on
2. **Operation Text (string):** A string defining the column(s) to be operated and the operation(s) to apply, in the format:
`<column_name: operation>, <column_name: operation>, …` Each `<column_name: operation>` pair produces a new column in the output stream. The new column name is formatted as `<column_name>_<operation>` in lowercase. This is like the GroupByStream’s operations string.
3. **Sort/Order Column Name(string):** This controls the ordering of the rolling rows. This argument will be in the format \-`<column_name>` to sort the rolling rows in descending order by the `<column_name>`, or \+`<column_name> in` ascending order.
4. **Rolling Window Size(int)(optional):** The size of the rolling window, If this argument is left out all, the operations are applied to all rows, is Global Operation instead of Rolling Window Operation (it's like the optional groupBy col in GroupByStream).
---
#### **Valid Aggregation Operations:**
* `Mean`: Arithmetic mean of the values.
* `Median`: Median of the values.
* `Mode`: Most frequently occurring value.
* `Range`: Difference between the max and min.
* `StdDev`: Standard deviation of the values.
* `Max`: Maximum value.
* `Min`: Minimum value.
* `First`: First value in the group.
* `Last`: Last value in the group.
* `Sum`: Sum of the values.
* `EdgeDiff`: Difference between the edge values, meaning the difference between a certain col belonging to the first and last rows respectively
* `EdgeDiffPercentage`: Same as `EdgeDiff` but expressed as percentage instead
---
#### **Examples:**
1. **NL:**
**Find the 20 d MA of SP 500 Futures close prices**
`bars = FutureHistoricalBars("ES", “ONE_DAY”, “2021-01-03T00:00:00”,“2021-01-11T00:00:00”)`
`result = MultirowRollingMathStream(bars, “close:Mean”, “-ts_event”, 20)`
2. **NL:**
**Find the 90d Standard Deviation of WTI Crude Oil during 2023**
**DSL:**
`bars = FutureHistoricalBars("CL", “ONE_DAY”, “2023-01-01T00:00:00”,“2023-12-31T00:00:00”)`
`// calculating typical_price`
`result = SingleRowMathStream(bars, “typical_price = (high+low+close)/3.0”)`
`result = MultirowRollingMathStream(bars,“typical_price:StdDev”, “-ts_event”, 90)`
`result=ProjectStream(result, “stddev_typical_price:90dStdDev”)`
3. **NL:**
**Find the Average daily volume of Natural Gas in 2023**
**DSL:**
`bars = FutureHistoricalBars("NG", “ONE_DAY”, “2023-01-03T00:00:00”,“2023-31-12T23:59:59”)`
`result = MultirowRollingMathStream(bars, ”volume:Mean”, “-ts_event”)`
`result = ProjectStream(result,”mean_volume:average_daily_volume”)`
---
#### **Schema Impact:**
The `MultirowRollingMathStream` modifies the schema of the parent stream by adding new columns for each operation. Existing columns are unaffected.
# **UnionStream (Operator Stream)**
Like the SQL Union appends all the rows from 2 streams together.
#### **Stream arguments (parameters):**
5. `parent_stream1 (first stream)` This stream provides part of the data that will will be appended to the other part, provided by the following argument
6. `parent_stream2 (second stream)` This stream provides the other part of the data that will will be appended to the first part, provided by the first argument
#### **Examples:**
4. **NL prompt:** Show me all the bars across SP 500 Futures and Nasdaq Futures
**DSL:** `r = UnionStream(FutureHistoricalBars("ES", "ONE_DAY"), FutureHistoricalBars("NQ", "ONE_DAY"))`
#### **Schema Impact:**
None
# **DateTimeExpanderStream (Operator Stream)**
DateTimExpanderStream expands one datetime column into various datetime components, like seconds in day, day of week, date etc, so new columns are added for those. The existing columns remain as is.
#### **Stream arguments (parameters):**
1. `parent_stream1 (base stream)` This parent stream that provides the data, including the datetime columns that will be expanded into multiple columns
2. `Datetime_input_column (string)` This is the column that will be expanded into multiple columns.
**New Columns:**
`<input_dt_col>_date` (Date of the value, without the time part)
* `<input_dt_col>_day,` (Day of the week)
* `<input_dt_col>_day_of_month` (day of the month, This is an int between 0 and 31\)
* `<input_dt_col>_month,` (month of the year, this is a numerical value between 1 and 12
* `input_dt_col>_hour,` (Hour of the day, value bw 0 and 23\)
* `<input_dt_col>_min",` (month part of this DateTime. Value is integer between 1 and 12).
* `<input_dt_col>_seconds_of_day",` (Total seconds of the day)
#### **Examples:**
**DSL:** `r = DateTimeExpanderStream(FutureHistoricalBars("ES", "ONE_DAY"), “ts_event”)`
**Input Schema:**
| id | ts\_event | contract | open | high | low | close | volume |
| :--- | :-------- | :------- | :--- | :--- | :--- | :---- | :----- |
**Output Schema:**
| id | ts\_event | contract | open | high | low | close | volume | | | | | | | |
| :--- | :-------- | :------- | :--- | :--- | :--- | :---- | :----- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
@@ -2,13 +2,9 @@
"prompts": [
{
"category": "Settlement",
"description": "This is a few shot learning example for an NL to DSL. The DSL function in question is Settlement (symbol_or_contract, datetime1). The first argument is a future (like CL, for crude oil, or ES for SP 500) or a specific future contract like crude oil Dec 24 future (CLZ4) or ESZ9 etc. The second argument is the date at which the settlement is to be fetched. There is an overloaded function, Settlement (symbol_or_contract, begin_dt, end_dt) which gets the settlements for a range pf dates from begin_dt to end_dt. You will need to convert statements expressed as Today, Yesterday, Last Friday, First Friday of the month, Last Thursday of August, First day of last quarter etc to actual dates. The function Settlement's second and third arguments are dates, so never pass a string like FirstDayOfLastQUarter. You will need to know what Today is, since there will be references to last week, yesterday etc.",
"description": "This is a few shot learning example for an NL to DSL. The DSL function in question is Settlement (symbol_or_contract, datetime1). The first argument is a future (like CL, for crude oil) or a specific future contract like crude oil Dec 24 future (CLZ4). The second argument is the date at which the settlement is to be fetched. There is an overloaded function, Settlement (symbol_or_contract, begin_dt, end_dt) which gets the settlements for all dates from begin_dt to end_dt. This will also have datetimes expressed as Today, Yesterday, Last Friday, First Friday of the month, Last Thursday of August etc. You will need to know what Today is, since there will be referecnes to last week, yesterday etc. These examples are assuming Today is 10/11/2024, however you should use the Today's real date",
"prompt": "What was the settlement for SP 500 Future on June 8th?",
"examples": [
{
"natural_language": "What was the settlement for SP 500 Future on June 8th?",
"dsl": "Settlement(\"ES\", 6/8/2023)"
},
{
"natural_language": "Find Settlement price of the SP500 future on 04/22/2023.",
"dsl": "Settlement(\"ES\", 4/22/2023)"
@@ -45,7 +41,7 @@
},
{
"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, period_start_date, period_end_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 start and end of the VWAP period, chronologically. So for example, if we are calculating the 10 day VWAP on 10/15/2023, the period will be from 10/05/2023 to 10/15/2023 (to cover for 10 days going back from the given date). 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. ",
"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": [
{
@@ -53,39 +49,31 @@
"dsl": "VWAP(\"ES\", 4/02/2023, 4/22/2023)"
},
{
"natural_language": "Find the 10 day VWAP of the Crude Oil Future on 10/10/2023.",
"dsl": "VWAP(\"CL\", 10/01/2023, 10/10/2023)"
"natural_language": "Find Yesterday's 10 day VWAP of the Crude Oil Future.",
"dsl": "VWAP(\"CL\", 10/01/2024, 10/10/2024)"
},
{
"natural_language": "Find today's 30-day VWAP of the 10 year treasury future on 9/11/2023.",
"dsl": "VWAP(\"ZN\", 8/11/2023, 9/11/2023)"
"natural_language": "Find today's 30-day VWAP of the 10 year treasury future.",
"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 7:00:00, 9/27/2024 17:00:00)"
},
{
"natural_language": "Find the 3 month VWAP of the SP 500 futures on 10/10/2023",
"dsl": "VWAP(\"ES\", 7/10/2023, 10/10/2023)"
}
]
},
{
"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 (I am not providing any function to find this date, you have to find the nearest trading date yourself), 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? (or how much did the SP 500 Future price move from July 10th to July 14th?)",
"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": "FuturePriceDiff(\"ZN\", 7/10/2024, 7/14/2024)"
},
{
"natural_language": "What is the difference in SP 500 Future price from the first day of the first quarter of 2020 to the last day of the first quarter of 2020?",
"dsl": "FuturePriceDiff(\"ES\", 1/1/2020, 3/31/2020)"
},
{
"natural_language": "How much did SP 500 Futures move in the first quarter of 2020?",
"dsl": "FuturePriceDiff(\"ES\", 1/1/2020, 3/31/2020)"
"natural_language": "What is the price difference in SP Future price from Sept 10th and Sept 5th?",
"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?",
@@ -96,19 +84,19 @@
"dsl": "FuturePriceDiff(\"ES\", 8/30/2023, 10/10/2023)"
},
{
"natural_language": "How much did SP 500 Future move during the first quarter of last year",
"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": "Contract Aggregate Volume",
"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/10/2024 and 7/14/2024",
"prompt": "Which SP 500 Future traded the most between 7/14/2024 and 7/10/2024",
"examples": [
{
"natural_language": "Which SP Future traded the most between 7/10/2024 and 7/14/2024",
"dsl": "VolumeAgg(\"ES\", \"Future\", 7/10/2024, 7/14/2024)"
"dsl": "VolumeAgg(\"ES\", \"Future\", 1/1/2024, 1/7/2024)"
},
{
"natural_language": "Which Gold Spread traded the most in the first month of this year",
@@ -130,8 +118,8 @@
"prompt": "When does the roll typically occur In SP 500?",
"examples": [
{
"natural_language": "When does the roll typically occur In SP 500?",
"dsl": "FuturesRoll(\"ES\")"
"natural_language": "When does the roll typically occur In CL?",
"dsl": "FuturesRoll(\"CL\")"
},
{
"natural_language": "When does the roll typically occur for 10 year treasury?",
@@ -175,8 +163,8 @@
]
},
{
"category": "Open Interest",
"description": "This is a few shot learning example for an NL to DSL.",
"category": "Average Volume",
"description": "This is a few shot learning example for an NL to DSL. For the purpose of this few shot prompts, lets assume today is 10/3/2024 ",
"prompt": "When does the most open interest typically shift from one contract to another in SP 500 Future?",
"examples": [
{
+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 "${CMAKE_INSTALL_BINDIR}")
+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"
}
+2 -2
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.TWSE; 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,7 +74,7 @@ type TaifexExchangeField =
| Origin of CustomerOrFirm
| ClearingAccount of string
type TwseExchangeField =
type OkxExchangeField =
| CTI of CTICode
| Origin of CustomerOrFirm
| ClearingAccount of string
@@ -800,7 +800,6 @@ let venueToString (venue:bts.api.Venue) =
| bts.api.Venue.FTX -> "FTX"
| bts.api.Venue.OKX -> "OKX"
| bts.api.Venue.TAIFEX -> "TAIFEX"
| bts.api.Venue.TWSE -> "TWSE"
let exchange_display expirationNameList =
let s = sprintf "%O" expirationNameList.product.exchange
+43
View File
@@ -8,6 +8,7 @@ using MathNet.Numerics.Providers.FourierTransform;
using BCG;
using System.Windows.Automation;
using bts.collections;
using mbts.co2;
namespace mbts.spark {
// Publishes theos for all options (on the side, also publishes the theos for TC2).
@@ -114,11 +115,53 @@ namespace mbts.spark {
}
sym_theos.set_data(k, tp);
}
_override_theos(ref rv);
foreach (var kv in rv) {
_bb.post(SparkTags.TC2Tags.LiveTheosTag(kv.Key), kv.Value);
}
}
private void _override_theos(ref Dictionary<(string, Venue), ExpStrikeKeyedData<TheoPoint>> theos) {
if (_compute_midmarket) {
SystemContractData sc_data = _bb.read_state(SparkTags.SystemContractsTag);
foreach (var kv in theos) {
string sym = kv.Key.Item1;
Venue exchange = kv.Key.Item2;
foreach (var t in kv.Value.get_data_linearly()) {
DateTime exp = t.first;
double strike = t.second;
TheoPoint tp = t.third;
var call_sec_md = sc_data.get_option(exchange, sym, exp, strike, PutOrCall.Call);
{
NativePriceSize call_bid, call_ask;
ulong time = 0;
call_sec_md.Value.md.get_bbo(ref time, out call_bid, out call_ask);
double call_mid = (call_bid.price + call_ask.price) / 2;
tp.call = call_mid;
}
{
var put_sec_md = sc_data.get_option(exchange, sym, exp, strike, PutOrCall.Put);
NativePriceSize put_bid, put_ask;
ulong time = 0;
put_sec_md.Value.md.get_bbo(ref time, out put_bid, out put_ask);
double put_mid = (put_bid.price + put_ask.price) / 2;
tp.put = put_mid;
}
}
}
//replace theos with market bid
// get it from the bus and override
} else {
// do nothing, passes unchanged
}
}
private bool _compute_midmarket {
get {
return true;
}
}
private void _interp_and_publish() {
var timer = Stopwatch.StartNew();
@@ -1,6 +1,8 @@
namespace mbts.spark {
using bts.api;
using Spark.Api.Data;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -23,14 +25,14 @@ namespace mbts.spark {
foreach (var kv in driver_map.get_price_map()) {
var exp = kv.Key;
var dmd = kv.Value;
IEnumerable<SecurityMD> exp_secs = sc_data.get_securities(
(sec_md) => {
var s = sec_md.security;
return s.security_type() == SecurityType.Option &&
s.Symbol == driver_map.option_symbol &&
s.earliest_expiration() == exp.expiration;
}
);
IEnumerable<SecurityMD> exp_secs = sc_data.get_securities((sec_md) => {
var s = sec_md.security;
return s.security_type() == SecurityType.Option &&
s.Symbol == driver_map.option_symbol &&
s.earliest_expiration() == (Security.is_perpetual_exp(exp.expiration)
? ExpirationDate.Perpetual
: ExpirationDate.FixedDate(exp.expiration));
});
foreach (var sec_md in exp_secs) {
rv[sec_md.security.Id] = kv.Value;
}
@@ -69,7 +69,7 @@ namespace mbts.spark {
case SecurityType.Future:
case SecurityType.Option:
rv.GetOrAdd((sec.Exchange, sec.security_type(), sec.Symbol), _ => new())
.Add(sec.earliest_expiration());
.Add(sec.earliest_expiration().Value);
break;
}
}
+11
View File
@@ -281,5 +281,16 @@ namespace mbts.spark {
else
return tp.put + (tp.pdelta * driver_diff) + (0.5 * tp.pgamma * driver_diff * driver_diff);
}
public static TheoPoint extrapolate(TheoPoint tp, double driver, bool is_call) {
TheoPoint rv = tp.Clone() as TheoPoint;
double h = driver - tp.driver;
if (is_call) {
rv.call = tp.call + h * (tp.cdelta);
} else {
rv.put = tp.put + h * (tp.cdelta);
}
return rv;
}
}
}
@@ -13,7 +13,7 @@ namespace mbts.analytics {
// prices (specified in the vs settings).
// It has public setters that get
// 1. contracts
// 2. vs inout
// 2. vs input
// 3. exp params(drivers, interst rate etc)
// 4. driver prices
// 5. vol time
+22 -8
View File
@@ -133,17 +133,31 @@ namespace bts {
// set 'unit quantity' for each sec
foreach (var smd in exch_secs.Values) {
var meta = _security_metadata.get(smd.security);
if (meta.HasValue) {
exch_secs[smd.id] = new(
smd.security,
smd.md,
meta.Value.HasPriceDisplay ? meta.Value.PriceDisplay : null,
meta.Value.HasQuantityDisplay ? meta.Value.QuantityDisplay : null);
}
var (priceDisplay, qtyDisplay) = GetDisplaySettings(smd.security);
exch_secs[smd.id] = new(smd.security, smd.md, priceDisplay, qtyDisplay);
}
_bus.post(SparkTags.ServerSecuritiesTag, exch_secs);
}
(TickDisplaySettingsImmutable? Price, TickDisplaySettingsImmutable? Quantity) GetDisplaySettings(SecurityImmutable sec) {
var meta = _security_metadata.get(sec);
var priceDisplay = meta?.PriceDisplay ?? Security.Defaults.price_display_settingsi;
var qtyDisplay = meta?.QuantityDisplay ?? Security.Defaults.quantity_display_settingsi;
if (sec.HasContractSpec) {
var pd = priceDisplay.CloneToMutable();
pd.TickFactors = TickUtils.get_tick(sec, 0).to_api_tick_factors();
priceDisplay = new(pd);
if (sec.ContractSpec.QuantityInfos.TryGetFirst(out var qtyInfo)) {
var qd = qtyDisplay.CloneToMutable();
qd.ShowDecimalPart = qtyInfo.TickBase.is_real_non_zero() && !(qtyInfo.TickUnit / qtyInfo.TickBase).is_int();
qd.TickFactors ??= new();
qd.TickFactors.TickBase = qtyInfo.TickBase;
qd.TickFactors.TickUnit = qtyInfo.TickUnit;
qtyDisplay = new(qd);
}
}
return (priceDisplay, qtyDisplay);
}
}
public void Dispose() =>
@@ -11,8 +11,9 @@ namespace Spark.Trading {
using mbts.analytics;
using mbts.spark;
using mbts.ui;
using Spark.Api.Data;
using Spark.Api.MarketData;
using Spark.Api.Orders;
using Spark.Api.Reactive;
using Spark.Core;
@@ -81,6 +82,11 @@ namespace Spark.Trading {
pnl = bus.subscribe(SparkTags.ReportBlackboardTags.PNL.PNLData)
.Select(d => d.position_data.ToDictionary(u => u.id(), PNLRowData.from_pnl_unit))
.Table(PNLRowData.key_fn)
.JoinBbo(
bus.subscribe(SparkTags.SecuritiesTag),
pnlRow => pnlRow.key.security_id,
(pnlRow, bbo) => pnlRow.with(bid_price: bbo.BidPrice, ask_price: bbo.AskPrice),
PNLRowData.key_fn)
.FullJoin(
order_data.working_account_security_orders,
PNLRowData.key_fn,
@@ -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,8 +10,15 @@ namespace bts.shell.market_view {
[ProtoContract]
class MarketViewFilterStates {
[ProtoMember(37)]
public Venue[] venue { get; set; }
[ProtoMember(38)]
public string[] currency { get; set; }
[ProtoMember(1)]
public string security { get; set; }
[ProtoMember(2)]
public DoubleMinMaxState bid_price { get; set; }
@@ -123,6 +132,8 @@ 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_discrete_values(currency, r => r.currency);
pb.add_regex(security, r => r.security.ToString());
pb.add_regex(security_key, r => r.security_key.ToString());
File diff suppressed because it is too large. Load diff
@@ -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);
@@ -98,7 +98,14 @@ namespace mbts.spark {
_sec_meta_store = sec_meta_store;
_settings_hub = settings_hub;
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
currency_filter =new RegexFilterVM<Row>(r => r.currency, nameof(Row.currency));
currency_filter = DiscreteValuesFilterVM.Create<Row, string>(
bus.subscribe(SparkTags.SecuritiesTag)
.Sample(TimeSpan.FromSeconds(1))
.Select(sec_mds => sec_mds.Values.Select(smd => smd.security.Currency).Distinct().ToList()),
r => r.currency,
nameof(Row.currency),
EqualityComparer<string>.Default,
Comparer<string>.Default);
// rows
_wrapped_rows = _rows.WrapSelector(r => r.security_md, s => {
var r = new Row(_bus, s, _orders_model, _position_model);
@@ -122,8 +129,9 @@ 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.Currency, r => r.currency, nameof(Row.currency), currency_filter, FilterType.DiscreteValues, ColWidth.Currency);
add_col(ColKey.Volume, r => r.volume_text, nameof(Row.volume), volume_filter, FilterType.ULongMinMax, ColWidth.Volume);
// last
@@ -388,8 +396,9 @@ 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, string[]> currency_filter { get; }
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<Row>(r => r.bid_price, nameof(Row.bid_price));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_qty_filter { get; } = new DoubleMinMax<Row>(r => r.bid_qty_display, nameof(Row.bid_qty_display));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> dbid_qty_filter { get; } = new DoubleMinMax<Row>(r => r.dbid_qty_display, nameof(Row.dbid_qty_display));
@@ -535,6 +544,8 @@ namespace mbts.spark {
public MarketViewFilterStates get_filter_states() =>
new() {
venue = venue_filter.get_state(),
currency = currency_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 +591,8 @@ namespace mbts.spark {
}
using var _ = rows_view.DeferRefresh();
venue_filter.load_state(filters.venue);
currency_filter.load_state(filters.currency);
security_filter.load_state(filters.security);
bid_price_filter.load_state(filters.bid_price);
bid_qty_filter.load_state(filters.bid_merged_size);
@@ -616,16 +629,16 @@ namespace mbts.spark {
static (string desc, Color fg) _fills_title(ColKey key) =>
key switch {
ColKey.Position => ("Day Fills", Colors.Black),
ColKey.RawDayPosition => ("Raw Day Fills", Colors.Black),
_ => ("Fills", Colors.Black),
ColKey.Position => ("Day Fills", BTSColors.DayFills.foreground),
ColKey.RawDayPosition => ("Raw Day Fills", BTSColors.DayFills.foreground),
_ => ("Fills", BTSColors.UI.default_text),
};
static (string desc, Color fg) _orderbook_title(ColKey key) =>
key switch {
ColKey.WorkingBids => ((string desc, Color fg))("Working Buy Orders", BTSColors.MarketData.bid),
ColKey.WorkingAsks => ((string desc, Color fg))("Working Sell Orders", BTSColors.MarketData.ask),
_ => ("Working Orders", Colors.Black),
_ => ("Working Orders", BTSColors.UI.default_text),
};
static GridSettings _hide_cols(GridSettings grid, IEnumerable<OrderBookGridView.ColumnKeys> columns) =>
@@ -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;
@@ -626,6 +626,22 @@
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock
Grid.Column="6"
x:Name="PART_SpreaderType_Label"
VerticalAlignment="Center"
Margin="{StaticResource Margin.LHeaderPanelItem}"
Text="Type"
/>
<ComboBox
x:Name="PART_SpreaderType_DropDown"
Grid.Column="7"
HorizontalAlignment="Stretch"
Margin="{StaticResource Margin.LHeaderPanelItem}"
ItemsSource="{TemplateBinding SpreaderTypeItemsSource}"
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
SelectedItem="{Binding SpreaderType, RelativeSource={RelativeSource TemplatedParent}}"
/>
<!-- <TextBlock
Grid.Column="6"
x:Name="PART_HedgePricing_Label"
VerticalAlignment="Center"
@@ -642,7 +658,7 @@
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
SelectedItem="{Binding HedgePricing, RelativeSource={RelativeSource TemplatedParent}}"
Visibility="Collapsed"
/>
/> -->
</Grid>
<c:DataGridEx
x:Name="PART_DataGrid"
@@ -237,6 +237,29 @@ namespace mbts.ui.spark {
}
#endregion
#region SpreaderTypeItemsSource
public static readonly DependencyProperty SpreaderTypeItemsSourceProperty =
WPFHelper.create_dp<IEnumerable, ExecutionStrategyEditor>(
nameof(SpreaderTypeItemsSource));
public IEnumerable SpreaderTypeItemsSource {
get => GetValue(SpreaderTypeItemsSourceProperty) as IEnumerable;
set => SetValue(SpreaderTypeItemsSourceProperty, value);
}
#endregion
#region SpreaderType
public static readonly DependencyProperty SpreaderTypeProperty =
WPFHelper.create_dp<object, ExecutionStrategyEditor>(
nameof(SpreaderType),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);
public object SpreaderType {
get => GetValue(SpreaderTypeProperty);
set => SetValue(SpreaderTypeProperty, value);
}
#endregion
public override void OnApplyTemplate() =>
_connect_leg_tuples();
@@ -117,6 +117,14 @@ namespace bts.shell {
public static QuoteInsideMarketDistanceThrottle QuoteInsideMarketDistanceThrottle => new QuoteInsideMarketDistanceThrottle();
#endregion
public static SniperQuoteParams SniperQuoteParams => new() {
NTicks = 0
};
public static ReverseHedgeQuoteParams ReverseHedgeQuoteParams => new() {
NTicks = 0
};
#region PreHedge
public static PreHedgeParams PreHedgeParams => new PreHedgeParams {
MaxPriceFromInsideMarket = PreHedgeMaxPriceFromInsideMarket,
@@ -36,6 +36,8 @@
ContractPickerVM="{Binding contract_picker_vm}"
HedgePricing="{Binding hedge_pricing}"
HedgePricingItemsSource="{Binding hedge_pricing_options}"
SpreaderType="{Binding spreader_type}"
SpreaderTypeItemsSource="{Binding spreader_type_options}"
UseFractionalTicks="{Binding use_fractional_ticks}"
IntegerTickSize="{Binding integer_tick_size}"
/>
@@ -1,23 +0,0 @@
using mbts.ui.spark;
using System.Collections.Generic;
using System.Linq;
namespace mbts.spark {
partial class SyntheticEditorVM {
class IsQuoteModel : RowModelBase<bool> {
public override string display_name { get; } = "Enable Quoting";
public override string help_text { get; } = "This parameter is in the form of a check box, where if the box is checked the leg will be quoted versus not checked, it will not be shown in the market. If it is not actively quoted in the market, the leg will still be used for calculation of the entire synthetic.";
public override string man_page { get; } = string.Empty;
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.IsActiveQuoting;
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) =>
leg_tup.leg_params.IsActiveQuoting = value;
public override bool has_errors(IReadOnlyCollection<LegSpreaderParamsTuple> leg_tups) =>
!leg_tups.Any(lt => lt.leg_params.IsActiveQuoting);
}
}
}
@@ -0,0 +1,25 @@
using mbts.ui.spark;
using System.Collections.Generic;
using System.Linq;
namespace mbts.spark;
partial class SyntheticEditorVM {
class QuoteEnabledModel : RowModelBase<bool> {
public override string display_name => string.Empty;
public override string help_text => string.Empty;
public override string man_page => string.Empty;
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote };
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.IsActiveQuoting;
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) =>
leg_tup.leg_params.IsActiveQuoting = value;
public override bool has_errors(IReadOnlyCollection<LegSpreaderParamsTuple> leg_tups) =>
!leg_tups.Any(lt => lt.leg_params.IsActiveQuoting);
}
}
@@ -0,0 +1,35 @@
using bts.shell;
using mbts.ui.spark;
namespace mbts.spark;
partial class SyntheticEditorVM {
class ReverseHedgeEnabledModel : RowModelBase<bool> {
readonly ReverseHedgeTicksModel _ticks_model;
public ReverseHedgeEnabledModel(ReverseHedgeTicksModel ticks_model) =>
_ticks_model = ticks_model;
public override string display_name => string.Empty;
public override string help_text => string.Empty;
public override string man_page => string.Empty;
public override ParameterSection[] section_path { get; } = new[] { Sections.ReverseHedge };
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.ReverseHedgeQuoteParams is not null;
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) {
if (value) {
if (leg_tup.leg_params.ReverseHedgeQuoteParams is null) {
leg_tup.leg_params.ReverseHedgeQuoteParams = SyntheticDataHelper.Defaults.ReverseHedgeQuoteParams;
_ticks_model.apply_value(leg_tup);
}
} else {
leg_tup.leg_params.ReverseHedgeQuoteParams = null;
}
_ticks_model.set_enabled(leg_tup, value);
}
}
}
@@ -0,0 +1,42 @@
using bts.spark;
using mbts.ui.spark;
using System.Collections.Generic;
namespace mbts.spark;
partial class SyntheticEditorVM {
internal class ReverseHedgeTicksModel : RowModelBase<int> {
readonly Dictionary<int, int> _value_store = new();
public override string display_name => "Ticks";
public override string help_text => string.Empty;
public override string man_page => string.Empty;
public override ParameterSection[] section_path { get; } = new[] { Sections.ReverseHedge };
public void apply_value(LegSpreaderParamsTuple leg_tup) =>
_get_params(leg_tup).NTicks = _value_from_store(leg_tup);
public override int get_value(LegSpreaderParamsTuple leg_tup) =>
_get_params(leg_tup)?.NTicks ?? _value_from_store(leg_tup);
public override void set_value(LegSpreaderParamsTuple leg_tup, int value) {
_value_store[leg_tup.id] = value;
var s = _get_params(leg_tup);
if (s is not null) {
s.NTicks = value;
}
}
protected override bool _get_enabled(LegSpreaderParamsTuple leg_tup) =>
_get_params(leg_tup) != null;
ReverseHedgeQuoteParams _get_params(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.ReverseHedgeQuoteParams;
int _value_from_store(LegSpreaderParamsTuple leg_tup) =>
_value_store.get_value(leg_tup.id, _ => 0);
}
}
@@ -94,7 +94,9 @@ namespace mbts.spark {
}
private static class Sections {
public static ParameterSection Quote => new ParameterSection("Quote");
public static ParameterSection Sniper => new("Sniper", true);
public static ParameterSection ReverseHedge => new("Reverse Hedge", true);
public static ParameterSection Quote => new("Quote", true);
public static ParameterSection PreHedge => new ParameterSection("Pre-Hedge");
public static ParameterSection PostHedge => new ParameterSection("Post-Hedge");
}
@@ -0,0 +1,35 @@
using bts.shell;
using mbts.ui.spark;
namespace mbts.spark;
partial class SyntheticEditorVM {
class SniperEnabledModel : RowModelBase<bool> {
readonly SniperTicksModel _ticks_model;
public SniperEnabledModel(SniperTicksModel ticks_model) =>
_ticks_model = ticks_model;
public override string display_name => string.Empty;
public override string help_text => string.Empty;
public override string man_page => string.Empty;
public override ParameterSection[] section_path { get; } = new[] { Sections.Sniper };
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.SniperQuoteParams is not null;
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) {
if (value) {
if (leg_tup.leg_params.SniperQuoteParams is null) {
leg_tup.leg_params.SniperQuoteParams = SyntheticDataHelper.Defaults.SniperQuoteParams;
_ticks_model.apply_value(leg_tup);
}
} else {
leg_tup.leg_params.SniperQuoteParams = null;
}
_ticks_model.set_enabled(leg_tup, value);
}
}
}
@@ -0,0 +1,42 @@
using bts.spark;
using mbts.ui.spark;
using System.Collections.Generic;
namespace mbts.spark;
partial class SyntheticEditorVM {
internal class SniperTicksModel : RowModelBase<int> {
readonly Dictionary<int, int> _value_store = new();
public override string display_name => "Ticks";
public override string help_text => string.Empty;
public override string man_page => string.Empty;
public override ParameterSection[] section_path { get; } = new[] { Sections.Sniper };
public void apply_value(LegSpreaderParamsTuple leg_tup) =>
_get_params(leg_tup).NTicks = _value_from_store(leg_tup);
public override int get_value(LegSpreaderParamsTuple leg_tup) =>
_get_params(leg_tup)?.NTicks ?? _value_from_store(leg_tup);
public override void set_value(LegSpreaderParamsTuple leg_tup, int value) {
_value_store[leg_tup.id] = value;
var s = _get_params(leg_tup);
if (s is not null) {
s.NTicks = value;
}
}
protected override bool _get_enabled(LegSpreaderParamsTuple leg_tup) =>
_get_params(leg_tup) != null;
SniperQuoteParams _get_params(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.SniperQuoteParams;
int _value_from_store(LegSpreaderParamsTuple leg_tup) =>
_value_store.get_value(leg_tup.id, _ => 0);
}
}
@@ -23,6 +23,7 @@ using System.Windows.Input;
using System.Windows.Media;
using HedgePricing = bts.spark.ExecutionStrategy.Types.HedgePricing;
using SpreaderType = bts.spark.ExecutionStrategy.Types.SpreaderType;
namespace mbts.spark {
/// <summary>
@@ -47,6 +48,7 @@ namespace mbts.spark {
int _integer_tick_size = 1;
ExecutionStrategyImmutable _strategy;
HedgePricing _hedge_pricing;
SpreaderType _spreader_type;
/// <summary>
/// Inititalizes a new instance of SyntheticEditorVM class. This constructor may only be used for already-existing synthetic securities (id must be non-zero).
@@ -198,6 +200,8 @@ namespace mbts.spark {
} else {
_can_add_leg = backing(nameof(can_add_leg), false);
}
var sniper_ticks_model = new SniperTicksModel();
var reverse_hedge_ticks_model = new ReverseHedgeTicksModel();
var show_quantity_quantity = new ShowQuantityQuantityModel();
var quote_throttle_outside_ticks = new QuoteThrottleOutsideTicksModel();
@@ -225,15 +229,18 @@ namespace mbts.spark {
var post_hedge_opposite_market_based_cross_threshold = new PostHedgeOppositeMarketVolumeBasedCrossThresholdModel();
var post_hedge_opposite_market_based_cross_cross_ticks = new PostHedgeOppositeMarketVolumeBasedCrossCrossTicksModel();
var is_quote_model = new IsQuoteModel();
rows = new IRowModel[] {
_quantity_model,
_price_mult_model,
is_quote_model,
//new EnableHedgeModel(), //TAHA_SPREADER
new HedgeRatioModel(),
new SniperEnabledModel(sniper_ticks_model),
sniper_ticks_model,
new ReverseHedgeEnabledModel(reverse_hedge_ticks_model),
reverse_hedge_ticks_model,
//new MinLeanQtyModel(), //TAHA_SPREADER
//new QuoteHiddenModel(),
new QuoteEnabledModel(),
new QuoteQueueHolderModel(),
new MarketDataTypeModel(),
new ShowQuantityEnabledModel(show_quantity_quantity),
@@ -337,6 +344,13 @@ namespace mbts.spark {
public HedgePricing hedge_pricing { get => _hedge_pricing; set => Set(ref _hedge_pricing, value); }
public IEnumerable<HedgePricing> hedge_pricing_options => EnumHelper.get_values<HedgePricing>();
// spreader type
public SpreaderType spreader_type {
get => _spreader_type;
set => Set(ref _spreader_type, value);
}
public IEnumerable<SpreaderType> spreader_type_options => EnumHelper.get_values<SpreaderType>();
public ExecutionStrategyImmutable get_strategy() {
var mutable_strategy = _strategy.CloneToMutable();
mutable_strategy.LegParamsMap.Clear();
@@ -352,6 +366,7 @@ namespace mbts.spark {
_synthetic.Exchange = Venue.Bts;
_strategy = strategy;
hedge_pricing = strategy.HedgePricing;
spreader_type = strategy.SpreaderType;
leg_tuples.SetItems(_get_legs(_synthetic, strategy));
if (synthetic.HasContractSpec) {
/// assign the fields <see cref="_use_fractional_ticks"/> and <see cref="_integer_tick_size"/>,
@@ -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;
@@ -237,6 +237,17 @@ namespace mbts.spark {
DefaultColumnWidths.Qty,
get_fg: r => r.side == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask);
add_col(
Columns.NotionalValue,
DefaultColumnHeaders.NotionalValue,
r => _get_row(r).security.spec_or_default().format(r.fill.notional_value()),
new DoubleMinMax<FillTuple>(r => r.fill.notional_value()),
DataGridFilterKnownTemplates.DoubleMinMax,
fs => fs.notional_value,
(fs, cf) => fs.notional_value = cf,
DefaultColumnWidths.NotionalValue,
get_fg: r => r.side == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask);
add_col(
Columns.Price,
DefaultColumnHeaders.Price,
@@ -21,6 +21,7 @@ namespace mbts.ui.spark {
Side,
Price,
Size,
NotionalValue,
FillKind,
FillStatus,
FillKey,
@@ -8,6 +8,7 @@ using mbts.ui.controls;
using ProtoBuf;
using Spark.Api.Data;
using Spark.Api.Orders;
using System;
@@ -61,6 +62,10 @@ namespace bts.shell.fills {
[DIYGridColumnID(nameof(Columns.Size))]
public DoubleMinMaxState size { get; set; }
[ProtoMember(18)]
[DIYGridColumnID(nameof(Columns.NotionalValue))]
public DoubleMinMaxState notional_value { get; set; }
[ProtoMember(11)]
[DIYGridColumnID(nameof(Columns.FillKey))]
public string fill_key { get; set; }
@@ -87,7 +92,7 @@ namespace bts.shell.fills {
// PNL's Expiration group popups use this prop to filtered data by a particular exp (it isn't
// serialized bc it's not part of the main window's settings)
public DateTime? exp_date { get; set; }
public ExpirationDate? exp_date { get; set; }
}
static class FillsFilterStatesExtensions {
@@ -113,10 +118,11 @@ namespace bts.shell.fills {
pb.add_discrete_values(fs.fill_status, ft => ft.fill.FillStatus);
// numeric
pb.add_numeric(fs.size, ft => ft.fill.LastShares);
pb.add_numeric(fs.notional_value, ft => ft.fill.notional_value());
pb.add_numeric(fs.price, ft => ft.fill.LastPrice);
if (fs.exp_date.HasValue) {
pb.add_date_time(fs.exp_date.Value, f => f.security?.earliest_expiration());
pb.add_exp_date(fs.exp_date.Value, f => f.security?.earliest_expiration());
}
return pb.to_predicate();
}
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,9 +14,9 @@ using mbts.ui.utility.text;
using mbts.utils;
using Spark.Core.Disposables;
using Spark.Core.Math;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
@@ -37,9 +37,6 @@ namespace mbts.spark {
partial class LadderGridHost : Control {
static readonly int _initial_row_count;
static readonly bool _show_scrollbar;
static readonly bool _lighten_market_cells;
static readonly Color? _bid_market_cell_background;
static readonly Color? _ask_market_cell_background;
readonly VisualHost _visual_host = new();
readonly LadderOrdersHost _ordershost;
@@ -62,6 +59,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 {
@@ -82,6 +80,7 @@ namespace mbts.spark {
LadderTactileAdorner _tactile_adorner;
ulong _prices_updated;
NativePriceSize[] _bids = new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH], _asks = new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH];
SecurityMDExtensions.Book _depth = SecurityMDExtensions.Book.Empty;
bool _price_dirty = false, _piq_dirty = false;
LadderPriceList _price_list = new() {
length = _initial_row_count,
@@ -92,8 +91,10 @@ 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();
Func<LadderPriceLevel, string> _fmt_price_level_impl = MDDisplayHelper.get_formatter_lpl(
MarketSizeDisplay.get_default(), Security.Defaults.quantity_display_settingsi, Security.Defaults.price_display_settingsi);
int _old_tooltip_row = int.MaxValue;
NativePriceSize? _old_last_trade;
string _direction_arrow = string.Empty;
@@ -105,17 +106,7 @@ namespace mbts.spark {
static LadderGridHost() {
var knobs = config.Configuration.get<bts.config.LadderKnobs>();
_show_scrollbar = knobs.ShowScrollbar;
_lighten_market_cells = knobs.LightenMarketCells;
_initial_row_count = (int)knobs.ScrollableTickWindowSize;
// For market cell backgrounds, 0 means null. Note that the full
// range of colors can still be expressed, as zero is merely one of
// 2^24 possible representations of 'transparent.'
if (knobs.BidMarketCellBackground != 0) {
_bid_market_cell_background = BTSColors.Ladder.bid_column_background;
}
if (knobs.AskMarketCellBackground != 0) {
_ask_market_cell_background = BTSColors.Ladder.ask_column_background;
}
}
public LadderGridHost() {
@@ -452,8 +443,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);
@@ -635,10 +626,30 @@ namespace mbts.spark {
void _send_parameterized_order(Point point, Side side) {
Disposable.dispose(ref _parameterized_order_popup);
if (side == Side.Undefined)
throw new ArgumentException("Side is undefined.", nameof(side));
if (!(_vm.size_calculator.value is double size) || size == 0 || !(_vm.order_properties_vm?.selected_algo_definition?.algo_root?.can_expand ?? false))
void on_cannot_send(string reason) {
Log.Ui.warn($"LadderGridHost cannot send parameterized order. {reason}");
}
if (_vm is null) {
on_cannot_send("ViewModel is null");
return;
}
switch (side) {
case Side.Buy:
case Side.Sell:
break;
default:
on_cannot_send($"Invalid side '{side}'");
return;
}
double size = _vm?.size_calculator?.value ?? 0;
if (size > 0) {
return;
}
bool can_expand = _vm?.order_properties_vm?.selected_algo_definition?.algo_root?.can_expand ?? false;
if (!can_expand) {
on_cannot_send("Selected algo not parameterized");
return;
}
double price = _price_for_row(_row_for_point(point));
var grid = new Grid { DataContext = _vm };
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
@@ -710,6 +721,10 @@ namespace mbts.spark {
}
async Task _show_mbo_popup(Point point) {
if (_vm is null) {
Log.Ui.warn("Cannot show mbo popup. ViewModel is null.");
return;
}
var query = MBOPopupModel.default_query.with(
security_id: _vm.model.security.Id,
price_level: price_for_point(point)
@@ -735,8 +750,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) {
@@ -870,32 +885,13 @@ namespace mbts.spark {
switch (column.column_type) {
case ColumnType.Order: _ordershost.set_col_width(column.settings.width); break;
case ColumnType.Bid:
render_market_col(_bids, _bid_market_cell_background);
render_market_col(_depth.Bids);
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);
render_market_col(_depth.Asks);
break;
case ColumnType.Volume:
_volume_container.mark_dirty();
@@ -908,9 +904,9 @@ namespace mbts.spark {
if (_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);
}
void render_market_col(NativePriceSize[] depth, Color? market_cell_background) {
void render_market_col(IEnumerable<(double Price, double Volume, double NotionalValue)> depth) {
var mid = LadderRender.index_of(_price_list, _last_origin_price, _price_comparer);
LadderRender.render_sizes(column.visual_tup.visual, column.visual_tup.glyph_context, mid, _price_list, depth, _settings.row_height, _spec, column.settings, _fmt_mkt_depth_item, _lighten_market_cells, market_cell_background);
LadderRender.render_sizes(column.visual_tup.visual, column.visual_tup.glyph_context, mid, _price_list, depth, _settings.row_height, _spec, column.settings, _fmt_price_level);
}
}
_apply_column_visual_offset();
@@ -956,6 +952,7 @@ namespace mbts.spark {
// **use local vm to protect against re-assignment,
// so the events are unhooked from the same instance to which they were added
// when the subs are disposed.
Disposable.dispose(ref _vm_sub);
var local_vm = _vm;
local_vm.on_tick += _tick;
local_vm.request_recenter += on_request_recenter;
@@ -1014,8 +1011,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,19 +1101,34 @@ 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) {
ulong unused = 0;
bool last_trade_changed = _vm.md.get_last_trade(ref unused, out _last_trade, out DateTime _, out ulong __);
bool depth_changed = _vm.md.get_depth(ref _prices_updated, ref _bids, ref _asks, NativePriceHelpers.COCONUT_MAX_DEPTH);
_depth = _vm.security_md.Depth(_bids, _asks, _vm.securities);
_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 +1165,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 +1175,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 +1195,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 +1219,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,33 +1295,50 @@ 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() {
Log.assert_d(_price_list != null);
int origin_index = LadderRender.index_of(_price_list, _mkt_break_lines.origin, _price_comparer);
LadderRender.render_sizes(_bid_vis_tup.visual, _bid_vis_tup.glyph_context, origin_index, _price_list, _bids, _settings.row_height, _spec, _settings.bid_column, _fmt_mkt_depth_item, _lighten_market_cells, _bid_market_cell_background);
LadderRender.render_sizes(_ask_vis_tup.visual, _ask_vis_tup.glyph_context, origin_index, _price_list, _asks, _settings.row_height, _spec, _settings.ask_column, _fmt_mkt_depth_item, _lighten_market_cells, _ask_market_cell_background);
LadderRender.render_sizes(_bid_vis_tup.visual, _bid_vis_tup.glyph_context, origin_index, _price_list, _depth.Bids, _settings.row_height, _spec, _settings.bid_column, _fmt_price_level);
LadderRender.render_sizes(_ask_vis_tup.visual, _ask_vis_tup.glyph_context, origin_index, _price_list, _depth.Asks, _settings.row_height, _spec, _settings.ask_column, _fmt_price_level);
}
/// <summary>
@@ -1345,7 +1393,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 +1409,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,29 +1446,42 @@ 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 ?? double.NaN);
return _spec.round_to_tick(v, RoundingMode.AbsDown);
}
double _normalize_price(double price) =>
TickUtils.normalize_price(_spec, price);
double _get_ask_market_break() {
if (_vm is null) {
return double.NaN;
}
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 ?? double.NaN);
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 ?? double.NaN);
return _spec.round_to_tick(v, RoundingMode.AbsDown);
}
double _get_session_low() => _normalize_price(_get_session_low_raw());
double _get_session_high() => _normalize_price(_get_session_high_raw());
double _get_session_low_raw() => _vm.md.session_low;
double _get_session_high_raw() => _vm.md.session_high;
double _get_session_low_raw() => _vm?.md?.session_low ?? double.NaN;
double _get_session_high_raw() => _vm?.md?.session_high ?? double.NaN;
void _render_mkt_break(DrawingVisual vis, double price, uint color) =>
LadderRender.render_mkt_break(vis, _row_for_price(price), _settings.row_height, color);
@@ -1441,11 +1502,17 @@ namespace mbts.spark {
}
/// The viewmodel will always have the most up-to-date settings, so ignore <see cref="s"/> and just get it from <see cref="_vm"/>.
/// This is a workaround for the global settings-defaults issue: when defaults are updated, some properties are inadvertantly overwritten, such as column-widths, etc.
_settings = _vm.get_save_state();
_settings = _vm?.get_save_state() ?? s;
_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 +1524,13 @@ namespace mbts.spark {
static GlyphContext glyph_context(LadderColumnSettings cs) =>
GlyphContext.create(cs.font_family, cs.font_weight);
var price_display = security.regional_ticks().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);
_fmt_price_level_impl = MDDisplayHelper.get_formatter_lpl(
_settings.size_display, _qty_display, price_display);
_set_columns();
_apply_column_visual_offset();
if (prev_row_height != _settings.row_height)
@@ -1469,18 +1540,16 @@ namespace mbts.spark {
_mkt_break_lines.fair_value_line_views.Clear();
// Clear and re-add the columns to the viewer to ensure offset/visibility is correct
_visual_host.Clear();
if (LadderSettings.mimic_tt) {
// The session_range_vis sits beneath the grid backgrounds (on the
// z-axis) to avoid obfuscating the font color (dj wants it to be
// pure white). Historically, it sat atop the price column, with a
// low opacity to slightly darken the range of prices inside of the
// session range (i.e., the price-band of the day's trades). In
// conjuction with this change, the price column's background
// opacity is made slightly less than 1 (in the default settings),
// while the font color remains fully opaque, so this layer will
// darken the background without affecting the font color.
_visual_host.AddVisualChild(_session_range_vis);
}
// The session_range_vis sits beneath the grid backgrounds (on the
// z-axis) to avoid obfuscating the font color (dj wants it to be
// pure white). Historically, it sat atop the price column, with a
// low opacity to slightly darken the range of prices inside of the
// session range (i.e., the price-band of the day's trades). In
// conjuction with this change, the price column's background
// opacity is made slightly less than 1 (in the default settings),
// while the font color remains fully opaque, so this layer will
// darken the background without affecting the font color.
_visual_host.AddVisualChild(_session_range_vis);
// Renders the grid lines and column backgrounds
_visual_host.AddVisualChild(_gridvis);
// Orders column
@@ -1509,19 +1578,19 @@ namespace mbts.spark {
_visual_host.AddVisualChild(_mid_mkt_break_vis);
_visual_host.AddVisualChild(_ask_mkt_break_vis);
_visual_host.AddVisualChild(_bid_mkt_break_vis);
_fair_value_lines_sub = ui.utility.ObservableAutoWrapper.connect(
_vm.fair_value_lines,
_mkt_break_lines.fair_value_line_views,
vm => {
var with_vis = new FairValueLineWithVis(vm);
_visual_host.AddVisualChild(with_vis.vis);
return with_vis;
},
with_vis => _visual_host.RemoveVisualChild(with_vis.vis));
if (_vm is not null) {
_fair_value_lines_sub = ui.utility.ObservableAutoWrapper.connect(
_vm.fair_value_lines,
_mkt_break_lines.fair_value_line_views,
vm => {
var with_vis = new FairValueLineWithVis(vm);
_visual_host.AddVisualChild(with_vis.vis);
return with_vis;
},
with_vis => _visual_host.RemoveVisualChild(with_vis.vis));
}
_visual_host.AddVisualChild(_session_low_vis);
_visual_host.AddVisualChild(_session_high_vis);
if (!LadderSettings.mimic_tt)
_visual_host.AddVisualChild(_session_range_vis);
_render_grid_background();
_init_tactile_adorner();
// this effectively sets the background of the "Last" column //deleted to fix color
@@ -1530,6 +1599,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 +1607,27 @@ 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_depth_item_impl.
string _fmt_price_level(LadderPriceLevel value) {
// 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_price_level_impl;
impl ??= MDDisplayHelper.get_formatter_lpl(
MarketSizeDisplay.get_default(), _qty_display, security.regional_ticks().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() {
@@ -0,0 +1,98 @@
namespace mbts.spark;
using bts;
using bts.utils;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
readonly struct LadderPriceLevel : IEquatable<LadderPriceLevel> {
public LadderPriceLevel(double price = double.NaN, double volume = 0, double notionalValue = double.NaN) {
Price = price;
Volume = volume;
NotionalValue = notionalValue.is_nan() ? volume * price : notionalValue;
}
public static LadderPriceLevel Empty => new();
public double Price { get; }
public double Volume { get; }
public double NotionalValue { get; }
public LadderPriceLevel Add(double price, double volume) {
if (!(volume > 0)) {
return this;
}
if (!(Volume > 0)) {
return new(price, volume);
}
Log.assert(Price.is_real());
Log.assert(price.is_real());
var newVolume = Volume + volume;
var newPrice = (Price * Volume + price * volume) / newVolume;
return new(newPrice, newVolume);
}
public LadderPriceLevel Add(IEnumerable<(double Price, double Volume)> items) {
var (totalPrice, totalVolume) = items.Sum();
return totalVolume > 0
? Add(totalPrice / totalVolume, totalVolume)
: this;
}
public Builder ToBuilder() =>
new() { Cost = Price, Volume = Volume };
public LadderPriceLevel With(double price = double.NaN, double volume = double.NaN, double notionalValue = double.NaN) =>
new(price.if_nan(Price), volume.if_nan(Volume), notionalValue.if_nan(NotionalValue));
public bool Equals(LadderPriceLevel other) =>
MathUtils.epsilon_eq_comparer.Equals(Price, other.Price)
&& MathUtils.epsilon_eq_comparer.Equals(Volume, other.Volume);
public override bool Equals([NotNullWhen(true)] object obj) =>
obj is LadderPriceLevel other && Equals(other);
public override int GetHashCode() =>
HashCode.Combine(
MathUtils.epsilon_eq_comparer.GetHashCode(Price),
MathUtils.epsilon_eq_comparer.GetHashCode(Volume));
public class Builder {
double notionalValue = double.NaN;
public double Cost { get; set; } = double.NaN;
public double Volume { get; set; } = 0;
public double NotionalValue => notionalValue.is_nan() ? Volume * Cost : notionalValue;
public void Add(double price, double volume) {
if (!(volume > 0)) {
return;
}
Log.assert(price.is_real());
Cost += price * volume;
Volume += volume;
}
public void Add(double price, double volume, double notionalValue) {
if (!(volume > 0)) {
return;
}
Log.assert(price.is_real());
Cost += price * volume;
Volume += volume;
if (this.notionalValue.is_nan()) {
this.notionalValue = notionalValue;
} else{
this.notionalValue += notionalValue;
}
}
public LadderPriceLevel ToImmutable() =>
Volume > 0
? new(Cost / Volume, Volume, NotionalValue)
: Empty;
}
}
@@ -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)}" };
}
}
@@ -57,8 +57,7 @@ namespace mbts.spark {
static class LadderRender {
// Note: the orders-column has a separate "row_padding" value: Badge.DefaultMargin
// (see how it is incorporated in measure_row_height() below)
public static Thickness row_padding => LadderSettings.mimic_tt ?
new Thickness(4, 2, 4, 2) : new Thickness(6);
public static Thickness row_padding => new Thickness(4, 2, 4, 2);
const string tick_separator_period = ".";
const string tick_separator_dash = "-";
@@ -79,13 +78,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 +101,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 +174,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));
@@ -205,69 +213,78 @@ namespace mbts.spark {
}
}
public static void RenderSizes(
DrawingVisual dv,
GlyphContext glyphContext,
int originIndex,
IList<double> prices,
IEnumerable<LadderPriceLevel> levels,
double rowHeight,
RegionalTickFactors spec,
LadderColumnSettings settings,
Func<LadderPriceLevel, string> fmt) {
var bg = settings.background.rgb_color().blend((BTSColor)0x2AFFFFFF).to_brush();
using (var dc = dv.RenderOpen())
using (_draw_with_clip(dc, settings, rowHeight, prices.Count))
using (var levelsEn = (levels ?? Enumerable.Empty<LadderPriceLevel>()).GetEnumerator()) {
var fg = settings.foreground.rgb_brush();
var currIdx = originIndex;
var currPrice = prices[currIdx];
while (levelsEn.MoveNext()) {
var level = levelsEn.Current;
var ticks = spec.count_ticks(level.Price);
var price = ticks.HasValue ? spec.tickup(0, ticks.Value) : level.Price;
// prices are in descending order, so get diff from curr_price - price
var tickDiff = spec.tickdiff(currPrice, price).assert_value();
if (!MathUtils.try_increment(ref currIdx, tickDiff) || currIdx < 0 || currIdx >= prices.Count) {
continue;
}
currPrice = price;
if (bg is not null) {
var y = currIdx * rowHeight + GRID_LINE_HEIGHT;
var height = rowHeight - GRID_LINE_HEIGHT;
dc.DrawRectangle(bg, null, new Rect(0, y, settings.width, height));
}
var gr = format_text(fmt(level), currIdx, rowHeight, settings, glyphContext);
dc.DrawGlyphRun(fg, gr);
}
}
}
public static IEnumerable<LadderPriceLevel> GetLevels(IEnumerable<(double Price, double Volume, double NotionalValue)> depth, RegionalTickFactors spec) =>
depth.TakeWhile(nps => nps.Volume > 0 && nps.Price.is_real())
.AggregateConsecutiveGroups(
LadderPriceLevel.Empty.ToBuilder,
(bd, rung) => {
bd.Add(rung.Price, rung.Volume, rung.NotionalValue);
return bd;
},
rung => {
var ticks = spec.count_ticks(rung.Price);
return ticks.HasValue ? spec.tickup(0, ticks.Value) : rung.Price;
})
.Select(bd => bd.ToImmutable());
public static void render_sizes(
DrawingVisual dv,
GlyphContext glyph_context,
int origin_index,
IList<double> prices,
NativePriceSize[] rungs,
IEnumerable<(double Price, double Volume, double NotionalValue)> rungs,
double row_height,
RegionalTickFactors spec,
LadderColumnSettings settings,
Func<NativePriceSize, string> fmt,
bool lighten_market_cells,
Color? market_cell_background) {
//var background = new SolidColorBrush(settings.background.rgb_color());
Brush market_cell_background_brush;
if (market_cell_background.HasValue) {
market_cell_background_brush = new SolidColorBrush(market_cell_background.Value);
} else if (lighten_market_cells) {
market_cell_background_brush = settings.background.rgb_color()
.blend((BTSColor)0x2AFFFFFF)
.to_brush();
} else {
market_cell_background_brush = null;
}
// market_cell_background_brush = settings.background.rgb_color().blend((BTSColor)0xFF000000).to_brush();
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())
return;
var foreground = settings.foreground.rgb_brush();
int curr_index = origin_index;
double curr_price = prices[curr_index];
NativePriceSize curr_size = NativePriceSize.NO_MARKET;
var row_tick_groups = rungs
.TakeWhile(nps => !nps.is_nan)
.AggregateConsecutiveGroups(() => NativePriceSize.NO_MARKET,
(aggr, rung) => rung.with(
direct_size: aggr.direct_size + rung.direct_size,
implied_size: aggr.implied_size + rung.implied_size),
rung => {
var ticks = spec.count_ticks(rung.price);
return ticks.HasValue ? spec.tickup(0, ticks.Value) : rung.price;
});
foreach (var rung in row_tick_groups) {
//double price = spec.round_to_tick(rung.price);
var ticks = spec.count_ticks(rung.price);
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;
}
curr_price = price;
if (curr_index >= 0 && curr_index < prices.Count) {
if (market_cell_background_brush != null)
dc.DrawRectangle(market_cell_background_brush, null,
new Rect(0, curr_index * row_height + GRID_LINE_HEIGHT,
settings.width, row_height - GRID_LINE_HEIGHT));
var gr = format_text(fmt(rung), curr_index, row_height, settings, glyph_context);
dc.DrawGlyphRun(foreground, gr);
} else break;
}
}
Func<LadderPriceLevel, string> fmt) {
RenderSizes(
dv,
glyph_context,
origin_index,
prices,
GetLevels(rungs, spec),
row_height,
spec,
settings,
fmt);
}
public static void render_volumes(
@@ -280,7 +297,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 +320,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 +352,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 +383,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 +413,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 +425,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 +442,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" />
@@ -179,7 +182,7 @@
</Style>
<Style x:Key="CancelAll" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
<Setter Property="Grid.Row" Value="2" />
<Setter Property="Grid.Row" Value="0"/>
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center">Cancel All</TextBlock>
@@ -217,10 +220,7 @@
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TT_CancelAll" TargetType="Button" BasedOn="{StaticResource CancelAll}">
<Setter Property="Grid.Row" Value="0"/>
</Style>
<Style x:Key="TT_CancelBuys" TargetType="Button" BasedOn="{StaticResource CancelAll}">
<Style x:Key="CancelBuys" TargetType="Button" BasedOn="{StaticResource CancelAll}">
<Setter Property="Grid.Row" Value="1" />
<Setter Property="Content">
<Setter.Value>
@@ -228,7 +228,7 @@
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TT_CancelSells" TargetType="Button" BasedOn="{StaticResource CancelAll}">
<Style x:Key="CancelSells" TargetType="Button" BasedOn="{StaticResource CancelAll}">
<Setter Property="Grid.Row" Value="2" />
<Setter Property="Content">
<Setter.Value>
@@ -236,83 +236,6 @@
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CancelBuys" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
<Setter Property="Grid.Row" Value="0" />
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center" Foreground="{Binding bid_foreground}">
Cancel Buys
</TextBlock>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{u:BTSColor UI.Button.normal_border}"
Background="{Binding bid_background}"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="True">
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"
RecognizesAccessKey="True"
Name="button_content" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="#99167ed3" />
<Setter
TargetName="Border"
Property="BorderBrush"
Value="{u:BTSColor UI.Button.mouseover_border}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CancelSells" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
<Setter Property="Background" Value="{u:BTSColor MarketData.ask}" />
<Setter Property="Grid.Row" Value="1" />
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center" Foreground="{Binding ask_foreground}">
Cancel Sells
</TextBlock>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{u:BTSColor UI.Button.normal_border}"
Background="{Binding ask_background}"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="True" >
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"
RecognizesAccessKey="True"
Name="button_content" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="#99c00000" />
<Setter
TargetName="Border"
Property="BorderBrush"
Value="{u:BTSColor UI.Button.mouseover_border}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="SettingsButton" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
<Setter Property="Content">
<Setter.Value>
@@ -363,13 +286,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 +447,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 +457,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 +582,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 +609,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 +617,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 +636,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 +656,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 +675,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 +695,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 +713,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 +734,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 +753,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 +772,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 +790,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 +810,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 +831,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 +839,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 +860,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 +869,7 @@
<!-- Volume (price) -->
<Border
Grid.Row="8"
Grid.Row="11"
Background="{Binding
volume_style.background_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
@@ -793,6 +910,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 +920,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 +943,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},
@@ -836,6 +956,13 @@
</Style>
</uag:OrderDetailsPopupControl.Resources>
</uag:OrderDetailsPopupControl>
<ComboBox
Margin="2"
ItemsSource="{Binding spreader_type_choices}"
SelectedItem="{Binding spreader_type}"
Visibility="{Binding HasItems, Mode=OneWay, RelativeSource={RelativeSource Self}, Converter={StaticResource BooleanToVisibilityConverter}}"
/>
<!-- Position into Qty Button -->
<Button
@@ -879,6 +1006,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 +1029,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 +1050,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 +1061,7 @@
<CheckBox
Margin="2"
IsChecked="{Binding auto_clear}"
ToolTip="Clear Size after order"
Visibility="{Binding
show_auto_clear, Converter={StaticResource BooleanToVisibilityConverter}}">
Auto Clear
@@ -973,19 +1104,19 @@
<Button
Command="{Binding cancel_cmd}"
CommandParameter="Bids"
Style="{StaticResource {x:Static sp:LadderView.CancelBuysButtonStyleKey}}"
Style="{StaticResource CancelBuys}"
Visibility="{Binding
show_cancel_buys, Converter={StaticResource BooleanToVisibilityConverter}}" />
<Button
Command="{Binding cancel_cmd}"
CommandParameter="Asks"
Style="{StaticResource {x:Static sp:LadderView.CancelSellsButtonStyleKey}}"
Style="{StaticResource CancelSells}"
Visibility="{Binding
show_cancel_sells, Converter={StaticResource BooleanToVisibilityConverter}}" />
<Button
Command="{Binding cancel_cmd}"
CommandParameter="All"
Style="{StaticResource {x:Static sp:LadderView.CancelAllButtonStyleKey}}"
Style="{StaticResource CancelAll}"
Visibility="{Binding
show_cancel_all, Converter={StaticResource BooleanToVisibilityConverter}}" />
</Grid>
@@ -27,22 +27,6 @@ namespace mbts.spark {
MinZoomFactor = 0.5,
MaxZoomFactor = 1.5;
static LadderView() {
var knobs = config.Configuration.get<bts.config.LadderKnobs>();
MimicTT = knobs.MimicTt;
}
public static bool MimicTT { get; }
public static string CancelBuysButtonStyleKey =>
MimicTT ? "TT_CancelBuys" : "CancelBuys";
public static string CancelSellsButtonStyleKey =>
MimicTT ? "TT_CancelSells" : "CancelSells";
public static string CancelAllButtonStyleKey =>
MimicTT ? "TT_CancelAll" : "CancelAll";
public static IMultiValueConverter PositionToolTipConverter =
ValueConverter.create<AccountTuple?, bool, bool, bool, string>((account, show_day_pos, show_raw_day_pos, show_total_pos) => {
const string day_pos_text = "day", raw_day_pos_text = "raw day", total_pos_text = "total";
@@ -76,14 +60,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();
@@ -5,6 +5,7 @@ using mbts.spark;
using mbts.ui;
using mbts.ui.shell;
using Spark.Api.Data;
using Spark.Core.Data;
using Spark.Core.Disposables;
using Spark.Core.Reflection;
@@ -1,4 +1,4 @@
using bts;
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman;
@@ -223,6 +223,10 @@ namespace mbts.spark {
Add(qty_column = new SizeColumn(
new DoubleMinMax<OrderRecordTupleWithRoot>(
ort => ort.order.order.quantity(), nameof(OrderBookRowVM.quantity))));
// notional value
Add(notional_value_column = new NotionalValueColumn(
new DoubleMinMax<OrderRecordTupleWithRoot>(
ort => ort.notional_value, nameof(OrderBookRowVM.notional_value))));
// edit quantity column
// not usually part of the main order book, but it's useful to add it
// rather than debugging the popup window
@@ -232,6 +236,10 @@ namespace mbts.spark {
new DoubleMinMax<OrderRecordTupleWithRoot>(
ort => ort.order.order.filled_quantity(),
nameof(OrderBookRowVM.filled_quantity))));
// filled notional value
Add(filled_notional_value_column = new FilledNotionalValueColumn(
new DoubleMinMax<OrderRecordTupleWithRoot>(
ort => ort.filled_notional_value, nameof(OrderBookRowVM.filled_notional_value))));
// price
Add(price_column = new SimpleTextColumn<DoubleMinMaxState>(
Columns.Price,
@@ -363,9 +371,11 @@ namespace mbts.spark {
public SimpleTextColumn<OrderSubType[]> sub_type_column { get; }
public SideColumn side_column { get; }
public SizeColumn qty_column { get; }
public NotionalValueColumn notional_value_column { get; }
public EditableQtyColumn edit_qty_column { get; }
public SimpleTextColumn<DoubleMinMaxState> price_column { get; }
public FilledColumn filled_qty_column { get; }
public FilledNotionalValueColumn filled_notional_value_column { get; }
public SimpleTextColumn<DoubleMinMaxState> last_price_column { get; }
public SimpleTextColumn<DoubleMinMaxState> avg_price_column { get; }
public SimpleTextColumn<OrderStatus[]> status_column { get; }
@@ -391,8 +401,10 @@ namespace mbts.spark {
order_sub_types = sub_type_column.filter.get_state(),
sides = side_column.filter.get_state(),
qty = qty_column.filter.get_state(),
notional_value = notional_value_column.filter.get_state(),
price = price_column.filter.get_state(),
qty_filled = filled_qty_column.filter.get_state(),
filled_notional_value = filled_notional_value_column.filter.get_state(),
avg_price = avg_price_column.filter.get_state(),
last_price = last_price_column.filter.get_state(),
statuses = status_column.filter.get_state(),
@@ -415,7 +427,9 @@ namespace mbts.spark {
account_column.filter.load_state(fs.accounts);
price_column.filter.load_state(fs.price);
qty_column.filter.load_state(fs.qty);
notional_value_column.filter.load_state(fs.notional_value);
filled_qty_column.filter.load_state(fs.qty_filled);
filled_notional_value_column.filter.load_state(fs.filled_notional_value);
side_column.filter.load_state(fs.sides);
order_type_column.filter.load_state(fs.order_types);
root_algo_type_column.filter.load_state(fs.root_algo_types);
@@ -645,6 +659,32 @@ namespace mbts.spark {
DefaultCellPadding);
}
public class NotionalValueColumn : OrderbookColumnBase<DoubleMinMaxState>, IRenderColumn<OrderBookRowVM> {
public NotionalValueColumn(IDataGridColumnPersistentFilter<OrderRecordTupleWithRoot, DoubleMinMaxState> filter)
: base(
Columns.NotionalValue,
DefaultColumnHeaders.NotionalValue,
r => r.notional_value_text,
DefaultColumnWidths.NotionalValue,
filter,
DataGridFilterKnownTemplates.DoubleMinMax,
_wrap_comparer(r => r.notional_value)) { }
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(
dc,
_get_glyph_context(row),
font_size,
row.notional_value_text,
position,
row_height,
RenderUtils.get_side_brush(row),
background_brush,
horizontal_alignment,
VerticalAlignment.Center,
DefaultCellPadding);
}
public class FilledColumn : OrderbookColumnBase<DoubleMinMaxState>, IRenderColumn<OrderBookRowVM> {
public FilledColumn(IDataGridColumnPersistentFilter<OrderRecordTupleWithRoot, DoubleMinMaxState> filter)
: base(Columns.Filled, "Filled Qty", r => r.display_filled_quantity.ToString(), DefaultColumnWidths.Qty, filter, DataGridFilterKnownTemplates.DoubleMinMax, _wrap_comparer(r => r.filled_quantity)) { }
@@ -664,6 +704,32 @@ namespace mbts.spark {
DefaultCellPadding);
}
public class FilledNotionalValueColumn : OrderbookColumnBase<DoubleMinMaxState>, IRenderColumn<OrderBookRowVM> {
public FilledNotionalValueColumn(IDataGridColumnPersistentFilter<OrderRecordTupleWithRoot, DoubleMinMaxState> filter)
: base(
Columns.FilledNotionalValue,
DefaultColumnHeaders.FilledNotionalValue,
r => r.filled_notional_value_text,
DefaultColumnWidths.FilledNotionalValue,
filter,
DataGridFilterKnownTemplates.DoubleMinMax,
_wrap_comparer(r => r.filled_notional_value)) { }
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(
dc,
_get_glyph_context(row),
font_size,
row.filled_notional_value_text,
position,
row_height,
RenderUtils.get_side_brush(row),
background_brush,
horizontal_alignment,
VerticalAlignment.Center,
DefaultCellPadding);
}
public class OrderBookOrderTypeColumn : OrderTypeColumn<Columns>, ICVOrderBookColumn {
readonly BindingBase _foreground_binding;
ListSortDirection? _cached_sort_direction;
@@ -1,4 +1,4 @@
using bts;
using bts;
using bts.api;
using bts.api.orderman;
using bts.collections;
@@ -8,6 +8,7 @@ using bts.utils;
using mbts.ui;
using mbts.ui.controls;
using Spark.Api.Data;
using Spark.Api.Orders;
using Spark.Core.Collections;
using Spark.ViewModel.Commands;
@@ -108,6 +109,8 @@ namespace mbts.spark {
public double display_quantity => quantity.safe_divide(_display_qty_divisor);
public string display_quantity_text =>
quantity_display_settings.to_tick_display_settings().format(display_quantity);
public double notional_value => order_tuple.notional_value;
public string notional_value_text => _fmt_price(notional_value);
public TickDisplaySettingsImmutable quantity_display_settings {
get => _quantity_display_settings;
set {
@@ -128,6 +131,8 @@ namespace mbts.spark {
filled_quantity.safe_divide(_display_qty_divisor);
public string display_filled_quantity_text =>
quantity_display_settings.to_tick_display_settings().format(display_filled_quantity);
public double filled_notional_value => order_tuple.filled_notional_value;
public string filled_notional_value_text => _fmt_price(filled_notional_value);
public string last_msg => order.last_msg();
// piq
public uint piq => order_tuple.order.piq ?? uint.MaxValue;
@@ -355,9 +360,15 @@ namespace mbts.spark {
if (_prospective_qty_translator_backing != null)
_prospective_qty_translator_backing.actual_value = quantity;
}
if ((Columns.NotionalValue & dirty) == Columns.NotionalValue) {
RaisePropertyChanged(nameof(notional_value), nameof(notional_value_text));
}
if ((Columns.Filled & dirty) != 0) {
RaisePropertyChanged(nameof(filled_quantity), nameof(display_filled_quantity));
}
if ((Columns.FilledNotionalValue & dirty) == Columns.FilledNotionalValue) {
RaisePropertyChanged(nameof(filled_notional_value), nameof(filled_notional_value_text));
}
// badge fill
if (old_order_tuple.order.meta?.badge_color != order_tuple.order.meta?.badge_color) {
// invalidate by setting to null (will be re-instantiated in the public getter)
@@ -521,6 +532,12 @@ namespace mbts.spark {
if (oldval.order.piq != newval.order.piq) {
dirty |= Columns.Piq;
}
if (oldval.notional_value != newval.notional_value) {
dirty |= Columns.NotionalValue;
}
if (oldval.filled_notional_value != newval.filled_notional_value) {
dirty |= Columns.FilledNotionalValue;
}
return dirty;
}
}
@@ -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;
}
@@ -1,160 +1,174 @@
using bts.api;
using bts.api.orderman;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.utility;
using ProtoBuf;
using Spark.Api.Orders;
using System;
namespace bts.shell.orderbook {
[ProtoContract]
class FilterStates : IEquatable<FilterStates> {
[ProtoMember(1)]
public SecurityCriteria security_criteria;
[ProtoMember(2)]
public string order_key_regex;
[ProtoMember(3)]
public TimeFilterTuple time_created;
[ProtoMember(4)]
public string[] users;
[ProtoMember(5)]
public string[] accounts;
[ProtoMember(6)]
public Venue[] exchanges;
[ProtoMember(7)]
public TimeInForce[] tifs;
//[ProtoMember(8)]
//public CustomOrderDefinition.CustomOneofCase[] order_types;
[ProtoMember(23)]
public AlgoID[] order_types;
[ProtoMember(24)]
public AlgoID[] root_algo_types;
[ProtoMember(9)]
public Side[] sides;
[ProtoMember(10)]
public DoubleMinMaxState qty;
[ProtoMember(11)]
public DoubleMinMaxState price;
[ProtoMember(12)]
public DoubleMinMaxState qty_filled;
[ProtoMember(13)]
public DoubleMinMaxState avg_price;
[ProtoMember(14)]
public DoubleMinMaxState last_price;
[ProtoMember(15)]
public OrderStatus[] statuses;
[ProtoMember(16)]
public string exchange_message_regex;
[ProtoMember(17)]
public TimeFilterTuple last_change;
//[ProtoMember(18)]
//public string security_regex;
[ProtoMember(19)]
public api.orderman.OrderSubType[] order_sub_types;
[ProtoMember(20)]
public string[] symbols;
[ProtoMember(21)]
public string parent_key_regex;
[ProtoMember(22)]
public string attached_target_order_key_regex;
[ProtoMember(25)]
public UIntMinMaxState piq;
public bool Equals(FilterStates other) =>
other != null &&
security_criteria.safe_equals(other.security_criteria) &&
order_key_regex == other.order_key_regex &&
parent_key_regex == other.parent_key_regex &&
attached_target_order_key_regex == other.attached_target_order_key_regex &&
time_created.Equals(other.time_created) &&
EqualityComparer.safe_sequence_equals(users, other.users) &&
EqualityComparer.safe_sequence_equals(accounts, other.accounts) &&
EqualityComparer.safe_sequence_equals(exchanges, other.exchanges) &&
EqualityComparer.safe_sequence_equals(tifs, other.tifs) &&
EqualityComparer.safe_sequence_equals(order_types, other.order_types) &&
EqualityComparer.safe_sequence_equals(root_algo_types, other.root_algo_types) &&
EqualityComparer.safe_sequence_equals(sides, other.sides) &&
qty.safe_equals(other.qty) &&
price.safe_equals(other.price) &&
qty_filled.safe_equals(other.qty_filled) &&
avg_price.safe_equals(other.avg_price) &&
last_price.safe_equals(other.last_price) &&
EqualityComparer.safe_sequence_equals(statuses, other.statuses) &&
exchange_message_regex == other.exchange_message_regex &&
last_change.Equals(other.last_change) &&
EqualityComparer.safe_sequence_equals(order_sub_types, other.order_sub_types) &&
EqualityComparer.safe_sequence_equals(symbols, other.symbols) &&
piq.safe_equals(other.piq);
public override bool Equals(object obj) => Equals(obj as FilterStates);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
security_criteria?.GetHashCode() ?? 0,
order_key_regex?.GetHashCode() ?? 0,
parent_key_regex?.GetHashCode() ?? 0,
attached_target_order_key_regex?.GetHashCode() ?? 0,
time_created.GetHashCode(),
EqualityComparer.safe_aggregate_hash(users),
EqualityComparer.safe_aggregate_hash(accounts),
EqualityComparer.safe_aggregate_hash(exchanges),
EqualityComparer.safe_aggregate_hash(tifs),
EqualityComparer.safe_aggregate_hash(order_types),
EqualityComparer.safe_aggregate_hash(root_algo_types),
EqualityComparer.safe_aggregate_hash(sides),
qty?.GetHashCode() ?? 0,
price?.GetHashCode() ?? 0,
qty_filled?.GetHashCode() ?? 0,
avg_price?.GetHashCode() ?? 0,
last_price?.GetHashCode() ?? 0,
EqualityComparer.safe_aggregate_hash(statuses),
exchange_message_regex?.GetHashCode() ?? 0,
last_change.GetHashCode(),
EqualityComparer.safe_aggregate_hash(order_sub_types),
EqualityComparer.safe_aggregate_hash(symbols),
piq?.GetHashCode() ?? 0);
}
static class FilterStatesExtensions {
public static Func<OrderRecordTupleWithRoot, bool> build_predicate(this FilterStates fs) {
var pb = new ColumnPredicateBuilder<OrderRecordTupleWithRoot>();
// security
pb.add_security_criteria(fs.security_criteria, o => o.order.security);
// regex
pb.add_regex(fs.order_key_regex, o => o.order.order.Key.ToString());
pb.add_regex(fs.exchange_message_regex, o => o.order.order.State.LastMsg);
pb.add_regex(fs.attached_target_order_key_regex, o => o.order.order.attached_target_order_key().ToString());
pb.add_regex(fs.parent_key_regex, o => o.order.order.ParentKey.ToString());
// time
pb.add_time_filter(fs.time_created, o => o.order.order.created());
pb.add_time_filter(fs.last_change, o => o.order.order.last_change());
// discrete values
pb.add_discrete_values(fs.users, o => o.order.order.user());
pb.add_discrete_values(fs.accounts, o => o.order.order.account());
pb.add_discrete_values(fs.exchanges, o => o.order.security?.Exchange ?? Venue.Unset);
pb.add_discrete_values(fs.tifs, o => o.order.order.time_in_force());
pb.add_discrete_values(fs.order_types, o => AlgoID.from_custom_def(o.order.order.Def.CustomDef));
pb.add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root.algo?.key ?? default));
pb.add_discrete_values(fs.order_sub_types, o => o.order.order.sub_type());
pb.add_discrete_values(fs.sides, o => o.order.order.side());
pb.add_discrete_values(fs.statuses, o => o.order.order.status());
pb.add_discrete_values(fs.symbols, o => o.order.security?.Symbol);
// numeric
pb.add_numeric(fs.qty, o => o.order.order.quantity());
pb.add_price(fs.price, o => o.order.order.ui_price(), o => o.order.security);
pb.add_numeric(fs.qty_filled, o => o.order.order.filled_quantity());
pb.add_numeric(fs.avg_price, o => o.order.order.avg_price());
pb.add_numeric(fs.last_price, o => o.order.order.last_price());
pb.add_numeric(fs.piq, o => o.order.piq ?? uint.MaxValue);
return pb.to_predicate();
}
}
}
using bts.api;
using bts.api.orderman;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.utility;
using ProtoBuf;
using Spark.Api.Orders;
using System;
namespace bts.shell.orderbook {
[ProtoContract]
class FilterStates : IEquatable<FilterStates> {
[ProtoMember(1)]
public SecurityCriteria security_criteria;
[ProtoMember(2)]
public string order_key_regex;
[ProtoMember(3)]
public TimeFilterTuple time_created;
[ProtoMember(4)]
public string[] users;
[ProtoMember(5)]
public string[] accounts;
[ProtoMember(6)]
public Venue[] exchanges;
[ProtoMember(7)]
public TimeInForce[] tifs;
//[ProtoMember(8)]
//public CustomOrderDefinition.CustomOneofCase[] order_types;
[ProtoMember(23)]
public AlgoID[] order_types;
[ProtoMember(24)]
public AlgoID[] root_algo_types;
[ProtoMember(9)]
public Side[] sides;
[ProtoMember(10)]
public DoubleMinMaxState qty;
[ProtoMember(26)]
public DoubleMinMaxState notional_value;
[ProtoMember(11)]
public DoubleMinMaxState price;
[ProtoMember(12)]
public DoubleMinMaxState qty_filled;
[ProtoMember(27)]
public DoubleMinMaxState filled_notional_value;
[ProtoMember(13)]
public DoubleMinMaxState avg_price;
[ProtoMember(14)]
public DoubleMinMaxState last_price;
[ProtoMember(15)]
public OrderStatus[] statuses;
[ProtoMember(16)]
public string exchange_message_regex;
[ProtoMember(17)]
public TimeFilterTuple last_change;
//[ProtoMember(18)]
//public string security_regex;
[ProtoMember(19)]
public api.orderman.OrderSubType[] order_sub_types;
[ProtoMember(20)]
public string[] symbols;
[ProtoMember(21)]
public string parent_key_regex;
[ProtoMember(22)]
public string attached_target_order_key_regex;
[ProtoMember(25)]
public UIntMinMaxState piq;
public bool Equals(FilterStates other) =>
other != null &&
security_criteria.safe_equals(other.security_criteria) &&
order_key_regex == other.order_key_regex &&
parent_key_regex == other.parent_key_regex &&
attached_target_order_key_regex == other.attached_target_order_key_regex &&
time_created.Equals(other.time_created) &&
EqualityComparer.safe_sequence_equals(users, other.users) &&
EqualityComparer.safe_sequence_equals(accounts, other.accounts) &&
EqualityComparer.safe_sequence_equals(exchanges, other.exchanges) &&
EqualityComparer.safe_sequence_equals(tifs, other.tifs) &&
EqualityComparer.safe_sequence_equals(order_types, other.order_types) &&
EqualityComparer.safe_sequence_equals(root_algo_types, other.root_algo_types) &&
EqualityComparer.safe_sequence_equals(sides, other.sides) &&
qty.safe_equals(other.qty) &&
notional_value.safe_equals(other.notional_value) &&
price.safe_equals(other.price) &&
qty_filled.safe_equals(other.qty_filled) &&
filled_notional_value.safe_equals(other.filled_notional_value) &&
avg_price.safe_equals(other.avg_price) &&
last_price.safe_equals(other.last_price) &&
EqualityComparer.safe_sequence_equals(statuses, other.statuses) &&
exchange_message_regex == other.exchange_message_regex &&
last_change.Equals(other.last_change) &&
EqualityComparer.safe_sequence_equals(order_sub_types, other.order_sub_types) &&
EqualityComparer.safe_sequence_equals(symbols, other.symbols) &&
piq.safe_equals(other.piq);
public override bool Equals(object obj) => Equals(obj as FilterStates);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
security_criteria?.GetHashCode() ?? 0,
order_key_regex?.GetHashCode() ?? 0,
parent_key_regex?.GetHashCode() ?? 0,
attached_target_order_key_regex?.GetHashCode() ?? 0,
time_created.GetHashCode(),
EqualityComparer.safe_aggregate_hash(users),
EqualityComparer.safe_aggregate_hash(accounts),
EqualityComparer.safe_aggregate_hash(exchanges),
EqualityComparer.safe_aggregate_hash(tifs),
EqualityComparer.safe_aggregate_hash(order_types),
EqualityComparer.safe_aggregate_hash(root_algo_types),
EqualityComparer.safe_aggregate_hash(sides),
qty?.GetHashCode() ?? 0,
notional_value?.GetHashCode() ?? 0,
price?.GetHashCode() ?? 0,
qty_filled?.GetHashCode() ?? 0,
filled_notional_value?.GetHashCode() ?? 0,
avg_price?.GetHashCode() ?? 0,
last_price?.GetHashCode() ?? 0,
EqualityComparer.safe_aggregate_hash(statuses),
exchange_message_regex?.GetHashCode() ?? 0,
last_change.GetHashCode(),
EqualityComparer.safe_aggregate_hash(order_sub_types),
EqualityComparer.safe_aggregate_hash(symbols),
piq?.GetHashCode() ?? 0);
}
static class FilterStatesExtensions {
public static Func<OrderRecordTupleWithRoot, bool> build_predicate(this FilterStates fs) {
var pb = new ColumnPredicateBuilder<OrderRecordTupleWithRoot>();
// security
pb.add_security_criteria(fs.security_criteria, o => o.order.security);
// regex
pb.add_regex(fs.order_key_regex, o => o.order.order.Key.ToString());
pb.add_regex(fs.exchange_message_regex, o => o.order.order.State.LastMsg);
pb.add_regex(fs.attached_target_order_key_regex, o => o.order.order.attached_target_order_key().ToString());
pb.add_regex(fs.parent_key_regex, o => o.order.order.ParentKey.ToString());
// time
pb.add_time_filter(fs.time_created, o => o.order.order.created());
pb.add_time_filter(fs.last_change, o => o.order.order.last_change());
// discrete values
pb.add_discrete_values(fs.users, o => o.order.order.user());
pb.add_discrete_values(fs.accounts, o => o.order.order.account());
pb.add_discrete_values(fs.exchanges, o => o.order.security?.Exchange ?? Venue.Unset);
pb.add_discrete_values(fs.tifs, o => o.order.order.time_in_force());
pb.add_discrete_values(fs.order_types, o => AlgoID.from_custom_def(o.order.order.Def.CustomDef));
pb.add_discrete_values(
Array.ConvertAll(fs.root_algo_types, a => (AlgoID?)a),
o => o.root.algo.HasValue
? AlgoID.from_algo_key(o.root.algo.Value.key)
: null);
pb.add_discrete_values(fs.order_sub_types, o => o.order.order.sub_type());
pb.add_discrete_values(fs.sides, o => o.order.order.side());
pb.add_discrete_values(fs.statuses, o => o.order.order.status());
pb.add_discrete_values(fs.symbols, o => o.order.security?.Symbol);
// numeric
pb.add_numeric(fs.qty, o => o.order.order.quantity());
pb.add_numeric(fs.notional_value, o => o.notional_value);
pb.add_price(fs.price, o => o.order.order.ui_price(), o => o.order.security);
pb.add_numeric(fs.qty_filled, o => o.order.order.filled_quantity());
pb.add_numeric(fs.filled_notional_value, o => o.filled_notional_value);
pb.add_numeric(fs.avg_price, o => o.order.order.avg_price());
pb.add_numeric(fs.last_price, o => o.order.order.last_price());
pb.add_numeric(fs.piq, o => o.order.piq ?? uint.MaxValue);
return pb.to_predicate();
}
}
}
@@ -29,7 +29,7 @@ namespace bts.shell.orderbook {
static OrderBookGridSettingsManager() {
// make font-weight immutable for all columns (the font-weight rule is hard-coded: Regular for root-orders, Light for all others)
// TODO: Remove the immutable font-weight flags when cell animation is ready
var col_settings_flags = EnumHelper.get_values<Columns>().ToDictionary(
var col_settings_flags = Enum.GetValuesAsUnderlyingType<Columns>().Cast<Columns>().ToDictionary(
c => c.ToString(), c => GridColumnSettingsFeatureFlags.ImmutableFontWeight);
// make font-color immutable for Side, Size, and Filled columns (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
// TODO: Remove the immutable font-color flags when cell animation is ready
@@ -1,270 +1,277 @@
using bts.api.orderman;
using bts.shell.fills;
using mbts;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
using GridSettings = mbts.ui.settings.GridSettings;
namespace bts.shell.orderbook {
using ColHeader = mbts.ui.controls.DIYGrid.DefaultColumnHeaders;
using ColWidth = mbts.ui.controls.DIYGrid.DefaultColumnWidths;
[ProtoContract]
class OrderBookStartupData {
const bool
_default_show_right_panel = true,
_default_show_order_params = true;
const double
_default_right_panel_width = 400,
_default_order_params_height = 250,
_default_fills_height = 300,
_default_ladder_height = 350;
[ProtoMember(16)]
OrderBookRightPanelSettings _right_panel;
// ALREADY USED TAGS
// [ProtoMember(5)]
// public readonly List<SortingCriteria> sort_criteria;
public OrderBookRightPanelSettings right_panel => _right_panel ?? (_right_panel = new OrderBookRightPanelSettings());
/// TODO: remove properties below, and redirect callers to <see cref="right_panel"/>
// right panel (host)
public bool show_right_panel { get => right_panel.show; set => right_panel.show = value; }
public double right_panel_width { get => right_panel.width; set => right_panel.width = value; }
// order params
public bool show_order_params { get => right_panel.stack.show_order_params; set => right_panel.stack.show_order_params = value; }
public double order_params_height { get; set; }
// ladder
public bool show_ladder { get; set; }
public double ladder_height { get => right_panel.stack.ladder_height; set => right_panel.stack.ladder_height = value; }
// fills
public bool show_fills { get => right_panel.stack.show_fills; set => right_panel.stack.show_fills = value; }
public double fills_height { get => right_panel.stack.fills_height; set => right_panel.stack.fills_height = value; }
// filters
[ProtoMember(10)]
public FilterStates filters;
// grid
[ProtoMember(15)]
[Obsolete]
public GridSettings grid { get; set; }
// orderbook grid
[ProtoMember(17)]
public OrderBookGridSettings orderbook_grid { get; set; }
// fills settings
[ProtoMember(26)]
public OrderBookFillsGridSettings fills_settings { get; set; }
// predicate animations
[ProtoMember(20)]
[Obsolete]
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
// use defaults
[ProtoMember(25)]
public bool use_defaults;
// menu display
[ProtoMember(27)]
public SparkMenuDisplaySettings menu_display { get; set; }
/// <summary>
/// Please use <see cref="Default"/> to get a new instance (the only reason the default constructor still exists is for ProtoBuf deserialization).
/// </summary>
[Obsolete]
public OrderBookStartupData() {
show_right_panel = _default_show_right_panel;
show_order_params = _default_show_order_params;
right_panel_width = _default_right_panel_width;
order_params_height = _default_order_params_height;
fills_height = _default_fills_height;
ladder_height = _default_ladder_height;
}
public OrderBookStartupData(FilterStates filters, OrderBookGridSettings orderbook_grid = default, bool show_right_panel = false, double right_panel_width = 0, double fills_height = 0, bool show_fills = false, bool use_defaults = false) {
this.filters = filters;
this.use_defaults = use_defaults;
this.show_right_panel = show_right_panel;
this.right_panel_width = right_panel_width;
this.fills_height = fills_height;
this.show_fills = show_fills;
this.orderbook_grid = orderbook_grid;
#pragma warning disable CS0612 // Type or member is obsolete
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
if (predicate_animations != null)
_predicate_animations.AddRange(predicate_animations);
#pragma warning restore CS0612 // Type or member is obsolete
}
#pragma warning disable CS0612 // Type or member is obsolete
[Obsolete]
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
public OrderBookGridSettings grid_settings_wrapper() {
if (use_defaults)
return null;
if (orderbook_grid == null) {
if (grid == null)
grid = _default_general_grid;
orderbook_grid = new OrderBookGridSettings(grid.Clone(), _predicate_animations);
}
return orderbook_grid;
}
#pragma warning restore CS0612 // Type or member is obsolete
public static OrderBookStartupData Default =>
new OrderBookStartupData(
new FilterStates { statuses = new[] { OrderStatus.Working } },
DefaultGrid,
_default_show_right_panel,
_default_right_panel_width,
_default_fills_height,
use_defaults: true) {
show_order_params = _default_show_order_params,
order_params_height = _default_order_params_height,
fills_settings = DefaultFillsGrid
};
public static OrderBookGridSettings DefaultGrid => new OrderBookGridSettings(_default_general_grid) {
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
};
public static OrderBookFillsGridSettings DefaultFillsGrid {
get {
var default_fills = FillsSettings.Default();
var rv = new OrderBookFillsGridSettings {
sorting_criteria = default_fills.sorting_criteria.ToList(),
filters = default_fills.filters,
general = FillsSettings.MiniGridSettings
};
return rv;
}
}
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.standard, DefaultColumns.sorting_criteria);
public static OrderBookGridSettings default_mini_grid => new OrderBookGridSettings(GridSettings.Default.with(DefaultColumns.mini, DefaultColumns.sorting_criteria)) {
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
};
static class DefaultColumns {
// standard
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.ToString(), DIYGrid.DefaultColumnHeaders.Security, width: DIYGrid.DefaultColumnWidths.Security);
public static GridColumnSettings symbol => new GridColumnSettings(Columns.Symbol.ToString(), DIYGrid.DefaultColumnHeaders.Symbol, width: DIYGrid.DefaultColumnWidths.Symbol, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings time_created => new GridColumnSettings(Columns.Time.ToString(), "Time", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings last_change => new GridColumnSettings(Columns.LastChange.ToString(), "Last Change", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings user => new GridColumnSettings(Columns.User.ToString(), DIYGrid.DefaultColumnHeaders.User, width: DIYGrid.DefaultColumnWidths.User, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings account => new GridColumnSettings(Columns.Account.ToString(), DIYGrid.DefaultColumnHeaders.Account, width: DIYGrid.DefaultColumnWidths.Account, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings exchange => new GridColumnSettings(Columns.Exchange.ToString(), DIYGrid.DefaultColumnHeaders.Exchange, width: DIYGrid.DefaultColumnWidths.Exchange, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings time_in_force => new GridColumnSettings(Columns.TimeInForce.ToString(), DIYGrid.DefaultColumnHeaders.TimeInForce, width: DIYGrid.DefaultColumnWidths.TimeInForce);
public static GridColumnSettings order_type => new GridColumnSettings(Columns.OrderType.ToString(), DIYGrid.DefaultColumnHeaders.OrderType, width: DIYGrid.DefaultColumnWidths.OrderType);
public static GridColumnSettings root_algo_type => new GridColumnSettings(Columns.RootAlgoType.ToString(), DIYGrid.DefaultColumnHeaders.RootAlgoType, width: DIYGrid.DefaultColumnWidths.OrderType);
public static GridColumnSettings sub_type => new GridColumnSettings(Columns.SubType.ToString(), "Sub Type", width: 80, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings side => new(
Columns.Side.ToString(), ColHeader.Side,
width: ColWidth.Side,
horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings quantity => new GridColumnSettings(Columns.Size.ToString(), "Qty", width: DIYGrid.DefaultColumnWidths.Qty, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings filled_quantity => new(
Columns.Filled.ToString(),
ColHeader.FilledQuantity,
width: ColWidth.Filled,
horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings avg_price => new(
Columns.AvgFilled.ToString(),
ColHeader.AvgFilled,
width: ColWidth.AveragePrice,
horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings last_price => new GridColumnSettings(Columns.LastPrice.ToString(), "Last Filled", width: DIYGrid.DefaultColumnWidths.LastPrice, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings status => new GridColumnSettings(Columns.Status.ToString(), "Status", width: 80, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings last_message => new GridColumnSettings(Columns.LastMessage.ToString(), "Last Message", width: DIYGrid.DefaultColumnWidths.LastMessage, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, is_visible: false, width: DIYGrid.DefaultColumnWidths.SecurityKey, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings piq => new GridColumnSettings(Columns.Piq.ToString(), "PIQ", width: DIYGrid.DefaultColumnWidths.Qty, horizontal_alignment: HorizontalAlignment.Right);
// editable
public static GridColumnSettings cancel => new GridColumnSettings(Columns.Cancel.ToString(), DIYGrid.DefaultColumnHeaders.Cancel, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Cancel);
public static GridColumnSettings attach_detach => new GridColumnSettings(Columns.Attach.ToString(), DIYGrid.DefaultColumnHeaders.AttachDetach, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.AttachDetach);
public static GridColumnSettings update => new GridColumnSettings(Columns.Update.ToString(), DIYGrid.DefaultColumnHeaders.Update, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Update);
public static GridColumnSettings edit_quantity => new GridColumnSettings(Columns.EditableQty.ToString(), DIYGrid.DefaultColumnHeaders.Qty, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.EditableSize);
public static GridColumnSettings edit_price => new GridColumnSettings(Columns.EditablePrice.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.EditablePrice);
public static GridColumnSettings commit => new GridColumnSettings(Columns.Commit.ToString(), width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Commit);
public static IEnumerable<SortingCriteria> sorting_criteria {
get {
yield return new SortingCriteria(time_created.id, false);
}
}
public static IEnumerable<GridColumnSettings> standard {
get {
yield return security;
yield return symbol;
yield return time_created;
yield return last_change;
yield return user;
yield return account;
yield return exchange;
yield return time_in_force;
yield return order_type;
yield return root_algo_type;
yield return sub_type;
yield return side;
yield return quantity;
yield return price;
yield return piq;
yield return filled_quantity;
yield return avg_price;
yield return last_price;
yield return status;
yield return last_message;
yield return security_key;
yield return order_key;
yield return parent_key;
yield return attached_target_order_key;
}
}
public static IEnumerable<GridColumnSettings> editable {
get {
yield return cancel;
yield return update;
yield return edit_quantity;
yield return edit_price;
yield return commit;
yield return attach_detach;
}
}
private static HashSet<GridColumnSettings> _mini_mode_columns_to_hide = new HashSet<GridColumnSettings> {
symbol, last_change, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key, attached_target_order_key
};
public static IEnumerable<GridColumnSettings> mini {
get {
foreach (var col_setting in editable)
yield return col_setting;
foreach (var col_setting in standard) {
if (_mini_mode_columns_to_hide.Contains(col_setting)) {
yield return col_setting.with(is_visible: false);
} else {
yield return col_setting;
}
}
}
}
}
}
}
using bts.api.orderman;
using bts.shell.fills;
using mbts;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
using GridSettings = mbts.ui.settings.GridSettings;
namespace bts.shell.orderbook {
using ColHeader = mbts.ui.controls.DIYGrid.DefaultColumnHeaders;
using ColWidth = mbts.ui.controls.DIYGrid.DefaultColumnWidths;
[ProtoContract]
class OrderBookStartupData {
const bool
_default_show_right_panel = true,
_default_show_order_params = true;
const double
_default_right_panel_width = 400,
_default_order_params_height = 250,
_default_fills_height = 300,
_default_ladder_height = 350;
[ProtoMember(16)]
OrderBookRightPanelSettings _right_panel;
// ALREADY USED TAGS
// [ProtoMember(5)]
// public readonly List<SortingCriteria> sort_criteria;
public OrderBookRightPanelSettings right_panel => _right_panel ?? (_right_panel = new OrderBookRightPanelSettings());
/// TODO: remove properties below, and redirect callers to <see cref="right_panel"/>
// right panel (host)
public bool show_right_panel { get => right_panel.show; set => right_panel.show = value; }
public double right_panel_width { get => right_panel.width; set => right_panel.width = value; }
// order params
public bool show_order_params { get => right_panel.stack.show_order_params; set => right_panel.stack.show_order_params = value; }
public double order_params_height { get; set; }
// ladder
public bool show_ladder { get; set; }
public double ladder_height { get => right_panel.stack.ladder_height; set => right_panel.stack.ladder_height = value; }
// fills
public bool show_fills { get => right_panel.stack.show_fills; set => right_panel.stack.show_fills = value; }
public double fills_height { get => right_panel.stack.fills_height; set => right_panel.stack.fills_height = value; }
// filters
[ProtoMember(10)]
public FilterStates filters;
// grid
[ProtoMember(15)]
[Obsolete]
public GridSettings grid { get; set; }
// orderbook grid
[ProtoMember(17)]
public OrderBookGridSettings orderbook_grid { get; set; }
// fills settings
[ProtoMember(26)]
public OrderBookFillsGridSettings fills_settings { get; set; }
// predicate animations
[ProtoMember(20)]
[Obsolete]
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
// use defaults
[ProtoMember(25)]
public bool use_defaults;
// menu display
[ProtoMember(27)]
public SparkMenuDisplaySettings menu_display { get; set; }
/// <summary>
/// Please use <see cref="Default"/> to get a new instance (the only reason the default constructor still exists is for ProtoBuf deserialization).
/// </summary>
[Obsolete]
public OrderBookStartupData() {
show_right_panel = _default_show_right_panel;
show_order_params = _default_show_order_params;
right_panel_width = _default_right_panel_width;
order_params_height = _default_order_params_height;
fills_height = _default_fills_height;
ladder_height = _default_ladder_height;
}
public OrderBookStartupData(FilterStates filters, OrderBookGridSettings orderbook_grid = default, bool show_right_panel = false, double right_panel_width = 0, double fills_height = 0, bool show_fills = false, bool use_defaults = false) {
this.filters = filters;
this.use_defaults = use_defaults;
this.show_right_panel = show_right_panel;
this.right_panel_width = right_panel_width;
this.fills_height = fills_height;
this.show_fills = show_fills;
this.orderbook_grid = orderbook_grid;
#pragma warning disable CS0612 // Type or member is obsolete
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
if (predicate_animations != null)
_predicate_animations.AddRange(predicate_animations);
#pragma warning restore CS0612 // Type or member is obsolete
}
#pragma warning disable CS0612 // Type or member is obsolete
[Obsolete]
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
public OrderBookGridSettings grid_settings_wrapper() {
if (use_defaults)
return null;
if (orderbook_grid == null) {
if (grid == null)
grid = _default_general_grid;
orderbook_grid = new OrderBookGridSettings(grid.Clone(), _predicate_animations);
}
return orderbook_grid;
}
#pragma warning restore CS0612 // Type or member is obsolete
public static OrderBookStartupData Default =>
new OrderBookStartupData(
new FilterStates { statuses = new[] { OrderStatus.Working } },
DefaultGrid,
_default_show_right_panel,
_default_right_panel_width,
_default_fills_height,
use_defaults: true) {
show_order_params = _default_show_order_params,
order_params_height = _default_order_params_height,
fills_settings = DefaultFillsGrid
};
public static OrderBookGridSettings DefaultGrid => new OrderBookGridSettings(_default_general_grid) {
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
};
public static OrderBookFillsGridSettings DefaultFillsGrid {
get {
var default_fills = FillsSettings.Default();
var rv = new OrderBookFillsGridSettings {
sorting_criteria = default_fills.sorting_criteria.ToList(),
filters = default_fills.filters,
general = FillsSettings.MiniGridSettings
};
return rv;
}
}
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.standard, DefaultColumns.sorting_criteria);
public static OrderBookGridSettings default_mini_grid => new OrderBookGridSettings(GridSettings.Default.with(DefaultColumns.mini, DefaultColumns.sorting_criteria)) {
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
};
static class DefaultColumns {
// standard
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.ToString(), DIYGrid.DefaultColumnHeaders.Security, width: DIYGrid.DefaultColumnWidths.Security);
public static GridColumnSettings symbol => new GridColumnSettings(Columns.Symbol.ToString(), DIYGrid.DefaultColumnHeaders.Symbol, width: DIYGrid.DefaultColumnWidths.Symbol, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings time_created => new GridColumnSettings(Columns.Time.ToString(), "Time", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings last_change => new GridColumnSettings(Columns.LastChange.ToString(), "Last Change", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings user => new GridColumnSettings(Columns.User.ToString(), DIYGrid.DefaultColumnHeaders.User, width: DIYGrid.DefaultColumnWidths.User, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings account => new GridColumnSettings(Columns.Account.ToString(), DIYGrid.DefaultColumnHeaders.Account, width: DIYGrid.DefaultColumnWidths.Account, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings exchange => new GridColumnSettings(Columns.Exchange.ToString(), DIYGrid.DefaultColumnHeaders.Exchange, width: DIYGrid.DefaultColumnWidths.Exchange, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings time_in_force => new GridColumnSettings(Columns.TimeInForce.ToString(), DIYGrid.DefaultColumnHeaders.TimeInForce, width: DIYGrid.DefaultColumnWidths.TimeInForce);
public static GridColumnSettings order_type => new GridColumnSettings(Columns.OrderType.ToString(), DIYGrid.DefaultColumnHeaders.OrderType, width: DIYGrid.DefaultColumnWidths.OrderType);
public static GridColumnSettings root_algo_type => new GridColumnSettings(Columns.RootAlgoType.ToString(), DIYGrid.DefaultColumnHeaders.RootAlgoType, width: DIYGrid.DefaultColumnWidths.OrderType);
public static GridColumnSettings sub_type => new GridColumnSettings(Columns.SubType.ToString(), "Sub Type", width: 80, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings side => new(
Columns.Side.ToString(), ColHeader.Side,
width: ColWidth.Side,
horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings quantity => new GridColumnSettings(Columns.Size.ToString(), "Qty", width: DIYGrid.DefaultColumnWidths.Qty, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings notional_value => new GridColumnSettings(Columns.NotionalValue.ToString(), DIYGrid.DefaultColumnHeaders.NotionalValue, width: DIYGrid.DefaultColumnWidths.NotionalValue, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings filled_quantity => new(
Columns.Filled.ToString(),
ColHeader.FilledQuantity,
width: ColWidth.Filled,
horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings filled_notional_value => new(
Columns.FilledNotionalValue.ToString(),
ColHeader.FilledNotionalValue,
width: ColWidth.FilledNotionalValue,
horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings avg_price => new(
Columns.AvgFilled.ToString(),
ColHeader.AvgFilled,
width: ColWidth.AveragePrice,
horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings last_price => new GridColumnSettings(Columns.LastPrice.ToString(), "Last Filled", width: DIYGrid.DefaultColumnWidths.LastPrice, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings status => new GridColumnSettings(Columns.Status.ToString(), "Status", width: 80, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings last_message => new GridColumnSettings(Columns.LastMessage.ToString(), "Last Message", width: DIYGrid.DefaultColumnWidths.LastMessage, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, is_visible: false, width: DIYGrid.DefaultColumnWidths.SecurityKey, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings piq => new GridColumnSettings(Columns.Piq.ToString(), "PIQ", width: DIYGrid.DefaultColumnWidths.Qty, horizontal_alignment: HorizontalAlignment.Right);
// editable
public static GridColumnSettings cancel => new GridColumnSettings(Columns.Cancel.ToString(), DIYGrid.DefaultColumnHeaders.Cancel, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Cancel);
public static GridColumnSettings attach_detach => new GridColumnSettings(Columns.Attach.ToString(), DIYGrid.DefaultColumnHeaders.AttachDetach, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.AttachDetach);
public static GridColumnSettings update => new GridColumnSettings(Columns.Update.ToString(), DIYGrid.DefaultColumnHeaders.Update, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Update);
public static GridColumnSettings edit_quantity => new GridColumnSettings(Columns.EditableQty.ToString(), DIYGrid.DefaultColumnHeaders.Qty, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.EditableSize);
public static GridColumnSettings edit_price => new GridColumnSettings(Columns.EditablePrice.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.EditablePrice);
public static GridColumnSettings commit => new GridColumnSettings(Columns.Commit.ToString(), width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Commit);
public static IEnumerable<SortingCriteria> sorting_criteria {
get {
yield return new SortingCriteria(time_created.id, false);
}
}
public static IEnumerable<GridColumnSettings> standard {
get {
yield return security;
yield return symbol;
yield return time_created;
yield return last_change;
yield return user;
yield return account;
yield return exchange;
yield return time_in_force;
yield return order_type;
yield return root_algo_type;
yield return sub_type;
yield return side;
yield return quantity;
yield return price;
yield return piq;
yield return filled_quantity;
yield return avg_price;
yield return last_price;
yield return status;
yield return last_message;
yield return security_key;
yield return order_key;
yield return parent_key;
yield return attached_target_order_key;
}
}
public static IEnumerable<GridColumnSettings> editable {
get {
yield return cancel;
yield return update;
yield return edit_quantity;
yield return edit_price;
yield return commit;
yield return attach_detach;
}
}
private static HashSet<GridColumnSettings> _mini_mode_columns_to_hide = new HashSet<GridColumnSettings> {
symbol, last_change, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key, attached_target_order_key
};
public static IEnumerable<GridColumnSettings> mini {
get {
foreach (var col_setting in editable)
yield return col_setting;
foreach (var col_setting in standard) {
if (_mini_mode_columns_to_hide.Contains(col_setting)) {
yield return col_setting.with(is_visible: false);
} else {
yield return col_setting;
}
}
}
}
}
}
}
@@ -1,107 +1,109 @@
using bts.api.orderman;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.converters;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace mbts.spark {
internal partial class OrderBookGridView : DIYGrid {
public const string
SubTypeFilterTemplateKey = "SubTypeFilterTemplate",
ExpandCollapseToggleStyleKey = "ExpandCollapseToggleStyle",
RowStyleKey = "RowStyle",
RowTemplateKey = "RowTemplateKey",
CVRowStyleKey = "CVRowStyle",
CVRowTemplateKey = "CVRowTemplateKey";
// IMPORTANT: these enums are stored as strings in the general-purpose GridColumnSettings proto, and are used to ID THE COLUMNS!
// Do NOT RENAME them! However, if you must, be sure to write an IShellWindowMigrationRule or something to account for backwards compatability.
public enum ColumnKeys {
Security = 1,
Symbol = 1 << 1,
Time = 1 << 2,
LastChange = 1 << 3,
User = 1 << 4,
Account = 1 << 5,
Exchange = 1 << 6,
TimeInForce = 1 << 7,
OrderType = 1 << 8,
SubType = 1 << 9,
Side = 1 << 10,
Size = 1 << 11,
Price = 1 << 12,
Filled = 1 << 13,
AvgFilled = 1 << 14,
LastPrice = 1 << 15,
Status = 1 << 16,
LastMessage = 1 << 17,
SecurityKey = 1 << 18,
OrderKey = 1 << 19,
ParentKey = 1 << 20,
AttachedTargetOrderKey = 1 << 21,
Cancel = 1 << 22,
EditableQty = 1 << 23,
EditablePrice = 1 << 24,
Attach = 1 << 25,
Detach = 1 << 26,
Update = 1 << 27,
Commit = 1 << 28,
RootAlgoType = 1 << 29,
Piq = 1 << 30,
Check = 1 << 31
}
public OrderBookGridView() {
InitializeComponent();
var row_template_factory = new FrameworkElementFactory(typeof(OrderBookRowView));
row_template_factory.SetValue(OrderBookRowView.BackgroundProperty, RowBackgroundHelper.row_background_binding);
RowTemplate = new DataTemplate { VisualTree = row_template_factory };
}
static class RowBackgroundHelper {
public static readonly BindingBase row_background_binding;
static RowBackgroundHelper() {
var bg_binding = new MultiBinding();
bg_binding.Bindings.Add(new Binding(nameof(OrderBookRowVM.status)));
bg_binding.Bindings.Add(new Binding(nameof(ListBoxItem.IsSelected)) { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(ListBoxItem), 1) });
double overlay_opacity = config.Configuration.get<bts.config.OrderBookOptions>().StatusColorOpacityWhenSelected;
if (overlay_opacity > 0.1 && overlay_opacity < 0.9) {
bg_binding.Converter = ValueConverter.create<OrderStatus, bool, Brush>((status, is_selected) => {
var color = _status_color(status);
return color.HasValue ? new SolidColorBrush(color.Value) { Opacity = is_selected ? overlay_opacity : 1.0 } : null;
});
} else {
bg_binding.Converter = ValueConverter.create<OrderStatus, bool, Brush>((status, is_selected) => {
if (is_selected)
return null;
var color = _status_color(status);
return color.HasValue ? new SolidColorBrush(color.Value) : null;
});
}
row_background_binding = bg_binding;
}
static Color? _status_color(OrderStatus status) {
switch (status) {
case OrderStatus.Canceled: return BTSColors.Orders.canceled_order_background;
case OrderStatus.Completed: return BTSColors.Orders.completed_order_background;
case OrderStatus.Disconnected:
case OrderStatus.RejectedBySpark:
case OrderStatus.Error:
return BTSColors.Orders.error_order_background;
default: return null;
}
}
}
}
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM> {
public OrderBookRowView() : base() { }
}
}
using bts.api.orderman;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.converters;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace mbts.spark {
internal partial class OrderBookGridView : DIYGrid {
public const string
SubTypeFilterTemplateKey = "SubTypeFilterTemplate",
ExpandCollapseToggleStyleKey = "ExpandCollapseToggleStyle",
RowStyleKey = "RowStyle",
RowTemplateKey = "RowTemplateKey",
CVRowStyleKey = "CVRowStyle",
CVRowTemplateKey = "CVRowTemplateKey";
// IMPORTANT: these enums are stored as strings in the general-purpose GridColumnSettings proto, and are used to ID THE COLUMNS!
// Do NOT RENAME them! However, if you must, be sure to write an IShellWindowMigrationRule or something to account for backwards compatability.
public enum ColumnKeys : long {
Security = 1,
Symbol = 1 << 1,
Time = 1 << 2,
LastChange = 1 << 3,
User = 1 << 4,
Account = 1 << 5,
Exchange = 1 << 6,
TimeInForce = 1 << 7,
OrderType = 1 << 8,
SubType = 1 << 9,
Side = 1 << 10,
Size = 1 << 11,
Price = 1 << 12,
Filled = 1 << 13,
AvgFilled = 1 << 14,
LastPrice = 1 << 15,
Status = 1 << 16,
LastMessage = 1 << 17,
SecurityKey = 1 << 18,
OrderKey = 1 << 19,
ParentKey = 1 << 20,
AttachedTargetOrderKey = 1 << 21,
Cancel = 1 << 22,
EditableQty = 1 << 23,
EditablePrice = 1 << 24,
Attach = 1 << 25,
Detach = 1 << 26,
Update = 1 << 27,
Commit = 1 << 28,
RootAlgoType = 1 << 29,
Piq = 1 << 30,
Check = 1 << 31,
NotionalValue = 1L << 32,
FilledNotionalValue = 1L << 33,
}
public OrderBookGridView() {
InitializeComponent();
var row_template_factory = new FrameworkElementFactory(typeof(OrderBookRowView));
row_template_factory.SetValue(OrderBookRowView.BackgroundProperty, RowBackgroundHelper.row_background_binding);
RowTemplate = new DataTemplate { VisualTree = row_template_factory };
}
static class RowBackgroundHelper {
public static readonly BindingBase row_background_binding;
static RowBackgroundHelper() {
var bg_binding = new MultiBinding();
bg_binding.Bindings.Add(new Binding(nameof(OrderBookRowVM.status)));
bg_binding.Bindings.Add(new Binding(nameof(ListBoxItem.IsSelected)) { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(ListBoxItem), 1) });
double overlay_opacity = config.Configuration.get<bts.config.OrderBookOptions>().StatusColorOpacityWhenSelected;
if (overlay_opacity > 0.1 && overlay_opacity < 0.9) {
bg_binding.Converter = ValueConverter.create<OrderStatus, bool, Brush>((status, is_selected) => {
var color = _status_color(status);
return color.HasValue ? new SolidColorBrush(color.Value) { Opacity = is_selected ? overlay_opacity : 1.0 } : null;
});
} else {
bg_binding.Converter = ValueConverter.create<OrderStatus, bool, Brush>((status, is_selected) => {
if (is_selected)
return null;
var color = _status_color(status);
return color.HasValue ? new SolidColorBrush(color.Value) : null;
});
}
row_background_binding = bg_binding;
}
static Color? _status_color(OrderStatus status) {
switch (status) {
case OrderStatus.Canceled: return BTSColors.Orders.canceled_order_background;
case OrderStatus.Completed: return BTSColors.Orders.completed_order_background;
case OrderStatus.Disconnected:
case OrderStatus.RejectedBySpark:
case OrderStatus.Error:
return BTSColors.Orders.error_order_background;
default: return null;
}
}
}
}
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM> {
public OrderBookRowView() : base() { }
}
}
@@ -1,526 +1,317 @@
using bts.api;
using bts.api.orderman;
using bts.api.position;
using bts.utils;
using mbts;
using mbts.analytics;
using mbts.spark;
using mbts.spark.utilities;
using mbts.ui;
using mbts.utils;
using Spark.Api.Orders;
using Spark.Api.Position;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
using PNLKey = bts.AccountSecurityKey;
namespace bts.shell.pnl {
public enum PNLCalculationType {
None,
Bid,
Ask,
Opposite,
Same,
[Description("Cascade")]
Waterfall,
Last,
Mid,
Settle
}
readonly struct WorkingOrderData {
public readonly WorkingOrderSideData long_data, short_data;
WorkingOrderData(WorkingOrderSideData long_data, WorkingOrderSideData short_data) {
this.long_data = long_data;
this.short_data = short_data;
}
public static WorkingOrderData Empty =>
new WorkingOrderData(WorkingOrderSideData.EmptyLong, WorkingOrderSideData.EmptyShort);
public double top_bid =>
long_data.data.Count > 0 ?
long_data.data.First().Key :
double.NaN;
public double top_ask =>
short_data.data.Count > 0 ?
short_data.data.First().Key :
double.NaN;
public PositionPriceSize summary =>
new PositionPriceSize(long_data.working_total, long_data.avg_price, short_data.working_total, short_data.avg_price);
public static WorkingOrderData Create(
IEnumerable<OrderRecordImmutable> orders, QuantityConversionData<double> qty_conversion) =>
Create(orders.Select(o => (o, qty_conversion)));
public static WorkingOrderData Create(
IEnumerable<(OrderRecordImmutable order, QuantityConversionData<double> qty_conversion)> orders) =>
orders.Aggregate(Empty, (seed, tup) => seed.Add(tup.order, tup.qty_conversion));
public WorkingOrderData Add(OrderRecordImmutable order, QuantityConversionData<double> qty_conversion) {
if (order.working_quantity() <= 0)
return new WorkingOrderData(long_data, short_data);
switch (order.side()) {
case Side.Buy: return new WorkingOrderData(long_data.Add(order, qty_conversion), short_data);
case Side.Sell: return new WorkingOrderData(long_data, short_data.Add(order, qty_conversion));
default: throw ExceptionBuilder.Argument.unexpected_case(order.side(), "order.side");
}
}
public WorkingOrderData Remove(OrderRecordImmutable order) {
if (order.working_quantity() <= 0)
return new WorkingOrderData(long_data, short_data);
switch (order.side()) {
case Side.Buy: return new WorkingOrderData(long_data.Remove(order.Key, order.price()), short_data);
case Side.Sell: return new WorkingOrderData(long_data, short_data.Remove(order.Key, order.price()));
default: throw ExceptionBuilder.Argument.unexpected_case(order.side(), "order.side");
}
}
}
readonly struct WorkingOrderSideData {
public readonly double avg_price, display_avg_price;
public readonly ImmutableSortedDictionary<double, ImmutableDictionary<uint, (OrderRecordImmutable order, QuantityConversionData<double> qty_conversion)>> data;
public readonly double working_total;
public readonly double display_working_total;
WorkingOrderSideData(
double avg_price,
double working_total,
double display_avg_price,
double display_working_total,
ImmutableSortedDictionary<double, ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData<double>)>> data
) {
this.avg_price = avg_price;
this.working_total = working_total;
this.display_avg_price = display_avg_price;
this.data = data;
this.display_working_total = display_working_total;
}
public static WorkingOrderSideData EmptyLong => new(
double.NaN, 0, double.NaN, 0,
ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData<double>)>>(
new ReverseComparer<double>(new OrderPriceComparer())));
public static WorkingOrderSideData EmptyShort => new(
double.NaN, 0, double.NaN, 0,
ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData<double>)>>(
new OrderPriceComparer()));
public double top_price =>
data.Count > 0 ?
data.First().Key :
double.NaN;
public double top_volume =>
data.Count > 0 ?
data.Values.First().Values.Sum(o => o.order.working_quantity()) :
0;
public double display_top_volume =>
data.Count > 0 ?
data.Values.First().Values.Sum(o => o.qty_conversion
.convert_numeric_type<double>()
.to_display(o.order.working_quantity())) :
0;
public WorkingOrderSideData Add(OrderRecordImmutable order_record) =>
Add(order_record, new());
public WorkingOrderSideData Add(
OrderRecordImmutable order_record, QuantityConversionData<double> qty_conversion) {
var working_qty = order_record.working_quantity();
if (working_qty <= 0)
throw new ArgumentException(nameof(working_qty));
uint order = order_record.Key;
var dworking_qty = qty_conversion.to_display(working_qty);
double price = order_record.price();
if (working_total == 0) {
Log.assert(display_working_total == 0);
return new WorkingOrderSideData(price, working_qty, display_avg_price, dworking_qty, data.Add(
price, ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData<double>)>.Empty.Add(order, (order_record, qty_conversion))));
} else return new WorkingOrderSideData(
avg_price + ((price - avg_price) * ((double)working_qty / (working_total + working_qty))),
working_total + working_qty,
display_avg_price + ((price - display_avg_price) * ((double)dworking_qty / (display_working_total + dworking_qty))),
display_working_total + dworking_qty,
data.SetItem(price, data.TryGetValue(price, out var curr) ?
curr.Add(order, (order_record, qty_conversion)) :
ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData<double>)>.Empty.Add(order, (order_record, qty_conversion))));
}
public WorkingOrderSideData Remove(uint order, double price) {
var tmp_data = data;
if (!tmp_data.TryGetValue(price, out var wdata))
throw new KeyNotFoundException(nameof(price));
if (!wdata.TryGetValue(order, out var worder))
throw new KeyNotFoundException(nameof(order));
var wqty = worder.order.working_quantity();
var dwqty = worder.qty_conversion.to_display(wqty);
wdata = wdata.Remove(order);
if (wdata.Count == 0)
tmp_data = tmp_data.Remove(price);
if (tmp_data.Count == 0)
return new(
double.NaN, 0, double.NaN, 0,
ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData<double>)>>(
data.KeyComparer));
return new(
avg_price - ((price - avg_price) * (wqty / (working_total - wqty))),
working_total - wqty,
display_avg_price - ((price - display_avg_price) * (dwqty / (display_working_total - dwqty))),
display_working_total - dwqty,
tmp_data);
}
public override string ToString() {
if (data.Count > 0) {
var best_order = data.Values.First().Values.First();
return OrderUtils.order_collection_summary(
best_order.order.price(),
display_avg_price,
best_order.qty_conversion.to_display(best_order.order.working_quantity()),
display_working_total,
Security.Defaults.ContractSpecificationImmutable);
}
return string.Empty;
}
}
readonly struct PNLRowData : IEquatable<PNLRowData> {
public static readonly Func<PNLRowData, PNLKey> key_fn = d => d.key;
public static IComparer<PNLRowData> key_comparer = Comparer<PNLKey>.Default.wrap<PNLKey, PNLRowData>(d => d.key);
static readonly WaterfallPriceFlags[] _waterfall_preferences = new[] {
WaterfallPriceFlags.LastInsideSpread,
WaterfallPriceFlags.Mid,
WaterfallPriceFlags.LastGTEBid,
WaterfallPriceFlags.Bid,
WaterfallPriceFlags.LastLTEAsk,
WaterfallPriceFlags.Ask,
WaterfallPriceFlags.Last,
WaterfallPriceFlags.Settle
};
public readonly PNLKey key;
public readonly SecurityImmutable? security;
public readonly QuantityConversionData<double> qty_conversion;
public readonly uint account_id;
public readonly string account_name;
public readonly PNLCalculationType calc_type;
public readonly PositionPriceSizeWithGreeks sod;
public readonly PositionPriceSizeWithGreeks day;
public readonly WorkingOrderData working;
public readonly double
default_cpp,
bid_price,
ask_price,
last_price,
settlement_price;
public readonly double? override_cpp;
public PNLRowData(
PNLKey key,
double default_cpp = 1,
double? override_cpp = null,
double pnl_price = double.NaN,
double bid_price = 0,
double ask_price = 0,
double last_price = 0,
double settlement_price = 0,
double sod_pnl = 0,
PositionPriceSizeWithGreeks? sod = null,
double day_pnl = 0,
PositionPriceSizeWithGreeks? day = null,
WorkingOrderData? working = null,
SecurityImmutable? security = null,
uint account_id = 0,
string account_name = null,
PNLCalculationType calc_type = PNLCalculationType.Mid,
QuantityConversionData<double>? qty_conversion = null) {
//
this.key = key;
this.default_cpp = default_cpp;
this.override_cpp = override_cpp;
this.pnl_price = pnl_price;
this.bid_price = bid_price;
this.ask_price = ask_price;
this.last_price = last_price;
this.settlement_price = settlement_price;
this.sod_pnl = sod_pnl;
this.sod = sod ?? PositionPriceSizeWithGreeks.empty;
this.day_pnl = day_pnl;
this.day = day ?? PositionPriceSizeWithGreeks.empty;
this.working = working ?? WorkingOrderData.Empty;
this.security = security;
this.account_id = account_id;
this.account_name = account_name;
this.calc_type = calc_type;
this.qty_conversion = qty_conversion ?? new();
}
[Flags]
enum WaterfallPriceFlags {
None = 0,
Bid = 1,
Ask = 1 << 1,
Last = 1 << 2,
Settle = 1 << 3,
Mid = Bid | Ask,
LastGTEBid = Bid | Last | 1 << 4,
LastLTEAsk = Ask | Last | 1 << 5,
LastInsideSpread = LastGTEBid | LastLTEAsk,
BidGTESettle = Bid | Settle | 1 << 6,
AskLTESettle = Ask | Settle | 1 << 7
}
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 double effective_cpp => override_cpp ?? default_cpp;
/// Market/Calc
public double mid_price => (bid_price + ask_price) / 2;
public double pnl_price { get; }
/// Sod
public double sod_pnl { get; }
public double sod_long_pos => sod.long_pos;
public double sod_long_avg_price => sod.long_avg;
public double sod_short_pos => sod.short_pos;
public double sod_short_avg_price => sod.short_avg;
public double sod_net_pos => sod.net_pos;
public double sod_net_avg_price => sod.net_avg;
public double sod_delta => sod.delta;
public double sod_gamma => sod.gamma;
public double sod_vega => sod.vega;
public double sod_theta => sod.theta;
public double display_sod_net_pos => qty_conversion.to_display(sod_net_pos);
public double display_sod_long_pos => qty_conversion.to_display(sod_long_pos);
public double display_sod_short_pos => qty_conversion.to_display(sod_short_pos);
/// Day
public double day_pnl { get; }
public double day_long_pos => day.long_pos;
public double day_long_avg_price => day.long_avg;
public double day_short_pos => day.short_pos;
public double day_short_avg_price => day.short_avg;
public double day_delta => day.delta;
public double day_gamma => day.gamma;
public double day_vega => day.vega;
public double day_theta => day.theta;
public bool has_day_trades => day.long_pos > 0 || day.short_pos > 0;
public double day_net_pos => day.net_pos;
public double day_net_avg => day.net_avg;
public double display_day_net_pos => qty_conversion.to_display(day_net_pos);
public double display_day_long_pos => qty_conversion.to_display(day_long_pos);
public double display_day_short_pos => qty_conversion.to_display(day_short_pos);
/// Total
public double total_pnl => new[] { sod_pnl, day_pnl }.sum_real();
public double total_long_pos => new[] { sod.long_pos, day.long_pos }.sum_real();
public double total_long_avg_price =>
MathUtils.weighted_average(
sod_long_avg_price.if_nan(0),
sod_long_pos.if_nan(0),
day_long_avg_price.if_nan(0),
day_long_pos.if_nan(0));
public double total_short_pos => new[] { sod.short_pos, day.short_pos }.sum_real();
public double total_short_avg_price =>
MathUtils.weighted_average(
sod_short_avg_price.if_nan(0),
sod_short_pos.if_nan(0),
day_short_avg_price.if_nan(0),
day_short_pos.if_nan(0));
public double total_net_pos => new[] { sod.long_pos, -sod.short_pos, day.long_pos, -day.short_pos }.Where(n => n.is_real()).Sum();
public double total_delta => sod.delta.add_real(day.delta);
public double total_gamma => sod.gamma.add_real(day.gamma);
public double total_vega => sod.vega.add_real(day.vega);
public double total_theta => sod.theta.add_real(day.theta);
public double display_total_net_pos => qty_conversion.to_display(total_net_pos);
public double display_total_long_pos => qty_conversion.to_display(total_long_pos);
public double display_total_short_pos => qty_conversion.to_display(total_short_pos);
/// Working
public double working_long_pos => working.long_data.working_total;
public double working_long_avg_price => effective_cpp * working.long_data.avg_price;
public double working_long_top_price => effective_cpp * working.long_data.top_price;
public double working_short_pos => working.short_data.working_total;
public double working_short_avg_price => effective_cpp * working.short_data.avg_price;
public double working_short_top_price => effective_cpp * working.short_data.top_price;
public double working_net_pos => working_long_pos - working_short_pos;
public double display_working_net_pos => qty_conversion.to_display(working_net_pos);
public double display_working_long_pos => qty_conversion.to_display(working_long_pos);
public double display_working_short_pos => qty_conversion.to_display(working_short_pos);
public static PNLRowData from_pnl_unit(IPNLUnit u) =>
new(
u.id(),
default_cpp: 1.0,
override_cpp: null,
pnl_price: u.theo,
bid_price: double.NaN,
ask_price: double.NaN,
last_price: double.NaN,
settlement_price: u.settle_price,
sod_pnl: u.start_pnl,
sod: new() {
price_size = new(
u.start_long_pos,
u.start_long_avg_cost,
u.start_short_pos,
u.start_short_avg_cost),
greeks = new() {
delta = u.start_delta,
gamma = u.start_gamma,
vega = u.start_vega,
theta = u.start_theta
}
},
day_pnl: u.day_pnl,
day: new() {
price_size = new(
u.day_long_pos,
u.day_long_avg_cost,
u.day_short_pos,
u.day_short_avg_cost),
greeks = new() {
delta = u.day_delta,
gamma = u.day_gamma,
vega = u.day_vega,
theta = u.day_theta
}
},
working: null,
security: u.security,
account_id: u.account_id,
account_name: u.account,
calc_type: PNLCalculationType.Mid,
qty_conversion: null);
public PNLRowData with(
SecurityImmutable? security = null,
uint? account_id = null,
string account_name = null,
WorkingOrderData? working = null,
PNLCalculationType? calc_type = null) =>
new PNLRowData(
key,
default_cpp,
override_cpp,
pnl_price,
bid_price,
ask_price,
last_price,
settlement_price,
sod_pnl: sod_pnl,
sod,
day_pnl: day_pnl,
day,
working ?? this.working,
security ?? this.security,
account_id ?? this.account_id,
account_name ?? this.account_name,
calc_type ?? this.calc_type,
qty_conversion: qty_conversion);
WaterfallPriceFlags _waterfall_flags() {
var flags = WaterfallPriceFlags.None;
if (!double.IsNaN(bid_price)) flags |= WaterfallPriceFlags.Bid;
if (!double.IsNaN(ask_price)) flags |= WaterfallPriceFlags.Ask;
if (!double.IsNaN(last_price)) flags |= WaterfallPriceFlags.Last;
if (last_price >= bid_price) flags |= WaterfallPriceFlags.LastGTEBid;
if (last_price <= ask_price) flags |= WaterfallPriceFlags.LastLTEAsk;
if (!double.IsNaN(settlement_price)) flags |= WaterfallPriceFlags.Settle;
if (bid_price >= settlement_price) flags |= WaterfallPriceFlags.BidGTESettle;
if (ask_price <= settlement_price) flags |= WaterfallPriceFlags.AskLTESettle;
return flags;
}
double _waterfall_price() {
var flags = _waterfall_flags();
// return price for the best match
for (int i = 0; i < _waterfall_preferences.Length; i++) {
if ((_waterfall_preferences[i] & flags) == _waterfall_preferences[i])
return _waterfall_price(_waterfall_preferences[i]);
}
return double.NaN;
}
double _waterfall_price(WaterfallPriceFlags ltp) {
switch (ltp) {
case WaterfallPriceFlags.Last:
case WaterfallPriceFlags.LastInsideSpread:
case WaterfallPriceFlags.LastGTEBid:
case WaterfallPriceFlags.LastLTEAsk: return last_price;
case WaterfallPriceFlags.Mid: return mid_price;
case WaterfallPriceFlags.Bid:
case WaterfallPriceFlags.BidGTESettle: return bid_price;
case WaterfallPriceFlags.Ask:
case WaterfallPriceFlags.AskLTESettle: return ask_price;
case WaterfallPriceFlags.Settle: return settlement_price;
default: return double.NaN;
}
}
public bool Equals(PNLRowData other) =>
key == other.key &&
default_cpp == other.default_cpp &&
override_cpp == other.override_cpp &&
security.HasValue == other.security.HasValue &&
account_name == other.account_name &&
sod == other.sod &&
day == other.day &&
bid_price.Equals(other.bid_price) &&
ask_price.Equals(other.ask_price) &&
last_price.Equals(other.last_price) &&
mid_price.Equals(other.mid_price) &&
settlement_price.Equals(other.settlement_price) &&
calc_type.Equals(other.calc_type) &&
working.summary == other.working.summary &&
qty_conversion == other.qty_conversion;
public override bool Equals(object obj) =>
obj is PNLRowData other && Equals(other);
// assuming callers will not combine different values with the same key,
// the other components shouldn't be included in the hash calculation.
public override int GetHashCode() =>
key.GetHashCode();
public static bool operator ==(PNLRowData a, PNLRowData b) => a.Equals(b);
public static bool operator !=(PNLRowData a, PNLRowData b) => !a.Equals(b);
}
static class PNLDataExtensions {
public static string pnl_symbol(this SecurityImmutable security) {
return bts.api.ExchangeSpecificUtilities.get_pnl_symbol(security);
}
// while override_cpp could use double.NaN to indicate the lack of an override,
// the nullable double makes it clear that it is an optional value.
public static (double default_cpp, double? override_cpp) pnl_cpp_tuple(this PNLUnitImmutable pnl_unit, SecurityImmutable? security) =>
security.HasValue ?
pnl_cpp_tuple(pnl_unit, security.Value) :
(pnl_unit.CashValue, pnl_unit.HasCppOverride ? pnl_unit.CppOverride.Value : (double?)null);
public static (double default_cpp, double? override_cpp) pnl_cpp_tuple(this PNLUnitImmutable pnl_unit, SecurityImmutable security) {
// TODO: actual impl
double calced_pnl = bts.api.ExchangeSpecificUtilities.get_cpp(pnl_unit, security);
return (calced_pnl, pnl_unit.HasCppOverride ? pnl_unit.CppOverride.Value : (double?)null);
}
}
}
using bts.api;
using bts.api.position;
using bts.utils;
using mbts;
using mbts.analytics;
using mbts.spark;
using mbts.utils;
using Spark.Api.Data;
using Spark.Api.Position;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using PNLKey = bts.AccountSecurityKey;
namespace bts.shell.pnl {
public enum PNLCalculationType {
None,
Bid,
Ask,
Opposite,
Same,
[Description("Cascade")]
Waterfall,
Last,
Mid,
Settle
}
readonly struct PNLRowData : IEquatable<PNLRowData> {
public static readonly Func<PNLRowData, PNLKey> key_fn = d => d.key;
public static IComparer<PNLRowData> key_comparer = Comparer<PNLKey>.Default.wrap<PNLKey, PNLRowData>(d => d.key);
public readonly PNLKey key;
public readonly SecurityImmutable? security;
public readonly QuantityConversionData<double> qty_conversion;
public readonly uint account_id;
public readonly string account_name;
public readonly PNLCalculationType calc_type;
public readonly PositionPriceSizeWithGreeks sod;
public readonly PositionPriceSizeWithGreeks day;
public readonly WorkingOrderData working;
public readonly double
default_cpp,
bid_price,
ask_price,
last_price,
settlement_price;
public readonly double? override_cpp;
public PNLRowData(
PNLKey key,
double default_cpp = 1,
double? override_cpp = null,
double pnl_price = double.NaN,
double bid_price = 0,
double ask_price = 0,
double last_price = 0,
double settlement_price = 0,
PositionPriceSizeWithGreeks? sod = null,
PositionPriceSizeWithGreeks? day = null,
WorkingOrderData? working = null,
SecurityImmutable? security = null,
uint account_id = 0,
string account_name = null,
PNLCalculationType calc_type = PNLCalculationType.Mid,
QuantityConversionData<double>? qty_conversion = null) {
//
this.key = key;
this.default_cpp = default_cpp;
this.override_cpp = override_cpp;
this.pnl_price = pnl_price;
this.bid_price = bid_price;
this.ask_price = ask_price;
this.last_price = last_price;
this.settlement_price = settlement_price;
this.sod = sod ?? PositionPriceSizeWithGreeks.empty;
this.day = day ?? PositionPriceSizeWithGreeks.empty;
this.working = working ?? WorkingOrderData.Empty;
this.security = security;
this.account_id = account_id;
this.account_name = account_name;
this.calc_type = calc_type;
this.qty_conversion = qty_conversion ?? new();
}
public AccountTuple account => new(account_id, account_name);
public string symbol => security?.pnl_symbol() ?? string.Empty;
public ExpirationDate expiration =>
security?.normal_expirations()?.MinOrDefault(ExpirationDate.None) ?? ExpirationDate.None;
public double effective_cpp => override_cpp ?? default_cpp;
/// Market/Calc
public double mid_price => (bid_price + ask_price) / 2;
public double pnl_price { get; }
/// Sod
public double sod_pnl => new[] { sod_realized_pnl, sod_unrealized_pnl }.sum_real();
public double sod_long_pos => sod.long_pos;
public double sod_long_avg_price => sod.long_avg;
public double sod_long_avg_pnl => bid_price - sod.long_avg;
public double sod_short_pos => sod.short_pos;
public double sod_short_avg_price => sod.short_avg;
public double sod_short_avg_pnl => sod.short_avg - ask_price;
public double sod_net_pos => sod.net_pos;
public double sod_net_avg_price => sod.net_avg;
public double sod_unrealized_pnl => sod_long_pos > sod_short_pos
? (sod_long_pos - sod_short_pos) * effective_cpp * (bid_price - sod_long_avg_price)
: (sod_short_pos - sod_long_pos) * effective_cpp * (sod_short_avg_price - ask_price);
public double sod_realized_pnl => (sod_short_avg_price - sod_long_avg_price) * effective_cpp * Math.Min(sod_long_pos, sod_short_pos);
public double sod_delta => sod.delta;
public double sod_gamma => sod.gamma;
public double sod_vega => sod.vega;
public double sod_theta => sod.theta;
public double display_sod_net_pos => qty_conversion.to_display(sod_net_pos);
public double display_sod_long_pos => qty_conversion.to_display(sod_long_pos);
public double display_sod_short_pos => qty_conversion.to_display(sod_short_pos);
/// Day
public double day_pnl => new[] { day_realized_pnl + day_unrealized_pnl }.sum_real();
public double day_long_pos => day.long_pos;
public double day_long_avg_price => day.long_avg;
public double day_long_avg_pnl => bid_price - day.long_avg;
public double day_short_pos => day.short_pos;
public double day_short_avg_price => day.short_avg;
public double day_short_avg_pnl => day.short_avg - ask_price;
public double day_delta => day.delta;
public double day_gamma => day.gamma;
public double day_vega => day.vega;
public double day_theta => day.theta;
public bool has_day_trades => day.long_pos > 0 || day.short_pos > 0;
public double day_net_pos => day.net_pos;
public double day_net_avg => day.net_avg;
public double day_unrealized_pnl => day_long_pos > day_short_pos
? (day_long_pos - day_short_pos) * effective_cpp * (bid_price - day_long_avg_price)
: (day_short_pos - day_long_pos) * effective_cpp * (day_short_avg_price - ask_price);
public double day_realized_pnl => (day_short_avg_price - day_long_avg_price) * effective_cpp * Math.Min(day_long_pos, day_short_pos);
public double display_day_net_pos => qty_conversion.to_display(day_net_pos);
public double display_day_long_pos => qty_conversion.to_display(day_long_pos);
public double display_day_short_pos => qty_conversion.to_display(day_short_pos);
/// Total
public double total_pnl => new[] { sod_pnl, day_pnl }.sum_real();
public double total_long_pos => new[] { sod.long_pos, day.long_pos }.sum_real();
public double total_long_avg_price =>
MathUtils.weighted_average(
sod_long_avg_price.if_nan(0),
sod_long_pos.if_nan(0),
day_long_avg_price.if_nan(0),
day_long_pos.if_nan(0));
public double total_long_avg_pnl => new[] { sod_long_avg_pnl, day_long_avg_pnl }.sum_real();
public double total_short_pos => new[] { sod.short_pos, day.short_pos }.sum_real();
public double total_short_avg_price =>
MathUtils.weighted_average(
sod_short_avg_price.if_nan(0),
sod_short_pos.if_nan(0),
day_short_avg_price.if_nan(0),
day_short_pos.if_nan(0));
public double total_short_avg_pnl => new[] { sod_short_avg_pnl, day_short_avg_pnl }.sum_real();
public double total_net_pos => new[] { sod.long_pos, -sod.short_pos, day.long_pos, -day.short_pos }.Where(n => n.is_real()).Sum();
public double total_unrealized_pnl => new[] { sod_unrealized_pnl, day_unrealized_pnl }.sum_real();
public double total_realized_pnl => new[] { sod_realized_pnl, day_realized_pnl }.sum_real();
public double total_delta => sod.delta.add_real(day.delta);
public double total_gamma => sod.gamma.add_real(day.gamma);
public double total_vega => sod.vega.add_real(day.vega);
public double total_theta => sod.theta.add_real(day.theta);
public double display_total_net_pos => qty_conversion.to_display(total_net_pos);
public double display_total_long_pos => qty_conversion.to_display(total_long_pos);
public double display_total_short_pos => qty_conversion.to_display(total_short_pos);
/// Working
public double working_long_pos => working.long_data.working_total;
public double working_long_avg_price => effective_cpp * working.long_data.avg_price;
public double working_long_top_price => effective_cpp * working.long_data.top_price;
public double working_short_pos => working.short_data.working_total;
public double working_short_avg_price => effective_cpp * working.short_data.avg_price;
public double working_short_top_price => effective_cpp * working.short_data.top_price;
public double working_net_pos => working_long_pos - working_short_pos;
public double display_working_net_pos => qty_conversion.to_display(working_net_pos);
public double display_working_long_pos => qty_conversion.to_display(working_long_pos);
public double display_working_short_pos => qty_conversion.to_display(working_short_pos);
public static PNLRowData from_pnl_unit(IPNLUnit u) =>
new(
u.id(),
default_cpp: u.security.spec_or_default().CashPerPoint,
override_cpp: null,
pnl_price: u.theo,
bid_price: double.NaN,
ask_price: double.NaN,
last_price: double.NaN,
settlement_price: u.settle_price,
// Now computed as the sum: sod_realized_pnl + sod_unrealized_pnl
// sod_pnl: u.start_pnl,
sod: new() {
price_size = new(
u.start_long_pos,
u.start_long_avg_cost,
u.start_short_pos,
-u.start_short_avg_cost),
greeks = new() {
delta = u.start_delta,
gamma = u.start_gamma,
vega = u.start_vega,
theta = u.start_theta
}
},
// Now computed as the sum: day_realized_pnl + day_unrealized_pnl
// day_pnl: u.day_pnl,
day: new() {
price_size = new(
u.day_long_pos,
u.day_long_avg_cost,
u.day_short_pos,
-u.day_short_avg_cost),
greeks = new() {
delta = u.day_delta,
gamma = u.day_gamma,
vega = u.day_vega,
theta = u.day_theta
}
},
working: null,
security: u.security,
account_id: u.account_id,
account_name: u.account,
calc_type: PNLCalculationType.Mid,
qty_conversion: null);
public PNLRowData with(
SecurityImmutable? security = null,
uint? account_id = null,
string account_name = null,
WorkingOrderData? working = null,
PNLCalculationType? calc_type = null,
double? bid_price = null,
double? ask_price = null) =>
new PNLRowData(
key,
default_cpp,
override_cpp,
pnl_price,
bid_price ?? this.bid_price,
ask_price ?? this.ask_price,
last_price,
settlement_price,
sod,
day,
working ?? this.working,
security ?? this.security,
account_id ?? this.account_id,
account_name ?? this.account_name,
calc_type ?? this.calc_type,
qty_conversion: qty_conversion);
public PNLRowData with(SecurityMD smd) {
ulong time = 0;
if (!smd.md.get_bbo(ref time, out var bid, out var ask)) {
return this;
}
return with(security: smd.security, bid_price: bid.price, ask_price: ask.price);
}
public bool Equals(PNLRowData other) =>
key == other.key &&
default_cpp == other.default_cpp &&
override_cpp == other.override_cpp &&
security.HasValue == other.security.HasValue &&
account_name == other.account_name &&
sod == other.sod &&
day == other.day &&
bid_price.Equals(other.bid_price) &&
ask_price.Equals(other.ask_price) &&
last_price.Equals(other.last_price) &&
mid_price.Equals(other.mid_price) &&
settlement_price.Equals(other.settlement_price) &&
calc_type.Equals(other.calc_type) &&
working.summary == other.working.summary &&
qty_conversion == other.qty_conversion;
public override bool Equals(object obj) =>
obj is PNLRowData other && Equals(other);
// assuming callers will not combine different values with the same key,
// the other components shouldn't be included in the hash calculation.
public override int GetHashCode() =>
key.GetHashCode();
public static bool operator ==(PNLRowData a, PNLRowData b) => a.Equals(b);
public static bool operator !=(PNLRowData a, PNLRowData b) => !a.Equals(b);
}
static class PNLDataExtensions {
public static string pnl_symbol(this SecurityImmutable security) {
return bts.api.ExchangeSpecificUtilities.get_pnl_symbol(security);
}
// while override_cpp could use double.NaN to indicate the lack of an override,
// the nullable double makes it clear that it is an optional value.
public static (double default_cpp, double? override_cpp) pnl_cpp_tuple(this PNLUnitImmutable pnl_unit, SecurityImmutable? security) =>
security.HasValue ?
pnl_cpp_tuple(pnl_unit, security.Value) :
(pnl_unit.CashValue, pnl_unit.HasCppOverride ? pnl_unit.CppOverride.Value : (double?)null);
public static (double default_cpp, double? override_cpp) pnl_cpp_tuple(this PNLUnitImmutable pnl_unit, SecurityImmutable security) {
// TODO: actual impl
double calced_pnl = bts.api.ExchangeSpecificUtilities.get_cpp(pnl_unit, security);
return (calced_pnl, pnl_unit.HasCppOverride ? pnl_unit.CppOverride.Value : (double?)null);
}
}
}
Loaded 100 of 705 files, more files were not shown because too many files have changed in this diff. Show more