Compare commits

..
100 Commits
Author SHA1 Message Date
Pedro Pinto e24e04ca4f Fixed OrderManager reentrancy problem reported by Taha 2019-03-12 10:01:57 -04:00
John Colvin 08a1e07ab6 Merged in SPARK-2098-spreader-orders-missing-security (pull request #192)
RTM SPARK-2098 security is not assigned to SpreaderOrderDefinition when sending orders
2019-03-11 23:57:58 +00:00
John Colvin 038e6d16af SPARK-2098 security is not assigned to SpreaderOrderDefinition when sending orders 2019-03-11 16:59:18 -05:00
Eric Harding fe2e1e9945 Merge branch 'AdditionalTracing-MinimumInterval' of bitbucket.org:bluetradesys/spark 2019-03-11 17:00:30 -04:00
Eric Harding 20d5276874 Remove trace::log_scope function and just use constructor 2019-03-11 16:53:12 -04:00
Eric Harding b2f3d093d2 Merged in AdditionalTracing-MinimumInterval (pull request #191)
RTM: cut down on tracing load by supplying a minimum interval
2019-03-11 20:28:05 +00:00
John Colvin 4860f706ad Merged in SPARK-2097-change-cover/lean-defaults (pull request #190)
SPARK-2097 change Lean/Cover defaults - Lean.MaxChaseTicks = 2, Cover.HedgeTicks = 1

Approved-by: Kevin Darby <kevin.darby@bluetradesys.com>
2019-03-11 20:24:06 +00:00
Eric Harding 29ddd6621c Move NativeOrder::on_activate trace message so it doesn't get triggered by warmup 2019-03-11 16:11:21 -04:00
Eric Harding e8e0f01fb7 Merged master into AdditionalTracing-MinimumInterval 2019-03-11 14:16:09 -04:00
Eric Harding 41a04de31b Update threshhold with help from compiler 2019-03-11 14:14:51 -04:00
John Colvin b3b1af68d0 SPARK-2097 change Lean/Cover defaults - Lean.MaxChaseTicks = 2, Cover.HedgeTicks = 1 2019-03-11 12:23:49 -05:00
Pedro Pinto 7cbf729607 Cleaned up release notes further. 2019-03-11 08:52:35 -04:00
Pedro Pinto 6e8851d92c Updated subrepo pointers 2019-03-11 08:51:31 -04:00
Pedro Pinto 460e747f55 Merge branch 'master' of bitbucket.org:bluetradesys/spark 2019-03-11 08:49:24 -04:00
Pedro Pinto 20978b10d4 Updated release notes 2019-03-11 08:49:19 -04:00
John Colvin 42e0cfba70 Merged in SPARK-2079-trigger-cleanup (pull request #183)
RTM - SPARK-2079 trigger cleanup
2019-03-11 12:48:44 +00:00
Michael Harding 728c485013 Merged in doc_temp_build (pull request #174)
RTM: Doc temp build
2019-03-11 12:47:40 +00:00
Eric Harding b83f88e098 Merged in AlmostNeverRecordSim (pull request #185)
RTM: Almost never want to record sim market data
2019-03-11 12:46:55 +00:00
Eric Harding 32b1a38655 Merged in DenyCopy (pull request #177)
RTM: Explicitly deny copying a service.  make_*_publisher points back to the owning service
2019-03-11 12:44:49 +00:00
Taha Afzal 24934f6607 Merged in SPARK-2058-netchange (pull request #184)
RTM SPARK-2058 netchange
2019-03-11 12:43:56 +00:00
Brian Muhr 90b56277ca Merged in Documentation (pull request #179)
RTM: Documentation - added Algo Studio, new images, freshened up quite a bit

Approved-by: Michael Harding <michael.harding@bluetradesys.com>
2019-03-11 12:42:51 +00:00
Eric Harding 20ca8a7ffe Merged in AdditionalTracing (pull request #189)
RTM: Additional Tracing

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-03-11 12:40:57 +00:00
Pedro Pinto 6ed4adbc14 Merged in fix_warmup_limit_polution (pull request #187)
RTM. Fixed order warm up code to clear errors detected during warmup
2019-03-11 12:38:05 +00:00
Eric Harding 9117e3e238 Add a minimum tracing interval for scoped logs 2019-03-11 07:58:43 -04:00
Eric Harding c0f079b7f6 Code review feedback 2019-03-11 07:31:03 -04:00
John Colvin 751f22645d SPARK-2089 [cherry-picked] Strange null ref exception in OrderBook row-selection changed callback during application shutdown 2019-03-10 22:21:53 -05:00
John Colvin 47daf439d0 OrderPropertiesEditor - observe_order_type() check if templates are enabled 2019-03-10 21:41:44 -05:00
Eric Harding 19ce3a7dd4 Additional spreader tracing 2019-03-10 16:19:15 -04:00
John Colvin 5686b57d47 ObservableAutoWrapper - fix typeo (change 'Reset' to 'Remove') 2019-03-10 14:38:06 -05:00
John Colvin cb456a439c fix typeo in LazySubscription 2019-03-10 14:33:42 -05:00
Pedro Pinto c1d4b9cd9f Fixed order warm up code to clear errors detected during warmup
This could cause later orders to fail even if the error condition no
longer obained.
2019-03-07 11:23:57 -05:00
Pedro Pinto dce856e4af Fixed duplication of entries in event_log_archive in perf_log 2019-03-07 11:04:11 -05:00
Jake Norbut f006d4ffdb Merge branch 'SPARK-2054' of bitbucket.org:bluetradesys/spark into SPARK-2058-netchange 2019-03-05 14:21:35 -06:00
John Colvin 31dd7849b9 SPARK-2086 SyntheticEditor - user can't type '.' in price multiplier cell 2019-03-05 14:17:19 -06:00
Jake Norbut bcf9b97420 Merge branch 'SPARK-2054' of bitbucket.org:bluetradesys/spark into SPARK-2058-netchange 2019-03-05 12:56:08 -06:00
John Colvin 697ffd71eb ObservableAutoWrapper.connect_item_actions - invoke action for existing items 2019-03-04 23:52:22 -06:00
Jake Norbut b56fc2445c != instead of !(); John leaves no stone unturned 2019-03-04 17:05:13 -06:00
Jake Norbut a4d162c3a2 EnumHelper equality be gone 2019-03-04 16:53:08 -06:00
Jake Norbut 527bc05b6f removing comment cruft 2019-03-04 15:49:17 -06:00
Jake Norbut a52d65859b 3 bools to 1 Enum in the protobuf 2019-03-04 15:45:30 -06:00
Jake Norbut 363b9e3e7a I missed an early return branch in _format_tick 2019-03-04 15:31:21 -06:00
Eric Harding 3a06684146 Almost never want to record sim market data 2019-03-04 16:15:53 -05:00
John Colvin 8643feabf2 SPARK-2079 [pr edit]child algo 'name' overlapped expander button 2019-03-04 13:44:50 -06:00
John Colvin e7f1c59210 SPARK-2079 [pr edit]remove #define ALLOW_FIXED_PRICE_TRIGGER 2019-03-04 13:27:44 -06:00
Jake Norbut c9f403973b Adding a ColSpan on the groupbox so it doesn't push over the merged combo box 2019-03-04 10:53:49 -06:00
Jake Norbut d6bc6fa289 simplifying logic regarding decision to format with decimal part 2019-03-04 10:02:15 -06:00
Taha Afzal 7557ca38cb Merge branch 'SPARK-2054' into SPARK-2058-netchange 2019-03-04 09:03:16 -05:00
Jake Norbut ef59511a01 woops, fixing bug 2019-03-04 07:50:10 -06:00
Jake Norbut 8dfbb106a6 hide decimals when in ticks mode 2019-03-04 07:46:59 -06:00
Taha Afzal 3e9a931c5f removing an unused function 2019-03-03 23:15:06 -05:00
Taha Afzal 71fac664e8 the net change test now tests for quoting prices on both legs 2019-03-03 16:52:29 -05:00
Taha Afzal 1b39ffc323 cleaning up the test 2019-03-03 16:30:19 -05:00
John Colvin bc69c669ae SPARK-2079 Trigger Clean up (simplify) 2019-03-02 21:50:17 -06:00
Taha Afzal 170f7426aa for net change, the reported price with filled_qty==0 was incorrect 2019-03-02 22:43:35 -05:00
John Colvin 16d30e450f refactor\cleanup OrderTypeVMBase and impls 2019-03-02 19:36:19 -06:00
Taha Afzal a3787ee65a Merge branch 'SPARK-2054' into SPARK-2058-netchange 2019-03-01 16:59:42 -05:00
Taha Afzal 14d5d36067 merging master 2019-03-01 16:57:26 -05:00
Jake Norbut 1af4b97b97 ui feedback from taha 2019-03-01 12:56:41 -06:00
Michael Harding faf60af9c3 Merge branch 'master' into doc_temp_build 2019-03-01 13:52:48 -05:00
Jake Norbut 9626bee38e resetting this width I inadvertently changed 2019-03-01 10:55:52 -06:00
Jake Norbut 776bc71c22 ladder prices in net change terms 2019-03-01 10:51:16 -06:00
Michael Harding 761657b56b Fix typos 2019-03-01 10:30:41 -05:00
Michael Harding 2988967f1f Merge branch 'master' into doc_temp_build 2019-03-01 09:29:36 -05:00
Michael Harding 09436473ed Merged master into Documentation 2019-02-28 13:27:25 -05:00
Michael Harding 5524e89a10 Add images 2019-02-28 13:26:02 -05:00
Taha Afzal d615dcc7d6 tests were failing because of direct vs merged 2019-02-28 11:10:56 -05:00
Taha Afzal d582ed8436 more tests 2019-02-27 19:29:18 -05:00
Taha Afzal c5768c4022 randomized the prices and markets in the net change test 2019-02-27 19:08:29 -05:00
Brian Muhr 6c652df3eb index.md edited online with Bitbucket 2019-02-27 20:34:00 +00:00
Michael Harding c4e684f934 SPARK-2047: change temp dir path 2019-02-27 14:41:43 -05:00
Taha Afzal 51ac39c166 test comments 2019-02-27 14:21:41 -05:00
Eric Harding f04a48d4f8 Explicitly deny copying a service. make_*_publisher points back to the owning service 2019-02-27 14:00:04 -05:00
Michael Harding f7baf6c05a Merge branch 'master' into doc_temp_build 2019-02-27 13:39:11 -05:00
Taha Afzal 1eaf3d8629 taming the net_change tests 2019-02-27 13:16:31 -05:00
Brian Muhr 62ecd1a721 index.md edited online with Bitbucket 2019-02-27 16:35:51 +00:00
Taha Afzal cabc84a577 tests for net change transformation 2019-02-26 17:04:59 -05:00
Taha Afzal 394c7fd954 Merge branch 'SPARK-2058-netchange' of bitbucket.org:bluetradesys/spark into SPARK-2058-netchange 2019-02-26 10:52:00 -05:00
Taha Afzal b2d1969e9f ticks in net change working now, by transforming the spread 2019-02-25 17:24:31 -05:00
Eric Harding 744797b91c Add compute_synthetic_book function 2019-02-25 14:26:23 -05:00
Michael Harding 6446e38aea Merge branch 'master' into doc_temp_build 2019-02-25 11:48:36 -05:00
Michael Harding 32df88914b Merge branch 'master' into doc_temp_build 2019-02-25 11:29:21 -05:00
Michael Harding 628490ac2d Hide JS errors in IE 2019-02-25 11:10:19 -05:00
Michael Harding ef475f7003 Merge branch 'master' into doc_temp_build 2019-02-25 11:09:41 -05:00
Taha Afzal 76a1367510 Price calc (standard vs net change) is a property on LegPropertyData 2019-02-24 20:21:21 -05:00
Taha Afzal fef5ae4aff Adhering spreader to bts::api::SyntheticContract::PriceCalc (from the old one) 2019-02-23 23:59:09 -05:00
Taha Afzal 2c9e83c6f0 Merge branch 'SPARK-2058-netchange' of bitbucket.org:bluetradesys/spark into SPARK-2058-netchange 2019-02-22 17:50:10 -05:00
Taha Afzal 35738c25f1 dividing by tick_size for net change 2019-02-22 17:50:02 -05:00
Eric Harding e64567f8ad SPARK-2062 Unify NetChange enums in UI 2019-02-22 17:15:52 -05:00
Michael Harding 5865ab21bc Downgrade js to es3 2019-02-22 17:05:11 -05:00
Eric Harding 78b6169750 Update SyntheticContract.PriceCalc enum values 2019-02-22 12:24:01 -05:00
Eric Harding e2c0d81f19 Update NetChange enums to be per security rather than per leg. Also non mutable 2019-02-22 11:59:51 -05:00
Eric Harding b811b960ec SPARK-2063 Ladder day change doesn't update as often as I'd like 2019-02-22 09:39:15 -05:00
Eric Harding bf0507e434 Fix synthetic_net_change unit test 2019-02-22 08:37:21 -05:00
John Colvin 1205a35b69 SyntheticEditorVM - add 'MarketDataType' param-row back (accidentally removed) 2019-02-21 19:53:18 -06:00
John Colvin 2f4d2a9c0d SPARK-2058 / SPARK-2062 Synthetic NetChange should be set per Synthetic not per Leg (UI Only) 2019-02-21 19:43:40 -06:00
John Colvin 08e5088d72 SPARK-2058 / SPARK-2059 Allow 100/100 tick size 2019-02-21 18:40:08 -06:00
Eric Harding e7b371aa6f SPARK-2060 NetChange in Ticks 2019-02-21 12:49:55 -05:00
Eric Harding 3094eee327 Add double division operator to IntPrice 2019-02-21 12:00:01 -05:00
Michael Harding 80ff61c9b2 Docs: change URL in config.toml 2019-02-20 09:40:27 -05:00
Michael Harding 2a42c1fab0 SPARK-2047: Build user docs in a temp dir 2019-02-19 16:38:17 -05:00
126 changed files with 1809 additions and 1048 deletions

No files matched your search

+1 -1
View File
@@ -1 +1 @@
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;/home/ppinto/Dropbox/bts/spark/doc/user/mkdocs.yml;bts-help-theme/404.html;bts-help-theme/base.html;bts-help-theme/content.html;bts-help-theme/css/base.css;bts-help-theme/css/bts.css;bts-help-theme/css/font-awesome.min.css;bts-help-theme/css/highlight.css;bts-help-theme/css/local.css;bts-help-theme/fonts/fontawesome-webfont.eot;bts-help-theme/fonts/glyphicons-halflings-regular.eot;bts-help-theme/img/favicon.ico;bts-help-theme/js/base.js;bts-help-theme/js/bootstrap-3.0.3.min.js;bts-help-theme/js/highlight.pack.js;bts-help-theme/js/jquery-1.10.2.min.js;bts-help-theme/keyboard-modal.html;bts-help-theme/main.html;bts-help-theme/nav-sub.html;bts-help-theme/nav.html;bts-help-theme/search-modal.html;bts-help-theme/toc.html;docs/Help.md;docs/Launcher.md;docs/Logging-In-to-the-System.md;docs/Market.md;docs/My-Windows.md;docs/Settings.md;docs/Spreader.md;docs/Trading.md;docs/images/image1.png;docs/images/image10.png;docs/images/image11.png;docs/images/image12.png;docs/images/image13.png;docs/images/image14.png;docs/images/image15.png;docs/images/image16.png;docs/images/image17.png;docs/images/image18.png;docs/images/image19.png;docs/images/image2.png;docs/images/image20.png;docs/images/image21.png;docs/images/image22.png;docs/images/image23.png;docs/images/image24.png;docs/images/image25.png;docs/images/image26.png;docs/images/image27.png;docs/images/image28.png;docs/images/image29.png;docs/images/image3.png;docs/images/image30.png;docs/images/image31.png;docs/images/image32.png;docs/images/image33.png;docs/images/image34.png;docs/images/image35.png;docs/images/image36.png;docs/images/image37.png;docs/images/image38.png;docs/images/image39.png;docs/images/image4.png;docs/images/image40.png;docs/images/image41.png;docs/images/image42.png;docs/images/image43.png;docs/images/image44.png;docs/images/image45.png;docs/images/image46.png;docs/images/image47.png;docs/images/image48.png;docs/images/image49.png;docs/images/image5.png;docs/images/image50.png;docs/images/image51.png;docs/images/image52.png;docs/images/image53.png;docs/images/image54.png;docs/images/image55.png;docs/images/image56.png;docs/images/image57.png;docs/images/image58.png;docs/images/image59.png;docs/images/image6.png;docs/images/image60.png;docs/images/image61.png;docs/images/image62.png;docs/images/image63.png;docs/images/image64.png;docs/images/image65.png;docs/images/image66.png;docs/images/image67.png;docs/images/image68.png;docs/images/image69.png;docs/images/image7.png;docs/images/image70.png;docs/images/image71.png;docs/images/image72.png;docs/images/image73.png;docs/images/image74.png;docs/images/image76.png;docs/images/image8.png;docs/images/image9.png;docs/index.md;docs/release_notes.md)
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Help/index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Launcher/index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Logging-In-to-the-System/index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/index.md;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/My-Windows/index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Settings/index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Spreader/index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/index.md;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/bts_spark_sdk/Client/Overview.md;content/bts_spark_sdk/Client/Threading.md;content/bts_spark_sdk/Client/_index.md;content/bts_spark_sdk/Server/_index.md;content/bts_spark_sdk/_index.md;content/release_notes/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
+1 -1
View File
@@ -22,7 +22,7 @@ update_deps_file("${All_Deps}")
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/web/docs/index.html
COMMAND /bin/bash ${CMAKE_CURRENT_SOURCE_DIR}/build.sh
ARGS ${CMAKE_BINARY_DIR}/web/docs/
ARGS ${CMAKE_BINARY_DIR}/web/docs/ ${CMAKE_BINARY_DIR}
COMMENT "Building Manual UI"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${All_Deps})
+15 -3
View File
@@ -1,8 +1,20 @@
#!/bin/bash
tmpfile=$(mktemp /tmp/quiet_build_log.XXXX)
cp -r ../../src/sdk/doc/* ./content/bts_spark_sdk/Server/
./run_zola.sh build -u '/docs/' -o $1 >$tmpfile 2>&1
rm ./content/bts_spark_sdk/Server/algos.md
cur_dir=$(pwd)
temp_path="$2/web_temp"
zola_path=$(readlink -f "../../externals/linux/bin/zola/zola")
mkdir -p $temp_path
cp -r ./* $temp_path
cp -r ../../src/sdk/doc/* $temp_path/content/bts_spark_sdk/Server/
cd $temp_path
$zola_path build -u '/docs/' -o $1 >$tmpfile 2>&1
cd $cur_dir
rm -r $temp_path
if [ $? -eq 0 ]
then
rm $tmpfile
+2 -2
View File
@@ -1,5 +1,5 @@
# The URL the site will be built for
base_url = "https://www.bluetradingsystems.com/docs"
base_url = "https://www.bluetradingsystems.com/documentation"
title = "BTS Spark Documentation"
@@ -15,4 +15,4 @@ build_search_index = true
[extra]
# Put all your custom variables here
book_numbered_chapters = false
book_numbered_chapters = false
Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

+1 -1
View File
@@ -25,7 +25,7 @@ insert_anchor_links = "right"
which will list what version you are running as well as who is
currently logged in.
![](image32.png)
![](about_spark.png)
## Activity log
This is an overall log viewer which will show
Binary file not shown.

After

Width:  |  Height:  |  Size: 942 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

+20 -16
View File
@@ -51,7 +51,7 @@ insert_anchor_links = "right"
securities you have chosen, in this example, it appears like the
following:
![](image63.png)
![](market_view.png)
- Once a market view is launched and you are viewing data, you can
customize the view further by the following:
@@ -88,31 +88,34 @@ insert_anchor_links = "right"
- Change securities
- Click on the settings icon ![](image12.png)which will launch the market view security selection.  You can add or remove Securities you have already selected and are shown in the market view.
- Click on the settings icon ![](change_securities.png) which will launch the market view security selection.  You can add or remove Securities you have already selected and are shown in the market view.
- Launch an order ticket
- This is done 1 of 2 ways:
- This is done 1 of 3 ways:
- Single left clicking on either the Bid Qty, Bid Price, Ask Price,
or Ask Qty which will bring up an order ticket pre-populated with
either the Bid or Ask price and volume.
- Highlighting a single security and clicking on the order ticket
![](image23.png) icon
on the left-hand browser window of the market view
- Highlighting a single security and right clicking bringing up the
menu options to launch an order ticket.
- Launch a ladder for 1 security
- You can double-click on any security in the market view, besides
the Bid Qty, Bid Price, Ask Price, or Ask Qty columns to launch a
ladder for that Security.
- This is done 1 of 2 ways:
- You can double-click on any security in the market view, besides
the Bid Qty, Bid Price, Ask Price, or Ask Qty columns to launch a
ladder for that Security.
- Highlighting a single security and right clicking bringing up the
menu options to launch a ladder.
- Launching multiple ladders
- If you hold down the Ctrl key and select multiple securities, you
can then click on the ladder icon
![](image69.png) on
the left-hand side to bring up the selected ladders all at
once.
- This is done 1 of 2 ways:
- If you hold down the Ctrl key and select multiple securities, you
can then click on the ladder icon on the left hand side to bring
up the selected ladders all at once.
- Highlighting multiple securities and right clicking to bring up
the menu options to launch a ladder.
- Launching a Fills window for 1 or many securities
@@ -158,8 +161,9 @@ insert_anchor_links = "right"
- On launching a security browser will appear, allowing you to choose
the products you would like to view.
![](image30.png)
![](time_and_sales.png)
- From time and sales, you can launch the same items as the market
view with the exception of a launching the synthetic window to
create a synthetic product
create a synthetic product
- From the settings icon ![](settings_icon.png) you can customize the time and sales modifying grid; the colors of the aggressor, status of trades coming in, as well as font sizes and which columns to see.
Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

+137 -40
View File
@@ -59,7 +59,7 @@ insert_anchor_links = "right"
functionality to quickly perform various tasks with live orders as
well as launch other modules.  It appears like the below:
- ![](image4.png)
![](spark_menu_order_book.png)
- ![](image48.png)Fills -
clicking on this will launch a fills window
@@ -79,6 +79,18 @@ insert_anchor_links = "right"
- ![](image2.png)Cancel
all - clicking on this will cancel all working orders you have the
order book
- ![](recreate_icon.png) Recreate - clicking this will recreate already cancelled orders, sending them back to market with the same price and quantity as they were previously at before being cancelled.
- ![](collapse_all_icon.png) Collapse all - This will collapse all trees inside the order book to show only the top level of the order.
- ![](expand_all_icon.png) Expand all - This will expand to show everything inside the order book.
- ![](settings_icon.png) Settings - This launches the settings for the orderbook.
- Furthermore, there is a “floating” or “mini orderbook” feature as well
integrated within the ladder itself.
- To access this, you need to center click with your mouse wheel over
working orders on the ladder.
- From here, you can change the price and quantity of any working order
within the mini orderbook straight from the window
- This looks like below:
![](mini_orderbook.png)
## Fills
@@ -89,6 +101,8 @@ insert_anchor_links = "right"
like
- All fill windows are brought up as tabs and can be combined with
all other windows within BTS
- There is also a settings button in the left hand tab which can be
launched to customize the fills window
## P&amp;L
@@ -104,10 +118,20 @@ insert_anchor_links = "right"
other windows within BTS
- All columns are sortable and either searchable or can be further
filtered by entering in min and max values.
- Below is an example P&L window showing a user’s multiple accounts
and various positions in each:
- There is also a settings button in the left hand tab which can be
launched to customize the P&amp;L window
- In regards to the PnL specifically, you can choose how you want the
PnL calculated based on Last, MidPoint, or Settle price.
- This is accomplished on the top bar, which when clicked on, displays
the choices for calculation, this appears like below:
![](image67.png)
![](pnl_calculation.png)
- Below is an example P&L window showing a user’s multiple accounts and
various positions in each:
![](pnl_window.png)
## Ladder
@@ -125,7 +149,7 @@ insert_anchor_links = "right"
market with a single click
- Below is an example ladder:
![](image36.png)
![](ladder.png)
- The ladder consists of 2 major parts, the Ladder itself and the
information and control section on the left-hand side
@@ -213,37 +237,59 @@ insert_anchor_links = "right"
         
![](image50.png)
- Lean
- This is a specialized algo order which allows the user to
- Lean
- This is a specialized algo order which allows the user to
essentially watch the opposite-inside market size and cross when
that size goes below the threshold defined.
- “Crossing” means to take an order working on the bid and 'cross' by
buying into the asks.  This is often referred to as “paying up” as
well
- “Cross” means to take an order working on the bid and 'cross' by
buying into the asks.  This is often referred to as “paying up” as well
![](image22.png?v2)
![](lean_algo_control.png)
- Join
- This is a specialized algo order which allows the user to enter an
- Join
- This is a specialized algo order which allows the user to enter an
order which will watch the opposite-inside market and join when the
market flips and has at least threshold size as defined.
- “Flip” refers to the event where the best offer becomes the best
bid or the market trades away from you, i.e. the line moves up in
the GUI.  The reverse is true if selling.
![](image41.png)
![](join_algo_control.png)
- Top
- This is a specialized algo order which allows the user to
- Top
- This is a specialized algo order which allows the user to
essentially not trade by themselves
- If the order is on the market (current bid price) and the quantity
- If the order is on the market (current bid price) and the quantity
on that bid price is below the top threshold defined the order will
cancel and do nothing else
![](top_algo_control.png)
![](image26.png)
- Offset
- The goal of the offset order is to send your order X amount of ticks
from where you actually place it on the ladder. This is sometimes
known as a "fat finger" safety.
![](offset_algo_control.png)
- OCO
- An OCO order creates a relationship between a child order and a 'stop'
or 'other' order such that when one of these is cancelled, both cancel,
or when one is filled, the other order's size is debited in the amount
of the fill. Composing this order with Cover simulates scalping
functionality.
![](oco_algo_control.png)
- Cover
- A Cover order’s purpose is to fire a hedge order (of any algo-type) in response to fills from it’s child order on the opposite side of the market N ticks away.
![](cover_algo_control.png)
- Trigger
- The Trigger order is exactly as it sounds, where it will trigger an order based on conditions defined.
- This order also has exit strategy built in where a user can define when the trigger order can auto-cancel based on time as well as price conditional.
![](trigger_algo_control.png)
- The middle of the control section is where you can click on
predefined values to quickly populate the volume at which you wish
@@ -264,20 +310,36 @@ insert_anchor_links = "right"
-  The bottom section of the control area of a ladder has 4 aspects
to it
- Cancel Buys, which will cancel all buys currently working on the
ladder
- Cancel Sells, which will cancel all sells currently working on the
ladder
- Cancel All, which will cancel all orders on the ladder
- Settings, which will launch the ladder settings window which
appears like below
- Cancel Buys, which will cancel all buys currently working on the
ladder
- Cancel Sells, which will cancel all sells currently working on the
ladder
- Cancel All, which will cancel all orders on the ladder
- Settings, which will launch the ladder settings window which
appears like below
![](ladder_settings.png)
- Fonts and Colors - This is where you can customize the overall main
aspects of the ladder.
- Market Break Lines - These are where can control the viewing of what
has been traded through various shading.
- Market Size Display - This is where you can define if you wish
to see implieds or not and how exactly you would like to see them.
- Volume Display - Where you can choose to show the volume traded at price
graph and what details you would like to see.
- Size Presets - Here you can define what volume of predefined quick buttons
you would like to see or any at all. You can remove these by clicking on the “X” next to the volume number to remove one or many.
- Algo Favorites - Here you can set up which “quick button” or user defined
algos to see. You can customize the amount of columns to be
displayed as well.
- Left Panel - This is where you can remove predefined information on the
left panel to save room and get rid of data that you might not require
to see.
![](image71.png)
- The other, main part of the ladder, is the ladder itself as
displayed below
![](image65.png)
![](ladder_main_part.png)
- There are 5 main columns
@@ -291,14 +353,10 @@ insert_anchor_links = "right"
where the last trade occurred
- Functionality of the ladder
- To enter an order directly by clicking in the ladder, left click on either the bid or ask volume with a volume greater than 0 on the predefined value box on the left-hand section
- A badge will be displayed within the orders column of the value you have chosen and the quantity filled as displayed below
- To enter an order directly by clicking in the ladder, left click on
either the bid or ask volume with a volume greater than 0 on the
predefined value box on the left-hand section
- A badge will be displayed within the orders column of the value you
have chosen and the quantity filled as displayed below
![](image46.png)
![](ladder_badges.png)
- To change the price level you wish to work the order, you can right-click and hold on the badge to move it up or down the ladder for the
desired price
@@ -344,3 +402,42 @@ insert_anchor_links = "right"
- Please note you can also launch the required parameters for algo or
synthetic orders by clicking on the right arrow next to the algo or
synthetic
## Algo Studio
- The BTS Algo Studio was built for users to experience algo composability
never seen before in a professional futures trading system.
- There is no limit for putting both “canned algos” already present in the
system as well as user defined algos fed into the system via the API.
- This is all in place in a very user friendly GUI as shown below:
![](algo_studio.png)
- There are 3 main areas of the Algo Studio:
- Left Pane (from top to bottom)
- Add Algo
- Clone Algo
- Save Algo
- Remove Algo
- Middle Pane
- This is where your customized algo will be displayed in a running list
- Right Pane
- This is where your complete customization happens of combining various
algos together and saving them to fit your exact needs
- On the very top are the name and abbreviation boxes
- For the name you can simply name the algo whatever you would like
- For the abbreviation section (which is optional), you can put in an
abbreviated name for the algo which will be displayed on the Algo
buttons on the ladder. (Example - Algo name = PROFIT_OCO_LEAN,
Abbrev. = POL)
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+11
View File
@@ -4,6 +4,17 @@ weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## 1.5.6
### User Visible Changes
- Fixed a bug that could cause limits to be erroneously enforced
- Improved algo documentation
- Changed how the spreader uses net change
- Simplified trigger algo user interface
### Internal changes
- Defaulted to not recording sim market data
- Improved documentation build
- Added additional performance tracing
## 1.5.5
- Fixed Market View 'stale working orders' bug
+197 -223
View File
@@ -1,223 +1,197 @@
function initToggleMenu() {
var $menu = document.querySelector(".menu");
var $menuIcon = document.querySelector(".menu-icon");
var $page = document.querySelector(".page");
$menuIcon.addEventListener("click", function() {
$menu.classList.toggle("menu-hidden");
$page.classList.toggle("page-without-menu");
});
}
function debounce(func, wait) {
var timeout;
return function () {
var context = this;
var args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function () {
timeout = null;
func.apply(context, args);
}, wait);
};
}
// Taken from mdbook
// The strategy is as follows:
// First, assign a value to each word in the document:
// Words that correspond to search terms (stemmer aware): 40
// Normal words: 2
// First word in a sentence: 8
// Then use a sliding window with a constant number of words and count the
// sum of the values of the words within the window. Then use the window that got the
// maximum sum. If there are multiple maximas, then get the last one.
// Enclose the terms in <b>.
function makeTeaser(body, terms) {
var TERM_WEIGHT = 40;
var NORMAL_WORD_WEIGHT = 2;
var FIRST_WORD_WEIGHT = 8;
var TEASER_MAX_WORDS = 30;
var stemmedTerms = terms.map(function (w) {
return elasticlunr.stemmer(w.toLowerCase());
});
var termFound = false;
var index = 0;
var weighted = []; // contains elements of ["word", weight, index_in_document]
// split in sentences, then words
var sentences = body.toLowerCase().split(". ");
for (var i in sentences) {
var words = sentences[i].split(" ");
var value = FIRST_WORD_WEIGHT;
for (var j in words) {
var word = words[j];
if (word.length > 0) {
for (var k in stemmedTerms) {
if (elasticlunr.stemmer(word).startsWith(stemmedTerms[k])) {
value = TERM_WEIGHT;
termFound = true;
}
}
weighted.push([word, value, index]);
value = NORMAL_WORD_WEIGHT;
}
index += word.length;
index += 1; // ' ' or '.' if last word in sentence
}
index += 1; // because we split at a two-char boundary '. '
}
if (weighted.length === 0) {
return body;
}
var windowWeights = [];
var windowSize = Math.min(weighted.length, TEASER_MAX_WORDS);
// We add a window with all the weights first
var curSum = 0;
for (var i = 0; i < windowSize; i++) {
curSum += weighted[i][1];
}
windowWeights.push(curSum);
for (var i = 0; i < weighted.length - windowSize; i++) {
curSum -= weighted[i][1];
curSum += weighted[i + windowSize][1];
windowWeights.push(curSum);
}
// If we didn't find the term, just pick the first window
var maxSumIndex = 0;
if (termFound) {
var maxFound = 0;
// backwards
for (var i = windowWeights.length - 1; i >= 0; i--) {
if (windowWeights[i] > maxFound) {
maxFound = windowWeights[i];
maxSumIndex = i;
}
}
}
var teaser = [];
var startIndex = weighted[maxSumIndex][2];
for (var i = maxSumIndex; i < maxSumIndex + windowSize; i++) {
var word = weighted[i];
if (startIndex < word[2]) {
// missing text from index to start of `word`
teaser.push(body.substring(startIndex, word[2]));
startIndex = word[2];
}
// add <em/> around search terms
if (word[1] === TERM_WEIGHT) {
teaser.push("<b>");
}
startIndex = word[2] + word[0].length;
teaser.push(body.substring(word[2], startIndex));
if (word[1] === TERM_WEIGHT) {
teaser.push("</b>");
}
}
teaser.push("…");
return teaser.join("");
}
function formatSearchResultItem(item, terms) {
var li = document.createElement("li");
li.classList.add("search-results__item");
li.innerHTML = `<a href="${item.ref}">${item.doc.title}</a>`;
li.innerHTML += `<div class="search-results__teaser">${makeTeaser(item.doc.body, terms)}</div>`;
return li;
}
// Go from the book view to the search view
function toggleSearchMode() {
var $bookContent = document.querySelector(".book-content");
var $searchContainer = document.querySelector(".search-container");
if ($searchContainer.classList.contains("search-container--is-visible")) {
$searchContainer.classList.remove("search-container--is-visible");
document.body.classList.remove("search-mode");
$bookContent.style.display = "block";
} else {
$searchContainer.classList.add("search-container--is-visible");
document.body.classList.add("search-mode");
$bookContent.style.display = "none";
document.getElementById("search").focus();
}
}
function initSearch() {
var $searchInput = document.getElementById("search");
if (!$searchInput) {
return;
}
var $searchIcon = document.querySelector(".search-icon");
$searchIcon.addEventListener("click", toggleSearchMode);
var $searchResults = document.querySelector(".search-results");
var $searchResultsHeader = document.querySelector(".search-results__header");
var $searchResultsItems = document.querySelector(".search-results__items");
var MAX_ITEMS = 10;
var options = {
bool: "AND",
fields: {
title: {boost: 2},
body: {boost: 1},
}
};
var currentTerm = "";
var index = elasticlunr.Index.load(window.searchIndex);
$searchInput.addEventListener("keyup", debounce(function() {
var term = $searchInput.value.trim();
if (term === currentTerm || !index) {
return;
}
$searchResults.style.display = term === "" ? "none" : "block";
$searchResultsItems.innerHTML = "";
if (term === "") {
return;
}
var results = index.search(term, options).filter(function (r) {
return r.doc.body !== "";
});
if (results.length === 0) {
$searchResultsHeader.innerText = `No search results for '${term}'.`;
return;
}
currentTerm = term;
$searchResultsHeader.innerText = `${results.length} search results for '${term}':`;
for (var i = 0; i < Math.min(results.length, MAX_ITEMS); i++) {
if (!results[i].doc.body) {
continue;
}
// var item = document.createElement("li");
// item.innerHTML = formatSearchResultItem(results[i], term.split(" "));
console.log(results[i]);
$searchResultsItems.appendChild(formatSearchResultItem(results[i], term.split(" ")));
}
}, 150));
}
if (document.readyState === "complete" ||
(document.readyState !== "loading" && !document.documentElement.doScroll)
) {
initToggleMenu();
} else {
document.addEventListener("DOMContentLoaded", function () {
initToggleMenu();
initSearch();
});
}
function initToggleMenu() {
var $menu = document.querySelector(".menu");
var $menuIcon = document.querySelector(".menu-icon");
var $page = document.querySelector(".page");
$menuIcon.addEventListener("click", function () {
$menu.classList.toggle("menu-hidden");
$page.classList.toggle("page-without-menu");
});
}
function debounce(func, wait) {
var timeout;
return function () {
var context = this;
var args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function () {
timeout = null;
func.apply(context, args);
}, wait);
};
}
// Taken from mdbook
// The strategy is as follows:
// First, assign a value to each word in the document:
// Words that correspond to search terms (stemmer aware): 40
// Normal words: 2
// First word in a sentence: 8
// Then use a sliding window with a constant number of words and count the
// sum of the values of the words within the window. Then use the window that got the
// maximum sum. If there are multiple maximas, then get the last one.
// Enclose the terms in <b>.
function makeTeaser(body, terms) {
var TERM_WEIGHT = 40;
var NORMAL_WORD_WEIGHT = 2;
var FIRST_WORD_WEIGHT = 8;
var TEASER_MAX_WORDS = 30;
var stemmedTerms = terms.map(function (w) {
return elasticlunr.stemmer(w.toLowerCase());
});
var termFound = false;
var index = 0;
var weighted = []; // contains elements of ["word", weight, index_in_document]
// split in sentences, then words
var sentences = body.toLowerCase().split(". ");
for (var i in sentences) {
var words = sentences[i].split(" ");
var value = FIRST_WORD_WEIGHT;
for (var j in words) {
var word = words[j];
if (word.length > 0) {
for (var k in stemmedTerms) {
if (elasticlunr.stemmer(word).startsWith(stemmedTerms[k])) {
value = TERM_WEIGHT;
termFound = true;
}
}
weighted.push([word, value, index]);
value = NORMAL_WORD_WEIGHT;
}
index += word.length;
index += 1; // ' ' or '.' if last word in sentence
}
index += 1; // because we split at a two-char boundary '. '
}
if (weighted.length === 0) {
return body;
}
var windowWeights = [];
var windowSize = Math.min(weighted.length, TEASER_MAX_WORDS);
// We add a window with all the weights first
var curSum = 0;
for (var i = 0; i < windowSize; i++) {
curSum += weighted[i][1];
}
windowWeights.push(curSum);
for (var i = 0; i < weighted.length - windowSize; i++) {
curSum -= weighted[i][1];
curSum += weighted[i + windowSize][1];
windowWeights.push(curSum);
}
// If we didn't find the term, just pick the first window
var maxSumIndex = 0;
if (termFound) {
var maxFound = 0;
// backwards
for (var i = windowWeights.length - 1; i >= 0; i--) {
if (windowWeights[i] > maxFound) {
maxFound = windowWeights[i];
maxSumIndex = i;
}
}
}
var teaser = [];
var startIndex = weighted[maxSumIndex][2];
for (var i = maxSumIndex; i < maxSumIndex + windowSize; i++) {
var word = weighted[i];
if (startIndex < word[2]) {
// missing text from index to start of `word`
teaser.push(body.substring(startIndex, word[2]));
startIndex = word[2];
}
// add <em/> around search terms
if (word[1] === TERM_WEIGHT) {
teaser.push("<b>");
}
startIndex = word[2] + word[0].length;
teaser.push(body.substring(word[2], startIndex));
if (word[1] === TERM_WEIGHT) {
teaser.push("</b>");
}
}
teaser.push("…");
return teaser.join("");
}
function formatSearchResultItem(item, terms) {
var li = document.createElement("li");
li.classList.add("search-results__item");
li.innerHTML = "<a href=\"" + item.ref + "\">" + item.doc.title + "</a>";
li.innerHTML += "<div class=\"search-results__teaser\">" + makeTeaser(item.doc.body, terms) + "</div>";
return li;
}
// Go from the book view to the search view
function toggleSearchMode() {
var $bookContent = document.querySelector(".book-content");
var $searchContainer = document.querySelector(".search-container");
if ($searchContainer.classList.contains("search-container--is-visible")) {
$searchContainer.classList.remove("search-container--is-visible");
document.body.classList.remove("search-mode");
$bookContent.style.display = "block";
}
else {
$searchContainer.classList.add("search-container--is-visible");
document.body.classList.add("search-mode");
$bookContent.style.display = "none";
document.getElementById("search").focus();
}
}
function initSearch() {
var $searchInput = document.getElementById("search");
if (!$searchInput) {
return;
}
var $searchIcon = document.querySelector(".search-icon");
$searchIcon.addEventListener("click", toggleSearchMode);
var $searchResults = document.querySelector(".search-results");
var $searchResultsHeader = document.querySelector(".search-results__header");
var $searchResultsItems = document.querySelector(".search-results__items");
var MAX_ITEMS = 10;
var options = {
bool: "AND",
fields: {
title: { boost: 2 },
body: { boost: 1 }
}
};
var currentTerm = "";
var index = elasticlunr.Index.load(window.searchIndex);
$searchInput.addEventListener("keyup", debounce(function () {
var term = $searchInput.value.trim();
if (term === currentTerm || !index) {
return;
}
$searchResults.style.display = term === "" ? "none" : "block";
$searchResultsItems.innerHTML = "";
if (term === "") {
return;
}
var results = index.search(term, options).filter(function (r) {
return r.doc.body !== "";
});
if (results.length === 0) {
$searchResultsHeader.innerText = "No search results for '" + term + "'.";
return;
}
currentTerm = term;
$searchResultsHeader.innerText = results.length + " search results for '" + term + "':";
for (var i = 0; i < Math.min(results.length, MAX_ITEMS); i++) {
if (!results[i].doc.body) {
continue;
}
// var item = document.createElement("li");
// item.innerHTML = formatSearchResultItem(results[i], term.split(" "));
console.log(results[i]);
$searchResultsItems.appendChild(formatSearchResultItem(results[i], term.split(" ")));
}
}, 150));
}
if (document.readyState === "complete" ||
(document.readyState !== "loading" && !document.documentElement.doScroll)) {
initToggleMenu();
}
else {
document.addEventListener("DOMContentLoaded", function () {
initToggleMenu();
initSearch();
});
}
+6 -1
View File
@@ -8,5 +8,10 @@ $(document).ready(function() {
$(".back-button")
.html("Go Back")
.click(_ => window.history.back());
.click(function(_) { window.history.back()});
if(navigator.userAgent.indexOf("Trident") > -1) {
console.log(navigator.userAgent);
$(".search-icon").css("display", "none");
}
});
@@ -20,6 +20,8 @@
<sys:Double x:Key="RowHeight">16</sys:Double>
<sys:Double x:Key="NegativeRowHeight">-16</sys:Double>
<sys:Double x:Key="ItemsHostCanBeDisabledCollapsedHeight">15</sys:Double>
<sys:Double x:Key="HeaderPanelItemSeparation">6</sys:Double>
<Thickness x:Key="Margin.LHeaderPanelItem" Left="{StaticResource HeaderPanelItemSeparation}"/>
<Thickness
x:Key="GroupLevelMargin"
Left="{x:Static usp:ExecutionStrategyEditor.GroupLevelIndent}"
@@ -100,7 +102,7 @@
</usp:DataGridParameterCellTemplateConverter.BooleanTemplate>
<usp:DataGridParameterCellTemplateConverter.DoubleTemplate>
<DataTemplate>
<c:TickUpDownDouble Value="{Binding value}" IsEnabled="{Binding enabled}" Increment="{Binding tick_increment, FallbackValue={u:Double 1}}"/>
<c:TickUpDownDouble Value="{Binding value}" IsEnabled="{Binding enabled}" Increment="{Binding tick_increment, FallbackValue={u:Double 1}}" FlushTextImmediately="False"/>
</DataTemplate>
</usp:DataGridParameterCellTemplateConverter.DoubleTemplate>
<usp:DataGridParameterCellTemplateConverter.IntTemplate>
@@ -369,16 +371,21 @@
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="7*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="5*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" IsEnabled="{TemplateBinding IsNew}">Name</TextBlock>
<TextBox
x:Name="PART_Name_TextBox" Grid.Column="1" Margin="6,0,0,0" HorizontalAlignment="Stretch" IsEnabled="{TemplateBinding IsNew}"
x:Name="PART_Name_TextBox"
Grid.Column="1"
Margin="{StaticResource Margin.LHeaderPanelItem}"
MinWidth="80"
HorizontalAlignment="Stretch"
IsEnabled="{TemplateBinding IsNew}"
Text="{Binding SyntheticName, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}, ValidatesOnDataErrors=True, ValidatesOnNotifyDataErrors=True, NotifyOnValidationError=True}"
>
<u:Behavior.BindingUpdateGroups>
@@ -390,18 +397,22 @@
</u:Behavior.BindingUpdateGroups>
</TextBox>
<TextBlock
Grid.Column="2" VerticalAlignment="Center" Margin="6,0,0,0" Text="Ticks" IsEnabled="{TemplateBinding IsNew}"
Grid.Column="2" VerticalAlignment="Center" Margin="{StaticResource Margin.LHeaderPanelItem}" Text="Ticks" IsEnabled="{TemplateBinding IsNew}"
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
/>
<c:TickFactorsPicker
x:Name="PART_TickFactorsPicker" Grid.Column="3" Margin="6,0,0,0" IsEnabled="{TemplateBinding IsNew}" HorizontalAlignment="Stretch"
x:Name="PART_TickFactorsPicker" Grid.Column="3" Margin="{StaticResource Margin.LHeaderPanelItem}" IsEnabled="{TemplateBinding IsNew}" HorizontalAlignment="Stretch"
SelectedTickFactors="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
/>
<TextBlock Grid.Column="4" VerticalAlignment="Center" Margin="6,0,0,0">Pricing</TextBlock>
<TextBlock Grid.Column="4" VerticalAlignment="Center" Margin="{StaticResource Margin.LHeaderPanelItem}" IsEnabled="{TemplateBinding IsNew}" >Pricing</TextBlock>
<ComboBox
Grid.Column="5" Margin="6,0,0,0" IsEnabled="False"
ItemsSource="{x:Static usp:ExecutionStrategyEditor.PriceCalcItems}" SelectedItem="{Binding PriceCalc, RelativeSource={RelativeSource TemplatedParent}}"
Grid.Column="5"
Margin="{StaticResource Margin.LHeaderPanelItem}"
Width="90"
IsEnabled="{TemplateBinding IsNew}"
ItemsSource="{x:Static usp:ExecutionStrategyEditor.PriceCalcItems}"
SelectedItem="{Binding price_calc}"
>
<ComboBox.ItemTemplate>
<DataTemplate>
@@ -409,7 +420,7 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<c:HelpButton Grid.Column="6" Margin="3,0,0,0"/>
<c:HelpButton Grid.Column="8" Margin="3,0,0,0" ManPage="bts_spark/spreader/#creating-a-synthetic" />
</Grid>
<c:DataGridEx
x:Name="PART_DataGrid"
@@ -99,7 +99,7 @@ namespace mbts.ui.spark {
#region PriceCalc
public static readonly DependencyProperty PriceCalcProperty = DependencyProperty.Register("PriceCalc", typeof(SyntheticContract.Types.PriceCalc), typeof(ExecutionStrategyEditor),
new FrameworkPropertyMetadata(SyntheticContract.Types.PriceCalc.Diff, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
new FrameworkPropertyMetadata(SyntheticContract.Types.PriceCalc.Standard, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public SyntheticContract.Types.PriceCalc PriceCalc { get => (SyntheticContract.Types.PriceCalc)GetValue(PriceCalcProperty); set => SetValue(PriceCalcProperty, value); }
public static readonly IEnumerable<SyntheticContract.Types.PriceCalc> PriceCalcItems = EnumHelper.get_values<SyntheticContract.Types.PriceCalc>();
@@ -315,7 +315,6 @@ namespace mbts.ui.spark {
else if (t == typeof(uint)) return UIntTemplate;
else if (t == typeof(QuantityMeasure)) return QuantityMeasureTemplate;
else if (t == typeof(LegSpreaderParams.Types.MarketDataType)) return MarketDataTypeTemplate;
else if (t == typeof(LegSpreaderParams.Types.LegMarketDataCalculation)) return MarketDataCalculationTemplate;
else if (t == typeof(ConditionalQuoting.Types.PriceType)) return ConditionalPriceTypeTemplate;
else return null;
}
@@ -1,18 +0,0 @@
using mbts.ui.spark;
using LegMarketDataCalculation = bts.spark.LegSpreaderParams.Types.LegMarketDataCalculation;
namespace mbts.spark {
partial class SyntheticEditorVM {
class MarketDataCalculationModel : RowModelBase<LegMarketDataCalculation> {
public override string display_name { get; } = "Market Data Calculation";
public override string help_text { get; }
public override string man_page { get; } = string.Empty;
public override LegMarketDataCalculation get_value(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.LegMdCalc;
public override void set_value(LegSpreaderParamsTuple leg_tup, LegMarketDataCalculation value) =>
leg_tup.leg_params.LegMdCalc = value;
}
}
}
@@ -26,6 +26,7 @@ namespace mbts.spark {
readonly PriceMultiplierModel _price_mult_model;
readonly List<IDisposable> _resources = new List<IDisposable>();
readonly Property<bool> _has_working_orders, _can_add_leg;
Security _synthetic;
bool _set_name_automatically, _set_ticks_automatically, _set_quantities_automatically, _set_price_mults_automatically, _is_setting_quantities, _is_setting_price_mults;
TickFactors _tick_factors;
@@ -207,7 +208,6 @@ namespace mbts.spark {
//new MinLeanQtyModel(), //TAHA_SPREADER
//new QuoteHiddenModel(),
new MarketDataTypeModel(),
new MarketDataCalculationModel(),
new QuoteThrottleEnabledModel(quote_throttle_inside_ticks, quote_throttle_outside_ticks),
quote_throttle_inside_ticks,
quote_throttle_outside_ticks,
@@ -291,7 +291,9 @@ namespace mbts.spark {
public ExecutionStrategyImmutable get_strategy() {
var mutable_strategy = _strategy.CloneToMutable();
mutable_strategy.LegParamsMap.Clear();
foreach (var lt in leg_tuples) mutable_strategy.LegParamsMap.Add(lt.leg.Security.Id, lt.leg_params);
foreach (var lt in leg_tuples) {
mutable_strategy.LegParamsMap.Add(lt.leg.Security.Id, lt.leg_params);
}
return new ExecutionStrategyImmutable(mutable_strategy);
}
@@ -10,7 +10,7 @@ namespace mbts.spark {
public readonly AlgoKey key;
public AlgoData(CustomOrderDefinitionImmutable value)
: this(value, value.CustomCase.display_name(), new AlgoKey(value.CustomCase)) { }
: this(value, value.display_name(), OrderDataHelper.get_shallow_factory_key(value)) { }
public AlgoData(CustomOrderDefinitionImmutable value, string name, AlgoKey key) {
this.value = value;
@@ -72,7 +72,7 @@ namespace mbts.spark {
_build_structure(definition.CloneToMutable(), ref structure, pos);
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, int pos) {
var cad = OrderDataHelper.get_child_algo_data(algo.CustomCase);
var cad = OrderDataHelper.get_child_algo_data(algo);
for (int di = 0; di < cad.Count; di++) {
if (cad[di].get_child(algo) is CustomOrderDefinition child_algo) {
ArrayHelper.ensure_capacity(ref structure, pos + 1);
@@ -87,7 +87,7 @@ namespace mbts.spark {
static void _build_def(int[] structure, ref int pos, ref CustomOrderDefinition algo) {
for (int i = pos; i < structure.Length; i++) {
var cad = OrderDataHelper.get_child_algo_data(algo.CustomCase);
var cad = OrderDataHelper.get_child_algo_data(algo);
for (int di = 0; di < cad.Count; di++) {
int mask = 1 << (31 - di);
if ((structure[i] & mask) == mask) {
@@ -95,10 +95,10 @@ namespace mbts.spark {
var child_algo = OrderDataHelper.Defaults.CustomOrderDefinition(type);
pos++;
_build_def(structure, ref pos, ref child_algo);
cad[di].set_child(algo, child_algo);
cad[di].assign_child(algo, child_algo);
}
}
}
}
}
}
}
@@ -105,19 +105,23 @@ namespace mbts.spark {
}
public void row_selection_changed(SelectionChangedEventArgs e) {
if (e.RemovedItems != null) {
foreach (var row_vm in e.RemovedItems.Cast<OrderBookRowVM>()) {
row_vm.is_selected = false;
try {
if (e.RemovedItems != null) {
foreach (var row_vm in e.RemovedItems.Cast<OrderBookRowVM>()) {
row_vm.is_selected = false;
}
}
}
if (e.AddedItems != null) {
foreach (var row_vm in e.AddedItems.Cast<OrderBookRowVM>()) {
row_vm.is_selected = true;
if (e.AddedItems != null) {
foreach (var row_vm in e.AddedItems.Cast<OrderBookRowVM>()) {
row_vm.is_selected = true;
}
}
RowSelectionHelper.apply_changes(_selected_rows, e);
selected_order_changed?.Invoke(current_order);
} catch (Exception ex) {
// [John]I observed a strange null-reference exception in this callback when the launcher was shutting down (see https://bluetradesys.atlassian.net/browse/SPARK-2089)
Log.Ui.error($"[{nameof(OrderBookVM)}] Error handling selection change: {ex}");
}
RowSelectionHelper.apply_changes(_selected_rows, e);
selected_order_changed?.Invoke(current_order);
}
public OrderBookStartupData create() {
@@ -346,7 +346,9 @@ namespace mbts.spark {
_time_in_force.observe_current(start_immediately);
public IObservable<IAlgoVM> observe_order_type(bool start_immediately = false) =>
_selected_algo_template.observe_current(start_immediately).Select(t => t.algo_root);
_model.algo_model.templates_enabled() ?
_selected_algo_template.observe_current(start_immediately).Select(t => t?.algo_root) :
_selected_algo_definition.observe_selected_item(start_immediately).Select(d => d?.algo_root);
public OrderDefinition create(Side side) {
if (HasErrors)
@@ -1,5 +1,8 @@
using bts.api;
using bts.api.orderman;
using bts.utils;
using Google.Protobuf.Reflection;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
@@ -10,6 +13,9 @@ namespace mbts.spark {
[Export]
public class DynamicOrderHelper
{
static readonly IReadOnlyDictionary<DynamicOrderParam.ValueOneofCase, (FieldDescriptor field_descriptor, Type field_type)> _descriptor_case_index =
ProtoHelper.get_oneof_descriptor_index<DynamicOrderParam, DynamicOrderParam.ValueOneofCase>(nameof(DynamicOrderParam.ValueCase));
readonly RegisteredDynamicOrderTypes _dynamic_types;
[ImportingConstructor]
private DynamicOrderHelper(OrderService order_service)
@@ -28,11 +34,47 @@ namespace mbts.spark {
foreach(var p in d.ParamDesc)
dorder.Dynamic.OrderParams.Add( p.ParamName, new DynamicOrderParam() );
defs.Add(new CustomOrderDefinitionImmutable(dorder));
var de = get_descriptor("");
}
}
internal DynamicOrderDescriptor get_descriptor(string dynamic_type) =>
_dynamic_types.DynamicOrderDesc.FirstOrDefault(d => d.DynamicOrderType == dynamic_type);
_dynamic_types.DynamicOrderDesc.FirstOrDefault(d => d.DynamicOrderType == dynamic_type) ?? new DynamicOrderDescriptor();
internal DynamicOrderDefinition create_def(string dynamic_type) {
var desc = get_descriptor(dynamic_type);
var def = new DynamicOrderDefinition();
foreach (var p in desc.ParamDesc) {
if (_is_child_algo_desc(p)) {
def.OrderParams[p.ParamName] = new DynamicOrderParam { AsOrderDef = new CustomOrderDefinition() };
} else if (_is_value_param_desc(p)) {
var value_case = _get_value_case(p.ParamType.Primitive);
if (_descriptor_case_index.TryGetValue(value_case, out (FieldDescriptor fd, Type type) tup)) {
var dop = new DynamicOrderParam();
tup.fd.Accessor.SetValue(dop, ConvertUtils.get_default(tup.type));
def.OrderParams[p.ParamName] = dop;
}
}
}
return def;
}
static DynamicOrderParam.ValueOneofCase _get_value_case(DynamicOrderPrimitiveDescriptor primitive_descriptor_case) {
switch (primitive_descriptor_case) {
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveBool: return DynamicOrderParam.ValueOneofCase.AsBool;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveDouble: return DynamicOrderParam.ValueOneofCase.AsBool;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: return DynamicOrderParam.ValueOneofCase.AsInt32;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: return DynamicOrderParam.ValueOneofCase.AsUint32;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: return DynamicOrderParam.ValueOneofCase.AsString;
default: throw ExceptionBuilder.Argument.unexpected_case(primitive_descriptor_case, nameof(primitive_descriptor_case));
}
}
static bool _is_child_algo_desc(DynamicOrderParamDescriptor desc) =>
desc.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Primitive &&
desc.ParamType.Primitive == DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef;
static bool _is_value_param_desc(DynamicOrderParamDescriptor desc) =>
desc.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Enumerated ||
(desc.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Primitive && desc.ParamType.Primitive != DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef);
}
}
}
@@ -1,28 +1,36 @@
using bts.api.orderman;
using mbts.collections;
using mbts.ui;
using mbts.ui.shell;
using mbts.spark.utilities;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Linq;
using System.Reactive.Linq;
namespace mbts.spark {
static partial class OrderTypeVMFactory {
class DynamicOrderVM : OrderTypeVMBase<DynamicOrderDefinition, DynamicOrderDefinitionImmutable> {
readonly ObservableKeyedCollection<DynamicOrderParamVM, string> _parameters = new ObservableKeyedCollection<DynamicOrderParamVM, string>(vm => vm.descriptor.ParamName);
readonly Property<string> _dyn_type;
OrdersModel _model;
bool _active;
public DynamicOrderVM(AlgoKey key, string dyn_type, string display_name, AlgoEditingPermissions editing_permissions)
: base(key, display_name, editing_permissions) {
_dyn_type = add_property(nameof(this.dyn_type), DynamicOrderDefinition.DynamicOrderTypeFieldNumber, dyn_type);
_parameters = new ObservableKeyedCollection<DynamicOrderParamVM, string>(vm => vm.descriptor.ParamName);
_resources.AddRange(
_dyn_type.Subscribe(_ => _reset_dynamic_types()),
_dyn_type.Subscribe(_ => {
_sync_dynamic_params();
sync_selectors();
}),
ObservableAutoWrapper.connect_item_hooks(_parameters.AsCollection(), pvm => {
add_property(pvm.descriptor.ParamName, mdef => mdef.OrderParams[pvm.descriptor.ParamName] = pvm.param, mdef => {
if (mdef.OrderParams.TryGetValue(pvm.descriptor.ParamName, out DynamicOrderParam p))
pvm.param = p;
});
return Disposable.Create(() => remove_property(pvm.descriptor.ParamName));
}),
ObservableAutoWrapper.connect_item_hooks(_parameters, p => {
p.PropertyChanged += on_param_prop_changed;
return Disposable.Create(() => p.PropertyChanged -= on_param_prop_changed);
@@ -31,103 +39,26 @@ namespace mbts.spark {
}
public string dyn_type => _dyn_type.Value;
public ObservableCollection<DynamicOrderParamVM> parameters {
get {
_ensure_init();
return _parameters;
public ObservableCollection<DynamicOrderParamVM> parameters => _parameters;
protected override void on_model_internal() =>
_sync_dynamic_params();
protected override IEnumerable<ChildAlgoData> get_selector_datum() =>
OrderDataHelper.get_child_algo_data(new CustomOrderDefinition {
Dynamic = model?.dynamic_helper?.create_def(dyn_type) ?? new DynamicOrderDefinition()
});
void _sync_dynamic_params() {
if (model != null) {
var desc = model.dynamic_helper.get_descriptor(dyn_type);
_parameters.SyncWithDictionary(desc.ParamDesc.Where(_is_value_param_desc).ToDictionary(p => p.ParamName), DynamicOrderParamVM.create);
}
}
protected override void assign_to_internal(DynamicOrderDefinition dynamic_def) {
foreach (var vm in _parameters.Values)
dynamic_def.OrderParams[vm.descriptor.ParamName] = vm.param;
}
protected override void load_from_internal(DynamicOrderDefinitionImmutable dyn_defi) {
foreach (var p in _parameters) {
if (dyn_defi.OrderParams.TryGetValue(p.descriptor.ParamName, out DynamicOrderParam dop)) {
p.param = dop;
} else {
p.param.ClearValue();
}
}
}
protected override async Task on_load_from_async(CustomOrderDefinitionImmutable defi) {
await Wait.until_true(() => _model != null, TimeSpan.FromSeconds(60));
_ensure_init();
}
void _ensure_init() {
if (!_active) {
_active = true;
// lazy initialization is required to avoid a stack overflow (otherwise, the algo-hierarchy would grow recursively without stopping).
_reset_dynamic_types();
}
}
protected override void on_model_internal(OrdersModel model) {
_model = model;
_reset_dynamic_types();
}
void _reset_dynamic_types() {
if (!_active)
return;
var value_descs = new Dictionary<string, DynamicOrderParamDescriptor>();
var child_descs = new Dictionary<string, DynamicOrderParamDescriptor>();
if (_model != null) {
var desc = _model.dynamic_helper.get_descriptor(dyn_type);
if (desc != null) {
foreach (var p in desc.ParamDesc) {
if (_is_value_param_desc(p)) {
value_descs[p.ParamName] = p;
} else if (_is_child_algo_desc(p)) {
child_descs[p.ParamName] = p;
}
}
}
}
_parameters.SyncWithDictionary(value_descs, DynamicOrderParamVM.create);
using (reset_selectors()) {
foreach (var kv in child_descs) {
add_selector(kv.Key, get_child, set_child);
// local methods
CustomOrderDefinitionImmutable? get_child(DynamicOrderDefinitionImmutable dyn_defi) {
if (dyn_defi.OrderParams.TryGetValue(kv.Key, out DynamicOrderParam p) && p.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef)
return new CustomOrderDefinitionImmutable(p.AsOrderDef);
return null;
}
void set_child(DynamicOrderDefinition dyn_def, CustomOrderDefinition child) =>
dyn_def.OrderParams[kv.Key] = new DynamicOrderParam { AsOrderDef = child };
}
}
}
static bool _is_child_algo_desc(DynamicOrderParamDescriptor desc) =>
desc.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Primitive &&
desc.ParamType.Primitive == DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef;
static bool _is_value_param_desc(DynamicOrderParamDescriptor desc) =>
desc.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Enumerated ||
(desc.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Primitive && desc.ParamType.Primitive != DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef);
public class SelectorVM : ViewModelBase {
readonly IAlgoSelector _selector;
public SelectorVM(IAlgoSelector selector) {
_selector = selector;
_selector.selected_algo_changed += _on_selected_algo_changed;
}
public IAlgoVM selected_algo { get => _selector.selected_algo; set => _selector.selected_algo = value; }
public ObservableCollection<IAlgoVM> algos => _selector.algos;
public void Dispose() =>
_selector.selected_algo_changed -= _on_selected_algo_changed;
void _on_selected_algo_changed(IAlgoVM oldval, IAlgoVM newval) =>
RaisePropertyChanged(nameof(selected_algo));
}
}
}
}
@@ -113,11 +113,12 @@ namespace mbts.spark {
readonly IDictionary<string, object> _children_vm = new ExpandoObject();
protected CompositeDisposable _resources = new CompositeDisposable();
readonly List<(Action<TMutable> assign, Action<TMutable> load)> _auto_props = new List<(Action<TMutable> assign, Action<TMutable> load)>();
readonly ObservableKeyedCollection<(string name, Action<TMutable> assign, Action<TMutable> load), string> _auto_props = new ObservableKeyedCollection<(string name, Action<TMutable> assign, Action<TMutable> load), string>(t => t.name);
readonly ObservableKeyedCollection<AlgoSelectorData, string> _child_algo_selector_data = new ObservableKeyedCollection<AlgoSelectorData, string>(sd => sd.prop_name);
readonly ObservableAutoWrapper<IAlgoSelector, AlgoSelectorData> _child_algo_selectors;
readonly Property<bool> _is_expanded, _can_save_values, _can_save_structure;
readonly DeferrableActionWrapper _def_change_notifier;
IDisposable _pending_load;
protected OrderTypeVMBase(AlgoKey id, string display_name, AlgoEditingPermissions editing_permissions) {
this.id = id;
@@ -127,9 +128,7 @@ namespace mbts.spark {
_can_save_values = backing(nameof(can_save_values), true);
_can_save_structure = backing(nameof(can_save_structure), true);
_is_expanded = backing<bool>(nameof(is_expanded));
foreach (var c in OrderDataHelper.get_child_algo_data(order_type)) {
_children_vm[c.name] = add_selector(c.name, c.descriptor);
}
meta = OrderDataHelper.get_metadata(order_type);
_def_change_notifier = new DeferrableActionWrapper(() => definition_changed?.Invoke());
PropertyChanged += (s, e) => {
switch (e.PropertyName) {
@@ -170,39 +169,41 @@ namespace mbts.spark {
}, true));
}
public AlgoEditingPermissions editing_permissions { get; private set; }
public event Action definition_changed;
// core
public AlgoDefi definition => new AlgoDefi(this.created_assigned());
public AlgoType order_type => OrderDataHelper.get_order_type(new TMutable());
public bool is_bts_type => display_name.StartsWith("BTS");
public AlgoKey id { get; }
// display
public bool is_bts_type => display_name.StartsWith("BTS");
public string display_name { get; private set; }
public OrderMetadata meta => OrderDataHelper.get_metadata(order_type);
public virtual bool can_expand => !meta.NoProps;
public virtual bool is_expanded {
get => _is_expanded.Value;
set => _is_expanded.Value = value;
}
public OrderMetadata meta { get; }
// child algos
public dynamic children_vm => _children_vm;
public ObservableCollection<IAlgoSelector> child_algo_selectors => _child_algo_selectors;
// editing permissions
public AlgoEditingPermissions editing_permissions { get; private set; }
public bool can_edit_values => EnumHelper.includes_flag(editing_permissions, AlgoEditingPermissions.AllowEditValues);
public bool can_edit_structure => EnumHelper.includes_flag(editing_permissions, AlgoEditingPermissions.AllowEditStructure);
public bool can_save_values => _can_save_values.Value;
public bool can_save_structure => _can_save_structure.Value;
// misc
public virtual bool can_expand => !meta.NoProps;
public virtual bool is_expanded { get => _is_expanded.Value; set => _is_expanded.Value = value; }
// protected (observed props)
protected OrdersModel model { get; private set; }
protected SecurityMD? security_md { get; private set; }
protected SecurityImmutable? security => security_md?.security;
public void assign_to(AlgoDef value) {
var algo = new TMutable();
foreach (var (assign, load) in _auto_props)
foreach (var (name, assign, load) in _auto_props)
assign(algo);
_descriptor.Accessor.SetValue(value, algo);
foreach (var sd in _child_algo_selector_data) {
if (sd.selector.selected_algo != null)
sd.set_order(value, sd.selector.selected_algo.created_assigned());
}
assign_to_internal(algo);
}
public void set_permissions(AlgoEditingPermissions editing_permissions) {
@@ -217,26 +218,23 @@ namespace mbts.spark {
public void load_from(AlgoDefi value) {
if (value.CustomCase != order_type)
return;
using (_def_change_notifier.defer_execution()) {
foreach (var sd in _child_algo_selector_data) {
var inner_def = sd.get_order(value);
if (inner_def.HasValue) {
if (security.HasValue) {
inner_def = OrderDataHelper.fit_to_type(inner_def.Value, security.Value.ContractCase);
if (!inner_def.HasValue)
continue;
var algom = _descriptor.Accessor.GetValue(value.CloneToMutable()) as TMutable ?? new TMutable();
Disposable.replace(ref _pending_load,
new CompositeDisposable(
ObservableAutoWrapper.connect_item_actions(_child_algo_selector_data.AsCollection(), sd => {
var inner_def = sd.get_order(value);
if (inner_def.HasValue) {
if (security.HasValue) {
inner_def = OrderDataHelper.fit_to_type(inner_def.Value, security.Value.ContractCase);
if (!inner_def.HasValue)
return;
}
var vm = sd.selector.algos.GetOrAdd(OrderDataHelper.get_shallow_factory_key(inner_def.Value), k => create(k, inner_def.Value.display_name(), inner_def.Value.CustomCase));
vm.load_from(inner_def.Value);
sd.selector.selected_algo = vm;
}
var vm = sd.selector.algos.GetOrAdd(OrderDataHelper.get_shallow_factory_key(inner_def.Value), k => create(k, inner_def.Value.display_name(), inner_def.Value.CustomCase));
vm.load_from(inner_def.Value);
sd.selector.selected_algo = vm;
}
}
var algom = _descriptor.Accessor.GetValue(value.CloneToMutable()) as TMutable ?? new TMutable();
var algoi = _create_immutable(algom);
foreach (var (assign, load) in _auto_props)
load(algom);
load_from_internal(algoi);
}
}),
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load(algom))));
}
public virtual async Task<AlgoDefi?> load_from_async(AlgoDefi defi) {
@@ -254,92 +252,72 @@ namespace mbts.spark {
}
}
var algom = _descriptor.Accessor.GetValue(defi.CloneToMutable()) as TMutable ?? new TMutable();
var algoi = _create_immutable(algom);
foreach (var (assign, load) in _auto_props)
foreach (var (name, assign, load) in _auto_props)
load(algom);
load_from_internal(algoi);
return defi;
}
}
protected virtual Task on_load_from_async(AlgoDefi defi) => Task.CompletedTask;
protected virtual void assign_to_internal(TMutable value) { }
protected virtual void load_from_internal(TImmutable value) { }
protected void raise_definition_changed() =>
_def_change_notifier.execute();
protected virtual void validate() { }
protected virtual void on_permissions_changed() {
foreach (var vm in child_algo_selectors.SelectMany(s => s.algos.AsCollection()))
vm.set_permissions(editing_permissions);
}
protected void raise_definition_changed() =>
_def_change_notifier.execute();
protected virtual void validate() { }
protected override void Dispose(bool disposing) =>
Disposable.dispose(ref _resources);
#region Chidl Algo Selectors (add/remove/reset)
protected IAlgoSelector add_selector(string vm_prop_name, FieldDescriptor inner_desc) {
return add_selector(vm_prop_name, def => {
if (_descriptor.Accessor.GetValue(def.CloneToMutable()) is TMutable mval) {
if (inner_desc.Accessor.GetValue(mval) is AlgoDef inner_def)
return new AlgoDefi(inner_def);
}
return null;
}, (mdef, inner_def) => {
if (_descriptor.Accessor.GetValue(mdef) is TMutable mval)
inner_desc.Accessor.SetValue(mval, inner_def);
});
}
protected IAlgoSelector add_selector(string prop_name, Func<AlgoDefi, AlgoDefi?> get_order, Action<AlgoDef, AlgoDef> set_order) =>
add_selector(prop_name, get_order, set_order, (s, e) => validate());
protected IAlgoSelector add_selector(string prop_name, Func<AlgoDefi, AlgoDefi?> get_order, Action<AlgoDef, AlgoDef> set_order, EventHandler<DataErrorsChangedEventArgs> on_errors_changed) {
var rv = new AlgoSelector();
_child_algo_selector_data[prop_name] = new AlgoSelectorData(prop_name, rv, get_order, set_order);
return rv;
}
protected IAlgoSelector add_selector(string prop_name, Func<TImmutable, AlgoDefi?> get_algo, Action<TMutable, AlgoDef> set_algo) {
var rv = new AlgoSelector();
_child_algo_selector_data[prop_name] = new AlgoSelectorData(prop_name, rv, def => _descriptor.Accessor.GetValue(def.CloneToMutable()) is TMutable mval ? get_algo(_create_immutable(mval)) : null,
(def, child_def) => {
if (_descriptor.Accessor.GetValue(def) is TMutable mval)
set_algo(mval, child_def);
});
return rv;
}
protected bool remove_selector(string prop_name) =>
_child_algo_selector_data.Remove(prop_name);
/// TODO: maybe <see cref="_child_algo_selector_data"/> should just be a protected field?
/// Conversely, it is nice to hide these sort of implementation-specific details from inheriting classes.
/// Fwiw, as of 2/12/2019, the only class that needs the ability to add/remove selectors dynamically is <see cref="DynamicOrderVM"/>.
/// The other implementations simply add a whatever selectors they need in their constructor.
protected IDisposable reset_selectors() {
var to_remove = _child_algo_selector_data.Keys.ToList();
_child_algo_selector_data.CollectionChanged += on_change;
void on_change(object sender, NotifyCollectionChangedEventArgs e) {
switch (e.Action) {
case NotifyCollectionChangedAction.Reset:
throw new InvalidOperationException($"{nameof(_child_algo_selector_data)} was reset; unable to continue processing incremental changes.");
default:
// catches Replace/Remove/Add ops (Add ops account for cases where the item was effectively replaced by a remove/re-add)
if (e.NewItems != null) {
foreach (AlgoSelectorData sd in e.NewItems)
to_remove.Remove(sd.prop_name);
}
break;
#region Child Algo Selectors
protected virtual IEnumerable<ChildAlgoData> get_selector_datum() =>
OrderDataHelper.get_child_algo_data(order_type);
protected void sync_selectors() {
using (reset()) {
foreach (var c in get_selector_datum()) {
_children_vm[c.name] = mk_selector(c.name, new Func<AlgoDefi, AlgoDefi?>(defi => {
var mchild = c.get_child(defi.CloneToMutable());
if (mchild != null)
return new AlgoDefi(mchild);
return null;
}),
c.assign_child);
}
_populate_selectors();
}
// local methods
IDisposable reset() {
var to_remove = _child_algo_selector_data.Keys.ToList();
_child_algo_selector_data.CollectionChanged += on_change;
void on_change(object sender, NotifyCollectionChangedEventArgs e) {
switch (e.Action) {
case NotifyCollectionChangedAction.Reset:
throw new InvalidOperationException($"{nameof(_child_algo_selector_data)} was reset; unable to continue processing incremental changes.");
default:
// catches Replace/Remove/Add ops (Add ops account for cases where the item was effectively replaced by a remove/re-add)
if (e.NewItems != null) {
foreach (AlgoSelectorData sd in e.NewItems)
to_remove.Remove(sd.prop_name);
}
break;
}
}
return Disposable.Create(() => {
_child_algo_selector_data.CollectionChanged -= on_change;
foreach (var id in to_remove)
_child_algo_selector_data.Remove(id);
});
}
IAlgoSelector mk_selector(string prop_name, Func<AlgoDefi, AlgoDefi?> get_order, Action<AlgoDef, AlgoDef> set_order) {
var rv = new AlgoSelector();
_child_algo_selector_data[prop_name] = new AlgoSelectorData(prop_name, rv, get_order, set_order);
return rv;
}
return Disposable.Create(() => {
_child_algo_selector_data.CollectionChanged -= on_change;
foreach (var id in to_remove)
_child_algo_selector_data.Remove(id);
});
}
#endregion
@@ -347,7 +325,7 @@ namespace mbts.spark {
protected Property<T> add_property<T>(string prop_name, int target_field_number, T init_value = default) {
var prop = backing(prop_name, init_value);
var field_desc = new TMutable().Descriptor.FindFieldByNumber(target_field_number);
_auto_props.Add((mval => {
_auto_props.Add((prop_name, mval => {
if (mval != null)
field_desc.Accessor.SetValue(mval, prop.Value);
}, mval => {
@@ -356,22 +334,12 @@ namespace mbts.spark {
return prop;
}
protected UserProperty<T> add_user_property<T>(string prop_name, int target_field_number, T init_value = default) {
var prop = user_backing(prop_name, init_value);
var field_desc = new TMutable().Descriptor.FindFieldByNumber(target_field_number);
add_property(
mdef => ConvertUtils.try_convert<T>(field_desc.Accessor.GetValue(mdef)),
(mdef, val) => field_desc.Accessor.SetValue(mdef, val),
val => prop.bypass_setter(val),
() => prop.Value);
return prop;
}
protected Property<TImmutableValue> add_property<TMutableValue, TImmutableValue>(string prop_name, int target_field_number, TImmutableValue init_value = default) where TMutableValue : class, IMessage, new() where TImmutableValue : IImmutableMessage {
var prop = backing(prop_name, init_value);
var field_desc = new TMutable().Descriptor.FindFieldByNumber(target_field_number);
var immutable_factory = OrderTypeVMUtils.immutable_factory<TMutableValue, TImmutableValue>();
add_property<TMutableValue>(
prop_name,
mdef => field_desc.Accessor.GetValue(mdef) as TMutableValue,
(mdef, mval) => field_desc.Accessor.SetValue(mdef, mval),
mval => prop.set(immutable_factory(mval ?? new TMutableValue())),
@@ -383,15 +351,36 @@ namespace mbts.spark {
return prop;
}
protected void add_property<TValue>(Func<TMutable, TValue> get_from_def, Action<TMutable, TValue> assign_to_def, Action<TValue> set_local, Func<TValue> get_local) =>
_auto_props.Add((def => {
protected void add_property<TValue>(string prop_name, Func<TMutable, TValue> get_from_def, Action<TMutable, TValue> assign_to_def, Action<TValue> set_local, Func<TValue> get_local) =>
_auto_props.Add((
prop_name,
def => {
assign_to_def(def, get_local());
}, def => {
set_local(get_from_def(def));
}));
protected virtual void on_model_internal(OrdersModel model) { }
protected virtual void on_security_md_internal(SecurityMD? security_md) { }
protected void add_property(string prop_name, Action<TMutable> assign, Action<TMutable> load) =>
_auto_props[prop_name] = (prop_name, assign, load);
protected UserProperty<T> add_user_property<T>(string prop_name, int target_field_number, T init_value = default) {
var prop = user_backing(prop_name, init_value);
var field_desc = new TMutable().Descriptor.FindFieldByNumber(target_field_number);
add_property(
prop_name,
mdef => ConvertUtils.try_convert<T>(field_desc.Accessor.GetValue(mdef)),
(mdef, val) => field_desc.Accessor.SetValue(mdef, val),
val => prop.bypass_setter(val),
() => prop.Value);
return prop;
}
protected void remove_property(string prop_name) =>
_auto_props.Remove(prop_name);
#endregion
protected virtual void on_model_internal() { }
protected virtual void on_security_md_internal() { }
// model
void IObserver<OrdersModel>.OnNext(OrdersModel model) {
@@ -402,36 +391,39 @@ namespace mbts.spark {
return;
}
this.model = model;
on_model_internal(model);
_reset();
using (_def_change_notifier.defer_execution()) {
on_model_internal();
sync_selectors();
}
}
void IObserver<OrdersModel>.OnError(Exception error) { }
void IObserver<OrdersModel>.OnCompleted() { }
// security md
void IObserver<SecurityMD?>.OnNext(SecurityMD? security_md) {
this.security_md = security_md;
on_security_md_internal(security_md);
_reset();
using (_def_change_notifier.defer_execution()) {
on_security_md_internal();
_populate_selectors();
}
}
void IObserver<SecurityMD?>.OnError(Exception error) { }
void IObserver<SecurityMD?>.OnCompleted() { }
IDisposable _hook;
void _reset() {
IDisposable _selector_data_hooks;
void _populate_selectors() {
if (model != null && security.HasValue) {
var algo_data = model.factory.default_algos(security.Value);
var default_algo = new AlgoKey(OrderDataHelper.get_default_algo(OrderDataHelper.get_algo_format(security.Value.ContractCase)));
foreach (var sd in _child_algo_selectors)
sd.load_algo_data_recursive(algo_data, default_algo);
Disposable.replace(ref _hook, ObservableAutoWrapper.connect_item_hooks(child_algo_selectors, s => {
s.load_algo_data_recursive(algo_data, default_algo);
return Disposable.empty;
}));
Disposable.replace(
ref _selector_data_hooks,
ObservableAutoWrapper.connect_item_hooks(child_algo_selectors, s => {
s.load_algo_data_recursive(algo_data, default_algo);
return Disposable.empty;
}));
}
}
public IDisposable Subscribe(IObserver<OrdersModel> observer) => throw new NotImplementedException();
#endregion
}
struct AlgoSelectorData {
@@ -521,4 +513,4 @@ namespace mbts.spark {
}
#endregion
}
}
}
@@ -14,8 +14,6 @@ namespace mbts.spark {
class SpreaderOrderVM : OrderTypeVMBase<SpreaderDef, SpreaderDefi>, IAlgoVM, ILazyAlgo, IObserver<SecurityMD?>, IObserver<OrdersModel> {
readonly Property<SpreaderDefi?> _template;
readonly StrategyEditorWrapper _strategy_editor_wrapper;
OrdersModel _model;
SecurityMD? _security_md;
IDisposable _strategy_sub;
public SpreaderOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
@@ -23,7 +21,38 @@ namespace mbts.spark {
_template = backing<SpreaderDefi?>(nameof(template));
/// <see cref="_strategy_editor_wrapper"/> depends on <see cref="_template"/> (i.e. initialize the latter first).
_strategy_editor_wrapper = new StrategyEditorWrapper(this);
concat_disposable(_strategy_editor_wrapper, _template.Subscribe(vals => validate()));
_resources.AddRange(_strategy_editor_wrapper, _template.Subscribe(vals => validate()));
// strategy
add_property(
nameof(SpreaderDef.ExecutionStrategy),
mdef => mdef.ExecutionStrategy,
(mdef, val) => mdef.ExecutionStrategy = val,
val => {
if (template.HasValue) {
template = template.Value.with(execution_strategy: val);
} else {
var mdef = new SpreaderDef {
Security = security?.CloneToMutable(),
ExecutionStrategy = val
};
template = new SpreaderDefi(mdef);
}
},
() => strategy_editor?.get_strategy().CloneToMutable());
// security
add_property(
nameof(SpreaderDef.Security),
mdef => mdef.Security,
(mdef, val) => mdef.Security = val,
val => {
if (template.HasValue) {
template = template.Value.with(security: val);
} else {
var mdef = new SpreaderDef { Security = val };
template = new SpreaderDefi(mdef);
}
},
() => security?.CloneToMutable());
validate();
}
@@ -43,12 +72,6 @@ namespace mbts.spark {
}
public bool is_loaded { get; private set; }
protected override void assign_to_internal(SpreaderDef value) =>
value.MergeFrom(template.HasValue ? template.Value.CloneToMutable() : new SpreaderDef());
protected override void load_from_internal(SpreaderDefi value) =>
_template.set(value);
protected override void validate() {
base.validate();
if (!template.HasValue)
@@ -56,26 +79,15 @@ namespace mbts.spark {
else clear_error(nameof(template));
}
void IObserver<SecurityMD?>.OnCompleted() { }
void IObserver<SecurityMD?>.OnError(Exception error) { }
void IObserver<SecurityMD?>.OnNext(SecurityMD? security_md) {
_security_md = security_md;
_reset_strategy_sub();
}
void IObserver<OrdersModel>.OnCompleted() { }
void IObserver<OrdersModel>.OnError(Exception error) { }
void IObserver<OrdersModel>.OnNext(OrdersModel model) {
_model = model;
_reset_strategy_sub();
}
protected override void on_security_md_internal() => _reset_strategy_sub();
protected override void on_model_internal() => _reset_strategy_sub();
void _reset_strategy_sub() {
Disposable.dispose(ref _strategy_sub);
if (_model != null) {
if (_security_md.HasValue) {
_strategy_sub = _model.spreader.security_strategy(_security_md.Value.security.Id)
.Select(strategy => new SpreaderDefi?(_model.factory.spreader_orderi(_security_md.Value.security, strategy)))
if (model != null) {
if (security_md.HasValue) {
_strategy_sub = model.spreader.security_strategy(security_md.Value.security.Id)
.Select(strategy => new SpreaderDefi?(model.factory.spreader_orderi(security_md.Value.security, strategy)))
.OnDispatcher()
.Subscribe(s => {
is_loaded = true;
@@ -115,7 +127,7 @@ namespace mbts.spark {
}
}
OrdersModel _model => _owner._model;
OrdersModel _model => _owner.model;
SpreaderDefi? _template { get => _owner.template; set => _owner.template = value; }
public void Dispose() {
@@ -31,11 +31,6 @@ namespace mbts.spark {
public ContractSpecificationImmutable? spec => _security_md.spec_or_default();
protected override void load_from_internal(StopLimitDefi value) {
// TODO: the StopLimitOrderDefinition, StopLimitOrderDefinitionImmutable needs relative vs absolute prices to function as a useful template (talk to Pedro)
_stop_price.bypass_setter(value.StopPrice);
}
// no need to constantly poll the md, just check the inline props when the popup opens, and right before sending an order
// (if the user never looks at the popup, assume that the bid_price will be used)
// TODO: add settings so the trigger-price can default to something relative to the md, e.g "bid-price" or "ask-price - nticks" or "best-bid-price with a minimum size of 10"
@@ -16,14 +16,26 @@ namespace mbts.spark {
public TimeBoxOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
: base(key, display_name, editing_permissions) {
add_property(def => def.Start, (def, val) => def.Start = val, val => absolute_start = TimeTextTuple.FromDateTime(val.ToDateTime()), () => {
_validate(out DateTime? start_time, out DateTime? _);
return start_time.GetValueOrDefault().ToUniversalTime().ToGoogleTime();
});
add_property(def => def.Stop, (def, val) => def.Stop = val, val => absolute_stop = TimeTextTuple.FromDateTime(val.ToDateTime()), () => {
_validate(out DateTime? _, out DateTime? stop_time);
return stop_time.GetValueOrDefault().ToUniversalTime().ToGoogleTime();
});
// start
add_property(
nameof(TimeBoxOrderDefinition.Start),
def => def.Start,
(def, val) => def.Start = val,
val => absolute_start = TimeTextTuple.FromDateTime(val.ToDateTime()),
() => {
_validate(out DateTime? start_time, out DateTime? _);
return start_time.GetValueOrDefault().ToUniversalTime().ToGoogleTime();
});
// stop
add_property(
nameof(TimeBoxOrderDefinition.Stop),
def => def.Stop,
(def, val) => def.Stop = val,
val => absolute_stop = TimeTextTuple.FromDateTime(val.ToDateTime()),
() => {
_validate(out DateTime? _, out DateTime? stop_time);
return stop_time.GetValueOrDefault().ToUniversalTime().ToGoogleTime();
});
validate();
}
@@ -1,5 +1,4 @@
using bts.api;
using bts.api.orderman;
using bts.api.orderman;
using mbts.ui.utility;
using mbts.utils;
using System;
@@ -31,13 +30,59 @@ namespace mbts.spark {
_is_priceout_checked = create_user_prop(nameof(is_priceout_checked), false);
_is_timeout_checked = create_user_prop(nameof(is_timeout_checked), false);
_timeout_secs = create_user_prop(nameof(timeout_secs), 10);
add_property(nameof(TriggerDef), assign, load);
validate();
//
// local methods
UserProperty<T> create_user_prop<T>(string name, T default_value = default) {
var rv = user_backing(name, default_value);
rv.Changed += validation_callback;
return rv;
}
void assign(TriggerDef trigger_def) {
trigger_def.TriggerAction = new TriggerAction();
if (security.HasValue)
trigger_def.TriggerAction.SecurityId = security.Value.Id;
trigger_def.TriggerCondition = new TriggerCondition() {
PriceType = trigger_price_type,
AbsolutePriceTrigger = new AbsolutePriceTrigger() { TriggerQuantity = new TriggerQuantity() },
};
trigger_def.ExitCondition = new TriggerExitCondition {
ConditionalCancel = is_priceout_checked,
TimeoutEnabled = is_timeout_checked,
TimeoutSeconds = (uint)(timeout_secs)
};
switch (order_price_case) {
case OrderpriceOneofCase.Price:
// 'fixed price' is no longer supported (ca. 03/04/2019)
goto case OrderpriceOneofCase.Payup;
case OrderpriceOneofCase.Payup:
trigger_def.TriggerAction.Price = double.NaN;
trigger_def.TriggerAction.Payup = new TriggerPayupPrice {
PayupTicks = payup_ticks,
PayupPriceType = payup_price_type
};
break;
}
}
void load(TriggerDef trigger_defi) {
if (trigger_defi.TriggerCondition is TriggerCondition c) {
trigger_price_type = c.PriceType;
}
if (trigger_defi.ExitCondition is TriggerExitCondition ec) {
is_priceout_checked = ec.ConditionalCancel;
is_timeout_checked = ec.TimeoutEnabled;
if (ec.TimeoutSeconds < int.MaxValue)
timeout_secs = (int)ec.TimeoutSeconds;
}
if (trigger_defi.TriggerAction is TriggerAction a) {
if (a.Payup is TriggerPayupPrice pp) {
order_price_case = OrderpriceOneofCase.Payup;
fixed_price = double.NaN;
payup_ticks = pp.PayupTicks;
payup_price_type = pp.PayupPriceType;
}
}
}
void validation_callback<T>(T oldval, T newval) => validate();
}
@@ -56,68 +101,20 @@ namespace mbts.spark {
public bool is_timeout_checked { get => _is_timeout_checked.Value; set => _is_timeout_checked.Value = value; }
public int timeout_secs { get => _timeout_secs.Value; set => _timeout_secs.Value = value; }
protected override void assign_to_internal(TriggerDef trigger_def) {
trigger_def.TriggerAction = new TriggerAction();
if (security.HasValue)
trigger_def.TriggerAction.SecurityId = security.Value.Id;
trigger_def.TriggerCondition = new TriggerCondition() {
PriceType = trigger_price_type,
AbsolutePriceTrigger = new AbsolutePriceTrigger() { TriggerQuantity = new TriggerQuantity() },
};
trigger_def.ExitCondition = new TriggerExitCondition {
ConditionalCancel = is_priceout_checked,
TimeoutEnabled = is_timeout_checked,
TimeoutSeconds = (uint)(timeout_secs)
};
switch (order_price_case) {
case OrderpriceOneofCase.Price:
// should already be null, setting it here for clarity
trigger_def.TriggerAction.Payup = null;
trigger_def.TriggerAction.Price = fixed_price;
break;
case OrderpriceOneofCase.Payup:
trigger_def.TriggerAction.Price = double.NaN;
trigger_def.TriggerAction.Payup = new TriggerPayupPrice {
PayupTicks = payup_ticks,
PayupPriceType = payup_price_type
};
break;
}
}
protected override void load_from_internal(TriggerDefi trigger_defi) {
if (trigger_defi.HasTriggerCondition) {
trigger_price_type = trigger_defi.TriggerCondition.PriceType;
}
if (trigger_defi.HasExitCondition) {
var exit_condition = trigger_defi.ExitCondition;
is_priceout_checked = exit_condition.ConditionalCancel;
is_timeout_checked = exit_condition.TimeoutEnabled;
if (exit_condition.TimeoutSeconds < int.MaxValue)
timeout_secs = (int)exit_condition.TimeoutSeconds;
}
if (trigger_defi.HasTriggerAction) {
var trigger_action = trigger_defi.TriggerAction;
if (trigger_action.HasPayup) {
order_price_case = OrderpriceOneofCase.Payup;
fixed_price = double.NaN;
var payup = trigger_action.Payup;
payup_ticks = payup.PayupTicks;
payup_price_type = payup.PayupPriceType;
} else {
order_price_case = OrderpriceOneofCase.Price;
fixed_price = trigger_action.Price;
}
}
}
void _refresh_md() {
protected override void on_security_md_internal() {
if (security.HasValue) {
security_md.Value.md.get_bbo(ref _bbo_ref, out co2.NativePriceSize bid, out co2.NativePriceSize ask);
_best_bid = bid.price;
_best_ask = ask.price;
security_md.Value.md.get_last_trade(ref _last_trade_ref, out co2.NativePriceSize ps, out DateTime ts, out ulong v);
}
if (double.IsNaN(fixed_price) || !_fixed_price.setter_called) {
switch (trigger_price_type) {
case TriggerPriceType.Bid: _fixed_price.bypass_setter(_best_bid); break;
case TriggerPriceType.Ask: _fixed_price.bypass_setter(_best_ask); break;
case TriggerPriceType.LastTradePrice: _fixed_price.bypass_setter(_last_trade_price); break;
}
}
}
protected override void validate() {
@@ -131,17 +128,6 @@ namespace mbts.spark {
}
assert(!is_timeout_checked || timeout_secs > 0, nameof(timeout_secs), "Please specify valid Timeout(secs).");
}
protected override void on_security_md_internal(SecurityMD? security_md) {
_refresh_md();
if (double.IsNaN(fixed_price) || !_fixed_price.setter_called) {
switch (trigger_price_type) {
case TriggerPriceType.Bid: _fixed_price.bypass_setter(_best_bid); break;
case TriggerPriceType.Ask: _fixed_price.bypass_setter(_best_ask); break;
case TriggerPriceType.LastTradePrice: _fixed_price.bypass_setter(_last_trade_price); break;
}
}
}
}
}
}
@@ -1,4 +1,3 @@
using bts.collections;
using bts.utils;
using mbts.utils;
using NUnit.Framework;
@@ -11,11 +10,6 @@ using System.Linq;
using System.Reactive.Disposables;
namespace mbts.ui.utility {
/// <summary>
/// The sole purpose of this class is to offer a less verbose alternative to
/// "new ObservableAutoWrapper<LongTypeName............, AnotherLongTypeName.........>(doodi, poodi)"
/// when the generic types are implicit
/// </summary>
static class ObservableAutoWrapper {
public static ObservableAutoWrapper<View, Model> Create<View, Model>(ObservableCollection<Model> models, Func<Model, View> converter) {
return new ObservableAutoWrapper<View, Model>(models, converter);
@@ -198,6 +192,18 @@ namespace mbts.ui.utility {
}
}
public static IDisposable connect_item_actions<T>(IReadOnlyObservableCollection<T> source, Action<T> action) {
foreach (T v in source)
action(v);
return source.Subscribe((s, e) => CollectionHelper.process_new_items(source, action, e));
}
public static IDisposable connect_item_actions<T>(ObservableCollection<T> source, Action<T> action) {
foreach (T v in source)
action(v);
return source.Subscribe((s, e) => CollectionHelper.process_new_items(source, action, e));
}
public static IDisposable connect_item_hooks<T>(IReadOnlyObservableCollection<T> source, Func<T, IDisposable> get_item_hook) {
var item_hooks = source.Select(get_item_hook).ToList();
return new CompositeDisposable(
@@ -298,7 +304,7 @@ namespace mbts.ui.utility {
void _remove_and_notify(T value) {
int pos = _assert_remove(value);
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, pos));
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, value, pos));
}
int _add(T value) {
@@ -10,7 +10,8 @@ namespace mbts {
#region Public Extensions (is_valid, format, parse, tick_up, tick_down)
public static bool is_valid(this TickFactors tick) =>
tick.tick_unit > 0 && tick.tick_unit <= (tick.tick_base * 0.5);
(tick.tick_unit > 0 && tick.tick_unit <= (tick.tick_base * 0.5)) ||
tick.tick_unit == tick.tick_base;
public static bool is_valid(this RegionalTickFactors regional_tick) =>
regional_tick.low_tick.is_valid() && regional_tick.high_tick.is_valid();
@@ -20,6 +21,9 @@ namespace mbts {
public static double reduced_base(this TickFactors tick) => factor_out(tick.tick_base, 10);
public static string format_without_decimal_part(this TickFactors tick, double value) =>
format(tick, value, double.NaN, false, _nan_text, true);
public static string format(this TickFactors tick, double value) =>
format(tick, value, double.NaN, false, _nan_text);
@@ -32,13 +36,16 @@ namespace mbts {
public static string format(this TickFactors tick, double value, double display_multiplier, bool show_commas) =>
format(tick, value, display_multiplier, show_commas, _nan_text);
public static string format(this TickFactors tick, double value, double display_multiplier, bool show_commas, string nan_text) {
public static string format(this TickFactors tick, double value, double display_multiplier, bool show_commas, string nan_text, bool hide_decimal_part = false) {
if (double.IsNaN(value)) return nan_text;
if (double.IsInfinity(value)) return value.ToString();
if (!tick.is_valid()) return _format_no_tick(value, display_multiplier, show_commas);
return _format_tick(tick, double.IsNaN(display_multiplier) ? value : value * display_multiplier, show_commas);
return _format_tick(tick, double.IsNaN(display_multiplier) ? value : value * display_multiplier, show_commas, hide_decimal_part);
}
public static string format_without_decimal_part(this RegionalTickFactors regional_tick, double value) =>
format_without_decimal_part(value <= regional_tick.low_tick_inclusive_end ? regional_tick.low_tick : regional_tick.high_tick, value);
public static string format(this RegionalTickFactors regional_tick, double value) =>
format(value <= regional_tick.low_tick_inclusive_end ? regional_tick.low_tick : regional_tick.high_tick, value);
@@ -193,7 +200,12 @@ namespace mbts {
if (DoubleHelper.IsNaNOrInfinity(input)) return input;
return input <= regional_tick.low_tick_inclusive_end ? regional_tick.low_tick.round(input) : regional_tick.high_tick.round(input);
}
public static int display_digits(this TickFactors tick) => display_digits(tick.tick_base);
public static int display_digits(this TickFactors tick) {
if (tick.tick_unit == tick.tick_base) {
return (int)Math.Floor(Math.Log10(tick.tick_base));
}
return display_digits(tick.tick_base);
}
#endregion
public static int display_digits(double tick_base) {
@@ -265,15 +277,21 @@ namespace mbts {
return integer_part + numerator_part / tick.tick_base;
}
private static string _format_tick(TickFactors tick, double value, bool show_commas) {
if (value < 0) return "-" + _format_tick(tick, Math.Abs(value), show_commas);
private static string _format_tick(TickFactors tick, double value, bool show_commas, bool hide_decimal_part) {
if (value < 0) return "-" + _format_tick(tick, Math.Abs(value), show_commas, hide_decimal_part);
try {
checked {
int integer_part = (int)Math.Floor(value);
double numerator_dec = value - integer_part;
double unit_frac = tick.tick_unit / tick.tick_base;
int n_units = (int)Math.Round(numerator_dec / unit_frac);
if (n_units == tick.tick_base) return (integer_part + 1).ToString() + tick.separator() + new string('0', display_digits(tick.tick_base));
if (n_units == tick.tick_base) {
if (hide_decimal_part) {
return integer_part.ToString();
} else {
return (integer_part + 1).ToString() + tick.separator() + new string('0', display_digits(tick.tick_base));
}
}
double decimal_part = n_units * tick.tick_unit;
if (decimal_part == tick.tick_base) {
++integer_part;
@@ -284,7 +302,11 @@ namespace mbts {
decimal_part = Math.Floor(decimal_part);
}
string integer_part_text = show_commas ? integer_part.ToString("#,0") : integer_part.ToString();
return integer_part_text + tick.separator() + decimal_part.ToString(new string('0', display_digits(tick.tick_base)));
if (hide_decimal_part) {
return integer_part_text;
} else {
return integer_part_text + tick.separator() + decimal_part.ToString(new string('0', display_digits(tick.tick_base)));
}
}
}
catch (OverflowException ex) {
@@ -75,6 +75,20 @@ namespace mbts.utils {
}
}
public static void process_new_items<T>(IEnumerable<T> source, Action<T> on_new_item, NotifyCollectionChangedEventArgs e) {
switch (e.Action) {
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Replace:
foreach (T v in e.NewItems)
on_new_item(v);
break;
case NotifyCollectionChangedAction.Reset:
foreach (T v in source)
on_new_item(v);
break;
}
}
// Note: this is not optimized, be wary of use with large collections
public static void sync_with_never_duplicate<T>(ObservableCollection<T> dest, IEnumerable<T> src) =>
sync_with_never_duplicate(dest, src, EqualityComparer<T>.Default);
@@ -0,0 +1,91 @@
using mbts;
using Google.Protobuf;
using Google.Protobuf.Reflection;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Linq;
using mbts.utils;
namespace bts.utils {
static class ProtoHelper {
const string
_case_suffix = "Case",
_oneof_case_suffix = "OneofCase";
public static MessageDescriptor Descriptor<T>() where T : IMessage, new() =>
_DescriptorImpl<T>.value;
public static OneofDescriptor get_oneof_descriptor<T>(string prop_name) where T : IMessage, new() {
prop_name = prop_name.iremove_suffix(_case_suffix);
return Descriptor<T>().Oneofs.FirstOrDefault(d => d.Name.iequals(prop_name));
}
public static IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)> get_oneof_descriptor_index<T, TOneofCase>(string prop_name)
where T : IMessage, new()
where TOneofCase : struct =>
_DescriptorImpl<T>.OneofImpl<TOneofCase>.get_descriptor_case_index(prop_name);
public static FieldDescriptor get_oneof_field_descriptor<T, TOneofCase>(TOneofCase oneof_case) where T : IMessage, new() where TOneofCase : struct {
string oneof_name = oneof_case.ToString();
return Descriptor<T>().Fields.InFieldNumberOrder().FirstOrDefault(fd => fd.Name.Replace("_", "").iequals(oneof_name));
}
public static Type get_field_type(FieldDescriptor field_descriptor) {
switch (field_descriptor.FieldType) {
case FieldType.Bool: return typeof(bool);
case FieldType.Bytes: return typeof(ByteString);
case FieldType.Double: return typeof(double);
case FieldType.Float: return typeof(float);
case FieldType.UInt32: return typeof(uint);
case FieldType.UInt64: return typeof(ulong);
// int
case FieldType.SFixed32:
case FieldType.Fixed32:
case FieldType.SInt32:
case FieldType.Int32: return typeof(int);
// long
case FieldType.SFixed64:
case FieldType.Fixed64:
case FieldType.SInt64:
case FieldType.Int64: return typeof(long);
//
case FieldType.String: return typeof(string);
case FieldType.Enum: return field_descriptor.EnumType.ClrType;
case FieldType.Message: return field_descriptor.MessageType.ClrType;
default: throw ExceptionBuilder.Argument.unexpected_case(field_descriptor.FieldType, nameof(field_descriptor));
}
}
public static IReadOnlyDictionary<TOneofCase, FieldDescriptor> get_oneof_descriptor_index<TOneofCase>(OneofDescriptor descriptor) where TOneofCase : struct =>
descriptor.Fields.ToDictionary(fd => EnumHelper.parse<TOneofCase>(fd.Name.Replace("_", "")));
static class _DescriptorImpl<T> where T : IMessage, new() {
public static readonly MessageDescriptor value = new T().Descriptor;
public static class OneofImpl<TOneofCase> where TOneofCase : struct {
static Dictionary<string, IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)>> _descriptor_case_indices =
new Dictionary<string, IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)>>();
public static IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)> get_descriptor_case_index(string prop_name) {
/// Use local copy because <see cref="_descriptor_case_indices"/> is updated atomically.
var dci = _descriptor_case_indices;
if (!dci.TryGetValue(prop_name, out IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)> rv)) {
/// Update <see cref="_descriptor_case_indices"/> atomically (lock-free).
/// 1) clone the local copy.
/// 2) add the new KeyValue to the clone.
/// 3) assign the clone back to <see cref="_descriptor_case_indices"/>.
rv = get_oneof_descriptor_index<TOneofCase>(get_oneof_descriptor<T>(prop_name))
.ToDictionary(kv => kv.Key, kv => (kv.Value, get_field_type(kv.Value)));
dci = dci.ToDictionary();
dci.Add(prop_name, rv);
/// It is possible for add ops to be overwritten.
/// This is perfectly safe, just means that the 'clone-add-assign' will be repeated the next time a caller requests this key.
_descriptor_case_indices = dci;
}
return rv;
}
}
}
}
}
@@ -461,117 +461,43 @@
/>
</Grid>
</GroupBox>
<GroupBox Grid.Row="2">
<GroupBox.Header>
<TextBlock FontWeight="SemiBold">Trigger Action</TextBlock>
</GroupBox.Header>
<!--Note: This GroupBox is used to horizontally align the controls inside of it with those in the 'Trigger Condition' GroupBox (above), and the 'Trigger Exit' GroupBox (below). Despite having no header, or border, it does still serve a purpose.-->
<GroupBox Grid.Row="2" BorderBrush="Transparent" BorderThickness="0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
<RowDefinition Height="*"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<GroupBox>
<GroupBox.Header>
<StackPanel Orientation="Horizontal">
<TextBlock FontWeight="SemiBold">Price</TextBlock>
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="The child order, resulting from the trigger condition being met, can be sent either at a specified price (Fixed) price or payup/less ticks from the Trigger Price." />
</StackPanel>
</GroupBox.Header>
<Grid x:Name="trigger_price_root">
<Grid.Resources>
<c:RadioButtonGroupManager x:Key="OrderPriceCaseRadioButtonManager" SelectedItem="{Binding DataContext.order_price_case, Source={u:DirectReference trigger_price_root}, Mode=TwoWay}"/>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<RadioButton
x:Name="fixed_price_rb"
Grid.Row="0"
c:RadioButtonGroupManager.Manager="{DynamicResource OrderPriceCaseRadioButtonManager}"
c:RadioButtonGroupManager.Value="{u:Enum Price, Type={u:Nested ord:TriggerAction.OrderpriceOneofCase}}"
IsEnabled="{Binding can_edit_values}"
VerticalContentAlignment="Center"
>
Fixed
</RadioButton>
<usp:PriceEntryControl
Grid.Row="0"
Grid.Column="2"
ContractSpec="{Binding spec}"
Value="{Binding fixed_price}"
>
<usp:PriceEntryControl.IsEnabled>
<MultiBinding Converter="{StaticResource AndConverter}">
<Binding Path="can_edit_values"/>
<Binding Path="IsChecked" ElementName="fixed_price_rb" Mode="OneWay"/>
</MultiBinding>
</usp:PriceEntryControl.IsEnabled>
</usp:PriceEntryControl>
<RadioButton
x:Name="payup_price_rb"
c:RadioButtonGroupManager.Manager="{DynamicResource OrderPriceCaseRadioButtonManager}"
c:RadioButtonGroupManager.Value="{u:Enum Payup, Type={u:Nested ord:TriggerAction.OrderpriceOneofCase}}"
Grid.Row="2"
VerticalContentAlignment="Center"
IsEnabled="{Binding can_edit_values}"
>
Payup
</RadioButton>
<Grid Grid.Row="2" Grid.Column="2">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<c:TickUpDownInt
Value="{Binding payup_ticks}"
MinWidth="34"
>
<c:TickUpDownInt.IsEnabled>
<MultiBinding Converter="{StaticResource AndConverter}">
<Binding Path="can_edit_values"/>
<Binding Path="IsChecked" ElementName="payup_price_rb" Mode="OneWay"/>
</MultiBinding>
</c:TickUpDownInt.IsEnabled>
</c:TickUpDownInt>
<ComboBox
Grid.Column="2"
ItemsSource="{Binding payup_price_types}"
SelectedItem="{Binding payup_price_type}"
>
<ComboBox.IsEnabled>
<MultiBinding Converter="{StaticResource AndConverter}">
<Binding Path="can_edit_values"/>
<Binding Path="use_fixed_price" Converter="{StaticResource NotConverter}" Mode="OneWay"/>
</MultiBinding>
</ComboBox.IsEnabled>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static uag:OrderDetailsPopupControl.TriggerPayupTypeDescriptionConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Grid>
</GroupBox>
<TextBlock>Payup\less</TextBlock>
<c:TickUpDownInt Grid.Column="2" IsEnabled="{Binding can_edit_values}" Value="{Binding payup_ticks}"/>
<uag:AlgoPickerNodeView
Grid.Row="2"
Grid.ColumnSpan="3"
ItemsSource="{Binding children_vm.child.algos_view}"
SelectedItem="{Binding children_vm.child.selected_algo}"
ContentTemplateSelector="{DynamicResource AlgoTemplateSelector}"
OrderKind="{x:Static usp:AlgoContentTemplateSelector.ChildKind}"
OrderKindTemplate="{StaticResource OrderKindTemplate}"
/>
>
<uag:AlgoPickerNodeView.OrderKindTemplate>
<DataTemplate>
<TextBlock x:Name="name_text" FontWeight="Bold" Text="{Binding name}"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding CanExpand, RelativeSource={RelativeSource AncestorType=uag:AlgoPickerNodeView}}" Value="{u:False}">
<Setter TargetName="name_text" Property="Margin" Value="-9,0,0,0"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</uag:AlgoPickerNodeView.OrderKindTemplate>
</uag:AlgoPickerNodeView>
</Grid>
</GroupBox>
<GroupBox Grid.Row="4" >
<GroupBox Grid.Row="4">
<GroupBox.Header>
<StackPanel Orientation="Horizontal">
<TextBlock FontWeight="Bold">Trigger Exit</TextBlock>
@@ -82,7 +82,7 @@ namespace mbts.ui.controls {
}));
static IEnumerable<double> _get_dbased_tick_unit_presets(int log10) =>
Enumerable.Range(0, log10).SelectMany(_coins);
Enumerable.Range(0, log10).SelectMany(_coins).Concat(new[] { Math.Pow(10, log10) });
static IEnumerable<double> _coins(int pow10) {
double d = Math.Pow(10, pow10);
@@ -9,10 +9,7 @@ namespace mbts.ui.converters {
class SyntheticPriceCalcDisplayTextConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
if (value is SyntheticContract.Types.PriceCalc price_calc) {
switch (price_calc) {
case SyntheticContract.Types.PriceCalc.Diff: return "Standard";
default: return DisplayNameHelper.camel_to_title_case(value.ToString());
}
return DisplayNameHelper.camel_to_title_case(value.ToString());
} else return value != null ? value.ToString() : string.Empty;
}
@@ -213,7 +213,7 @@ namespace mbts.spark {
// local methods
void impl(StringBuilder sb, CustomOrderDefinition mdef) {
sb.Append(abbreviate(mdef.CustomCase));
foreach (var cad in OrderDataHelper.get_child_algo_data(def.CustomCase)) {
foreach (var cad in OrderDataHelper.get_child_algo_data(def.CloneToMutable())) {
var child = cad.get_child(mdef);
sb.Append(".[");
impl(sb, child);
@@ -24,6 +24,13 @@ namespace mbts.spark {
public static double ticksize(this ContractSpecificationImmutable spec, double price) =>
Math.Abs(price) > spec.PriceBreak ? spec.HighTick : spec.LowTick;
public static string format(this ContractSpecificationImmutable spec, double price, double with_regard_to_price, bool in_tick_terms) {
double net_change = price - with_regard_to_price;
return (in_tick_terms) ?
TickStore.get(spec).format_without_decimal_part((net_change) / spec.ticksize(net_change)) :
TickStore.get(spec).format(net_change);
}
public static string format(this ContractSpecificationImmutable spec, double price) =>
TickStore.get(spec).format(price);
@@ -289,7 +296,7 @@ namespace mbts.spark {
}
static bool _check_tick_size(double tick_size) =>
tick_size > 0 && tick_size <= 0.5;
(tick_size > 0 && tick_size <= 0.5) || tick_size == 1;
struct SpecKey : IEquatable<SpecKey> {
public readonly uint denominator, display_digits;
@@ -6,6 +6,7 @@ using mbts.co2;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.ui.utility.text;
using mbts.utils;
using System;
@@ -355,14 +356,27 @@ namespace mbts.spark {
case ColumnType.Order: _ordershost.set_col_width(column.settings.width); break;
case ColumnType.Bid: render_market_col(_bids); break;
case ColumnType.Price:
LadderRender.render_prices(
column.visual_tup.visual,
column.visual_tup.glyph_context,
_row_count,
_settings.row_height,
_price_list,
column.settings,
security.ContractSpec);
if (_settings.net_change != LadderSettings.NetChangeTerms.None) {
LadderRender.render_prices(
column.visual_tup.visual,
column.visual_tup.glyph_context,
_row_count,
_settings.row_height,
_price_list,
_vm.md.settlement_price,
_settings.net_change == LadderSettings.NetChangeTerms.Ticks,
column.settings,
security.ContractSpec);
} else {
LadderRender.render_prices(
column.visual_tup.visual,
column.visual_tup.glyph_context,
_row_count,
_settings.row_height,
_price_list,
column.settings,
security.ContractSpec);
}
break;
case ColumnType.Ask: render_market_col(_asks); break;
case ColumnType.Volume:
@@ -556,7 +570,9 @@ namespace mbts.spark {
void _refresh_prices() {
var prev_price_list = _price_list;
_price_list = LadderRender.render_prices(_price_vis_tup.visual, _price_vis_tup.glyph_context, _mkt_break_lines.origin, _row_count, _settings.row_height, _settings.price_column, security.ContractSpec);
_price_list = (!(_settings.net_change == LadderSettings.NetChangeTerms.None)) ?
LadderRender.render_prices(_price_vis_tup.visual, _price_vis_tup.glyph_context, _mkt_break_lines.origin, _vm.md.settlement_price, _settings.net_change == LadderSettings.NetChangeTerms.Ticks, _row_count, _settings.row_height, _settings.price_column, security.ContractSpec) :
LadderRender.render_prices(_price_vis_tup.visual, _price_vis_tup.glyph_context, _mkt_break_lines.origin, _row_count, _settings.row_height, _settings.price_column, security.ContractSpec);
_ordershost.update_settings(_price_list, _price_comparer, _settings.row_height, _settings.orders_column);
if (prev_price_list != null) {
// maintain previous visible price range if possible
@@ -299,12 +299,15 @@ namespace mbts.spark {
order_entry_panel_initial_width = _default_order_entry_panel_width,
zoom_slider_visible = false,
zoom_factor = _default_zoom_factor,
net_change = NetChangeTerms.None,
};
}
}
public enum BadgeStyle { Normal, Alternate }
public enum NetChangeTerms { None, Dollars, Ticks }
[ProtoMember(1)]
public double row_height { get; set; }
[ProtoMember(2)]
@@ -411,6 +414,9 @@ namespace mbts.spark {
[ProtoMember(47)]
public LadderFavoriteAlgosSettings favorite_algos { get; set; }
[ProtoMember(48)]
public NetChangeTerms net_change { get; set; }
public bool Equals(LadderSettings other) =>
other != null &&
row_height == other.row_height &&
@@ -456,7 +462,8 @@ namespace mbts.spark {
zoom_slider_visible == other.zoom_slider_visible &&
zoom_factor == other.zoom_factor &&
use_defaults == other.use_defaults &&
favorite_algos == other.favorite_algos;
favorite_algos == other.favorite_algos &&
net_change == other.net_change;
public override bool Equals(object obj) => Equals(obj as LadderSettings);
@@ -505,7 +512,8 @@ namespace mbts.spark {
zoom_slider_visible.GetHashCode(),
zoom_factor.GetHashCode(),
use_defaults.GetHashCode(),
favorite_algos.GetHashCode());
favorite_algos.GetHashCode(),
net_change.GetHashCode());
public struct WindowSpecifics {
public readonly double order_entry_panel_width, zoom_factor;
@@ -56,6 +56,10 @@ namespace mbts.spark {
_cancel_buys_visibility = new OrderEntryPanelItemVisibilityVM("Cancel Buys"),
_cancel_sells_visibility = new OrderEntryPanelItemVisibilityVM("Cancel Sells"),
_cancel_all_visibility = new OrderEntryPanelItemVisibilityVM("Cancel All");
readonly Property<bool>
_show_in_net_change,
_show_in_net_change_dollars,
_show_in_net_change_ticks;
public LadderSettingsVM(OrdersModel model, bool is_synthetic, Func<double> get_left_panel_width, Func<double> get_zoom_factor) {
this.is_synthetic = is_synthetic;
@@ -67,6 +71,10 @@ namespace mbts.spark {
_alternate_order_size = backing<uint>(nameof(alternate_order_size));
_get_order_entry_panel_width = get_left_panel_width;
_get_zoom_factor = get_zoom_factor;
_show_in_net_change = backing<bool>(nameof(show_in_net_change));
_show_in_net_change_dollars = backing<bool>(nameof(show_in_net_change_dollars));
_show_in_net_change_ticks = backing<bool>(nameof(show_in_net_change_ticks));
// be sure to initialize backings before this measure_row_height call
_row_height = backing(nameof(row_height), () => LadderRender.measure_row_height(get_result()));
_orders.font_size_changed += on_font_size_changed;
@@ -74,6 +82,7 @@ namespace mbts.spark {
_price.font_size_changed += on_font_size_changed;
_ask.font_size_changed += on_font_size_changed;
_last.font_size_changed += on_font_size_changed;
void on_font_size_changed(double oldval, double newval) => _row_height.refresh();
}
@@ -159,6 +168,32 @@ namespace mbts.spark {
}
}
public bool show_in_net_change {
get => _show_in_net_change.Value;
set {
// when unchecking, clear out dollars and ticks
if (!value) {
show_in_net_change_dollars = false;
show_in_net_change_ticks = false;
}
// when checking, by defalt check dollars
if (value) {
show_in_net_change_dollars = true;
}
_show_in_net_change.Value = value;
}
}
public bool show_in_net_change_dollars {
get => _show_in_net_change_dollars.Value;
set => _show_in_net_change_dollars.Value = value;
}
public bool show_in_net_change_ticks {
get => _show_in_net_change_ticks.Value;
set => _show_in_net_change_ticks.Value = value;
}
public string key => GridOrLadderSettingsSelectorKeys.Ladder;
public void load(LadderSettings settings) {
@@ -201,6 +236,9 @@ namespace mbts.spark {
// because it could have changed from the UI
// there might be a better way of doing this, like how
// does the widths of the ladder grid column pull this off?
show_in_net_change = settings.net_change != LadderSettings.NetChangeTerms.None;
show_in_net_change_dollars = settings.net_change == LadderSettings.NetChangeTerms.Dollars;
show_in_net_change_ticks = settings.net_change == LadderSettings.NetChangeTerms.Ticks;
}
public LadderSettings get_result() {
@@ -242,7 +280,19 @@ namespace mbts.spark {
clone.order_entry_panel_initial_width = _get_order_entry_panel_width();
clone.zoom_slider_visible = _zoom_slider_visbility.show;
clone.zoom_factor = _get_zoom_factor();
clone.net_change = _create_net_change_terms(show_in_net_change_dollars, show_in_net_change_ticks);
return clone;
LadderSettings.NetChangeTerms _create_net_change_terms(bool dollars, bool ticks) {
if (dollars) {
return LadderSettings.NetChangeTerms.Dollars;
}
if (ticks) {
return LadderSettings.NetChangeTerms.Ticks;
}
return LadderSettings.NetChangeTerms.None;
}
}
public void Dispose() => favorite_algos_vm.Dispose();
@@ -134,7 +134,7 @@
<TextBlock Grid.Row="2" Grid.Column="1" VerticalAlignment="Center">Alternate Order Qty</TextBlock>
<TextBox Grid.Row="2" Grid.Column="3" Text="{Binding alternate_order_size}" u:TextBoxBehavior.InputFilter="{u:RegexInputFilter Regex={x:Static Member=Integer, MemberType={u:Nested txt:TextUtils.RegexFilters}}}"/>
<TabControl Grid.Row="4" Grid.ColumnSpan="5" Width="543" Height="239">
<TabControl Grid.Row="4" Grid.ColumnSpan="5" Width="543" Height="275">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem" BasedOn="{StaticResource {x:Type TabItem}}">
<Setter Property="VerticalAlignment" Value="Stretch"/>
@@ -353,13 +353,14 @@
</ItemsControl>
</Grid>
</TabItem>
<!-- Market Size Tab -->
<TabItem Header="Market Size Display">
<!-- Market Price & Size Tab -->
<TabItem Header="Market Price and Size Display">
<Grid DataContext="{Binding size_display}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
@@ -381,6 +382,31 @@
FontStyle="Italic"
Foreground="{Binding can_choose_alternate_implied_format, Converter={StaticResource IsEnabledForegroundConverter}}"
/>
<GroupBox Grid.Row="3" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,10,0,0" Grid.ColumnSpan="3">
<GroupBox.Header>
<CheckBox
x:Name="show_name_change_chk"
IsChecked="{Binding DataContext.show_in_net_change, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}">
Show Price in Net Change
</CheckBox>
</GroupBox.Header>
<StackPanel Margin="10,5,0,0">
<RadioButton
Content="Price"
GroupName="FormatTerms"
Style="{x:Null}"
IsEnabled="{Binding ElementName=show_name_change_chk, Path=IsChecked}"
IsChecked="{Binding DataContext.show_in_net_change_dollars, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}"
/>
<RadioButton
Content="Ticks"
GroupName="FormatTerms"
Style="{x:Null}"
IsEnabled="{Binding ElementName=show_name_change_chk, Path=IsChecked}"
IsChecked="{Binding DataContext.show_in_net_change_ticks, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}"
/>
</StackPanel>
</GroupBox>
<ComboBox
Grid.Row="2"
Grid.Column="2"
@@ -34,6 +34,7 @@ namespace mbts.spark {
readonly Property<Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
IWindowSettingsManager<LadderSettings> _settings_manager;
ulong _misc_time = 0;
ulong _last_trade_time = 0;
IEqualityComparer<double> _equality;
Dictionary<double, LadderOrderVM> _slot_price_vs_orders;
@@ -355,25 +356,24 @@ namespace mbts.spark {
}
void _update_header() {
if (_misc_time != _model.md.misc_timestamp) {
var spec = security.ContractSpec;
bool last_updated = md.get_last_trade(ref _last_trade_time, out NativePriceSize last, out DateTime time, out ulong volume);
if (_misc_time != _model.md.misc_timestamp || last_updated) {
_high.set(md.session_high);
_low.set(md.session_low);
_open.set(md.open_price);
_settlement.set(md.settlement_price);
ulong unused = 0;
md.get_last_trade(ref unused, out NativePriceSize last, out DateTime time, out ulong volume);
if (!double.IsNaN(last.price)) {
_change.set(last.price - md.settlement_price);
} else {
unused = 0;
ulong unused = 0;
_model.md.get_bbo(ref unused, out NativePriceSize bid, out NativePriceSize ask);
double mid = TickUtils.mid_tick(spec, bid.price, ask.price);
double mid = TickUtils.mid_tick(security.ContractSpec, bid.price, ask.price);
_change.set(mid - md.settlement_price);
}
_volume.set(volume);
_misc_time = _model.md.misc_timestamp;
}
}
void _show_book() =>
@@ -73,6 +73,12 @@ namespace mbts.spark {
return result;
}
public static GlyphRun[] format_prices(double[] prices, double reference_price, bool format_in_tick_terms, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, ContractSpecificationImmutable spec) {
var result = new GlyphRun[prices.Length];
Parallel.For(0, prices.Length, 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(double[] prices, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, ContractSpecificationImmutable spec) {
var result = new GlyphRun[prices.Length];
Parallel.For(0, prices.Length, i =>
@@ -80,6 +86,13 @@ namespace mbts.spark {
return result;
}
public static double[] render_prices(DrawingVisual price, GlyphContext glyph_context, double mid, double wrt_price, bool format_in_tick_terms, int row_count, double row_height,
LadderColumnSettings settings, ContractSpecificationImmutable spec) {
var dprices = gen_prices(mid, row_count, spec);
render_prices(price, glyph_context, row_count, row_height, dprices, wrt_price, format_in_tick_terms, settings, spec);
return dprices;
}
public static double[] render_prices(DrawingVisual price, GlyphContext glyph_context, double mid, int row_count, double row_height,
LadderColumnSettings settings, ContractSpecificationImmutable spec) {
var dprices = gen_prices(mid, row_count, spec);
@@ -87,6 +100,18 @@ namespace mbts.spark {
return dprices;
}
public static void render_prices(DrawingVisual price, GlyphContext glyph_context, int row_count, double row_height, double[] dprices, double wrt_price, bool format_in_tick_terms, LadderColumnSettings settings, ContractSpecificationImmutable spec) {
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, spec);
using (var dc = price.RenderOpen())
using (_draw_with_clip(dc, settings, row_height, row_count)) {
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, row_count * row_height));
for (int x = 0; x < row_count; ++x)
dc.DrawGlyphRun(foreground, fprices[x]);
}
}
public static void render_prices(DrawingVisual price, GlyphContext glyph_context, int row_count, double row_height, double[] dprices, LadderColumnSettings settings, ContractSpecificationImmutable spec) {
var foreground = settings.foreground.rgb_brush();
//var background = new SolidColorBrush(settings.background.rgb_color());
@@ -17,12 +17,27 @@ using System.Reactive.Linq;
using OrderState = bts.api.orderman.OrderState;
namespace mbts.spark.utilities {
struct ChildAlgoData {
public readonly string name;
public readonly Func<IMessage, CustomOrderDefinition> get_child_from_impl;
public readonly Action<IMessage, CustomOrderDefinition> assign_child_to_impl;
public readonly Func<CustomOrderDefinition, CustomOrderDefinition> get_child;
public readonly Action<CustomOrderDefinition, CustomOrderDefinition> assign_child;
public ChildAlgoData(string name, Func<IMessage, CustomOrderDefinition> get_child_from_impl, Action<IMessage, CustomOrderDefinition> assign_child_to_impl, Func<CustomOrderDefinition, CustomOrderDefinition> get_child, Action<CustomOrderDefinition, CustomOrderDefinition> assign_child) {
this.name = name;
this.get_child = get_child;
this.assign_child = assign_child;
this.get_child_from_impl = get_child_from_impl;
this.assign_child_to_impl = assign_child_to_impl;
}
}
// at some point, user-default settings for orders can be managed here
// OrdersModel imports and exposes OrderFactory as a public property
[Export(typeof(OrderFactory))]
class OrderFactory : IDisposable {
static readonly OrderMetadata _default_order_metadata = new OrderMetadata { KeyBinding = "", Color = BTSColors.Orders.spreader_order };
static readonly OrderMetadata _default_order_metadata = new OrderMetadata { KeyBinding = "", Color = BTSColors.Orders.default_order };
readonly AdminModel _admin_model;
readonly DynamicOrderHelper _dynamic_helper;
IDisposable _dispose;
@@ -250,20 +265,6 @@ namespace mbts.spark.utilities {
}
}
public struct ChildAlgoData {
public readonly Func<CustomOrderDefinition, CustomOrderDefinition> get_child;
public readonly Action<CustomOrderDefinition, CustomOrderDefinition> set_child;
public readonly string name;
public readonly FieldDescriptor descriptor;
public ChildAlgoData(string name, FieldDescriptor descriptor, Func<CustomOrderDefinition, CustomOrderDefinition> get_child, Action<CustomOrderDefinition, CustomOrderDefinition> set_child) {
this.name = name;
this.get_child = get_child;
this.set_child = set_child;
this.descriptor = descriptor;
}
}
// algo-type-case maps
// Note: obv this will break if we change the name of the 'CustomOneofCase' field
static readonly OneofDescriptor _oneof_desc = CustomOrderDefinition.Descriptor.Oneofs.FirstOrDefault(fd => fd.Name == "custom");
@@ -283,12 +284,17 @@ namespace mbts.spark.utilities {
var value_descriptors = new List<FieldDescriptor>(0);
foreach (var f in fd.MessageType.Fields.InDeclarationOrder()) {
if (is_child_algo_field(f)) {
child_algo_data.Add(new ChildAlgoData(f.Name, f,
child_algo_data.Add(new ChildAlgoData(f.Name,
msg => f.Accessor.GetValue(msg) as CustomOrderDefinition,
(msg, child) => f.Accessor.SetValue(msg, child),
def => {
var type_def = fd.Accessor.GetValue(def) as IMessage;
return f.Accessor.GetValue(type_def) as CustomOrderDefinition;
},
(def, child) => f.Accessor.SetValue(def, child)));
(def, child) => {
if (fd.Accessor.GetValue(def) is IMessage m)
f.Accessor.SetValue(m, child);
}));
} else {
value_descriptors.Add(f);
}
@@ -302,6 +308,30 @@ namespace mbts.spark.utilities {
f.FieldType == FieldType.Message && f.MessageType.ClrType == typeof(CustomOrderDefinition);
}
public static IReadOnlyList<ChildAlgoData> get_child_algo_data(CustomOrderDefinitionImmutable defi) =>
get_child_algo_data(defi.CloneToMutable());
public static IReadOnlyList<ChildAlgoData> get_child_algo_data(CustomOrderDefinition algo) {
switch (algo.CustomCase) {
case CustomOrderDefinition.CustomOneofCase.Dynamic:
return algo.Dynamic.OrderParams.Where(kv => kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef).Select(kv => new ChildAlgoData(kv.Key, def => {
var p = ((DynamicOrderDefinition)def).OrderParams[kv.Key];
if (p.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef)
return p.AsOrderDef;
return null;
},
(msg, child) => {
if (msg is DynamicOrderDefinition dyn)
dyn.OrderParams.GetOrAdd(kv.Key, k => new DynamicOrderParam()).AsOrderDef = child;
},
def => def.Dynamic.OrderParams.TryGetValue(kv.Key, out DynamicOrderParam param) && param.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef ? param.AsOrderDef : null,
(def, child) => def.Dynamic.OrderParams.GetOrAdd(kv.Key, k => new DynamicOrderParam()).AsOrderDef = child))
.ToList();
default:
return get_child_algo_data(algo.CustomCase);
}
}
public static IReadOnlyList<ChildAlgoData> get_child_algo_data(CustomOrderDefinition.CustomOneofCase algo_type) =>
_algo_type_case_descriptor_data.TryGetValue(algo_type, out AlgoTypeCaseDescriptorData d) ? d.child_algo_data : Array.Empty<ChildAlgoData>();
@@ -366,7 +396,7 @@ namespace mbts.spark.utilities {
// hide mutable-def implementation
IEnumerable<CustomOrderDefinition.CustomOneofCase> impl(CustomOrderDefinition mdef) {
yield return mdef.CustomCase;
foreach (var sd in get_child_algo_data(mdef.CustomCase)) {
foreach (var sd in get_child_algo_data(mdef)) {
var child = sd.get_child(mdef);
if (child != null) {
yield return child.CustomCase;
@@ -412,7 +442,7 @@ namespace mbts.spark.utilities {
if (child == null)
return null;
var type_def = (IMessage)desc_data.type_case_descriptor.Accessor.GetValue(def);
cad.descriptor.Accessor.SetValue(type_def, child);
cad.assign_child_to_impl(type_def, child);
}
break;
}
@@ -429,13 +459,11 @@ namespace mbts.spark.utilities {
def = new CustomOrderDefinition { Limit = new LimitOrderDefinition() };
break;
default:
var desc_data = _algo_type_case_descriptor_data[def.CustomCase];
foreach (var cad in desc_data.child_algo_data) {
foreach (var cad in get_child_algo_data(def)) {
var child = outright_impl(cad.get_child(def));
if (child == null)
return null;
var type_def = (IMessage)desc_data.type_case_descriptor.Accessor.GetValue(def);
cad.descriptor.Accessor.SetValue(type_def, child);
cad.assign_child(def, child);
}
break;
}
@@ -501,8 +529,8 @@ namespace mbts.spark.utilities {
fd.Accessor.SetValue(tgt_type_def, fd.Accessor.GetValue(src_type_def));
}
foreach (var cad in desc_data.child_algo_data) {
var src_child = cad.descriptor.Accessor.GetValue(src_type_def) as CustomOrderDefinition;
var tgt_child = cad.descriptor.Accessor.GetValue(tgt_type_def) as CustomOrderDefinition;
var src_child = cad.get_child_from_impl(src_type_def);
var tgt_child = cad.get_child_from_impl(tgt_type_def);
copy_values(src_child, ref tgt_child);
}
}
@@ -558,7 +586,7 @@ namespace mbts.spark.utilities {
public static bool structure_equals(CustomOrderDefinitionImmutable a, CustomOrderDefinitionImmutable b) {
return impl(a.CloneToMutable(), b.CloneToMutable());
bool impl(CustomOrderDefinition ma, CustomOrderDefinition mb) =>
a.CustomCase == b.CustomCase && get_child_algo_data(a.CustomCase).All(cad => impl(cad.get_child(ma), cad.get_child(mb)));
ma.CustomCase == mb.CustomCase && get_child_algo_data(ma).All(cad => impl(cad.get_child(ma), cad.get_child(mb)));
}
public static bool iceberg_equals(IcebergOrderDefinitionImmutable a, IcebergOrderDefinitionImmutable b) =>
@@ -883,10 +911,10 @@ namespace mbts.spark.utilities {
public static IcebergOrderDefinition IcebergOrderDefinition() => new IcebergOrderDefinition { Child = CustomOrderDefinition(), ShowQuantity = 1 };
public static TimeBoxOrderDefinition TimeBoxOrderDefinition() => new TimeBoxOrderDefinition { Child = CustomOrderDefinition(), Start = Timestamp.FromDateTime(DateTime.UtcNow), Stop = Timestamp.FromDateTime(DateTime.UtcNow) };
public static TickOrderDefinition TickOrderDefinition() => new TickOrderDefinition { Child = CustomOrderDefinition(), CrossThreshold = QuantityMeasure };
public static LeanOrderDefinition LeanOrderDefinition() => new LeanOrderDefinition { Child = CustomOrderDefinition(), CrossThreshold = QuantityMeasure };
public static LeanOrderDefinition LeanOrderDefinition() => new LeanOrderDefinition { Child = CustomOrderDefinition(), CrossThreshold = QuantityMeasure, MaxChaseTicks = 2 };
public static JoinOrderDefinition JoinOrderDefinition() => new JoinOrderDefinition { Child = CustomOrderDefinition(), JoinThreshold = QuantityMeasure };
public static TopOrderDefinition TopOrderDefinition() => new TopOrderDefinition { Child = CustomOrderDefinition(), TopThreshold = QuantityMeasure };
public static CoverOrderDefinition CoverOrderDefinition() => new CoverOrderDefinition { Child = CustomOrderDefinition(), Hedge = CustomOrderDefinition() };
public static CoverOrderDefinition CoverOrderDefinition() => new CoverOrderDefinition { Child = CustomOrderDefinition(), Hedge = CustomOrderDefinition(), HedgeTicks = 1 };
public static OCOOrderDefinition OCOOrderDefinition() => new OCOOrderDefinition { Child = CustomOrderDefinition(), Other = CustomOrderDefinition(), OtherSide = RelativeSide.Same };
public static PricerOrderDefinition PricerOrderDefinition() => new PricerOrderDefinition { Child = CustomOrderDefinition() };
public static OffsetOrderDefinition OffsetOrderDefinition() => new OffsetOrderDefinition { Child = CustomOrderDefinition(), };
@@ -895,4 +923,4 @@ namespace mbts.spark.utilities {
public static SpreaderOrderDefinition SpreaderOrderDefinition() => new SpreaderOrderDefinition { ExecutionStrategy = new ExecutionStrategy() };
}
}
}
}
@@ -14,7 +14,7 @@ namespace bts.api {
lock (this) {
if (!_is_disposed && !_is_subscribed) {
_subscription = _subscribe();
_subscription = null;
_subscribe = null;
_is_subscribed = true;
}
}
@@ -19,6 +19,12 @@ namespace mbts {
public static bool iequals(this string str, string comparand) =>
string.Equals(str, comparand, StringComparison.CurrentCultureIgnoreCase);
public static string iremove_suffix(this string str, string suffix) {
if (str.EndsWith(suffix, StringComparison.CurrentCultureIgnoreCase))
str = str.Remove(str.Length - suffix.Length);
return str;
}
public static T choose<T>(this Random r, IEnumerable<T> range) {
var index = r.Next(0, range.Count());
return range.Skip(index).First();
@@ -40,7 +40,7 @@ namespace bts {
Currency = Currency.Usd,
Synthetic = new SyntheticContract {
Name = "Synthetic1",
PriceCalc = SyntheticContract.Types.PriceCalc.Ratio,
PriceCalc = SyntheticContract.Types.PriceCalc.Standard,
Legs = {
new ContractLeg {
Security = new Security {
+1 -1
View File
@@ -1 +1 @@
set(sources files/Onixs.BTS.lic;files/cert_secure_logon.txt;files/perf_trace/compiled.css;files/perf_trace/index.html;files/perf_trace/jquery-3.3.1.min.js;files/perf_trace/main.js;files/perf_trace/perf_log.sql;files/perf_trace/perf_trace_analysis.sql;files/perf_trace/plotly-latest.min.js)
set(sources files/Onixs.BTS.lic;files/cert_secure_logon.txt;files/futures.coconut.xz;files/perf_trace/compiled.css;files/perf_trace/index.html;files/perf_trace/jquery-3.3.1.min.js;files/perf_trace/main.js;files/perf_trace/perf_log.sql;files/perf_trace/perf_trace_analysis.sql;files/perf_trace/plotly-latest.min.js)
+9 -9
View File
@@ -282,19 +282,19 @@ group by
drop view if exists event_log_aux;
create view event_log_aux as
select
log_archive.*,
log_view.*,
event_type,
event_id,
event_archive.duration event_duration,
event_archive.hts_start hts_start,
event_archive.start event_start,
event_archive.stop event_stop
event_view.duration event_duration,
event_view.hts_start hts_start,
event_view.start event_start,
event_view.stop event_stop
from
log_archive,
event_archive
log_view,
event_view
where
log_archive.session_start = event_archive.session_start and
log_archive.ts >= event_archive.start and log_archive.ts <= event_archive.stop;
log_view.session_start = event_view.session_start and
log_view.ts >= event_view.start and log_view.ts <= event_view.stop;
begin transaction;
insert into hedge_trace_archive select * from hedge_trace_view;
+1 -1
View File
@@ -1 +1 @@
set(sources api/apicore.proto;api/apigateway.proto;api/apioptions.proto;api/core/ApiClientInterop.cpp;api/core/ApiServerInterop.cpp;api/public/ApiServer.cpp;api/public/ClientChannel.cpp;api/public/QuickbusClientChannel.cpp;api/public/ServerChannel.cpp;api/public/ServiceContext.cpp;api/service_helpers/AdminServiceUtils.cpp;api/services/AdminService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.proto;api/services/OrderService.proto;api/services/OutOfProcessURLListener.proto;api/services/PersistenceService.proto;api/services/PositionService.proto;api/services/ShellDiagnosticService.proto;api/services/ShellLatencyService.proto;api/services/SyntheticSecurityService.proto;api/test/ApiTests.cpp;api/test/HelloServiceServer.cpp;config/ConfigCheck.cpp;config/ConfigDownloader.cpp;config/ConfigErrorCollector.cpp;config/ConfigLog.cpp;config/ConfigPropertyEvaluator.cpp;config/ConfigValueQueue.cpp;config/ConfigurationException.cpp;config/ConfigurationState.cpp;config/PreprocessInputStream.cpp;config/config.cpp;config/defaults/MarketData.conf;config/defaults/Ports.conf;config/defaults/Symbols.conf;config/defaults/cme_reference_data.conf;config/defaults/edge.conf;config/defaults/jetfix.conf;config/defaults/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.conf;config/defaults/spreader.conf;config/defaults/testkeys.conf;config/defaults/threads.conf;config/defaults/toggles.conf;config/extensions/extensions.proto;config/invalid/invalid.proto;config/protos/ExchangeSimCfg.proto;config/protos/MarketData.proto;config/protos/Ports.proto;config/protos/Symbols.proto;config/protos/UI.proto;config/protos/cme_reference_data.proto;config/protos/jetfix.proto;config/protos/logging.proto;config/protos/mercury_cfg.proto;config/protos/orderman_cfg.proto;config/protos/orders_fills_cleanup.proto;config/protos/perflog_cfg.proto;config/protos/quickbus_cfg.proto;config/protos/spreader.proto;config/protos/system.proto;config/protos/testkeys.proto;config/protos/threads.proto;config/protos/toggles.proto;config/tests/HTTPServer.cpp;config/tests/config_ref_tests.cpp;config/tests/config_tests.cpp;config/text_format/text_format.cpp;domain_model/Security.cpp;domain_model/Security.proto;domain_model/SecurityTests.cpp;domain_model/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_tests.cpp;jetfix/codec/FIXArray.cpp;jetfix/common/OutFIX.cpp;jetfix/connection/AsyncReader.cpp;jetfix/connection/IConnection.cpp;jetfix/connection/SocketConnection.cpp;jetfix/gateway/CoreGateway.cpp;jetfix/gateway/JetFIXGateway.cpp;jetfix/gateway/Session.cpp;jetfix/store/FIXFileStore.cpp;jetfix/store/session_name.cpp;jetfix/tests/async_reader_tests.cpp;jetfix/tests/decoding_tests.cpp;jetfix/tests/fix_encoding_stream_tests.cpp;jetfix/tests/fix_fragment_tests.cpp;jetfix/tests/jetfix_performance_tests.cpp;jetfix/tests/sample_fix.cpp;jetfix/tests/store_tests.cpp;logging/LoggerHub.cpp;logging/Sink.cpp;logging/logging.cpp;logging/tests/logging_tests.cpp;md/Diag/MDPeriodicDumper.cpp;md/Diag/ShmDumper.cpp;md/MDConsumer.cpp;md/MDConsumerHandle.cpp;md/MDConsumerInterop.cpp;md/MDProducer.cpp;md/MDProducerHandle.cpp;md/MDRecoverableConsumer.cpp;md/MemorySegmentUtils.cpp;md/PlatformLoader.cpp;md/PriceSize.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.cpp;md/SettlementRecorder.cpp;md/Shm/FixedSecurity.cpp;md/SyntheticSecurityService.cpp;md/cme/MDP3Utils.cpp;md/common.cpp;md/protos/co2.proto;md/relay/QueryServer.cpp;md/relay/RelayClient.cpp;md/relay/RelayRecorder.cpp;md/test/Co2ManagedTestHelper.cpp;md/test/Co2TestInstrument.cpp;md/test/md2_tests.cpp;md/test/synthetic_book_merge_tests.cpp;mercury/actors/IPollableActor.cpp;mercury/broker/Broker.cpp;mercury/broker/BrokerProxy.cpp;mercury/broker/ILocalBroker.cpp;mercury/broker/IRemoteBroker.cpp;mercury/common/mercury_common.cpp;mercury/events/TimerEvent.cpp;mercury/executor/Executor.cpp;mercury/topics/Topic.cpp;metrics/metrics.cpp;metrics/system_metrics.cpp;orderman/actors/AsyncFactory.cpp;orderman/actors/ExecutionGateway.cpp;orderman/actors/FillStore.cpp;orderman/actors/MDSupplementarySource.cpp;orderman/actors/OrderStore.cpp;orderman/actors/SODPosition.cpp;orderman/actors/SecuritySrc.cpp;orderman/actors/ServiceBridge.cpp;orderman/actors/order_manager/OrderBuilder.cpp;orderman/actors/order_manager/OrderManager.cpp;orderman/actors/order_manager/OrderServicesData.cpp;orderman/events/CancelRejectEvent.cpp;orderman/events/NewExecutionReportEvent.cpp;orderman/limiter/LimitStore.cpp;orderman/limiter/Limiter.cpp;orderman/limiter/ProductLimitNode.cpp;orderman/limiter/ProductLimitTree.cpp;orderman/limiter/SecurityLimitNode.cpp;orderman/limiter/SecurityLimitTree.cpp;orderman/limiter/tests/limit_tests.cpp;orderman/order_factory/DynaLine truncated
set(sources api/apicore.proto;api/apigateway.proto;api/apioptions.proto;api/core/ApiClientInterop.cpp;api/core/ApiServerInterop.cpp;api/public/ApiServer.cpp;api/public/ClientChannel.cpp;api/public/QuickbusClientChannel.cpp;api/public/ServerChannel.cpp;api/public/ServiceContext.cpp;api/service_helpers/AdminServiceUtils.cpp;api/service_helpers/AlgoServiceUtils.cpp;api/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.proto;api/services/OrderService.proto;api/services/OutOfProcessURLListener.proto;api/services/PersistenceService.proto;api/services/PositionService.proto;api/services/ShellDiagnosticService.proto;api/services/ShellLatencyService.proto;api/services/SyntheticSecurityService.proto;api/test/ApiTests.cpp;api/test/HelloServiceServer.cpp;config/ConfigCheck.cpp;config/ConfigDownloader.cpp;config/ConfigErrorCollector.cpp;config/ConfigLog.cpp;config/ConfigPropertyEvaluator.cpp;config/ConfigValueQueue.cpp;config/ConfigurationException.cpp;config/ConfigurationState.cpp;config/PreprocessInputStream.cpp;config/config.cpp;config/defaults/MarketData.conf;config/defaults/Ports.conf;config/defaults/Symbols.conf;config/defaults/cme_reference_data.conf;config/defaults/edge.conf;config/defaults/jetfix.conf;config/defaults/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.conf;config/defaults/spreader.conf;config/defaults/testkeys.conf;config/defaults/threads.conf;config/defaults/toggles.conf;config/extensions/extensions.proto;config/invalid/invalid.proto;config/protos/ExchangeSimCfg.proto;config/protos/MarketData.proto;config/protos/Ports.proto;config/protos/Symbols.proto;config/protos/UI.proto;config/protos/cme_reference_data.proto;config/protos/jetfix.proto;config/protos/logging.proto;config/protos/mercury_cfg.proto;config/protos/orderman_cfg.proto;config/protos/orders_fills_cleanup.proto;config/protos/perflog_cfg.proto;config/protos/quickbus_cfg.proto;config/protos/spreader.proto;config/protos/system.proto;config/protos/testkeys.proto;config/protos/threads.proto;config/protos/toggles.proto;config/tests/HTTPServer.cpp;config/tests/config_ref_tests.cpp;config/tests/config_tests.cpp;config/text_format/text_format.cpp;domain_model/Security.cpp;domain_model/Security.proto;domain_model/SecurityTests.cpp;domain_model/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_tests.cpp;jetfix/codec/FIXArray.cpp;jetfix/common/OutFIX.cpp;jetfix/connection/AsyncReader.cpp;jetfix/connection/IConnection.cpp;jetfix/connection/SocketConnection.cpp;jetfix/gateway/CoreGateway.cpp;jetfix/gateway/JetFIXGateway.cpp;jetfix/gateway/Session.cpp;jetfix/store/FIXFileStore.cpp;jetfix/store/session_name.cpp;jetfix/tests/async_reader_tests.cpp;jetfix/tests/decoding_tests.cpp;jetfix/tests/fix_encoding_stream_tests.cpp;jetfix/tests/fix_fragment_tests.cpp;jetfix/tests/jetfix_performance_tests.cpp;jetfix/tests/sample_fix.cpp;jetfix/tests/store_tests.cpp;logging/LoggerHub.cpp;logging/Sink.cpp;logging/logging.cpp;logging/tests/logging_tests.cpp;md/Diag/MDPeriodicDumper.cpp;md/Diag/ShmDumper.cpp;md/MDClient.cpp;md/MDConsumer.cpp;md/MDConsumerHandle.cpp;md/MDConsumerInterop.cpp;md/MDProducer.cpp;md/MDProducerHandle.cpp;md/MemorySegmentUtils.cpp;md/PlatformLoader.cpp;md/PriceSize.cpp;md/SecurityCache.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.cpp;md/SecurityOracle.cpp;md/SettlementRecorder.cpp;md/Shm/FixedSecurity.cpp;md/SyntheticSecurityService.cpp;md/cme/MDP3Utils.cpp;md/common.cpp;md/protos/co2.proto;md/relay/QueryServer.cpp;md/relay/RelayClient.cpp;md/relay/RelayRecorder.cpp;md/test/Co2ManagedTestHelper.cpp;md/test/Co2TestInstrument.cpp;md/test/md2_tests.cpp;md/test/synthetic_book_merge_tests.cpp;mercury/actors/IPollableActor.cpp;mercury/broker/Broker.cpp;mercury/broker/BrokerProxy.cpp;mercury/broker/ILocalBroker.cpp;mercury/broker/IRemoteBroker.cpp;mercury/common/mercury_common.cpp;mercury/events/TimerEvent.cpp;mercury/executor/Executor.cpp;mercury/topics/Topic.cpp;metrics/metrics.cpp;metrics/system_metrics.cpp;orderman/actors/AsyncFactory.cpp;orderman/actors/ExecutionGateway.cpp;orderman/actors/FillStore.cpp;orderman/actors/MDSupplementarySource.cpp;orderman/actors/OrderStore.cpp;orderman/actors/SODPosition.cpp;orderman/actors/SecuritySrc.cpp;orderman/actors/ServiceBridge.cpp;orderman/actors/order_manager/OrderBuilder.cpp;orderman/actors/order_manager/OrderManager.cpp;orderman/actors/order_manager/OrderServicesData.cpp;orderman/events/CancelRejectEvent.cpp;orderman/events/NewExecutionReportEvent.cpp;orderman/limiter/LimitStore.cpp;orderman/limiter/Limiter.cpp;orderman/limiter/ProductLimitNode.cpp;orderman/limiter/ProductLimitTree.cpp;oLine truncated
@@ -32,6 +32,7 @@ public:
protected:
Authenticated{{name}}Base(bts::api::ServiceContext& context, const std::string& qualifier="");
Authenticated{{name}}Base(const Authenticated{{name}}Base&) = delete;
const char* name() const override;
@@ -72,6 +73,7 @@ private:
class {{name}}Base : public Authenticated{{name}}Base {
protected:
{{name}}Base(bts::api::ServiceContext& context, const std::string& qualifier="") : Authenticated{{name}}Base(context, qualifier) {}
{{name}}Base(const {{name}}Base&)=delete;
public:
{{#each method}}
{{#unless server_streaming}}
+1
View File
@@ -129,6 +129,7 @@ message RecordOptions {
// optional string output_path = 2 [default = "/home/bts/md/today"];
optional bool enable_depth = 3 [default = false];
optional bool enable_future_depth = 4 [default = true];
optional bool record_sim = 5 [default=false];
}
//
+3 -3
View File
@@ -135,9 +135,9 @@ message SyntheticContract {
repeated ContractLeg legs = 20;
PriceCalc price_calc = 30;
enum PriceCalc {
diff = 0;
net_change = 1;
ratio = 2;
PRICECALC_STANDARD = 0;
PRICECALC_NET_CHANGE = 1;
// RATIO = 2;
}
}
+34 -35
View File
@@ -32,6 +32,7 @@ class IntPrice {
int64_t _mantissa : 56;
int8_t _exponent : 8;
#endif
public:
IntPrice() : _mantissa(0), _exponent(127) {/*nan*/}
IntPrice(int64_t mantissa, int8_t exponent) : _mantissa(mantissa), _exponent(exponent) {}
@@ -57,49 +58,24 @@ class IntPrice {
}
}
IntPrice operator - (const IntPrice& o) const {
if(_exponent==o._exponent) {
return IntPrice(_mantissa-o.mantissa(), _exponent);
}
else {
// not sure there's ever a cause for a positive exponent unless we get into scientific computing
if (is_nan() || o.is_nan()) return IntPrice();
bts_assert_d(o.exponent() <= 0 && _exponent <= 0);
if (o.exponent() < _exponent) {
int8_t delta = _exponent - o.exponent();
int64_t m = _mantissa * pow10(delta);
return { m - o.mantissa(), o.exponent() };
}
else {
int8_t delta = o.exponent() - _exponent;
int64_t m = o.mantissa() * pow10(delta);
return { _mantissa - m, exponent() };
}
}
return op(o, [](int64_t m1, int64_t m2) { return m1 - m2; });
}
IntPrice operator + (const IntPrice& o) const {
if (_exponent==o._exponent) {
return IntPrice { _mantissa+o.mantissa(), exponent() };
}
else {
if (is_nan() || o.is_nan()) return IntPrice();
bts_assert_d(o.exponent() <= 0 && _exponent <= 0);
if (o.exponent() < _exponent) {
int8_t delta = _exponent - o.exponent();
int64_t m = _mantissa * pow10(delta);
return { m + o.mantissa(), o.exponent() };
}
else {
int8_t delta = o.exponent() - _exponent;
int64_t m = o.mantissa() * pow10(delta);
return { _mantissa + m, exponent() };
}
}
return op(o, [](int64_t m1, int64_t m2) { return m1 + m2; });
}
IntPrice operator * (const IntPrice& other) const {
return op(other, [](int64_t m1, int64_t m2) { return m1 * m2; });
}
IntPrice operator / (double v) const {
return { (int64_t)(_mantissa / v), exponent() };
}
IntPrice sign_extend(int8_t new_exponent) const {
int8_t delta = _exponent - new_exponent;
int64_t m = _mantissa * pow10(delta);
return { m, new_exponent };
}
int64_t mantissa() const { return _mantissa; }
int8_t exponent() const { return _exponent; }
double price() const {
@@ -107,6 +83,29 @@ class IntPrice {
return mantissa() * pow10(exponent());
}
bool is_nan() const { return _exponent == 127; }
private:
template <class MantissaOp>
IntPrice op(const IntPrice& o, MantissaOp operation) const {
if (_exponent==o._exponent) {
return IntPrice { operation(_mantissa,o.mantissa()), exponent() };
}
else {
if (is_nan() || o.is_nan()) return IntPrice();
bts_assert_d(o.exponent() <= 0 && _exponent <= 0);
if (o.exponent() < _exponent) {
int8_t delta = _exponent - o.exponent();
int64_t m = _mantissa * pow10(delta);
return { operation(m, o.mantissa()), o.exponent() };
}
else {
int8_t delta = o.exponent() - _exponent;
int64_t m = o.mantissa() * pow10(delta);
return { operation(_mantissa, m), exponent() };
}
}
}
};
static_assert(sizeof(IntPrice) == 8, "IntPrice should be this big |--------|");
extern IntPrice INT_NAN;
+6 -3
View File
@@ -255,11 +255,14 @@ TimeStamp copy_synthetic_depth(const SyntheticStrategy& synth, size_t count, Pri
if (r == HandleReadResult::BUSY || t == TimeStamp::invalid()) return TimeStamp::invalid();
// NET Change
if (leg_params.leg_md_calc() == spark::LegSpreaderParams::NET_CHANGE) {
if (synth.security.synthetic().price_calc() == api::SyntheticContract::PRICECALC_NET_CHANGE) {
auto settle = synth.legs[x].settlement_price();
double tick = synth.legs[x].get_minimum_tick();
for (size_t d = 0; d < COCONUT_MAX_DEPTH; ++d) {
bids[d] = bids[d] - settle;
asks[d] = asks[d] - settle;
auto b = (bids[d] - settle);
auto a = (asks[d] - settle);
bids[d] = { b.int_price() / tick, b.size() };
asks[d] = { a.int_price() / tick, a.size() };
}
}
+9
View File
@@ -197,6 +197,15 @@ public:
}
bts_assert(false);
}
double get_minimum_tick() const {
switch(_type) {
case HANDLETYPE_DEPTH: return book.cdata->security.low_tick * pow10(book.cdata->security.display_factor);
case HANDLETYPE_BBO: return bbo.cdata->security.low_tick * pow10(bbo.cdata->security.display_factor);
case HANDLETYPE_SYNTHETIC: return synthetic.def->security.contract_spec().low_tick();
default:
case HANDLETYPE_VOID: return os::nan();
}
}
TimeStamp last_market_update() const {
switch(_type) {
+5
View File
@@ -57,6 +57,11 @@ public:
res._price=res._price-price;
return res;
}
PriceSize get_divided_price(double divisor) const{
PriceSize res=*this;
res._price=res._price/divisor;
return res;
}
//
// Accessors
+14 -7
View File
@@ -335,12 +335,12 @@ void relay_everything() {
relay_selected(ctx, system_symbols, producer);
}
void local_relay(IRelayWriter& writer) {
api::ServiceContext ctx = elevated_relay_context();
ctx.login(api::auth::generate_wheel_token());
auto relay = ctx.locate<MarketDataRelay>();
std::set<UserSymbol> get_relay_symbols(bool record_sim) {
std::set<UserSymbol> symbols;
{
if (record_sim) {
get_system_md_symbols(symbols);
}
else {
std::set<UserSymbol> system_symbols;
get_system_md_symbols(system_symbols);
@@ -351,6 +351,14 @@ void local_relay(IRelayWriter& writer) {
}
}
}
return symbols;
}
void local_relay(IRelayWriter& writer) {
api::ServiceContext ctx = elevated_relay_context();
ctx.login(api::auth::generate_wheel_token());
auto relay = ctx.locate<MarketDataRelay>();
auto symbols = get_relay_symbols(false);
relay_selected(ctx, symbols, writer);
}
@@ -382,8 +390,7 @@ void record_everything(std::string path, std::string filename) {
api::ServiceContext ctx = elevated_relay_context();
auto relay = ctx.locate<MarketDataRelay>();
std::set<UserSymbol> system_symbols;
get_system_md_symbols(system_symbols);
std::set<UserSymbol> system_symbols = get_relay_symbols(options.record_sim());
RelayClient client(relay, nullptr, system_symbols, recorder);
RelayScheduler scheduler(ctx, naggle_ms, client);
+3
View File
@@ -15,6 +15,9 @@ struct Co2TestInstrument
MDProducerHandle producer();
MDConsumerHandle consumer();
ColdEntry& cold() { return *_cold; }
private:
std::shared_ptr<uint8_t> _cold_buffer;
ColdEntry* _cold;
+6
View File
@@ -2407,6 +2407,12 @@ TEST_CASE("IntPrice", "[md]") {
auto buy = round_to_quote_price(tick, price, api::SIDE_BUY);
REQUIRE(buy == IntPrice_from_double<7>(-4.360));
}
SECTION("division by double") {
IntPrice price = { -43595000, -7 };
IntPrice price_div10 = { -43595000, -8 };
REQUIRE(price / 10.0 == price_div10);
}
}
+32
View File
@@ -0,0 +1,32 @@
#pragma once
#include "lib/md/IntPrice.h"
#include "lib/md/PriceSize.h"
#include <vector>
namespace bts {
namespace md {
namespace test {
struct PS {
double price;
Size size;
bool operator == (const PS& other) const {
// Convert to IntPrice to avoid double rounding issues
auto p1 = IntPrice_from_double<11>(price);
auto p2 = IntPrice_from_double<11>(other.price);
return p1 == p2 && size == other.size;
}
};
struct Book {
std::vector<PS> bids;
std::vector<PS> asks;
};
Book compute_synthetic_book(const api::Security& security, const std::vector<Book>& leg_books);
std::ostream& operator<< (std::ostream& os, const PS& val);
}}}//namespace
+67 -22
View File
@@ -1,6 +1,7 @@
#ifndef _WIN32
#include "catch/catch.hpp"
#include "../MDConsumerHandle.h"
#include "md_test_utils.h"
#include "Co2TestInstrument.h"
#include "lib/utils/sane_algorithm.h"
#include <memory>
@@ -23,16 +24,6 @@ namespace md {
namespace test {
// std::tuple is hard to read in the debugger
struct PS {
double price;
Size size;
bool operator == (const PS& other) const {
// Convert to IntPrice to avoid double rounding issues
auto p1 = IntPrice_from_double<11>(price);
auto p2 = IntPrice_from_double<11>(other.price);
return p1 == p2 && size == other.size;
}
};
ostream& operator<< (ostream& os, const PS& val) {
os << "{ " << val.price << "," << val.size << " }";
@@ -103,22 +94,29 @@ tuple<shared_ptr<SyntheticStrategy>,vector<Co2TestInstrument>> make_securities(c
api::Side side;
std::tie(quantity, mult, tick_size, side) = ldesc;
Co2TestInstrument instr("ES", api::SECURITYTYPE_FUTURE);
// tick_size / display_factor
auto display_factor = pow10(instr.cold().security.display_factor);
instr.cold().security.low_tick = tick_size / display_factor;
instr.cold().security.high_tick = tick_size / display_factor;
instr.producer().set_key(security_key);
++security_key;
auto& s = synthetic->security;
// Set the synthetic tick size equal to the (last) leg. (needs to not be 0)
s.mutable_contract_spec()->set_low_tick(tick_size);
s.mutable_contract_spec()->set_high_tick(tick_size);
auto* ps = s.mutable_synthetic();
auto l = ps->mutable_legs()->Add();
l->mutable_security()->CopyFrom(instr.consumer().get_normalized_security());
l->mutable_security()->mutable_contract_spec()->set_low_tick(tick_size);
l->mutable_security()->mutable_contract_spec()->set_high_tick(tick_size);
l->set_quantity(quantity);
l->set_price_multiplier(quantity * (side == api::SIDE_BUY ? 1.0 : -1));
l->set_side(side);
synthetic->legs.push_back(instr.consumer());
legs.push_back(instr);
}
return { synthetic, legs };
}
// tuple = (quantity, tick_size, side)
@@ -142,6 +140,56 @@ void set_leg_params(SyntheticStrategy& strat, uint32_t security_key, const spark
map->insert({ security_key, params });
}
Book compute_synthetic_book(const api::Security& security, const std::vector<Book>& leg_books) {
auto synthetic = std::make_shared<SyntheticStrategy>();
synthetic->security.CopyFrom(security);
vector<Co2TestInstrument> leg_instruments;
for (const auto& leg : synthetic->security.synthetic().legs()) {
const auto& security = leg.security();
Co2TestInstrument instr(security.symbol(), securitytype_of(security));
convert_security(security, instr.cold().security, true);
leg_instruments.push_back(instr);
synthetic->legs.push_back(instr.consumer());
}
// Write leg books
for (size_t x = 0; x < leg_books.size(); ++x) {
std::array<PriceSize,COCONUT_MAX_DEPTH> bids = {}, asks = {};
std::transform(
leg_books[x].bids.begin(),
leg_books[x].bids.end(),
bids.begin(),
[](const PS& ps) {
return PriceSize_from_double<7>(ps.price, ps.size);
});
std::transform(
leg_books[x].asks.begin(),
leg_books[x].asks.end(),
asks.begin(),
[](const PS& ps) {
return PriceSize_from_double<7>(ps.price, ps.size);
});
auto ts = TimeStamp::now();
leg_instruments[x].producer().update_direct(ts, bids, asks);
}
// Read synthetic book back
MDConsumerHandle synthetic_handle { synthetic };
std::array<PriceSize,COCONUT_MAX_DEPTH> raw_bids, raw_asks;
TimeStamp t;
synthetic_handle.get_direct_book(t, COCONUT_MAX_DEPTH, raw_bids.data(), raw_asks.data());
Book result {};
sane::map(raw_bids, result.bids, [](PriceSize ps) -> PS { return {ps.price(), ps.size()}; });
sane::map(raw_asks, result.asks, [](PriceSize ps) -> PS { return {ps.price(), ps.size()}; });
return result;
}
///////////////////////
// Test Cases
///////////////////////
TEST_CASE("SyntheticBookMerge", "[md][synthetic]") {
shared_ptr<SyntheticStrategy> synthetic;
vector<Co2TestInstrument> test_instr;
@@ -381,17 +429,16 @@ TEST_CASE("AggregationAcrossLevels", "[md][synthetic]") {
TEST_CASE("Synthetic_net_change", "[md][synthetic]") {
shared_ptr<SyntheticStrategy> synthetic;
vector<Co2TestInstrument> test_instr;
tie(synthetic, test_instr) = make_securities({ {1.0, 0.01, api::SIDE_BUY}, {2.0, 0.01, api::SIDE_SELL }});
// This tick size is what you get when you make up a test and then later have to figure out the tick size because NetChange suddenly depends on tick size
tie(synthetic, test_instr) = make_securities({ {1.0, 10.0, api::SIDE_BUY}, {2.0, 10.0, api::SIDE_SELL }});
// Set to tick in "# ticks"
synthetic->security.mutable_contract_spec()->set_low_tick(1.0);
synthetic->security.mutable_contract_spec()->set_high_tick(1.0);
synthetic->security.mutable_synthetic()->set_price_calc(api::SyntheticContract::PRICECALC_NET_CHANGE);
auto es_buy1 = test_instr[0];
auto es_sell2 = test_instr[1];
MDConsumerHandle synthetic_handle{synthetic};
// Set legs to use net change
spark::LegSpreaderParams lparam;
lparam.set_leg_md_calc(spark::LegSpreaderParams::NET_CHANGE);
set_leg_params(*synthetic, es_buy1.producer().key(), lparam);
set_leg_params(*synthetic, es_sell2.producer().key(), lparam);
// set settlement prices
es_buy1.producer().update_settlement(TimeStamp::now(), { 100, 0 }, datetime::today());
es_sell2.producer().update_settlement(TimeStamp::now(), { 50, 0 }, datetime::today());
@@ -404,11 +451,9 @@ TEST_CASE("Synthetic_net_change", "[md][synthetic]") {
{{90,5}, {80,9}, {70,11}, {60,10}, {50,1}},
{{100,16}, {110,8}, {120,12}, {130,5}});
// Validate merge
expect_market(synthetic_handle,
{{-10,1}, {-20,7}, {-40,4}, {-70,4}, {-80,2}, {-100,2}},
{{20,2}, {50,5}, {70,1}, {80,4}, {100,4}, {110,1}, {130,1}});
// precise {{20,2}, {40,1}, {50,4}, {70,1}, {80,4}, {90,1}, {100,3}, {110,1}, {120,1}});
{{-1,1}, {-2,7}, {-4,4}, {-7,4}, {-8,2}, {-10,2}},
{{2,2}, {5,5}, {7,1}, {8,4}, {10,4}, {11,1}, {13,1}});
}
}
+16 -8
View File
@@ -5,6 +5,7 @@
#include <memory>
#include <unordered_map>
#include <boost/container/small_vector.hpp>
#include "lib/tracing/PerfLog.h"
#include "lib/utils/bts_assert.h"
@@ -40,33 +41,37 @@ template <typename EV, typename KE = KeyExtractor> class ContentSubscription {
}
};
ContentSubscription(Actor &actor)
void init() {
_name = utils::type_name<EV>();
}
ContentSubscription(Actor &actor, trace::PerfLog& log)
: _actor(&actor), _sub(_actor->subscribe<EV>(
[this](auto const &u) { this->on_update(u); })),
_my_subs(), _dispatching(false), _current_sub_id(1),
_pre_sub([](auto) {}) {}
_pre_sub([](auto) {}), _log(log) {init();}
ContentSubscription(Actor &actor, std::function<void(Key)> const &pscb)
ContentSubscription(Actor &actor, std::function<void(Key)> const &pscb, trace::PerfLog& log)
: _actor(&actor), _sub(_actor->subscribe<EV>(
[this](auto const &u) { this->on_update(u); })),
_my_subs(), _dispatching(false), _current_sub_id(0), _pre_sub(pscb) {}
_my_subs(), _dispatching(false), _current_sub_id(0), _pre_sub(pscb), _log(log) {init();}
template <class Tag>
ContentSubscription(Actor &actor, Tag const &tag)
ContentSubscription(Actor &actor, Tag const &tag, trace::PerfLog& log)
: _actor(&actor),
_sub(_actor->subscribe<EV>(
tag, [this](auto const &u) { this->on_update(u); })),
_my_subs(), _dispatching(false), _current_sub_id(1),
_pre_sub([](auto) {}) {}
_pre_sub([](auto) {}), _log(log) {init();}
template <class Tag>
ContentSubscription(Actor &actor, Tag const &tag,
std::function<void(Key)> const &pscb)
std::function<void(Key)> const &pscb, trace::PerfLog& log)
: _actor(&actor),
_sub(_actor->subscribe<EV>(
tag, [this](auto const &u) { this->on_update(u); })),
_my_subs(), _dispatching(false), _current_sub_id(1),
_pre_sub(pscb) {}
_pre_sub(pscb), _log(log) {init();}
template <class CBT> SubID sub(Key k, CBT const &cb) {
_pre_sub(k);
@@ -111,8 +116,11 @@ template <typename EV, typename KE = KeyExtractor> class ContentSubscription {
bool _dispatching;
uint32_t _current_sub_id;
std::function<void(Key k)> _pre_sub;
trace::PerfLog& _log;
std::string _name;
void on_update(Update<EV> const &u) {
auto scoped_do_work = trace::log_scope(_log, {}, "ContentSub::on_update", 0, _name.c_str());
KE ke;
_dispatching = true;
if (u.events)
+4 -1
View File
@@ -3,6 +3,9 @@
#include <memory>
namespace bts {
namespace trace {
class PerfLog;
}
namespace mercury {
// interface to mark an actor that requires on_poll() calls
@@ -10,7 +13,7 @@ class IPollableActor
{
public:
~IPollableActor();
virtual void on_poll() = 0;
virtual void on_poll(trace::PerfLog&) = 0;
};
+1 -1
View File
@@ -21,7 +21,7 @@ class Timer : public Actor, public mercury::IPollableActor {
}
private:
void on_poll() override {
void on_poll(trace::PerfLog&) override {
auto now = bts::Timer::now();
if (ms_diff(now, _last_ts) > 10) {
_last_ts = now;
Loaded 100 of 126 files, more files were not shown because too many files have changed in this diff. Show more