Compare commits

..
56 Commits
Author SHA1 Message Date
Michael Harding ebaf8ff51f Merged master into doc-theme 2019-11-04 16:17:12 -05:00
Taha Afzal b3369db7cc Release notes 2019-11-04 13:07:02 -05:00
Taha Afzal 279abcfefd Made the fractional_hedging test a bit easier to do 2019-11-04 10:41:27 -05:00
Taha Afzal 872e2465a6 Fixing the test 2019-11-01 14:15:11 -04:00
Taha Afzal 89fb79c556 Manual tests added 2019-10-29 18:53:17 -04:00
Taha Afzal c92e9df77b Merge branch 'hide_deleted_accounts' 2019-10-29 14:58:11 -04:00
Taha Afzal 81aaa853d5 Merging SP-248 2019-10-29 14:40:00 -04:00
Taha Afzal 4f308faba2 Merge branch 'SP-247-order-entry-sounds' 2019-10-29 14:09:30 -04:00
Taha Afzal 0b924033c2 Merge branch 'SP-252-root-spreader-security' 2019-10-29 14:08:29 -04:00
Taha Afzal 3606c707e4 Merge branch 'SP-204-local-fills' 2019-10-29 14:07:42 -04:00
Taha Afzal 5998107e6a Merge branch 'SP-225-investigate-the-lack-of-price-rep' 2019-10-29 14:03:12 -04:00
Taha Afzal 297982433b Merge branch 'SP-249' 2019-10-29 13:58:28 -04:00
Taha Afzal 15ac9d945b Modified tests 2019-10-29 13:53:20 -04:00
Jake Norbut a0f1d11692 adding manual fills don't show admin fills test 2019-10-28 16:15:01 -05:00
Jake Norbut e7890a459c test for order sent sound 2019-10-28 15:57:27 -05:00
Jake Norbut f7fb5ed400 adding test for deleted accounts 2019-10-28 15:48:47 -05:00
Jake Norbut 75e98d353f adding left panel test steps 2019-10-28 15:41:39 -05:00
Michael Harding 01ab0dd3e5 update manual deploy script 2019-10-28 14:28:59 -04:00
Michael Harding fbdf56b4dc Move lightbox assets into the manual build 2019-10-28 14:28:01 -04:00
Michael Harding b698b64879 Merge master into doc-theme 2019-10-28 11:41:34 -04:00
John Colvin 5fc6ff4c00 SP-204 Local fills UI erroneously shows admin fills too 2019-10-26 16:40:32 -05:00
Taha Afzal 6951272633 Just has to update the last_price 2019-10-25 13:05:44 -04:00
Taha Afzal 548611b44f Adding tooltips, and hooking to the right proto 2019-10-24 18:04:54 -04:00
Taha Afzal 322da04723 Merge branch 'SP-249' of bitbucket.org:bluetradesys/spark into SP-249 2019-10-24 17:09:30 -04:00
Taha Afzal 3544eac815 Cleaned up the quote throttling code 2019-10-24 17:09:24 -04:00
John Colvin 7fef7d88ce refactor QuoteInsideMarketDistance to new proto type (cannot recycle old type bc it must be null in existing synths) 2019-10-24 15:41:30 -05:00
John Colvin 7531cf5eb4 remove unused synth param 'QuoteInsideMarketDistanceAwayModel' 2019-10-24 15:38:15 -05:00
John Colvin 5826a083c0 SyntheticEditor - remove Quote Inside Market Away Distance param 2019-10-24 12:44:47 -05:00
John Colvin ec0d5d22f1 SyntheticEditor - disable Quote Throttle/Inside Market Distance by default 2019-10-24 12:37:13 -05:00
John Colvin d433368795 fix typo in 'order sent alert' predicate (creation time comparison was backwards) 2019-10-24 11:51:39 -05:00
Jake Norbut dc4453e510 SP-256 - hide deleted accounts 2019-10-24 08:32:38 -05:00
Jake Norbut 1b1583abda removing OJ and KC IFU entries; regen cme ref data file 2019-10-23 09:24:28 -05:00
Jake Norbut 9d2f2888bb updating reference data with latest for RodrigoSGU and CNH 2019-10-23 09:08:20 -05:00
John Colvin 26816f0048 SP-249 New spreader quoting throttle parameter 2019-10-22 12:12:17 -05:00
Taha Afzal e97e6b93c7 Adding proto 2019-10-21 16:44:00 -04:00
John Colvin 0d8252e131 SP-252 Parent spreader badge should show the spreader name as well 2019-10-21 12:58:21 -05:00
John Colvin f03a413855 SP-247 Order Entry Sounds 2019-10-17 13:55:52 -05:00
John Colvin c9da022c58 SP-248 Ladder left panel scroll 2019-10-15 12:12:20 -05:00
Michael Harding 68a822c342 Fix build 2019-10-15 11:42:11 -04:00
John Colvin 0772a13b09 introduce ladder settings 'profile-equivalent' logic to avoid disconnecting ladders from profiles due to trivial settings changes 2019-10-14 20:55:00 -05:00
Michael Harding 5ee2cfb5f6 Merged master into doc-theme 2019-10-14 17:05:38 -04:00
John Colvin c53f6ad906 finally remove mutable members from LadderSettings GetHashCode impl 2019-10-14 16:01:24 -05:00
Michael Harding e8d4fa1200 Manual: Add page descriptions 2019-10-14 16:48:31 -04:00
Michael Harding 8d47584ee8 Manual: make navigation fixed 2019-10-11 14:05:48 -04:00
Michael Harding 7b95174171 Manual: Fix menu on mobile 2019-10-11 13:36:08 -04:00
Michael Harding 31e913fb1b manual: improve lightbox 2019-10-11 10:48:36 -04:00
Michael Harding 20f6c76050 Manual: screen_recording short code better youtube 2019-10-10 17:05:54 -04:00
Michael Harding 4a41f9b166 Manual: image shortcode 2019-10-10 11:27:07 -04:00
BTS f4bb2aaada Manual: image processing 2019-10-09 17:45:01 -04:00
BTS 36b43024ac Manual Dark Mode 2019-10-09 16:35:21 -04:00
Michael Harding ffd1835218 Manual: Add icons, hover heading links 2019-09-17 11:07:06 -04:00
BTS 18af3dce74 manual: make images floatable 2019-09-16 17:46:59 -04:00
BTS 88c42ee6bd Manual: fixes for High DPI and icon short code 2019-09-16 17:13:26 -04:00
BTS ba61d5de76 Add image shorttags in the manual 2019-09-13 19:48:10 -04:00
BTS ffbdd81a9e Add Youtube lightbox to manual 2019-09-13 14:57:24 -04:00
Michael Harding 66f58e1d4b Add meta description to Manual 2019-09-13 10:14:19 -04:00
79 changed files with 1534 additions and 471 deletions

No files matched your search

@@ -3,6 +3,10 @@ title = "Glossary of Terms"
weight = 9
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "Definitions of common terms used in BTS Spark and this manual"
+++
+7 -7
View File
@@ -3,12 +3,12 @@ title = "Help"
weight = 3
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "Report a problem or find the activity log from the Help module in BTS Spark"
+++
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/yGQfr_puT3w" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
{{ youtube(id="yGQfr_puT3w") }}
## Report a Problem
If there is any issue at all, you can click here
@@ -18,14 +18,14 @@ insert_anchor_links = "right"
send, BTS will get an alert of everything you submitted and will be
in contact with you shortly.
![](image58.png)
{{ image(name="image58.png", alt="screenshot") }}
## About Spark
Upon launching this, you will see an info window
which will list what version you are running as well as who is
currently logged in.
![](about_spark.png)
{{ image(name="about_spark.png", alt="screenshot") }}
## Activity log
This is an overall log viewer which will show
@@ -34,4 +34,4 @@ insert_anchor_links = "right"
messages in a text box in the right top corner.
 
![](image39.png)
{{ image(name="image39.png", alt="screenshot") }}
@@ -1,15 +1,15 @@
+++
title = "Module Launcher"
title = "Launcher"
weight = 2
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "The basics of the BTS Spark launcher and user interface"
+++
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/OxVqQBKFIDw" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
{{ youtube(id="OxVqQBKFIDw") }}
- The module launcher is the first item that will come up after
logging in to Spark.
@@ -18,7 +18,7 @@ insert_anchor_links = "right"
each module
- It appears like the following:
![](image38.png)
{{ image(name="image38.png", alt="screenshot") }}
- If you right-click anywhere on the launcher you can modify
various display settings including:
@@ -29,7 +29,7 @@ insert_anchor_links = "right"
- Ability to exit the front end, this can also be done by simply clicking the “X” on the top right of this window or by clicking on exit from this options window
- These options are all displayed like the following:
![](image68.png)
{{ image(name="image68.png", alt="screenshot") }}
## Spark User interface
@@ -3,17 +3,21 @@ title = "Logging in to the system"
weight = 1
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "How to log into BTS Spark"
+++
The shortcut icon will appear on your desktop like below:
![](image64.png)
{{ image(name="image64.png", alt="BTS icon") }}
Simply double click or single click and hit enter to launch the
login screen:
![](image3.png)
{{ image(name="image3.png", alt="login screen") }}
Choose your username from the drop down, choose your initial
workspace, enter your password, and click Login
+20 -22
View File
@@ -3,22 +3,22 @@ title = "Market"
weight = 4
sort_by = "weight"
insert_anchor_links = "right"
+++
[extra]
page_description = "The BTS Spark Market module contains the Market View and Time and Sales windows"
+++
- This is one of the main items on the module launcher
- When hovering over it, it will show 2 different modules which can
be launched from it, market view and then, time and sales
- It appears like the below:
![](image53.png)
{{ image(name="image53.png", alt="screenshot") }}
## Market View
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/Y54S1uscT_g" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
{{ youtube(id="Y54S1uscT_g") }}
- This provides a grid of outright, spread, and synthetic products
showing real-time market data all in one place
@@ -29,7 +29,7 @@ insert_anchor_links = "right"
- You choose which products you would like to view upon launching it,
the security browser will appear as shown below:
![](image21.png)
{{ image(name="image21.png", alt="security picker") }}
- Click on individual securities that you wish to view and click add,
they will then show up in the selected securities window below.  If
@@ -45,13 +45,13 @@ insert_anchor_links = "right"
- Below is an example of an outright, spread, all futures, and
synthetic chosen:
![](image14.png)
{{ image(name="image14.png", alt="screenshot") }}
- Clicking OK will bring up the Market View with the selected
securities you have chosen, in this example, it appears like the
following:
![](market_view.png)
{{ image(name="market_view.png", alt="screenshot") }}
- Once a market view is launched and you are viewing data, you can
customize the view further by the following:
@@ -61,19 +61,19 @@ insert_anchor_links = "right"
- Filtering - by hovering over the column header, a filtering box
will appear like below:
![](image66.png)
{{ image(name="image66.png", alt="screenshot") }}
- All columns let you sort by that columns minimum and maximum
information in a box that appears like the following:
![](image24.png)
{{ image(name="image24.png", alt="screenshot") }}
- Specifically for the securities column, there is a search box that
will appear by bringing up the filtering box, which enables users to
type the security in which they are looking for.  This appears like
the following:
![](image11.png)
{{ image(name="image11.png", alt="screenshot") }}
- Besides viewing the market data, you can do a lot within the market
view by utilizing the icons to the left of the main window, these
@@ -88,7 +88,7 @@ insert_anchor_links = "right"
- Change securities
- 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.
- Click on the settings icon {{ image(name="change_securities.png", alt="screenshot") }} 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 3 ways:
@@ -96,7 +96,7 @@ insert_anchor_links = "right"
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
{{ image(name="image23.png", alt="screenshot") }} 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.
@@ -121,7 +121,7 @@ insert_anchor_links = "right"
- This is done by either highlighting 1 or many securities and then
clicking on the Fills icon
![](image20.png) on
{{ image(name="image20.png", alt="screenshot") }} on
the left-hand side to bring up a fill window for the selected
securities.  
@@ -132,14 +132,14 @@ insert_anchor_links = "right"
- This is accomplished by highlighting 1 or many securities and then
clicking on the time and sales icon
![](image49.png) on
{{ image(name="image49.png", alt="screenshot") }} on
the left-hand side
- Launching the synthetic window to create a synthetic product
- This is accomplished by highlighting either 1 or 2 securities and
clicking on the Synthetic icon
![](image34.png) on
{{ image(name="image34.png", alt="screenshot") }} on
the lower left-hand side
- Please note - If only 1 security is chosen, a create synthetic
@@ -148,9 +148,7 @@ insert_anchor_links = "right"
## Time and Sales
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/nEkSGacjt0s" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
{{ youtube(id="nEkSGacjt0s") }}
- Provides a display of real-time fills as they happen for 1 or many
securities
@@ -161,9 +159,9 @@ insert_anchor_links = "right"
- On launching a security browser will appear, allowing you to choose
the products you would like to view.
![](time_and_sales.png)
{{ image(name="time_and_sales.png", alt="screenshot") }}
- 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
- 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.
- From the settings icon {{ image(name="settings_icon.png", alt="screenshot") }} 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.
@@ -3,13 +3,13 @@ title = "My Windows"
weight = 7
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "Name and load windows with the My Windows module in BTS Spark"
+++
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/pjm1JJ9Q3jM" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
{{ youtube(id="pjm1JJ9Q3jM") }}
This is the housing of the windows that you have manually named and
creates a place where you can quickly and easily launch windows
@@ -20,18 +20,18 @@ click on any tab and the “Add to My Windows” menu will appear.  If you
click on Add to My Windows, it will give you the ability to type in a
text box on the tab itself.
![](image16.png)
{{ image(name="image16.png", alt="screenshot") }}
Once you have named the tab, click enter to get out of the editing view
of the text box.  Once that is done, it will display that name for that
tab moving forward, which will be displayed within the My Windows
section of the module launcher.
![](image15.png)
{{ image(name="image15.png", alt="screenshot") }}
Right-clicking on a tab that has already had its default title modified
will show the following which will allow you to rename the modified
title or remove it from the My Windows, which will then restore the
default standard title.
![](image37.png)
{{ image(name="image37.png", alt="screenshot") }}
@@ -3,12 +3,13 @@ title = "Settings"
weight = 8
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "The BTS Spark Settings module and workspaces management"
+++
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/yGQfr_puT3w" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
{{ youtube(id="yGQfr_puT3w") }}
## Workspaces
@@ -16,7 +17,7 @@ This is where your workspaces can be saved or switched
to pre-saved ones that you have created.  If you click on the
Settings module, the Workspaces
![](image9.png)
{{ image(name="image9.png", alt="screenshot") }}
Once the Workspace menu is launched you can save your current workspace
by typing in a name in the text box and clicking save.  This will save
@@ -26,4 +27,4 @@ switching to it from the workspace launcher later on.
You can also delete workspaces by clicking on the “X” to the right of
the saved workspace from the workspace menu.
![](image51.png)
{{ image(name="image51.png", alt="screenshot") }}
+26 -42
View File
@@ -3,47 +3,33 @@ title = "Spreader"
weight = 6
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "How to create and edit synthetic spreads in BTS Spark's spreader"
+++
{{ youtube(id="RZU9Wj0cETo") }}
{{ youtube(id="XEZpuPAgMC0") }}
{{ youtube(id="EVFK2yNQFS4") }}
{{ youtube(id="d5ywlHen6yo") }}
{{ youtube(id="Mmw8PNJA0Dw") }}
{{ youtube(id="QK-bBSH6Rc0") }}
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/XEZpuPAgMC0" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/EVFK2yNQFS4" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/d5ywlHen6yo" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/Mmw8PNJA0Dw" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/QK-bBSH6Rc0" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
- Launching the spreader from the module launcher will bring up the
- Launching the spreader from the launcher will bring up the
synthetic management browser which appears like below:
![](image35.png)
{{ image(name="image35.png", alt="screenshot") }}
- From this window, it will show any synthetics that you have already
created as well as various other options of both managing the
synthetics and other tasks all from the left-hand side pane within
the window, including:
- Adding a new synthetic ![](image8.png)
- Removing a synthetic ![](image13.png)
- Changing a synthetic ![](image55.png)
- Cloning a synthetic ![](image28.png)
- Launching a ladder of a highlighted synthetic ![](image7.png)
- Launching an order book of selected synthetic ![](image43.png)
- Adding a new synthetic {{ image(name="image8.png", alt="screenshot") }}
- Removing a synthetic {{ image(name="image13.png", alt="screenshot") }}
- Changing a synthetic {{ image(name="image55.png", alt="screenshot") }}
- Cloning a synthetic {{ image(name="image28.png", alt="screenshot") }}
- Launching a ladder of a highlighted synthetic {{ image(name="image7.png", alt="screenshot") }}
- Launching an order book of selected synthetic {{ image(name="image43.png", alt="screenshot") }}
@@ -63,7 +49,7 @@ insert_anchor_links = "right"
- An empty synthetic browser will look like below without any legs
chosen:
![](image31.png)
{{ image(name="image31.png", alt="screenshot") }}
- Clicking on Add Leg on the top right of the window will launch the
security browser, which will allow you to choose the first leg
@@ -77,13 +63,13 @@ insert_anchor_links = "right"
name your synthetic product.  By default, it will auto-populate
based on the symbols you have chosen:
![](image29.png)
{{ image(name="image29.png", alt="screenshot") }}
- You can click into the text box to the right of the name and
customize the name to be whatever you would like, we’ll call this
ES\_CAL:
![](image19.png)
{{ image(name="image19.png", alt="screenshot") }}
- Also included on this row are what ticks you would like this to be
priced in as well as the format of the pricing
@@ -92,7 +78,7 @@ insert_anchor_links = "right"
as the option to add additional legs through the add leg box to the
right of the symbols:
![](image18.png)
{{ image(name="image18.png", alt="screenshot") }}
- The following rows are all part of the main parameters which can
all be modified according to the traders style of designing the
@@ -104,14 +90,14 @@ insert_anchor_links = "right"
details of how that parameter functions.  Below is an example of the
throttle details displayed:
![](image56.png)
{{ image(name="image56.png", alt="screenshot") }}
- Main parameters - these are the top 4 rows in the Create Synthetic
window which all synthetics will be required to have set.
 Everything below these can be customized to a trader's needs, however, the top 4 are the main foundational aspects used for the calculation of
the synthetic:
![](image10.png)
{{ image(name="image10.png", alt="screenshot") }}
- Quantity - This is the number of contracts of the security you
would like to be included in the calculation of the synthetic as
@@ -139,7 +125,7 @@ insert_anchor_links = "right"
displayed and click on such will display further details on how this
parameter is used:
![](image72.png)
{{ image(name="image72.png", alt="screenshot") }}
- Quote
@@ -148,7 +134,7 @@ insert_anchor_links = "right"
by defining a region on either side of the working quote order,
inside which quote updates will not be sent:
![](image6.png)
{{ image(name="image6.png", alt="screenshot") }}
- Pre-Hedge
@@ -161,7 +147,7 @@ insert_anchor_links = "right"
more than the threshold defined.  The hedge order will pay up by the
“Payup Ticks” as defined per leg.
![](image54.png)
{{ image(name="image54.png", alt="screenshot") }}
- Post-Hedge
@@ -172,7 +158,7 @@ insert_anchor_links = "right"
opposite inside market, it is re-priced to cross the opposite inside
market as defined by “cross ticks”
![](image70.png)
{{ image(name="image70.png", alt="screenshot") }}
- Time Based Cross - Basically the same as Ticks Away Cross,
however, this is based on time (in ms) on when to automatically cross
@@ -181,7 +167,7 @@ insert_anchor_links = "right"
away from the opposite inside market, it is re-priced to cross the
opposite inside market as defined by “cross ticks”
![](image40.png)
{{ image(name="image40.png", alt="screenshot") }}
- Size Based Cross - similar to ticks and time based cross, but based
on size instead.  If you define a threshold of the opposite inside
@@ -189,5 +175,3 @@ insert_anchor_links = "right"
the market.  If a legged hedge order’s price gets X amount of
“ticks” away from the opposite inside market, it is re-priced to
cross the opposite inside market as defined by “cross ticks”
![](image76.png)
@@ -4,6 +4,10 @@ weight = 1
sort_by = "weight"
insert_anchor_links = "right"
aliases = ["./bts_spark/Algos/"]
[extra]
page_description = "Definitions of the Algos built into BTS Spark and their parameters"
+++
## Lean
+53 -62
View File
@@ -3,6 +3,9 @@ title = "Trading"
weight = 5
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "BTS Spark's Trading module, including The Order Book, Fills, P&amp;L, Ladder, Order Ticket, and Algo Studio"
+++
@@ -10,13 +13,11 @@ insert_anchor_links = "right"
Order Book, Fills, P&L, Ladder, and Order Ticket
- It appears like below:
![](image44.png)
{{ image( name = "image44.png", alt = "screenshot") }}
## Order Book
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/ygMFkjBBGeU" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
{{ youtube(id="ygMFkjBBGeU") }}
- This provides a list of all orders a user has placed, by default it
will display only working orders
@@ -26,7 +27,7 @@ insert_anchor_links = "right"
other windows within BTS
- Appears like the following:
![](image5.png)
{{ lightbox( name = "image5.png", alt = "screenshot") }}
- You can sort all columns by single left clicking on the column
header which will sort in either descending or ascending order, an
@@ -34,14 +35,14 @@ insert_anchor_links = "right"
 Below is a display of the security column sorting in descending
fashion.
![](image60.png)
{{ image( name = "image60.png", alt = "screenshot") }}
- Filtering can occur, by hovering over any column header which will
display a “Filter” box.  Clicking on this will then display the
available components which can be filtered, below is an example of
clicking on the securities column
![](image52.png)
{{ image( name = "image52.png", alt = "screenshot") }}
- If the column is displaying time base information, a special style
box will appear when clicking on the filtering box providing more
@@ -53,49 +54,45 @@ insert_anchor_links = "right"
- Since Open
- A specific user-defined period of time
![](image25.png)
{{ image( name = "image25.png", alt = "screenshot") }}
- The left-hand box within an order book provides users with further
functionality to quickly perform various tasks with live orders as
well as launch other modules.  It appears like the below:
well as launch other modules.
{{ image(name="spark_menu_order_book.png", alt="the spark menu in the order book", pull="right") }}
![](spark_menu_order_book.png)
- ![](image48.png)Fills -
- {{ icon(name="image48.png", alt="Fills icon") }} Fills -
clicking on this will launch a fills window
- ![](image57.png) Modify -
- {{ icon(name="image57.png", alt = "modify icon") }} Modify -
clicking on this will launch an order ticket for a live order
highlighted in the Order Book.
- ![](image73.png)Cancel
- {{ icon(name="image73.png", alt = "cancel selected icon")}} Cancel
selected - clicking on this will cancel the selected orders you have
highlighted in the order book.  You can choose more than one by
holding hold the Ctrl key when clicking on each row of orders
- ![](image74.png)Cancel
- {{ icon(name="image74.png", alt = "cancel buys icon")}} Cancel
buys - clicking on this will cancel all the buy orders you have in
the order book
- ![](image33.png)Cancel
- {{ icon(name="image33.png", alt = "cancel all icon")}} Cancel
sells - clicking on this will cancel all the sell orders you have in
the order book
- ![](image2.png)Cancel
- {{ icon(name="image2.png", alt = "cancel all icon")}} 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)
- {{ icon(name = "recreate_icon.png", alt = "screenshot") }} 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.
- {{ image( name = "collapse_all_icon.png", alt = "screenshot") }} Collapse all - This will collapse all trees inside the order book to show only the top level of the order.
- {{ icon( name = "expand_all_icon.png", alt = "screenshot") }} Expand all - This will expand to show everything inside the order book.
- {{ icon( name = "settings_icon.png", alt = "screenshot") }} Settings - This launches the settings for the orderbook.
- The mini orderbook is integrated into the ladder
- Middle click (click the mouse wheel) on a working order to display the mini orderbook
- From here, you can change the price and quantity of any working order
{{ image( name = "mini_orderbook.png", alt = "screenshot") }}
## Fills
- This provides a list of all fills a user has had for a given time
period, by default it will open up with “since open” timeframe
period, by default it will open up with “since open” time frame
selected showing you fills on the day thus far
- You can create multiple fill windows, filtered however you
like
@@ -106,9 +103,7 @@ insert_anchor_links = "right"
## P&amp;L
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/xj6cEQ6By34" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
{{ youtube(id="xj6cEQ6By34") }}
- This provides a running ledger of a users account(s) profit and
loss, broken down per symbol
@@ -125,20 +120,18 @@ insert_anchor_links = "right"
- This is accomplished on the top bar, which when clicked on, displays
the choices for calculation, this appears like below:
![](pnl_calculation.png)
{{ image( name = "pnl_calculation.png", alt = "screenshot") }}
- Below is an example P&L window showing a user’s multiple accounts and
various positions in each:
![](pnl_window.png)
{{ image( name = "pnl_window.png", alt = "screenshot") }}
## Ladder
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/Q0qsyc1nvYo" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
{{ youtube(id="Q0qsyc1nvYo") }}
- The ladder was designed to be the most logically produced ladder on
the market, engineered and designed by professional traders for
@@ -149,12 +142,12 @@ insert_anchor_links = "right"
market with a single click
- Below is an example ladder:
![](ladder.png)
{{ image( name = "ladder.png", alt = "screenshot") }}
- The ladder consists of 2 major parts, the Ladder itself and the
information and control section on the left-hand side
- The information section on the left contains the following: &nbsp; ![](image1.png)
- The information section on the left contains the following: &nbsp; {{ image( name = "image1.png", alt = "screenshot") }}
- Day - showing the current price of the security on the day
- High - showing the high price of the security on the day
- Low - showing the low price of the security on the day
@@ -167,7 +160,7 @@ insert_anchor_links = "right"
- The control section right underneath the information section on the
left-hand side offers the following:
![](image62.png)
{{ image( name = "image62.png", alt = "screenshot") }}
- The account in which the orders will flow into when trading this
security
@@ -202,7 +195,7 @@ insert_anchor_links = "right"
the stop price, by clicking on the “&lt;” to bring up the stop price
option:
![](image61.png)
{{ image( name = "image61.png", alt = "screenshot") }}
- Iceberg
@@ -213,7 +206,7 @@ insert_anchor_links = "right"
quantity.  You will need to define what type of order that will
be used for the child order to be sent.
![](image47.png)
{{ image( name = "image47.png", alt = "screenshot") }}
- Time Box
@@ -233,9 +226,9 @@ insert_anchor_links = "right"
- Below are images of the type of time frame that can be used within
the time box
![](image42.png)
{{ image( name = "image42.png", alt = "screenshot") }}
         
![](image50.png)
{{ image( name = "image50.png", alt = "screenshot") }}
- Lean
- This is a specialized algo order which allows the user to
@@ -244,14 +237,14 @@ insert_anchor_links = "right"
- “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
![](lean_algo_control.png)
{{ image( name = "lean_algo_control.png", alt = "screenshot") }}
- 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.
![](join_algo_control.png)
{{ image( name = "join_algo_control.png", alt = "screenshot") }}
- Top
- This is a specialized algo order which allows the user to
@@ -259,14 +252,14 @@ insert_anchor_links = "right"
- 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)
{{ image( name = "top_algo_control.png", alt = "screenshot") }}
- 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)
{{ image( name = "offset_algo_control.png", alt = "screenshot") }}
- OCO
@@ -276,26 +269,26 @@ insert_anchor_links = "right"
of the fill. Composing this order with Cover simulates scalping
functionality.
![](oco_algo_control.png)
{{ image( name = "oco_algo_control.png", alt = "screenshot") }}
- 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)
{{ image( name = "cover_algo_control.png", alt = "screenshot") }}
- 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)
{{ image( name = "trigger_algo_control.png", alt = "screenshot") }}
- The middle of the control section is where you can click on
predefined values to quickly populate the volume at which you wish
to enter the market.
![](image17.png)
{{ image( name = "image17.png", alt = "screenshot") }}
- You can manually type in a quantity here as well
- Furthermore, if you have a value in the volume bar, you can
@@ -305,7 +298,7 @@ insert_anchor_links = "right"
- By having the auto clear box checked, it will bring the value back
to zero after placing an order at the size chosen.
![](image59.png)
{{ image( name = "image59.png", alt = "screenshot") }}
-  The bottom section of the control area of a ladder has 4 aspects
to it
@@ -317,7 +310,7 @@ insert_anchor_links = "right"
- 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)
{{ image( name = "ladder_settings.PNG", alt = "screenshot") }}
- 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
@@ -339,7 +332,7 @@ insert_anchor_links = "right"
- The other, main part of the ladder, is the ladder itself as
displayed below
![](ladder_main_part.png)
{{ image( name = "ladder_main_part.png", alt = "screenshot") }}
- There are 5 main columns
@@ -356,7 +349,7 @@ insert_anchor_links = "right"
- 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
![](ladder_badges.png)
{{ image( name = "ladder_badges.png", alt = "screenshot") }}
- 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
@@ -367,9 +360,7 @@ insert_anchor_links = "right"
## Order Ticket
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/bdC5pZ6T35I" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
{{ youtube(id="bdC5pZ6T35I") }}
- The order ticket was designed to provide another means other than
the ladder to enter orders into the market
@@ -378,7 +369,7 @@ insert_anchor_links = "right"
which security to populate
- The order ticket appears like below
![](image27.png)
{{ image( name = "image27.png", alt = "screenshot") }}
- On the top left, you have the price for the order as well as the
volume
@@ -416,7 +407,7 @@ insert_anchor_links = "right"
- This is all in place in a very user friendly GUI as shown below:
![](algo_studio.png)
{{ image( name = "algo_studio.png", alt = "screenshot") }}
- There are 3 main areas of the Algo Studio:
+3
View File
@@ -3,6 +3,9 @@ title = "BTS Spark"
weight = 1
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "Documentation for Blue Trading Systems' BTS Spark"
+++
Click on the headings on the left to expand their contents. If you need specific information quickly use the Search button in the top right of this page.
+25 -11
View File
@@ -3,7 +3,21 @@ title = "Release notes"
weight = 3
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "Release notes for BTS Spark"
+++
## 2.13.0
### User Visible Changes
- Added the name of the spreader to the parent algo badge (for spreader child orders), in the fills, orderbook and floating orderbook windows
- Added support for sounds for new orders
- Added a new quoting throttle parameter for the spreader, that throttles quotes that are moving toward the inside market but are still a configurable number of ticks away
- Enabled the ladder to remember the left panel scroll position across front end restarts
### Bug Fixes
- Fixed the local fills window so that it does not show the admin fills
- Removed the deleted accounts from the ladder accounts dropdown
- Fixed the orderbook price for iceberg of spreaders
## 2.12.0
### User Visible Changes
- Added support for ICE market data and order execution
@@ -18,11 +32,11 @@ insert_anchor_links = "right"
- Fixed an issue that could cause erroneous fill account attribution in the event of a crash
### Internal Changes
- Added support for tag 1031. Currently defaults to Y, can it be override through config.
- To facilitate support, allowed the bts user to view and edit algos from all other users
- To facilitate support, allowed the bts user to view and edit algos from all other users
- Improved system tests
- Improved log messages in UI to include dispatcher thread name
- Added support for Windows core dumps in the UI
## 2.10.0
### User Visible Changes
- Fills now include information about the algo that originated the fill
@@ -142,7 +156,7 @@ insert_anchor_links = "right"
## 2.6.0
### User Visible Changes
- In the ladder made last trade more distinctive when overlayed on the volumn column
- In the ladder made last trade more distinctive when overlaid on the volume column
- In the ladder, corrected display of volume tool tip
- In the ladder, changed the consolidation of last trade to consider only the price ignoring aggressor changes
- Added an option for the Top Algo to delete itself if within N ticks from top of book
@@ -159,11 +173,11 @@ insert_anchor_links = "right"
- Made algos, synthetics and user names private so that multiple users can share a single system privately.
- Allowed the mini order book defaults for column widths to be changed from the mini order book
- Allowed the mini order book to shrink to the minimum size that will be needed to display the selected columns
- Improved accuracy of the directional arrow when displaying last trades in the volumn column
- Improved accuracy of the directional arrow when displaying last trades in the volume column
## 2.4.0
### User Visible Settings
- In the ladder, changed the last trade column to accumulate successive trades at the same price
- Fixed a bug in the order book where the right side ladder would display the top leve order's security rather then current row's
- Fixed a bug in the order book where the right side ladder would display the top level order's security rather then current row's
- Added OK, Cancel buttons to the security picker for ladder profiles or sounds
- Added ladder settings for the mini order book
- Added a safety check to guard against unintentional market crosses
@@ -179,7 +193,7 @@ insert_anchor_links = "right"
### User Visible Changes
- Introduced multiple settings profiles for the ladder
- This allows multiple standard configuration for the ladder, optionally with security specific variations
- Made Cover order more independent from its hedging chidren:
- Made Cover order more independent from its hedging children:
- Cover continues to operate even if an hedging child is canceled
- When a cover is canceled it will detach from any running hedging children, instead of canceling them
- Changed Lean, Join and Cover algos to hide themselves in the ladder once they have completed their work
@@ -226,7 +240,7 @@ insert_anchor_links = "right"
## 2.2.1
### User Visible Changes
- Fixed order book form exception when display a spreader order for whichs the legs were not present yet
- Fixed order book form exception when display a spreader order for which the legs were not present yet
## 2.1.1
### User Visible Changes
@@ -238,7 +252,7 @@ insert_anchor_links = "right"
- Added support for the GLBX Iceberg order type
- Allowed quick algo param edits by middle-clicking on either the bid/ask columns or a "favorite" button
- Added support for manual fills, either entered manually or imported from file. Manual fills are used only for P&L purposes, they have no impact on limits
- Allowed the LTQ and VAP values to be overlayed on the same ladder column
- Allowed the LTQ and VAP values to be overlaid on the same ladder column
- Added day position information to the ladder
- Added PIQ to the mini order book
- Fixed a rare bug that could lead to server failure when requesting market data
@@ -319,7 +333,7 @@ insert_anchor_links = "right"
- Integrated stunnel into UI to make VPN setup unnecessary in unsecured environments.
## 1.5.7
### User Visible Changes
- New videos have been added to the manual describing the [Algo Studio](./bts_spark/Trading/_index.md#algo-studio) and [Spreader](./bts_spark/Spreader/_index.md)
- New videos have been added to the manual describing the [Algo Studio](@/bts_spark/Trading/_index.md#algo-studio) and [Spreader](@/bts_spark/Spreader/_index.md)
- Fixed a bug that occasionally would cause windows to be position outside the visible screen on startup
- Fixed a bug in the order book form that would occasionally cause the Recreate Order option to be unavailable
- Added a confirmation step to Cancel All, Cancel Buys and Cancel Sells commands
@@ -395,7 +409,7 @@ insert_anchor_links = "right"
- Fixed a bug in handling of built-in bts to user that could lead to failure to login as bts in old systems
- Improved market data subsystem to support security definition updates
- Fixed a startup race in market data that could lead to data corruption on startup
- Increased default order price precision to 8 digis as required by some CME products
- Increased default order price precision to 8 digits as required by some CME products
## 1.4.11
### New Features
@@ -463,7 +477,7 @@ insert_anchor_links = "right"
### Ladder Usability Improvements
- Controls found in Left Pane of the ladder can now be hidden via the checkboxes found under Ladder Form -> Settings button -> Left Pane tab
- The Left Pane is now scrollable with the mouse wheel when there isn't enough vertical space to accomodate the entire form
- The Left Pane is now scrollable with the mouse wheel when there isn't enough vertical space to accommodate the entire form
- The Left Pane can now grown and shrunk horizontally via the splitter between the left pane and the main ladder area
### Performance Improvements
- Requoting and hedging latency has been reduced by up to 30%
@@ -3,6 +3,9 @@ title = "System Requirements"
weight = 4
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "Recommended and minimum systems requirements for BTS Spark"
+++
+7 -2
View File
@@ -1,15 +1,20 @@
#/bin/bash
#python -m mkdocs build -d site
./run_zola.sh build
ssh bts@bluetradesys.com "rm -r /home/bts/bts_website/zola/content/docs"
scp -r ./content bts@bluetradesys.com:/home/bts/bts_website/zola/content/docs
scp -r ./public bts@bluetradesys.com:/home/bts/doc/
ssh bts@bluetradesys.com << EOF
cd doc
cd /home/bts/doc/
sudo rm -r /var/www/documentation/*
sudo mv ./public/* /var/www/documentation/
cd /home/bts/bts_website/zola/
./fix_links.sh
./deploy.sh
EOF
rm -r public
+18 -9
View File
@@ -1,4 +1,4 @@
.page {
.page, .menu-open .page {
position: absolute;
width: calc(100% - #{$sidebar-width});
height: 100%;
@@ -7,15 +7,24 @@
background: #f6f6f6;
padding-bottom: 20px;
transition: 0.5s;
left: $sidebar-width;
h1:hover .zola-anchor, h2:hover .zola-anchor, h3:hover .zola-anchor, h4:hover .zola-anchor, h5:hover .zola-anchor, h6:hover .zola-anchor {
visibility: visible;
}
.zola-anchor {
color: #4183c4;
padding-left: 10px;
padding-right: .5em;
margin-left: -1.5em;
text-decoration: none;
font-weight: initial;
float: left;
visibility: hidden;
&:hover {
text-decoration: underline;
text-decoration: none;
visibility: visible;
}
}
@@ -155,13 +164,13 @@
}
}
@include min-screen(600px) {
.page {
left: $sidebar-width;
}
}
// @include min-screen(600px) {
// .page {
// left: $sidebar-width;
// }
// }
.page-without-menu {
.menu-closed .page {
width: 100%;
left: 0;
+46 -19
View File
@@ -123,27 +123,9 @@
.page__header {
height: 50px;
position: fixed;
.menu-icon {
height: 50px;
width: 50px;
font-size: 24px;
text-align: center;
float: left;
position: relative;
transition: background .5s;
cursor: pointer;
@include menu-icon();
&:hover {
span {
background-color: black;
}
}
}
.search-icon {
height: 50px;
width: 50px;
display: inline-block;
@@ -156,5 +138,50 @@
&:hover {
color: black
}
@include menu-icon();
&:hover {
span {
background-color: black;
}
}
}
.search-icon, .font-icon {
height: 40px;
width: 40px;
display: inline-block;
text-align: center;
line-height: 40px;
color: rgba(0, 0, 0, 0.5);
cursor: pointer;
font-size: 2rem;
&:hover {
color: #eee;
background-color:#666;
border-radius: .2ex;
}
}
.font-icon{
display: inline-block;
// padding:0.6ex 1ex;
margin-left:1rem;
background:#333;
color:#eee;
border-radius: .2ex;
&:hover {
background:#666;
}
}
}
.dark-mode .font-icon{
background:#aaa;
color:#333;
}
+17 -2
View File
@@ -1,4 +1,4 @@
.menu {
.menu, .menu-open .menu {
height: 100%;
position: absolute;
left: 0;
@@ -39,7 +39,7 @@
}
}
.menu-hidden {
.menu-closed .menu {
width: 0;
}
@@ -47,4 +47,19 @@
.menu {
width: 0;
}
.menu-close-icon {
display: block;
padding:1ex;
font-size: 1.5em;
float:right;
}
}
@include min-screen(600px) {
.menu-close-icon {
display: none;
}
}
+98 -3
View File
@@ -1,6 +1,7 @@
@charset "utf-8";
@import "normalize";
@import "colorbox";
* {
box-sizing: border-box;
@@ -21,6 +22,10 @@ body {
letter-spacing: 0.2px;
}
body.dark-mode .page{
background:#444;
color:#eee;
}
@mixin min-screen($min-width: $body-width) {
@media screen and (min-width: $min-width) {
@@ -40,9 +45,21 @@ $sidebar-width: 300px;
@import "content";
@import "header";
img {
transform: scale(0.666);
transform-origin: top left;
@media screen and (min-width: 1920px)
{
// img.scaled-image {
// transform: scale(0.666);
// transform-origin: top left;
// }
}
.colorbox-inline img {
max-width: 100%;
}
.icon-image {
display:inline;
max-height: 1.4em;
}
.back-button {
@@ -58,4 +75,82 @@ img {
iframe {
width:560px;
height:315px;
}
.youtube-thumb {
margin-top:2em;
position:relative;
max-width:500px;
}
.youtube-overlay {
position:absolute;
width:8rem;
height:4.5rem;
line-height:4.5rem;
font-size: 2.5rem;
left:calc(50% - 4rem);
top:calc(50% - 2.25rem);
text-align:center;
opacity:.8;
background:#f68a28;
color:white;
border-radius: .3rem;
}
.youtube-overlay:hover {
background:#f68a28;
opacity:1;
}
.video-parent {
position: relative;
width: fit-content;
}
.video-parent:hover .play-pause, .video-parent.paused .play-pause {
display: block;
}
.play-pause {
display:none;
opacity:.7;
position:absolute;
width:8rem;
height:4.5rem;
line-height:4.5rem;
font-size: 2.5rem;
left:calc(50% - 4rem);
top:calc(50% - 2.25rem);
text-align:center;
background:#f68a28;
color:#fff;
border-radius: .3rem;
z-index:1000;
}
.play-pause:hover {
opacity: 0.9;
}
@media (hover: none) {
.play-pause {
display:block;
}
.video-parent.playing:hover .play-pause {
display:none;
}
}
.is-clearfix {
clear:both;
}
.is-pulled-left {
float: left;
}
.is-pulled-right {
float: right;
}
+42
View File
@@ -0,0 +1,42 @@
#colorbox, #cboxOverlay, #cboxWrapper{position:absolute;top:0;left:0;z-index:9999;overflow:hidden;-webkit-transform:translate3d(0,0,0);}
#cboxWrapper{max-width:none;}
#cboxOverlay{position:fixed;width:100%;height:100%;}
#cboxMiddleLeft, #cboxBottomLeft{clear:left;}
#cboxContent{position:relative;}
#cboxLoadedContent{overflow:auto;-webkit-overflow-scrolling:touch;}
#cboxTitle{margin:0;}
#cboxLoadingOverlay, #cboxLoadingGraphic{position:absolute;top:0;left:0;width:100%;height:100%;}
#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{cursor:pointer;}
.cboxPhoto{float:left;margin:auto;border:0;display:block;max-width:none;-ms-interpolation-mode:bicubic;}
.cboxIframe{width:100%;height:100%;display:block;border:0;padding:0;margin:0;}
#colorbox, #cboxContent, #cboxLoadedContent{box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;}
#cboxOverlay{background:#000;opacity:0.9;filter:alpha(opacity = 90);}
#colorbox{outline:0;}
#cboxTopLeft{width:14px;height:14px;background:url(colorbox-images/controls.png) no-repeat 0 0;}
#cboxTopCenter{height:14px;background:url(colorbox-images/border.png) repeat-x top left;}
#cboxTopRight{width:14px;height:14px;background:url(colorbox-images/controls.png) no-repeat -36px 0;}
#cboxBottomLeft{width:14px;height:43px;background:url(colorbox-images/controls.png) no-repeat 0 -32px;}
#cboxBottomCenter{height:43px;background:url(colorbox-images/border.png) repeat-x bottom left;}
#cboxBottomRight{width:14px;height:43px;background:url(colorbox-images/controls.png) no-repeat -36px -32px;}
#cboxMiddleLeft{width:14px;background:url(colorbox-images/controls.png) repeat-y -175px 0;}
#cboxMiddleRight{width:14px;background:url(colorbox-images/controls.png) repeat-y -211px 0;}
#cboxContent{background:#fff;overflow:visible;}
.cboxIframe{background:#fff;}
#cboxError{padding:50px;border:1px solid #ccc;}
#cboxLoadedContent{margin-bottom:5px;}
#cboxLoadingOverlay{background:url(colorbox-images/loading_background.png) no-repeat center center;}
#cboxLoadingGraphic{background:url(colorbox-images/loading.gif) no-repeat center center;}
#cboxTitle{position:absolute;bottom:-25px;left:0;text-align:center;width:100%;font-weight:bold;color:#7C7C7C;}
#cboxCurrent{position:absolute;bottom:-25px;left:58px;font-weight:bold;color:#7C7C7C;}
#cboxPrevious, #cboxNext, #cboxSlideshow, #cboxClose{border:0;padding:0;margin:0;overflow:visible;position:absolute;bottom:-29px;background:url(colorbox-images/controls.png) no-repeat 0px 0px;width:23px;height:23px;text-indent:-9999px;}
#cboxPrevious:active, #cboxNext:active, #cboxSlideshow:active, #cboxClose:active{outline:0;}
#cboxPrevious{left:0px;background-position:-51px -25px;}
#cboxPrevious:hover{background-position:-51px 0px;}
#cboxNext{left:27px;background-position:-75px -25px;}
#cboxNext:hover{background-position:-75px 0px;}
#cboxClose{right:0;background-position:-100px -25px;}
#cboxClose:hover{background-position:-100px 0px;}
.cboxSlideshow_on #cboxSlideshow{background-position:-125px 0px;right:27px;}
.cboxSlideshow_on #cboxSlideshow:hover{background-position:-150px 0px;}
.cboxSlideshow_off #cboxSlideshow{background-position:-150px -25px;right:27px;}
.cboxSlideshow_off #cboxSlideshow:hover{background-position:-125px 0px;}
+15 -6
View File
@@ -1,10 +1,19 @@
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");
$(".menu-icon").click(function (e) {
let body = $("body");
let mobile = window.innerWidth < 600;
let menu_closed = body.hasClass("menu-closed");
let menu_open = body.hasClass("menu-open");
let new_page = !(menu_closed || menu_open);
if((!mobile && (new_page || menu_open)) || mobile && menu_open) {
body.addClass("menu-closed");
body.removeClass("menu-open");
}
else {
body.removeClass("menu-closed");
body.addClass("menu-open");
}
});
}
function debounce(func, wait) {
Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 B

+58 -4
View File
@@ -1,11 +1,40 @@
$(document).ready(function() {
$("img").each(function(idx, elm){
$(elm).css("margin-bottom", elm.height * -0.333);
}).on("load", function(e){
$(e.target).css("margin-bottom", e.target.height * -0.333);
jQuery('a.thumbnail').colorbox({rel:'gal'});
jQuery('a.gallery_image').colorbox({rel:'gal'});
jQuery(".youtube").colorbox({iframe:true, innerWidth:'75%', innerHeight:'75%'});
jQuery(".font-icon").click(function (){
jQuery("body").toggleClass("dark-mode");
if(getCookie("dark-mode") === "true") {
setCookie("dark-mode", "false", 365);
}
else {
setCookie("dark-mode", "true", 365);
}
});
if(getCookie("dark-mode") === "true") {
jQuery("body").toggleClass("dark-mode");
}
if(window.innerWidth >= 1920) {
// $("img.scaled-image").each(function(idx, elm){
// $(elm).css("margin-bottom", elm.height * -0.333);
// }).on("load", function(e){
// $(e.target).css("margin-bottom", e.target.height * -0.333);
// });
$("a.colorbox-inline").each(function(idx, elm){
let child = $(elm).children("img")[0];
let w = (child.naturalWidth * 0.666) + 1
let h = (child.naturalHeight * 0.666) + 1
$(elm).colorbox({inline: true, innerWidth: w, innerHeight: h, scrolling: false});
});
}
else {
jQuery("a.colorbox-inline").colorbox({inline:true});
}
$(".back-button")
.html("Go Back")
.click(function(_) { window.history.back()});
@@ -14,4 +43,29 @@ $(document).ready(function() {
console.log(navigator.userAgent);
$(".search-icon").css("display", "none");
}
});
function setCookie(cname, cvalue, exdays) {
var d = new Date();
d.setTime(d.getTime() + (exdays*24*60*60*1000));
var expires = "expires="+ d.toUTCString();
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
}
function getCookie(cname) {
var name = cname + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(';');
for(var i = 0; i <ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
+6
View File
@@ -0,0 +1,6 @@
/*!
Colorbox 1.6.3
license: MIT
http://www.jacklmoore.com/colorbox
*/
(function(t,e,i){function n(i,n,o){var r=e.createElement(i);return n&&(r.id=Z+n),o&&(r.style.cssText=o),t(r)}function o(){return i.innerHeight?i.innerHeight:t(i).height()}function r(e,i){i!==Object(i)&&(i={}),this.cache={},this.el=e,this.value=function(e){var n;return void 0===this.cache[e]&&(n=t(this.el).attr("data-cbox-"+e),void 0!==n?this.cache[e]=n:void 0!==i[e]?this.cache[e]=i[e]:void 0!==X[e]&&(this.cache[e]=X[e])),this.cache[e]},this.get=function(e){var i=this.value(e);return t.isFunction(i)?i.call(this.el,this):i}}function h(t){var e=W.length,i=(A+t)%e;return 0>i?e+i:i}function a(t,e){return Math.round((/%/.test(t)?("x"===e?E.width():o())/100:1)*parseInt(t,10))}function s(t,e){return t.get("photo")||t.get("photoRegex").test(e)}function l(t,e){return t.get("retinaUrl")&&i.devicePixelRatio>1?e.replace(t.get("photoRegex"),t.get("retinaSuffix")):e}function d(t){"contains"in x[0]&&!x[0].contains(t.target)&&t.target!==v[0]&&(t.stopPropagation(),x.focus())}function c(t){c.str!==t&&(x.add(v).removeClass(c.str).addClass(t),c.str=t)}function g(e){A=0,e&&e!==!1&&"nofollow"!==e?(W=t("."+te).filter(function(){var i=t.data(this,Y),n=new r(this,i);return n.get("rel")===e}),A=W.index(_.el),-1===A&&(W=W.add(_.el),A=W.length-1)):W=t(_.el)}function u(i){t(e).trigger(i),ae.triggerHandler(i)}function f(i){var o;if(!G){if(o=t(i).data(Y),_=new r(i,o),g(_.get("rel")),!$){$=q=!0,c(_.get("className")),x.css({visibility:"hidden",display:"block",opacity:""}),I=n(se,"LoadedContent","width:0; height:0; overflow:hidden; visibility:hidden"),b.css({width:"",height:""}).append(I),j=T.height()+k.height()+b.outerHeight(!0)-b.height(),D=C.width()+H.width()+b.outerWidth(!0)-b.width(),N=I.outerHeight(!0),z=I.outerWidth(!0);var h=a(_.get("initialWidth"),"x"),s=a(_.get("initialHeight"),"y"),l=_.get("maxWidth"),f=_.get("maxHeight");_.w=Math.max((l!==!1?Math.min(h,a(l,"x")):h)-z-D,0),_.h=Math.max((f!==!1?Math.min(s,a(f,"y")):s)-N-j,0),I.css({width:"",height:_.h}),J.position(),u(ee),_.get("onOpen"),O.add(F).hide(),x.focus(),_.get("trapFocus")&&e.addEventListener&&(e.addEventListener("focus",d,!0),ae.one(re,function(){e.removeEventListener("focus",d,!0)})),_.get("returnFocus")&&ae.one(re,function(){t(_.el).focus()})}var p=parseFloat(_.get("opacity"));v.css({opacity:p===p?p:"",cursor:_.get("overlayClose")?"pointer":"",visibility:"visible"}).show(),_.get("closeButton")?B.html(_.get("close")).appendTo(b):B.appendTo("<div/>"),w()}}function p(){x||(V=!1,E=t(i),x=n(se).attr({id:Y,"class":t.support.opacity===!1?Z+"IE":"",role:"dialog",tabindex:"-1"}).hide(),v=n(se,"Overlay").hide(),L=t([n(se,"LoadingOverlay")[0],n(se,"LoadingGraphic")[0]]),y=n(se,"Wrapper"),b=n(se,"Content").append(F=n(se,"Title"),R=n(se,"Current"),P=t('<button type="button"/>').attr({id:Z+"Previous"}),K=t('<button type="button"/>').attr({id:Z+"Next"}),S=n("button","Slideshow"),L),B=t('<button type="button"/>').attr({id:Z+"Close"}),y.append(n(se).append(n(se,"TopLeft"),T=n(se,"TopCenter"),n(se,"TopRight")),n(se,!1,"clear:left").append(C=n(se,"MiddleLeft"),b,H=n(se,"MiddleRight")),n(se,!1,"clear:left").append(n(se,"BottomLeft"),k=n(se,"BottomCenter"),n(se,"BottomRight"))).find("div div").css({"float":"left"}),M=n(se,!1,"position:absolute; width:9999px; visibility:hidden; display:none; max-width:none;"),O=K.add(P).add(R).add(S)),e.body&&!x.parent().length&&t(e.body).append(v,x.append(y,M))}function m(){function i(t){t.which>1||t.shiftKey||t.altKey||t.metaKey||t.ctrlKey||(t.preventDefault(),f(this))}return x?(V||(V=!0,K.click(function(){J.next()}),P.click(function(){J.prev()}),B.click(function(){J.close()}),v.click(function(){_.get("overlayClose")&&J.close()}),t(e).bind("keydown."+Z,function(t){var e=t.keyCode;$&&_.get("escKey")&&27===e&&(t.preventDefault(),J.close()),$&&_.get("arrowKey")&&W[1]&&!t.altKey&&(37===e?(t.preventDefault(),P.click()):39===e&&(t.preventDefault(),K.click()))}),t.isFunction(t.fn.on)?t(e).on("click."+Z,"."+te,i):t("."+te).live("click."+Z,i)),!0):!1}function w(){var e,o,r,h=J.prep,d=++le;if(q=!0,U=!1,u(he),u(ie),_.get("onLoad"),_.h=_.get("height")?a(_.get("height"),"y")-N-j:_.get("innerHeight")&&a(_.get("innerHeight"),"y"),_.w=_.get("width")?a(_.get("width"),"x")-z-D:_.get("innerWidth")&&a(_.get("innerWidth"),"x"),_.mw=_.w,_.mh=_.h,_.get("maxWidth")&&(_.mw=a(_.get("maxWidth"),"x")-z-D,_.mw=_.w&&_.w<_.mw?_.w:_.mw),_.get("maxHeight")&&(_.mh=a(_.get("maxHeight"),"y")-N-j,_.mh=_.h&&_.h<_.mh?_.h:_.mh),e=_.get("href"),Q=setTimeout(function(){L.show()},100),_.get("inline")){var c=t(e);r=t("<div>").hide().insertBefore(c),ae.one(he,function(){r.replaceWith(c)}),h(c)}else _.get("iframe")?h(" "):_.get("html")?h(_.get("html")):s(_,e)?(e=l(_,e),U=_.get("createImg"),t(U).addClass(Z+"Photo").bind("error."+Z,function(){h(n(se,"Error").html(_.get("imgError")))}).one("load",function(){d===le&&setTimeout(function(){var e;_.get("retinaImage")&&i.devicePixelRatio>1&&(U.height=U.height/i.devicePixelRatio,U.width=U.width/i.devicePixelRatio),_.get("scalePhotos")&&(o=function(){U.height-Line truncated
+131
View File
@@ -0,0 +1,131 @@
jQuery(function($)
{
// Default time delay before checking location
var call_back_delay = 500;
// Set some flags for tracking & execution
var scroll_timer = null;
var scroller = false; // indicate the user has begun scrolling
scroll_points_in_page = jQuery(".scroll_point");
video_screenshots = jQuery(".video-screenshot");
video_parents = jQuery(".video-parent");
var apply_video_click = function (video) {
$(video).click(function (e) {
var v = $(this)[0];
if(v.paused) {
v.play();
$(v).attr("data-paused", "false");
}
else {
v.pause();
$(v).attr("data-paused", "true");
}
});
}
for(var i = 0; i < video_parents.length; i++) {
let video_with_event = $(video_parents[i]).children("video")[0];
let play_icon = $($(video_parents[i]).children(".play-pause")[0]).children("i");
let parent = $(video_parents[i]);
parent.addClass("playing");
video_with_event.onpause = function () {
parent.addClass("paused");
parent.removeClass("playing");
play_icon.addClass("fa-play");
play_icon.removeClass("fa-pause");
}
video_with_event.onplay = function () {
parent.removeClass("paused");
parent.addClass("playing");
play_icon.removeClass("fa-play");
play_icon.addClass("fa-pause");
}
}
for(var i = 0; i < video_screenshots.length; i++) {
apply_video_click(video_screenshots[i]);
}
$(".play-pause").click(function (e) { $(this).next("video").click();});
// Check the location and track user
function track_location()
{
view_bottom = $(window).height() + $(window).scrollTop();// the bottom of the visible area
// loop through the scroll points and send events for any within the visible part of the page
for(var i = 0; i < scroll_points_in_page.length; i++)
{
check_scroll_point(scroll_points_in_page[i]);
}
for(var i = 0; i < video_screenshots.length; i++)
{
video_screenshots_play_pause(video_screenshots[i]);
}
}
var check_scroll_point = function(current_value)
{
var current_object = $(current_value);
if (view_bottom >= current_object.offset().top && $(window).scrollTop() <= current_object.offset().top && (current_object.attr("hit") === undefined || current_object.attr("hit") === null))
{
if (!debug_mode)
{
ga('send', 'event', current_object.attr("data-eventCategory") + "_scroll_points", "in_view", current_object.attr("data-eventLabel"));
}
else
{
console.log(current_object.attr("data-eventCategory") + "_scroll_points, " + current_object.attr("data-eventLabel"));
}
$("[data-eventLabel='" + current_object.attr("data-eventLabel")+ "']").attr("hit", "1"); // after the point has been hit don't count it again
}
}
var video_screenshots_play_pause = function(current_value)
{
var current_object = $(current_value);
var video_middle = current_object.offset().top + (current_object.height() / 2);
var video_on_screen = view_bottom > video_middle && video_middle > $(window).scrollTop();
if (
window.innerWidth > 600
&& video_on_screen
&& current_value.paused
&& current_object.attr("data-paused") === "false"
)
{
current_value.play();
}
else if (!current_value.paused && !video_on_screen) {
current_value.pause();
}
}
// Track the scrolling and track location
$(".page").scroll(function()
{
if (scroll_timer) {
clearTimeout(scroll_timer);
}
// Use a buffer so we don't call track_location too often.
scroll_timer = setTimeout(track_location, call_back_delay);
});
});
+1
View File
@@ -0,0 +1 @@
<a class="zola-anchor" href="#{{ id }}" aria-label="Anchor link for: {{ id }}"><i class="fas fa-link"></i></a>
+23 -13
View File
@@ -5,9 +5,12 @@
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600" rel="stylesheet" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1">
<meta name="keywords" content="Futures, Options, Derivatives, Trade, Trading, Professional, options trading, futures trading, Software" />
<meta name="rights" content="© Blue Trading Systems" />
<meta name="description" content="Documentation for Blue Trading Systems' BTS Spark" />
<!-- <meta name="keywords" content="Futures, Derivatives, Trade, Trading, Trader, Professional, futures trading, Software" />
<meta name="description" content="Documentation for Blue Trading Systems' BTS Spark" /> -->
{% block meta %}
{% endblock meta %}
<title>{% block title %}{{ config.title }}{% endblock title %}</title>
@@ -36,12 +39,8 @@
}
</script>
<script src="https://kit.fontawesome.com/93aca32d82.js"></script>
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script type="text/javascript" src="{{ get_url(path="image.js") | safe }}"></script>
{% block js %}
{% endblock js %}
@@ -60,6 +59,9 @@
{% block before_menu %}
{% endblock before_menu %}
<nav role="navigation">
<div class="menu-icon menu-close-icon">
<i class="fas fa-arrow-left"></i>
</div>
<ul>
{% block menu %}
{% set index = get_section(path="_index.md") %}
@@ -116,14 +118,14 @@
<div class="page">
<div class="page__header">
<div class="menu-icon">
<span></span>
<span></span>
<span></span>
</div>
<span class="menu-icon">
<i class="fas fa-bars"></i>
</span>
{% if config.build_search_index %}
<span class="search-icon">🔎</span>
<span class="search-icon"><i class="fas fa-search"></i></span>
{% endif %}
<span class="font-icon"><i class="fas fa-font"></i></span>
</div>
<div class="page__content">
@@ -154,11 +156,19 @@
</div>
{% block js_body %}
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script type="text/javascript" src="{{ get_url(path="jquery.colorbox-min.js") | safe }}"></script>
<script type="text/javascript" src="{{ get_url(path="image.js") | safe }}"></script>
<script type="text/javascript" src="{{ get_url(path="scroll_actions.js") | safe }}"></script>
{% if config.build_search_index %}
<script type="text/javascript" src="{{ get_url(path="elasticlunr.min.js") | safe }}"></script>
<script type="text/javascript" src="{{ get_url(path="search_index.en.js") | safe }}"></script>
{% endif %}
<script type="text/javascript" src="{{ get_url(path="book.js") | safe }}"></script>
{% endblock js_body %}
</body>
+8
View File
@@ -1,5 +1,13 @@
{% extends "index.html" %}
{% block meta %}
{% if page.extra.page_description %}
<meta name="description" content="{{ page.extra.page_description }}" />
{% else %}
<meta name="description" content="{{ page.title }} Documentation for Blue Trading Systems' BTS Spark" />
{% endif %}
{% endblock meta %}
{% block title %}{{ page.title }} &bull; {{ config.title }}{% endblock title %}
{% block content %}
+8
View File
@@ -1,5 +1,13 @@
{% extends "index.html" %}
{% block meta %}
{% if section.extra.page_description %}
<meta name="description" content="{{ section.extra.page_description }}" />
{% else %}
<meta name="description" content="{{ section.title }} Documentation for Blue Trading Systems' BTS Spark" />
{% endif %}
{% endblock meta %}
{% block title %}{{ section.title }} &bull; {{ config.title }}{% endblock title %}
{% block content %}
+1
View File
@@ -0,0 +1 @@
<div style="clear:both;"></div>
+17
View File
@@ -0,0 +1,17 @@
{% if class %}
{% set cls = class %}
{% else %}
{% set cls = "" %}
{% endif %}
{% if page %}
{% set context = page %}
{% elif section %}
{% set context = section %}
{% endif %}
<img
class="icon-image {{cls}}"
src="{{name}}"
alt="{{alt}}"
/>
+34
View File
@@ -0,0 +1,34 @@
{% if pull %}
{% if pull == "left" %}
{% set float = "is-pulled-left" %}
{% elif pull == "right" %}
{% set float = "is-pulled-right" %}
{% else %}
{% set float = "is-pulled-over-there" %}
{% endif %}
{% else %}
{% set float = " " %}
{% endif %}
{% if class %}
{% set cls = class %}
{% else %}
{% set cls = "" %}
{% endif %}
{% if page %}
{% set context = page %}
{% elif section %}
{% set context = section %}
{% endif %}
{% set image_path = context.path ~ name %}
{% set meta = get_image_metadata(path = image_path) %}
{% set image_width = (meta.width * 0.666)%}
<img
class="{{cls}} {{float}}"
style="width:{{image_width}}px;"
src="{{name}}"
alt="{{alt}}"
/>
@@ -0,0 +1,52 @@
{% if pull %}
{% if pull == "left" %}
{% set float = "is-pulled-left" %}
{% elif pull == "right" %}
{% set float = "is-pulled-right" %}
{% else %}
{% set float = "is-pulled-over-there" %}
{% endif %}
{% else %}
{% set float = " " %}
{% endif %}
{% if class %}
{% set cls = class %}
{% else %}
{% set cls = "" %}
{% endif %}
{% if width %}
{% set wdth = width %}
{% else %}
{% set wdth = "100%" %}
{% endif %}
{% if page %}
{% set context = page %}
{% elif section %}
{% set context = section %}
{% endif %}
{% set image_path = context.path ~ name %}
{% set meta = get_image_metadata(path = image_path) %}
{% set image_width = (meta.width * 0.666)%}
{% set id = name | replace(from=".", to="-") %}
<a class="colorbox-inline" href="#{{id}}">
<img
class="{{cls}} {{float}}"
style="width: {{wdth}}; max-width:{{image_width}}"
src="{{name}}"
alt="{{alt}}"
/>
</a>
<div id="colorbox-inline-content" style="display: none;">
<div id="{{id}}">
<img class = "" style="width:{{image_width}}; max-width:95vw; max-height:95vh; object-fit: scale-down"
src="{{name}}"
alt="{{alt}}"
/>
</div>
</div>
@@ -0,0 +1,6 @@
<div class="video-parent">
<div class="play-pause"><i class="fas fa-play"></i></div>
<video class="video-screenshot" style="width: {{width}}; max-width:100%;" muted loop data-paused="false">
<source src="{{name}}" type="video/mp4">
</video>
</div>
@@ -0,0 +1,8 @@
<div {% if class %}class="{{class}}"{% endif %}>
<div class="embed-responsive embed-responsive-16by9 youtube-thumb">
<a class="gallery_image youtube cboxElement" href="https://www.youtube.com/embed/{{id}}" style="display:block;">
<img class="video_thumb" style="width:100%;" src="https://img.youtube.com/vi/{{id}}/0.jpg" alt="Video Thumbnail" />
<div class="youtube-overlay"><i class="fas fa-play"></i></div>
</a>
</div>
</div>
+1 -1
View File
@@ -25,7 +25,7 @@ namespace mbts.spark {
usr.UserData.AccountPermissions.AccountPermissions
.Where(kv => (kv.Value.TradingPermission != null && kv.Value.TradingPermission.HasPermissions) || kv.Value.AdminPermission.HasPermissions)
.Select(kv => ad.Accounts.Where(a => a.Id == kv.Key).TryGetSingle(out var acc) ? acc : (BTSAccountImmutable?)null)
.Where(acc => acc.HasValue)
.Where(acc => acc.HasValue && acc.Value.IsPresent)
.Select(acc => acc.Value)
.ToDictionary(acc => acc.Id, acc => acc.Name) :
new Dictionary<uint, string>(0))
@@ -81,9 +81,9 @@ namespace bts.shell {
#region QuoteParams
public static QuoteParams QuoteParams => new QuoteParams {
LeanQuantityRatio = 1,
QuoteInsideMarketDistance = QuoteInsideMarketDistance,
//QuoteInsideMarketDistance = QuoteInsideMarketDistance,
QuoteMaxOrderMove = QuoteMaxOrderMove,
QuoteMuteZone = QuoteMuteZone,
//QuoteMuteZone = QuoteMuteZone,
QuoteRepriceThrottle = QuoteRepriceThrottle,
QuoteInsideMarketDistanceWithTravel = QuoteInsideMarketDistanceWithTravel,
};
@@ -113,6 +113,7 @@ namespace bts.shell {
};
public static QuoteInsideMarketDistanceWithTravel QuoteInsideMarketDistanceWithTravel => new QuoteInsideMarketDistanceWithTravel { Priority = RulePriority };
public static QuoteInsideMarketDistance QuoteInsideMarketDistance => new QuoteInsideMarketDistance { Priority = RulePriority };
public static QuoteInsideMarketDistanceThrottle QuoteInsideMarketDistanceThrottle => new QuoteInsideMarketDistanceThrottle();
#endregion
#region PreHedge
@@ -0,0 +1,33 @@
using bts.shell;
using mbts.ui.spark;
namespace mbts.spark {
partial class SyntheticEditorVM {
class QuoteInsideMarketDistanceEnabledModel : RowModelBase<bool> {
readonly QuoteInsideMarketDistanceTowardModel _toward;
public QuoteInsideMarketDistanceEnabledModel(QuoteInsideMarketDistanceTowardModel toward) =>
_toward = toward;
public override string display_name { get; } = string.Empty;
public override string help_text { get; } = string.Empty;
public override string man_page { get; } = string.Empty;
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.InsideMarketDistance };
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.QuoteParams.QuoteInsideMarketDistanceThrottle != null;
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) {
if (value) {
if (leg_tup.leg_params.QuoteParams.QuoteInsideMarketDistanceThrottle == null) {
leg_tup.leg_params.QuoteParams.QuoteInsideMarketDistanceThrottle = SyntheticDataHelper.Defaults.QuoteInsideMarketDistanceThrottle;
_toward.apply_value(leg_tup);
}
} else {
leg_tup.leg_params.QuoteParams.QuoteInsideMarketDistanceThrottle = null;
}
_toward.set_enabled(leg_tup, value);
}
}
}
}
@@ -0,0 +1,39 @@
using bts.spark;
using mbts.ui.spark;
using System.Collections.Generic;
namespace mbts.spark {
partial class SyntheticEditorVM {
class QuoteInsideMarketDistanceTowardModel : RowModelBase<uint> {
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
public override string display_name { get; } = "Toward Distance";
public override string help_text { get; } = string.Empty;
public override string man_page { get; } = string.Empty;
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.InsideMarketDistance };
public void apply_value(LegSpreaderParamsTuple leg_tup) =>
_get_inside_market_distance(leg_tup).OnTowardDistance = _value_from_store(leg_tup);
public override uint get_value(LegSpreaderParamsTuple leg_tup) =>
_get_inside_market_distance(leg_tup) is QuoteInsideMarketDistanceThrottle inside_market_distance ?
inside_market_distance.OnTowardDistance :
_value_from_store(leg_tup);
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
_value_store[leg_tup.SecurityKey] = value;
if (_get_inside_market_distance(leg_tup) is QuoteInsideMarketDistanceThrottle inside_market_distance)
inside_market_distance.OnTowardDistance = value;
}
protected override bool _get_enabled(LegSpreaderParamsTuple leg_tup) =>
_get_inside_market_distance(leg_tup) != null;
QuoteInsideMarketDistanceThrottle _get_inside_market_distance(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.QuoteParams.QuoteInsideMarketDistanceThrottle;
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
_value_store.GetValueOrDefault(leg_tup.SecurityKey);
}
}
}
@@ -115,6 +115,11 @@ namespace mbts.spark {
"dictates if this logic applies to the quoting market or to the synthetic spread " +
"market");
public static ParameterSection InsideMarketDistance =>
new ParameterSection("Inside Market Distance", true,
"Reduces re-quoting by throttling quotes that are moving toward the inside market " +
"but are more than 'Toward Distance' away from the inside market");
public static ParameterSection InsideMarketThrottleThresholds =>
new ParameterSection("Inside Market Throttle Thresholds", true,
"Reduces re-quoting for quotes that are moving toward the inside market. " +
@@ -190,6 +190,8 @@ namespace mbts.spark {
var quote_inside_market_throttle_ticks = new QuoteInsideMarketThrottleTicksModel();
var quote_inside_market_throttle_min_move = new QuoteInsideMarketThrottleMinMoveModel();
var quote_inside_market_distance_toward = new QuoteInsideMarketDistanceTowardModel();
var pre_hedge_payless_threshold = new PreHedgePaylessThresholdModel();
var pre_hedge_payless_ticks = new PreHedgePaylessTicksModel();
@@ -227,6 +229,8 @@ namespace mbts.spark {
//quote_inside_market_throttle_ticks,
//quote_inside_market_throttle_min_move,
//new MaxTicksInsideMarketModel(),
new QuoteInsideMarketDistanceEnabledModel(quote_inside_market_distance_toward),
quote_inside_market_distance_toward,
new PreHedgePaylessEnabledModel(pre_hedge_payless_threshold, pre_hedge_payless_ticks),
pre_hedge_payless_threshold,
pre_hedge_payless_ticks,
@@ -285,7 +285,6 @@ namespace mbts.spark {
public DateTime fill_time => _fill.Timestamp.to_local_time();
public TimeInForce time_in_force => _order.time_in_force();
public OrderType order_type => _order.order_type();
//public OrderType root_algo_type => _or
public string display_name => OrderUtils.display_name(_order, fill_tuple.algo);
public OrderMetadata order_metadata => _order_metadata ?? (_order_metadata = _model.factory.get_metadata(_order.Def.CustomDef.CustomCase));
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(fill_tuple.algo_meta, order_metadata));
@@ -304,7 +303,7 @@ namespace mbts.spark {
public OrderType root_algo_order_type => _root_algo.HasValue ? _root_algo.Value.value.CustomCase : OrderType.None;
public OrderMetadata root_algo_order_metadata => _model.factory.get_metadata(root_algo_order_type);
public Color root_algo_badge_color => _root_algo_badge_color ?? (_root_algo_badge_color = OrderUtils.badge_color(_root_algo_meta, root_algo_order_metadata)).Value;
public string root_algo_display_name => _root_algo?.name ?? root_algo_order_type.display_name();
public string root_algo_display_name => fill_tuple.root_order_security?.display_name() ?? OrderType.None.display_name();
public bool Equals(Row other) => other != null && other._fill.Equals(_fill) && other._order.Equals(_order);
public override bool Equals(object obj) => Equals(obj as Row);
@@ -23,6 +23,10 @@ namespace mbts.spark {
// TODO: somehow allow windows that are using the default settings to still have individual column widths.
[ProtoContract]
class LadderColumnSettings : IEquatable<LadderColumnSettings> {
// this class is 100% mutable (there are literally 0 readonly members),
// so GetHashCode must return the same hash for every instance.
static readonly int _type_hash = typeof(LadderColumnSettings).GetHashCode();
public static LadderColumnSettings Default => new LadderColumnSettings {
show = true,
width = 50,
@@ -74,14 +78,7 @@ namespace mbts.spark {
other.show == show;
public override bool Equals(object obj) => Equals(obj as LadderColumnSettings);
public override int GetHashCode() =>
width.GetHashCode() +
background.GetHashCode() +
foreground.GetHashCode() +
font_family.GetHashCode() +
font_size.GetHashCode() +
font_weight.GetHashCode();
public override int GetHashCode() => _type_hash;
public static bool operator ==(LadderColumnSettings x, LadderColumnSettings y) => x.safe_equals(y);
public static bool operator !=(LadderColumnSettings x, LadderColumnSettings y) => !(x == y);
@@ -89,6 +86,9 @@ namespace mbts.spark {
[ProtoContract]
class HistogramBarAnimation : IEquatable<HistogramBarAnimation> {
// this class is 100% mutable (there are literally 0 readonly members),
// so GetHashCode must return the same hash for every instance.
static readonly int _type_hash = typeof(HistogramBarAnimation).GetHashCode();
[ProtoMember(1)]
public ColorShowProto bars { get; set; }
[ProtoMember(2)]
@@ -102,14 +102,14 @@ namespace mbts.spark {
show_numbers == other.show_numbers;
public override bool Equals(object obj) => Equals(obj as HistogramBarAnimation);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
bars.GetHashCode(),
show_numbers.GetHashCode());
public override int GetHashCode() => _type_hash;
}
[ProtoContract]
class VolumeColumnSettings : IEquatable<VolumeColumnSettings> {
// this class is 100% mutable (there are literally 0 readonly members),
// so GetHashCode must return the same hash for every instance.
static readonly int _type_hash = typeof(VolumeColumnSettings).GetHashCode();
public static VolumeColumnSettings Default {
get {
var gen = LadderColumnSettings.Default;
@@ -149,19 +149,14 @@ namespace mbts.spark {
show_ltq.Equals(other.show_ltq);
public override bool Equals(object obj) => Equals(obj as VolumeColumnSettings);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
general.GetHashCode(),
buy.GetHashCode(),
sell.GetHashCode(),
passive.GetHashCode(),
total.GetHashCode(),
show_ltq.GetHashCode());
public override int GetHashCode() => _type_hash;
}
[ProtoContract]
class LadderMarketBreakLine : IEquatable<LadderMarketBreakLine> {
// this class is 100% mutable (there are literally 0 readonly members),
// so GetHashCode must return the same hash for every instance.
static readonly int _type_hash = typeof(LadderMarketBreakLine).GetHashCode();
[ProtoMember(1)]
public bool show { get; set; }
[ProtoMember(2)]
@@ -184,8 +179,7 @@ namespace mbts.spark {
Equals(obj as LadderMarketBreakLine);
public override int GetHashCode() =>
show.GetHashCode() +
(int)color;
_type_hash;
}
[ProtoContract]
@@ -255,6 +249,11 @@ namespace mbts.spark {
const double _default_order_entry_panel_width = 110.0;
const double _default_zoom_factor = 1.0;
const bool _default_show_warn_on_cross = true;
public static readonly IEqualityComparer<LadderSettings> profile_equality_comparer =
EqualityComparer.Create<LadderSettings>(profile_equals);
// this class is 100% mutable (there are literally 0 readonly members),
// so GetHashCode must return the same hash for every instance.
static readonly int _type_hash = typeof(LadderSettings).GetHashCode();
// Default proto constructor
// Added this so that when clients upgrade to a version with new settings,
@@ -497,6 +496,9 @@ namespace mbts.spark {
[ProtoMember(55)]
public LadderMiniOrderBookSettings mini_ob { get; set; }
[ProtoMember(57)]
public double left_panel_scroll_position { get; set; }
public void sanitize() =>
sanitize(this);
@@ -533,112 +535,81 @@ namespace mbts.spark {
return s;
}
/// <summary>
/// Determines whether or not the specified LadderSettings are 'profile equivalent.'
/// This means that 2 ladders, each with a different one of the specified settings, could share the same profile.
/// </summary>
///
/// Note: the impetus for this version of equality is to avoid disconnecting a ladder from its profile due to 'trivial' settings changes.
/// Consider the '<see cref="default_algo"/>' property:
/// If a ladder is using a settings profile with Limit as the default algo, and the user decides to send a 'Market' order,
/// the ladder would suddenly be disconnected from the profile, because it's settings state would no longer be equal to the profile's settings,
/// since they changed the selected algo type to 'Market.'
/// To avoid such travesties, a separate equals logic is needed: this is called 'profile-equivalent' and is implemented in <see cref="profile_equals)"/>.
/// Devs: be sure to account for properties that are excluded in the 'profile-equals' logic in the [regular]'equals' logic.
public static bool profile_equals(LadderSettings a, LadderSettings b) =>
a == null ? b == null :
b != null &&
a.row_height == b.row_height &&
a.grid_line_color == b.grid_line_color &&
a.orders_column.safe_equals(b.orders_column) &&
a.bid_column.safe_equals(b.bid_column) &&
a.ask_column.safe_equals(b.ask_column) &&
a.price_column.safe_equals(b.price_column) &&
a.last_column.safe_equals(b.last_column) &&
a.piq_column.safe_equals(b.piq_column) &&
a.calculator_buttons.safe_equals(b.calculator_buttons, Enumerable.SequenceEqual) &&
a.scroll_bar_visible == b.scroll_bar_visible &&
a.default_account == b.default_account &&
a.default_time_in_force == b.default_time_in_force &&
a.size == b.size &&
a.animate_recenter == b.animate_recenter &&
a.badge_style == b.badge_style &&
a.auto_clear == b.auto_clear &&
a.bid_break_line.safe_equals(b.bid_break_line) &&
a.ask_break_line.safe_equals(b.ask_break_line) &&
a.mid_break_line.safe_equals(b.mid_break_line) &&
a.alternate_order_size == b.alternate_order_size &&
a.session_low_line.safe_equals(b.session_low_line) &&
a.session_high_line.safe_equals(b.session_high_line) &&
a.session_range_fill.safe_equals(b.session_range_fill) &&
a.volume_column.safe_equals(b.volume_column) &&
a.size_display.safe_equals(b.size_display) &&
a.day_visible == b.day_visible &&
a.high_visible == b.high_visible &&
a.low_visible == b.low_visible &&
a.open_visible == b.open_visible &&
a.settle_visible == b.settle_visible &&
a.volume_visible == b.volume_visible &&
a.account_visible == b.account_visible &&
a.tif_visible == b.tif_visible &&
a.order_type_visible == b.order_type_visible &&
a.account_position_visible == b.account_position_visible &&
a.day_position_visible == b.day_position_visible &&
a.raw_day_position_visible == b.raw_day_position_visible &&
a.size_presets_visible == b.size_presets_visible &&
a.cancel_buys_visible == b.cancel_buys_visible &&
a.cancel_sells_visible == b.cancel_sells_visible &&
a.cancel_all_visible == b.cancel_all_visible &&
a.auto_clear_visible == b.auto_clear_visible &&
a.clear_visible == b.clear_visible &&
a.order_entry_panel_initial_width == b.order_entry_panel_initial_width &&
a.zoom_slider_visible == b.zoom_slider_visible &&
a.zoom_factor == b.zoom_factor &&
a.use_defaults == b.use_defaults &&
a.favorite_algos == b.favorite_algos &&
a.net_change == b.net_change &&
a.show_warn_on_cross == b.show_warn_on_cross &&
a.mini_ob == b.mini_ob;
public bool Equals(LadderSettings other) =>
other != null &&
row_height == other.row_height &&
grid_line_color == other.grid_line_color &&
orders_column.safe_equals(other.orders_column) &&
bid_column.safe_equals(other.bid_column) &&
ask_column.safe_equals(other.ask_column) &&
price_column.safe_equals(other.price_column) &&
last_column.safe_equals(other.last_column) &&
piq_column.safe_equals(other.piq_column) &&
calculator_buttons.safe_equals(other.calculator_buttons, (a, b) => a.SequenceEqual(b)) &&
scroll_bar_visible == other.scroll_bar_visible &&
default_account == other.default_account &&
profile_equals(this, other) &&
default_algo == other.default_algo &&
default_time_in_force == other.default_time_in_force &&
size == other.size &&
animate_recenter == other.animate_recenter &&
badge_style == other.badge_style &&
auto_clear == other.auto_clear &&
bid_break_line.safe_equals(other.bid_break_line) &&
ask_break_line.safe_equals(other.ask_break_line) &&
mid_break_line.safe_equals(other.mid_break_line) &&
alternate_order_size == other.alternate_order_size &&
session_low_line.safe_equals(other.session_low_line) &&
session_high_line.safe_equals(other.session_high_line) &&
session_range_fill.safe_equals(other.session_range_fill) &&
volume_column.safe_equals(other.volume_column) &&
size_display.safe_equals(other.size_display) &&
day_visible == other.day_visible &&
high_visible == other.high_visible &&
low_visible == other.low_visible &&
open_visible == other.open_visible &&
settle_visible == other.settle_visible &&
volume_visible == other.volume_visible &&
account_visible == other.account_visible &&
tif_visible == other.tif_visible &&
order_type_visible == other.order_type_visible &&
account_position_visible == other.account_position_visible &&
day_position_visible == other.day_position_visible &&
raw_day_position_visible == other.raw_day_position_visible &&
size_presets_visible == other.size_presets_visible &&
cancel_buys_visible == other.cancel_buys_visible &&
cancel_sells_visible == other.cancel_sells_visible &&
cancel_all_visible == other.cancel_all_visible &&
auto_clear_visible == other.auto_clear_visible &&
clear_visible == other.clear_visible &&
order_entry_panel_initial_width == other.order_entry_panel_initial_width &&
zoom_slider_visible == other.zoom_slider_visible &&
zoom_factor == other.zoom_factor &&
use_defaults == other.use_defaults &&
favorite_algos == other.favorite_algos &&
net_change == other.net_change &&
show_warn_on_cross == other.show_warn_on_cross &&
mini_ob == other.mini_ob;
left_panel_scroll_position == other.left_panel_scroll_position;
public override bool Equals(object obj) => Equals(obj as LadderSettings);
// TODO: remove mutable props from hash logic (or conversely, make them immutable).
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
row_height.GetHashCode(),
grid_line_color.GetHashCode(),
EqualityComparer.safe_get_hash_code(orders_column),
EqualityComparer.safe_get_hash_code(bid_column),
EqualityComparer.safe_get_hash_code(ask_column),
EqualityComparer.safe_get_hash_code(price_column),
EqualityComparer.safe_get_hash_code(last_column),
EqualityComparer.safe_get_hash_code(piq_column),
EqualityComparer.safe_get_hash_code(calculator_buttons),
scroll_bar_visible.GetHashCode(),
default_time_in_force.GetHashCode(),
size.GetHashCode(),
animate_recenter.GetHashCode(),
badge_style.GetHashCode(),
EqualityComparer.safe_get_hash_code(bid_break_line),
EqualityComparer.safe_get_hash_code(ask_break_line),
EqualityComparer.safe_get_hash_code(mid_break_line),
alternate_order_size.GetHashCode(),
EqualityComparer.safe_get_hash_code(session_low_line),
EqualityComparer.safe_get_hash_code(session_high_line),
EqualityComparer.safe_get_hash_code(session_range_fill),
EqualityComparer.safe_get_hash_code(volume_column),
EqualityComparer.safe_get_hash_code(size_display),
day_visible.GetHashCode(),
high_visible.GetHashCode(),
low_visible.GetHashCode(),
open_visible.GetHashCode(),
settle_visible.GetHashCode(),
volume_visible.GetHashCode(),
account_visible.GetHashCode(),
tif_visible.GetHashCode(),
order_type_visible.GetHashCode(),
account_position_visible.GetHashCode(),
size_presets_visible.GetHashCode(),
cancel_buys_visible.GetHashCode(),
cancel_sells_visible.GetHashCode(),
cancel_all_visible.GetHashCode(),
auto_clear_visible.GetHashCode(),
clear_visible.GetHashCode(),
order_entry_panel_initial_width.GetHashCode(),
zoom_slider_visible.GetHashCode(),
zoom_factor.GetHashCode(),
use_defaults.GetHashCode(),
favorite_algos.GetHashCode(),
net_change.GetHashCode(),
show_warn_on_cross.GetHashCode());
public override int GetHashCode() => _type_hash;
public struct WindowSpecifics {
public readonly double order_entry_panel_width, zoom_factor;
@@ -81,11 +81,9 @@ namespace bts.shell {
if (!_settings_hub.current.ladder_profiles.TryGetFirst(lp => lp.id == id, out var result)) {
// profile was deleted
id = 0;
} else {
if (!s.Equals(result.value)) {
// ladder's settings diverged from the profile (this happens when the user changes settings outside of the settings dialog, such as column widths, left panel width, etc.)
id = 0;
}
} else if (!LadderSettings.profile_equals(s, result.value)) {
// ladder's settings diverged from the profile (this happens when the user changes settings outside of the settings dialog, such as column widths, left panel width, etc.)
id = 0;
}
}
return new LadderSaveState { security_key = vm.security.Id, settings = s, settings_id = id };
@@ -149,7 +147,7 @@ namespace bts.shell {
var curr = vm.get_save_state();
if (settings_id == 0) {
last_value = curr;
} else if (!curr.Equals(last_value)) {
} else if (!LadderSettings.profile_equals(curr, last_value)) {
settings_id = 0;
last_value = curr;
}
@@ -163,7 +161,7 @@ namespace bts.shell {
// not on a profile, don't respond to default settings changes
return;
} else if (us.ladder_profiles.TryGetFirst(p => p.id == settings_id, out var profile)) {
if (!last_value.Equals(vm.get_save_state())) {
if (!LadderSettings.profile_equals(last_value, vm.get_save_state())) {
settings_id = 0;
} else {
settings_subject.OnNext(last_value = profile.value);
@@ -211,6 +209,7 @@ namespace bts.shell {
_ => _,
vm.get_save_state(),
UserSettings.FileExtensions.LADDER,
LadderSettings.profile_equals,
settings_id);
dialog_vm.applied += apply;
// dialog
@@ -35,7 +35,14 @@ namespace mbts.spark {
_show_cancel_sells, _show_cancel_all, _show_zoom;
readonly PropertyAutoWrapper<LadderSettings, Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
readonly HashSet<AlgoKey> _sticky_algos = new HashSet<AlgoKey>();
double _position, _day_position, _raw_day_position;
double
_position,
_day_position,
_raw_day_position,
_left_panel_extent_height,
_left_panel_view_port_height,
_left_panel_scroll_position,
_left_panel_scroll_position_target;
bool _show_account_position, _show_day_position, _show_raw_day_position;
ulong _misc_time = 0;
ulong _last_trade_time = 0;
@@ -76,6 +83,7 @@ namespace mbts.spark {
_volume_column_width = backing(nameof(volume_column_width), ws.volume_column_width);
_left_panel_width = backing(nameof(left_panel_width), ws.order_entry_panel_width);
_zoom_factor = backing(nameof(zoom_factor), ws.zoom_factor);
_left_panel_scroll_position_target = init.left_panel_scroll_position;
// remember: account_positions needs to subscribe to selected_account; both the sub and account_positions need to be disposed
_volume = backing(nameof(volume), 0ul);
_auto_clear = backing(nameof(auto_clear), init.auto_clear);
@@ -333,6 +341,33 @@ namespace mbts.spark {
public bool show_cancel_sells => _show_cancel_sells.view;
public bool show_cancel_all => _show_cancel_all.view;
public bool show_zoom => _show_zoom.view;
// left panel scroll props
public double left_panel_view_port_height {
get => _left_panel_view_port_height;
set {
using (detect_property_changes(nameof(left_panel_scroll_position)))
_left_panel_view_port_height = value;
}
}
public double left_panel_scroll_position {
get {
if (!_left_panel_scroll_position_target.is_nan()) {
if (_left_panel_scroll_position_target < _max_left_panel_scroll_position) {
_left_panel_scroll_position = _left_panel_scroll_position_target;
_left_panel_scroll_position_target = double.NaN;
}
}
return Math.Min(_left_panel_scroll_position, _left_panel_extent_height);
}
set => Set(ref _left_panel_scroll_position, value);
}
public double left_panel_extent_height {
get => _left_panel_extent_height;
set {
using (detect_property_changes(nameof(left_panel_scroll_position)))
Set(ref _left_panel_extent_height, value);
}
}
public double left_panel_width { get => _left_panel_width.Value; set => _left_panel_width.set(value); }
public double zoom_factor { get => _zoom_factor.Value; set => _zoom_factor.set(value); }
public string position_display_text {
@@ -364,6 +399,10 @@ namespace mbts.spark {
public bool show_warn_on_cross_from_settings => current_settings.show_warn_on_cross;
internal LadderModel model => _model;
internal IObservable<LadderSettings> settings { get; }
double _max_left_panel_scroll_position =>
Math.Max(0, _left_panel_extent_height - _left_panel_view_port_height);
internal OrderRecordImmutable get_root_at_price((OrderRecordImmutable order, uint? _, AlgoData? __, AlgoDefinitionMetadata? meta) op) {
var order = op.order;
double p = order.price();
@@ -394,6 +433,7 @@ namespace mbts.spark {
zoom_factor,
piq_column_width));
rv.auto_clear = auto_clear;
rv.left_panel_scroll_position = left_panel_scroll_position;
if (order_properties_vm != null) {
rv.default_time_in_force = order_properties_vm.selected_time_in_force;
if (order_properties_vm.selected_account.HasValue)
@@ -301,7 +301,14 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0" Grid.RowSpan="2" VerticalScrollBarVisibility="Hidden">
<ScrollViewer
x:Name="_left_panel_scroll"
Grid.Row="0"
Grid.RowSpan="2"
mbts:ScrollViewerBehavior.VerticalOffset="{Binding left_panel_scroll_position, Mode=TwoWay}"
mbts:ScrollViewerBehavior.EnableTwoWayVerticalOffsetBinding="True"
VerticalScrollBarVisibility="Hidden"
>
<Grid RowSpan="2" Background="{mbts:BTSColor UI.spark_menu_background}">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
@@ -6,10 +6,12 @@ using mbts.ui.converters;
using mbts.ui.extensions;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
@@ -85,6 +87,7 @@ namespace mbts.spark {
_ladder.visible_column_types.Select(t => ColumnFactory.create(_ladder, t)));
this.vm = vm;
this.ConnectDataContext<LadderVM>(lvm => lvm.bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(sec_map => Resources[ResourceKeys.Securities] = sec_map));
vm.SubscribeDisposed(_connect_left_panel_scroll());
DataContext = vm;
DataContextChanged += datacontext_changed;
_ui_scaler.MouseDoubleClick += _ui_scaler_MouseDoubleClick;
@@ -92,7 +95,39 @@ namespace mbts.spark {
this.PreviewMouseDown += LadderView_PreviewMouseDown;
}
IDisposable _connect_left_panel_scroll() {
// extent height
bool busy = false;
var extent_height_hook = _left_panel_scroll.ObserveProperty(ScrollViewer.ExtentHeightProperty, (s, e) => {
if (!busy) {
double scroll_pos = vm.left_panel_scroll_position;
busy = true;
Dispatcher.InvokeAsync(() => {
vm.left_panel_extent_height = _left_panel_scroll.ExtentHeight;
if (_left_panel_scroll.VerticalOffset != scroll_pos)
_left_panel_scroll.TryScrollToVerticalOffset(scroll_pos);
busy = false;
}, System.Windows.Threading.DispatcherPriority.ApplicationIdle);
}
});
// view port height
_left_panel_scroll.Loaded += on_loaded;
var dispose = Disposable.CreateDisposeOnce(dispose_impl);
return Disposable.Create(dispose, vm.SubscribeDisposed(dispose));
// local methods
void on_loaded(object sender, RoutedEventArgs e) {
if (_left_panel_scroll.descendants_of_type<ScrollBar>().TryGetFirst(sb => sb.Orientation == Orientation.Vertical, out var vscroll)) {
vscroll.SetBinding(ScrollBar.ViewportSizeProperty, new Binding(nameof(LadderVM.left_panel_view_port_height)) {
Converter = ValueConverter.create<double, Size>(d => new Size(0, d), sz => sz.Height),
Mode = BindingMode.OneWayToSource
});
}
}
void dispose_impl() {
Disposable.dispose(ref extent_height_hook);
_left_panel_scroll.Loaded -= on_loaded;
}
}
// (Left|Right) CTRL + Wheel to zoom
// This is neat b/c the user doesn't even need to have the slider visible to zoom
@@ -108,7 +108,7 @@ namespace mbts.spark {
public CustomOrderDefinition.CustomOneofCase root_algo_order_type => order_tuple.root.algo.value.CustomCase;
public OrderMetadata root_algo_order_metadata => _owner.get_metadata(root_algo_order_type);
public Brush root_algo_badge_fill => _root_algo_badge_fill ?? (_root_algo_badge_fill = new SolidColorBrush(_root_algo_badge_color));
public string root_algo_display_name => order_tuple.root.algo.name;
public string root_algo_display_name => order_tuple.root.display_name();
// Tree
public bool is_parent { get => _is_parent; set => Set(ref _is_parent, value); }
+3 -1
View File
@@ -36,6 +36,8 @@ namespace mbts.spark {
public AlgoDefinitionMetadata? root_algo_meta =>
_order_tuple.HasValue ? _order_tuple.Value.root.meta : null;
public OrderSecurityAlgo? root_order_security => _order_tuple?.root;
public string account {
get {
var account_string = string.Empty;
@@ -85,4 +87,4 @@ namespace mbts.spark {
public static string user(this FillTuple fill_tuple) =>
fill_tuple.order.HasValue ? fill_tuple.order.Value.user() : string.Empty;
}
}
}
+4 -2
View File
@@ -158,7 +158,9 @@ namespace mbts.spark {
public static string display_name(this OrderSecurity order_tuple) =>
display_name(order_tuple.order, order_tuple.algo);
public static string display_name(OrderRecordImmutable order, AlgoData? algo) {
public static string display_name(OrderRecordImmutable order, AlgoData? algo, SecurityImmutable? security = null) {
if (order.order_type() == CustomOrderDefinition.CustomOneofCase.Spreader && security.HasValue)
return security.Value.ToString();
if (algo.HasValue) {
return algo.Value.name;
} else if (order.HasDef && order.Def.HasCustomDef) {
@@ -250,7 +252,7 @@ namespace mbts.spark {
order_type.ToString().ToUpper();
public static string display_name(this OrderSecurityAlgo order_sec_algo) =>
display_name(order_sec_algo.order, order_sec_algo.algo);
display_name(order_sec_algo.order, order_sec_algo.algo, order_sec_algo.security);
public static string display_name(this CustomOrderDefinitionImmutable algo) =>
algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic ? algo.Dynamic.DynamicOrderType : algo.CustomCase.display_name();
+1
View File
@@ -210,6 +210,7 @@ namespace mbts.spark {
// manual fills
on_manual_fills_securities = _position_service.subscribei_manual_fills()
.Naggle(interval, scheduler)
.Filter(f => f.FillType == ManualFill.Types.FillType.Local)
.join(sstore, mf => mf.SecurityKey)
.Table().Cache();
var manual_fill_tuples_account_join = on_manual_fills_securities.LeftJoin(
@@ -0,0 +1,80 @@
using bts.api;
using bts.api.orderman;
using bts.shell;
using bts.utils;
using mbts.spark;
using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace mbts {
static class OrderSentEventPredicateVMFactory {
public static Dictionary<OrderSentEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderSentEventPredicate>> get_oneofs(IBus bus) =>
new Dictionary<OrderSentEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderSentEventPredicate>> {
{ OrderSentEventPredicate.PredicateTypeOneofCase.AnyOrder, PredicateVMWrapper.create<OrderSentEventPredicate>(p => p.security = null) },
{ OrderSentEventPredicate.PredicateTypeOneofCase.SecurityOrder, SecurityCriteriaVMFactory.create<OrderSentEventPredicate>(bus, o => o.security?.is_value ?? new SecurityCriteria(),
(p, s) => {
if (p.security == null) {
p.security = new SecurityPredicate();
}
p.security.is_value = s;
})
}
};
}
[ProtoContract]
class OrderSentEventPredicate : IEquatable<OrderSentEventPredicate> {
static readonly int _type_hash = typeof(OrderSentEventPredicate).GetHashCode();
// arbitrary short time period (can override in config)
static readonly TimeSpan _stale_order_threshold = TimeSpan.FromSeconds(config.Configuration.get<bts.config.OrderAlerts>().stale_order_threshold_seconds);
public enum PredicateTypeOneofCase {
[Description("(any order)")]
AnyOrder,
[Description("choose securities")]
SecurityOrder
}
[ProtoMember(1)]
public SecurityPredicate security { get; set; }
public PredicateTypeOneofCase predicate_type_case {
get {
if (security != null)
return PredicateTypeOneofCase.SecurityOrder;
return PredicateTypeOneofCase.AnyOrder;
}
}
public IPredicate<OrderSecurity> create(object obj) =>
create_predicate((INotificationServiceDataCache)obj);
public IPredicate<OrderSecurity> create_predicate(INotificationServiceDataCache data_cache) {
var sec_pred = security?.create();
return PredicateBuilder.create<OrderSecurity>(os =>
// verify security
(sec_pred == null || (os.security.HasValue && sec_pred.trigger(os.security.Value))) &&
// verify not stale
((DateTime.Now - os.order.created()) < _stale_order_threshold) &&
// verify order hasn't been added yet
// Note: the notification service should offer a way to connect predicates so that they only receive items in TableUpdate.added,
// however, the notification service predated TableUpdate.Changes, so for now, just check that the history.Count == 1)
(data_cache.order_history.TryGetValue(os.order.Key, out var hist) && hist.Count == 1));
}
public INotificationPredicate<OrderSecurity> create_toast(INotificationServiceDataCache data_cache) =>
throw new NotImplementedException();
public bool Equals(OrderSentEventPredicate other) =>
other != null && other.security.safe_equals(other.security);
public override bool Equals(object obj) =>
Equals(obj as OrderSentEventPredicate);
public override int GetHashCode() =>
EqualityComparer.auto_phase(_type_hash, security?.GetHashCode() ?? 0);
}
}
@@ -5,14 +5,9 @@ using bts.utils;
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.utility;
using mbts.utils;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
using PTOC = mbts.OrderRecordTuplePredicate.PredicateTypeOneofCase;
namespace mbts {
@@ -20,6 +15,7 @@ namespace mbts {
// TODO: uncomment the remaining cases when ready
public static Dictionary<PTOC, ITemplatedPropertyWrapperVM<OrderRecordTuplePredicate>> get_oneofs(IBus bus, OrdersModel orders_model) {
return new Dictionary<PTOC, ITemplatedPropertyWrapperVM<OrderRecordTuplePredicate>> {
{ PTOC.SentEvent, wrap(rp => rp.sent_event, (rp, p) => rp.sent_event = p, p => p.predicate_type_case, OrderSentEventPredicateVMFactory.get_oneofs(bus)) },
{ PTOC.FilledEvent, wrap(rp => rp.filled_event, (rp, p) => rp.filled_event = p, p => p.predicate_type_case, OrderFilledEventPredicateVMFactory.get_oneofs(bus)) },
{ PTOC.CompletedEvent, wrap(rp => rp.completed_event, (rp, p) => rp.completed_event = p, p => p.predicate_type_case, OrderCompletedEventPredicateVMFactory.get_oneofs(bus)) },
{ PTOC.CanceledEvent, wrap(rp => rp.canceled_event, (rp, p) => rp.canceled_event = p, p => p.predicate_type_case, OrderCanceledEventPredicateVMFactory.get_oneofs(bus)) },
@@ -77,6 +73,7 @@ namespace mbts {
OrderFilledEventPredicate _filled_event;
OrderCompletedEventPredicate _completed_event;
OrderCanceledEventPredicate _canceled_event;
OrderSentEventPredicate _sent_event;
public enum PredicateTypeOneofCase {
None,
@@ -120,6 +117,8 @@ namespace mbts {
OrderKey,
[PredicateCase(is_toastable:false)]
ParentKey,
[PredicateCase("When order is sent", PredicateCategory.Event)]
SentEvent,
[PredicateCase("When order gets a fill", PredicateCategory.Event)]
FilledEvent,
[PredicateCase("When order is completed", PredicateCategory.Event)]
@@ -176,6 +175,8 @@ namespace mbts {
public OrderCompletedEventPredicate completed_event { get => _completed_event; set => _set(ref _completed_event, value); }
[ProtoMember(23)]
public OrderCanceledEventPredicate canceled_event { get => _canceled_event; set => _set(ref _canceled_event, value); }
[ProtoMember(26)]
public OrderSentEventPredicate sent_event { get => _sent_event; set => _set(ref _sent_event, value); }
[ProtoMember(24)]
public bool is_toast_enabled { get; set; }
[ProtoMember(25)]
@@ -206,6 +207,7 @@ namespace mbts {
if (_filled_event != null) return PTOC.FilledEvent;
if (_completed_event != null) return PTOC.CompletedEvent;
if (_canceled_event != null) return PTOC.CanceledEvent;
if (_sent_event != null) return PTOC.SentEvent;
if (_attached_target_order_key != null) return PTOC.AttachedTargetOrderKey;
return PTOC.None;
}
@@ -236,6 +238,7 @@ namespace mbts {
case PTOC.FilledEvent: return _filled_event.create_predicate(data_cache);
case PTOC.CompletedEvent: return _completed_event.create_predicate(data_cache);
case PTOC.CanceledEvent: return _canceled_event.create(data_cache);
case PTOC.SentEvent: return _sent_event.create(data_cache);
case PTOC.AttachedTargetOrderKey: return wrap(_attached_target_order_key.create(), r => r.order.attached_target_order_key());
default: return null;
}
@@ -285,6 +288,7 @@ namespace mbts {
case PTOC.FilledEvent: return _filled_event.Equals(other._filled_event);
case PTOC.CompletedEvent: return _completed_event.Equals(other._completed_event);
case PTOC.CanceledEvent: return _canceled_event.Equals(other._canceled_event);
case PTOC.SentEvent: return _sent_event.Equals(other._sent_event);
case PTOC.AttachedTargetOrderKey: return _attached_target_order_key.Equals(other._attached_target_order_key);
case PTOC.None: return true;
default: throw ExceptionHelper.unexpected_oneof_case(ptoc);
@@ -320,6 +324,7 @@ namespace mbts {
case PTOC.FilledEvent: raw_hash = _filled_event.GetHashCode(); break;
case PTOC.CompletedEvent: raw_hash = _completed_event.GetHashCode(); break;
case PTOC.CanceledEvent: raw_hash = _canceled_event.GetHashCode(); break;
case PTOC.SentEvent: raw_hash = _sent_event.GetHashCode(); break;
case PTOC.AttachedTargetOrderKey: raw_hash = _attached_target_order_key.GetHashCode(); break;
case PTOC.None: raw_hash = 0; break;
default: throw ExceptionHelper.unexpected_oneof_case(ptoc);
@@ -355,6 +360,7 @@ namespace mbts {
_filled_event = null;
_completed_event = null;
_canceled_event = null;
_sent_event = null;
_attached_target_order_key = null;
}
field = value;
@@ -1,7 +1,36 @@
using System.Linq;
using System;
using System.Linq;
namespace mbts {
public static class DoubleHelper {
public static bool any_nan_or_infinity(params double[] values) {
for (int i = 0; i < values.Length; i++) {
if (double.IsNaN(values[i]) || double.IsInfinity(values[i]))
return true;
}
return false;
}
public static bool are_close(double v1, double v2, double threshold) =>
!any_nan_or_infinity(v1, v2, threshold) && Math.Abs(threshold) > Math.Abs(v1 - v2);
// bc double.IsNaN gets annoying write, and a lazy programmer is a happy programmer ;)
public static bool is_nan(this double value) =>
double.IsNaN(value);
/// <summary>
/// If the specified value is a <see cref="double"/>, <see cref="int"/>, or <see cref="uint"/>, returns the equivalent <see cref="double"/>; otherwise, returns <see cref="double.NaN"/>.
/// </summary>
/// <param name="value">The value to convert to a <see cref="double"/>.</param>
/// <returns>The <see cref="double"/> equivalent value if the cast succeeds, otherwise <see cref="double.NaN"/>.</returns>
public static double nan_cast(object value) {
// devs: add numeric type checks as needed
if (value is double d) return d;
if (value is int i) return i;
if (value is uint u) return u;
return double.NaN;
}
public static bool IsNaNOrInfinity(double d) => double.IsNaN(d) || double.IsInfinity(d);
public static bool IsNaNOrInfinityOr(double d, params double[] values) => IsNaNOrInfinity(d) || values.Contains(d);
public static bool IsOneOf(double d, params double[] values) => values.Contains(d);
@@ -4,6 +4,8 @@ using System.Linq;
namespace mbts.ui.controls {
static class DoubleUtil {
public const int API_DECIMAL_PRECISION = 9;
// arbitrary small number that can be used to determine whether or not scroll changes are significant
public const double MIN_SCROLL_DELTA = 0.000015;
/* smallest such that 1.0+DBL_EPSILON != 1.0 */
private const double EPSILON = 2.2204460492503131e-016;
private const double BIG_EPSILON = EPSILON * 10;
@@ -11,6 +11,9 @@ namespace mbts.ui.converters {
public static IValueConverter create<TIn, TOut>(Func<TIn, TOut> convert) =>
new AnonymousValueConverter<TIn, TOut>(convert);
public static IValueConverter create<TIn, TOut>(Func<TIn, TOut> convert, Func<TOut, TIn> convert_back) =>
new AnonymousValueConverter<TIn, TOut>(convert, convert_back);
public static IMultiValueConverter create<TIn1, TIn2, TOut>(Func<TIn1, TIn2, TOut> convert) =>
new AnonymousMultiValueConverter<TIn1, TIn2, TOut>(convert);
@@ -9,6 +9,29 @@ using CompositeDisposable = System.Reactive.Disposables.CompositeDisposable;
namespace mbts.ui.extensions {
static class FrameworkElementHelper {
/// <summary>
/// Invokes the specified action immediately if the element is already loaded, otherwise hooks up to the <see cref="FrameworkElement.Loaded"/> event,
/// and [conditionally]invokes the specified action if <paramref name="should_still_execute"/> returns true.
/// </summary>
/// <param name="element">The element that must be loaded before the specified action is invoked.</param>
/// <param name="action">The action to invoke when the element is loaded.</param>
/// <param name="should_still_execute">If the element is not already loaded, this delegate determines whether or not to invoke the action in the Loaded event callback.</param>
/// <returns></returns>
public static void InvokeOnLoaded(this FrameworkElement element, Action action, Func<bool> should_still_execute) {
if (action == null)
throw new ArgumentNullException(nameof(action));
if (element.IsLoaded) {
action();
} else {
element.Loaded += on_loaded;
void on_loaded(object sender, RoutedEventArgs e) {
element.Loaded -= on_loaded;
if (should_still_execute())
action();
}
}
}
public static Point GetCenter(this FrameworkElement element) =>
new Point(element.ActualWidth * 0.5, element.ActualHeight * 0.5);
@@ -0,0 +1,61 @@
using mbts.ui.controls;
using mbts.ui.extensions;
using System.Windows;
using System.Windows.Controls;
namespace mbts.ui {
static class ScrollViewerBehavior {
public static readonly DependencyProperty VerticalOffsetProperty =
DependencyProperty.RegisterAttached("VerticalOffset", typeof(double), typeof(ScrollViewerBehavior), new PropertyMetadata(double.NaN, OnVerticalOffsetChanged));
static void OnVerticalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (d is ScrollViewer scroll) {
double voff = DoubleHelper.nan_cast(e.NewValue);
// Ignore small deltas to prevent strange layout rounding issues/loops.
// Be mindful that small deltas are not accumulated into a single larger delta.
if (!double.IsNaN(voff) && !DoubleHelper.are_close(DoubleHelper.nan_cast(e.OldValue), voff, DoubleUtil.MIN_SCROLL_DELTA)) {
scroll.InvokeOnLoaded(() => scroll.ScrollToVerticalOffset(voff),
() => scroll.GetValue(VerticalOffsetProperty) is double curr_voff && curr_voff == voff);
}
}
}
public static double GetVerticalOffset(DependencyObject d) => (double)d.GetValue(VerticalOffsetProperty);
public static void SetVerticalOffset(DependencyObject d, double value) => d.SetValue(VerticalOffsetProperty, value);
public static readonly DependencyProperty EnableTwoWayVerticalOffsetBindingProperty =
DependencyProperty.RegisterAttached("EnableTwoWayVerticalOffsetBinding", typeof(bool), typeof(ScrollViewerBehavior), new PropertyMetadata(false, OnEnableTwoWayVerticalOffsetBindingChanged));
static void OnEnableTwoWayVerticalOffsetBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (d is ScrollViewer scroll) {
if ((bool)e.NewValue) {
scroll.ScrollChanged += EnableTwoWayVerticalOffsetBindingUtils.on_scroll_changed;
} else {
scroll.ScrollChanged -= EnableTwoWayVerticalOffsetBindingUtils.on_scroll_changed;
}
}
}
public static bool GetEnableTwoWayVerticalOffsetBinding(DependencyObject d) => (bool)d.GetValue(EnableTwoWayVerticalOffsetBindingProperty);
public static void SetEnableTwoWayVerticalOffsetBinding(DependencyObject d, bool value) => d.SetValue(EnableTwoWayVerticalOffsetBindingProperty, value);
public static readonly DependencyProperty VerticalOffsetMirrorBusyProperty =
DependencyProperty.RegisterAttached("VerticalOffsetMirrorBusy", typeof(bool), typeof(ScrollViewerBehavior), new PropertyMetadata(false));
static class EnableTwoWayVerticalOffsetBindingUtils {
public static void on_scroll_changed(object sender, ScrollChangedEventArgs e) {
if (e.VerticalChange != 0 && sender is ScrollViewer scroll) {
if (!(bool)scroll.GetValue(VerticalOffsetMirrorBusyProperty)) {
scroll.SetValue(VerticalOffsetMirrorBusyProperty, true);
// scroll changes could get noisy while layouts are being processed;
// let everything settle down before updating the VerticalOffset binding sources/targets.
scroll.Dispatcher.InvokeAsync(() => {
scroll.SetCurrentValue(VerticalOffsetProperty, scroll.VerticalOffset);
scroll.SetValue(VerticalOffsetMirrorBusyProperty, false);
}, System.Windows.Threading.DispatcherPriority.ApplicationIdle);
}
}
}
}
}
}
@@ -236,6 +236,14 @@ namespace mbts.ui.extensions {
target.ScrollChanged += callback;
return Disposable.Create(() => target.ScrollChanged -= callback);
}
public static bool TryScrollToVerticalOffset(this ScrollViewer scroll_viewer, double vertical_offset) {
if (vertical_offset >= 0 && scroll_viewer.IsLoaded && vertical_offset < scroll_viewer.ExtentHeight - scroll_viewer.ViewportHeight) {
scroll_viewer.ScrollToVerticalOffset(vertical_offset);
return true;
}
return false;
}
}
public static class TextBoxExtensions {
@@ -130,8 +130,10 @@ namespace mbts.spark {
DialogSkirtItem _apply_item, _ok_item, _cancel_item;
MenuItemVM _import_item, _export_item, _load_factory_item;
public ProfiledShellSettingsDialogVM(ISettingsProfileService<T> profile_service, Func<UserSettings, T> get_fallback, Func<T, TVM> vm_factory, Func<T, T> normalize, T init, string file_ext, uint settings_id = 0) {
public ProfiledShellSettingsDialogVM(ISettingsProfileService<T> profile_service, Func<UserSettings, T> get_fallback, Func<T, TVM> vm_factory, Func<T, T> normalize, T init, string file_ext, Func<T, T, bool> profile_equals = null, uint settings_id = 0) {
_vm_factory = vm_factory;
if (profile_equals == null)
profile_equals = EqualityComparer<T>.Default.Equals;
// profile collections
var skip_local = profiles.WrapSkip(1);
var skip_local_wrapper = skip_local.WrapSelector(
@@ -185,12 +187,19 @@ namespace mbts.spark {
continue;
i = ~i;
var temp = ns.with(value: normalize(ns.value));
skip_local_wrapper.Insert(i, temp);
if (settings_id == temp.id) {
if (temp.value.Equals(init))
selected_profile = skip_local[i];
settings_id = 0;
if (profile_equals(temp.value, init)) {
if (!temp.value.Equals(init)) {
// they aren't exactly equal (don't overwrite any props in 'init' - use it to create the vm)
temp = temp.with(value: init);
}
skip_local_wrapper.Insert(i, temp);
selected_profile = skip_local[i];
continue;
}
}
skip_local_wrapper.Insert(i, temp);
}
}));
+39 -37
View File
@@ -3,11 +3,10 @@
# source: tools/cpp_helper/generate-cpps.ps1
#
CppConfig
{
value
CppConfig
{
value
{
cpp_entry { key: "098" value: 2000 }
cpp_entry { key: "0A" value: 100 }
cpp_entry { key: "0B" value: 100 }
cpp_entry { key: "0C" value: 100 }
@@ -459,9 +458,9 @@ CppConfig
cpp_entry { key: "DDE" value: 1000 }
cpp_entry { key: "DDI" value: 1000 }
cpp_entry { key: "DDO" value: 1000 }
cpp_entry { key: "DEB" value: 720 }
cpp_entry { key: "DEB" value: 745 }
cpp_entry { key: "DEN" value: 250 }
cpp_entry { key: "DEP" value: 252 }
cpp_entry { key: "DEP" value: 276 }
cpp_entry { key: "DFL" value: 100 }
cpp_entry { key: "DFN" value: 100 }
cpp_entry { key: "DG2" value: 100 }
@@ -730,8 +729,8 @@ CppConfig
cpp_entry { key: "IPO" value: 10 }
cpp_entry { key: "IPT" value: 10 }
cpp_entry { key: "IS" value: 50 }
cpp_entry { key: "ITB" value: 720 }
cpp_entry { key: "ITP" value: 252 }
cpp_entry { key: "ITB" value: 745 }
cpp_entry { key: "ITP" value: 276 }
cpp_entry { key: "IW" value: 50 }
cpp_entry { key: "J4L" value: 80 }
cpp_entry { key: "J7" value: 6250000 }
@@ -773,7 +772,6 @@ CppConfig
cpp_entry { key: "K5" value: 20 }
cpp_entry { key: "K5K" value: 20 }
cpp_entry { key: "K5N" value: 20 }
cpp_entry { key: "KC" value: 375 }
cpp_entry { key: "KE" value: 50 }
cpp_entry { key: "KET" value: 50 }
cpp_entry { key: "KQ" value: 20 }
@@ -809,6 +807,7 @@ CppConfig
cpp_entry { key: "LIY" value: 1000 }
cpp_entry { key: "LL" value: 42000 }
cpp_entry { key: "LLR" value: 1000 }
cpp_entry { key: "LNG" value: 10000 }
cpp_entry { key: "LPE" value: 47000 }
cpp_entry { key: "LPS" value: 1000 }
cpp_entry { key: "LSF" value: 1 }
@@ -1003,7 +1002,6 @@ CppConfig
cpp_entry { key: "OAD" value: 1 }
cpp_entry { key: "ODO" value: 1000 }
cpp_entry { key: "OFF" value: 5 }
cpp_entry { key: "OJ" value: 150 }
cpp_entry { key: "OML" value: 5 }
cpp_entry { key: "OMM" value: 80 }
cpp_entry { key: "OOD" value: 5 }
@@ -1146,7 +1144,7 @@ CppConfig
cpp_entry { key: "SBT" value: 50 }
cpp_entry { key: "SD" value: 1000 }
cpp_entry { key: "SDA" value: 250 }
cpp_entry { key: "SDD" value: 1600 }
cpp_entry { key: "SDD" value: 1840 }
cpp_entry { key: "SDG" value: 250 }
cpp_entry { key: "SDI" value: 1000 }
cpp_entry { key: "SDM" value: 100 }
@@ -1163,10 +1161,12 @@ CppConfig
cpp_entry { key: "SFR" value: 250 }
cpp_entry { key: "SG" value: 250 }
cpp_entry { key: "SGB" value: 1000 }
cpp_entry { key: "SGC" value: 1000 }
cpp_entry { key: "SGE" value: 42000 }
cpp_entry { key: "SGF" value: 1000 }
cpp_entry { key: "SGG" value: 1000 }
cpp_entry { key: "SGT" value: 250 }
cpp_entry { key: "SGU" value: 32.15 }
cpp_entry { key: "SI" value: 5000 }
cpp_entry { key: "SIL" value: 1000 }
cpp_entry { key: "SIR" value: 500 }
@@ -1185,7 +1185,7 @@ CppConfig
cpp_entry { key: "SR3" value: 2500 }
cpp_entry { key: "SR5" value: 1000 }
cpp_entry { key: "SRB" value: 1000 }
cpp_entry { key: "SRT" value: 1600 }
cpp_entry { key: "SRT" value: 1840 }
cpp_entry { key: "SSI" value: 1000 }
cpp_entry { key: "SSP" value: 5000 }
cpp_entry { key: "SSW" value: 1000 }
@@ -1197,31 +1197,31 @@ CppConfig
cpp_entry { key: "SU" value: 250 }
cpp_entry { key: "SUT" value: 250 }
cpp_entry { key: "T01" value: 800 }
cpp_entry { key: "T02" value: 800 }
cpp_entry { key: "T03" value: 800 }
cpp_entry { key: "T04" value: 800 }
cpp_entry { key: "T05" value: 800 }
cpp_entry { key: "T06" value: 800 }
cpp_entry { key: "T07" value: 800 }
cpp_entry { key: "T08" value: 800 }
cpp_entry { key: "T09" value: 800 }
cpp_entry { key: "T10" value: 800 }
cpp_entry { key: "T11" value: 800 }
cpp_entry { key: "T12" value: 800 }
cpp_entry { key: "T13" value: 800 }
cpp_entry { key: "T15" value: 800 }
cpp_entry { key: "T16" value: 800 }
cpp_entry { key: "T17" value: 800 }
cpp_entry { key: "T18" value: 800 }
cpp_entry { key: "T19" value: 800 }
cpp_entry { key: "T1U" value: 1000 }
cpp_entry { key: "T20" value: 800 }
cpp_entry { key: "T21" value: 800 }
cpp_entry { key: "T22" value: 800 }
cpp_entry { key: "T23" value: 800 }
cpp_entry { key: "T24" value: 800 }
cpp_entry { key: "T25" value: 800 }
cpp_entry { key: "T26" value: 800 }
cpp_entry { key: "T27" value: 800 }
cpp_entry { key: "T28" value: 800 }
cpp_entry { key: "T29" value: 800 }
cpp_entry { key: "T2B" value: 1000 }
cpp_entry { key: "T2D" value: 1000 }
cpp_entry { key: "T2M" value: 100 }
cpp_entry { key: "T30" value: 800 }
cpp_entry { key: "T31" value: 800 }
cpp_entry { key: "T3B" value: 1000 }
cpp_entry { key: "T3L" value: 80 }
cpp_entry { key: "T4B" value: 1000 }
@@ -1276,7 +1276,7 @@ CppConfig
cpp_entry { key: "TT" value: 50000 }
cpp_entry { key: "TTD" value: 24 }
cpp_entry { key: "TTE" value: 10000 }
cpp_entry { key: "TTF" value: 720 }
cpp_entry { key: "TTF" value: 745 }
cpp_entry { key: "TUB" value: 1000 }
cpp_entry { key: "TUF" value: 1000 }
cpp_entry { key: "TUL" value: 1000 }
@@ -1299,7 +1299,7 @@ CppConfig
cpp_entry { key: "UFU" value: 100 }
cpp_entry { key: "UFV" value: 100 }
cpp_entry { key: "UGG" value: 42000 }
cpp_entry { key: "UKG" value: 300 }
cpp_entry { key: "UKG" value: 310 }
cpp_entry { key: "ULB" value: 42000 }
cpp_entry { key: "UMB" value: 1000 }
cpp_entry { key: "UMD" value: 1000 }
@@ -1457,14 +1457,13 @@ CppConfig
cpp_entry { key: "ZT" value: 2000 }
cpp_entry { key: "ZW" value: 50 }
cpp_entry { key: "ZWT" value: 50 }
}
}
}
}
CmeSymbolMics
{
value
CmeSymbolMics
{
value
{
cme_mic { key: "098" value: CBT }
cme_mic { key: "0A" value: NYMEX }
cme_mic { key: "0B" value: NYMEX }
cme_mic { key: "0C" value: NYMEX }
@@ -2265,6 +2264,7 @@ CmeSymbolMics
cme_mic { key: "LIY" value: CBT }
cme_mic { key: "LL" value: NYMEX }
cme_mic { key: "LLR" value: NYMEX }
cme_mic { key: "LNG" value: NYMEX }
cme_mic { key: "LPE" value: NYMEX }
cme_mic { key: "LPS" value: NYMEX }
cme_mic { key: "LSF" value: COMEX }
@@ -2618,10 +2618,12 @@ CmeSymbolMics
cme_mic { key: "SFR" value: CME }
cme_mic { key: "SG" value: CME }
cme_mic { key: "SGB" value: NYMEX }
cme_mic { key: "SGC" value: COMEX }
cme_mic { key: "SGE" value: NYMEX }
cme_mic { key: "SGF" value: NYMEX }
cme_mic { key: "SGG" value: NYMEX }
cme_mic { key: "SGT" value: CME }
cme_mic { key: "SGU" value: COMEX }
cme_mic { key: "SI" value: COMEX }
cme_mic { key: "SIL" value: COMEX }
cme_mic { key: "SIR" value: CME }
@@ -2652,31 +2654,31 @@ CmeSymbolMics
cme_mic { key: "SU" value: CME }
cme_mic { key: "SUT" value: CME }
cme_mic { key: "T01" value: NYMEX }
cme_mic { key: "T02" value: NYMEX }
cme_mic { key: "T03" value: NYMEX }
cme_mic { key: "T04" value: NYMEX }
cme_mic { key: "T05" value: NYMEX }
cme_mic { key: "T06" value: NYMEX }
cme_mic { key: "T07" value: NYMEX }
cme_mic { key: "T08" value: NYMEX }
cme_mic { key: "T09" value: NYMEX }
cme_mic { key: "T10" value: NYMEX }
cme_mic { key: "T11" value: NYMEX }
cme_mic { key: "T12" value: NYMEX }
cme_mic { key: "T13" value: NYMEX }
cme_mic { key: "T15" value: NYMEX }
cme_mic { key: "T16" value: NYMEX }
cme_mic { key: "T17" value: NYMEX }
cme_mic { key: "T18" value: NYMEX }
cme_mic { key: "T19" value: NYMEX }
cme_mic { key: "T1U" value: CBT }
cme_mic { key: "T20" value: NYMEX }
cme_mic { key: "T21" value: NYMEX }
cme_mic { key: "T22" value: NYMEX }
cme_mic { key: "T23" value: NYMEX }
cme_mic { key: "T24" value: NYMEX }
cme_mic { key: "T25" value: NYMEX }
cme_mic { key: "T26" value: NYMEX }
cme_mic { key: "T27" value: NYMEX }
cme_mic { key: "T28" value: NYMEX }
cme_mic { key: "T29" value: NYMEX }
cme_mic { key: "T2B" value: NYMEX }
cme_mic { key: "T2D" value: NYMEX }
cme_mic { key: "T2M" value: NYMEX }
cme_mic { key: "T30" value: NYMEX }
cme_mic { key: "T31" value: NYMEX }
cme_mic { key: "T3B" value: NYMEX }
cme_mic { key: "T3L" value: NYMEX }
cme_mic { key: "T4B" value: NYMEX }
@@ -2912,5 +2914,5 @@ CmeSymbolMics
cme_mic { key: "ZT" value: CBT }
cme_mic { key: "ZW" value: CBT }
cme_mic { key: "ZWT" value: CBT }
}
}
}
+7
View File
@@ -78,6 +78,13 @@ message OrderBookOptions {
optional double status_color_opacity_when_selected = 2 [default = 0.35];
}
message OrderAlerts {
// when the client sees an order for the first time,
// this time period is used to determine if the order is actually new,
// or [presumably]just arrived bc shell is still initializing, or the server restarted.
optional int32 stale_order_threshold_seconds = 1 [default = 60];
}
message ShellPingOptions {
optional bool shell_ping_is_enabled = 1 [default = true];
optional int32 workloop_milliseconds_interval = 2 [default = 5000];
+23 -3
View File
@@ -43,6 +43,8 @@ inline api::Side other_side(api::Side side) {
inline double market_epislon() { return 1e-10; }
// better == p1 more aggressive than p1 ie for a buy order p1 is higher
// than p2, and for a sell order p1 is lower than p2
inline bool is_better(api::Side side, Price p1, Price p2) {
switch (side) {
case api::SIDE_BUY:
@@ -54,6 +56,7 @@ inline bool is_better(api::Side side, Price p1, Price p2) {
}
}
// like the above function
inline Price improve_price(api::Side side, Price p, Price offset) {
switch (side) {
case api::SIDE_BUY:
@@ -169,13 +172,30 @@ inline Price tick_size(api::Security const &sec) {
// Looking to round md::IntPrice? Look in IntPrice.h
// computes the difference between 2 prices in ticks.
// the result is an a signed int so the order of args
// matter, its second price - first price
// computes price_2 - price_1 in ticks
inline int price_diff_to_ticks(double price_1, double price_2,
double tick_size) {
return static_cast<int>(std::round((price_2 - price_1) / tick_size));
}
// returns the difference (in ticks) bw and order and a market.
// if the order has crossed the market, returns -ve ticks
// (ie the buy order price > market_price or
// sell_order_price < market_price)
inline int order_diff_market(double order_price,
double market_price,
api::Side side,
double tick_size){
switch (side) {
case api::SIDE_BUY:
return price_diff_to_ticks(order_price,market_price,tick_size);
case api::SIDE_SELL:
return price_diff_to_ticks(market_price,order_price,tick_size);
default:
bts_assert(false);
break;
}
return 0;
}
// because of prices not being completely rounded to the tick,
// simple double equality can not be used to compare prices.
// this function deems 2 prices to be equal if they are within
@@ -463,6 +463,7 @@ void SpreaderOrder::_comp_and_report_state (){
reported_state.set_quantity(bookkeeping_data.quantity().value());
reported_state.set_filled_quantity(filled_qty.value());
reported_state.set_avg_price(avg_filled_price.value());
reported_state.set_last_price(avg_filled_price.value());
}
// if order is cons'ed from disc, with inited_new==false,
// we just publish what was given to us
@@ -9,6 +9,7 @@
#include "lib/orderman/orders/spreader_orders/spreader/quoter/QuoteLegDriver.h"
#include "lib/api/services/OrderService.pb.h"
#include "lib/orderman/actors/order_manager/OrderBuilder.h"
#include "lib/os/os.h"
#include <random>
namespace bts {
@@ -284,35 +285,66 @@ public:
////////////////////////////////////////////////////
// quoting params //////////////////////////////////
////////////////////////////////////////////////////
// given the new price, applies the quoting parameters (mute etc)
// to see if quote needs to be repriced
static bool reprice_quote_mute_zone(double current_price,
double new_price,
api::Side quote_side,
double tick_size,
const bts::spark::QuoteMuteZone& quote_mute_zone){
int price_change_ticks = bts::securities::price_diff_to_ticks (current_price, new_price, tick_size);
if (price_change_ticks != 0){
bool price_increasing = price_change_ticks > 0;
uint32_t abs_price_change_ticks = static_cast<uint32_t>(std::abs(price_change_ticks));
LOG(OrderMan, Diag, "SpreaderUtils::reprice_quote_mute_zone current_price: {} new_price: {} price_change_ticks: {} abs_price_change_ticks: {} inside_limit_ticks: {} outside_limit_ticks: {}",
current_price, new_price, price_change_ticks, abs_price_change_ticks, quote_mute_zone.inside_ticks(), quote_mute_zone.outside_ticks());
if (quote_side == api::SIDE_BUY){
if (price_increasing) // moving toward inside market
return abs_price_change_ticks > quote_mute_zone.inside_ticks();
else
return abs_price_change_ticks > quote_mute_zone.outside_ticks();
}
else{
if (!price_increasing) // moving toward inside market
return abs_price_change_ticks > quote_mute_zone.inside_ticks();
else
return abs_price_change_ticks > quote_mute_zone.outside_ticks();
}
}
else
return false;
static bool is_throttle_reject(double current_price,
double new_price,
const Book& qbook,
api::Side quote_side,
double tick_size,
const bts::spark::QuoteParams& quote_params){
bool reject =
(quote_params.has_quote_mute_zone() &&
is_quote_mute_zone_reject(current_price,
new_price,
quote_side,
tick_size,
quote_params.quote_mute_zone())) ||
(quote_params.has_quote_inside_market_distance_throttle() &&
is_quote_inside_market_reject(current_price,
new_price,
qbook.side(quote_side)[0].price(),
quote_side,
tick_size,
quote_params.quote_inside_market_distance_throttle()));
return reject;
}
// returns true is quote mute zone rejects the price change
static bool is_quote_mute_zone_reject(double current_price,
double new_price,
api::Side quote_side,
double tick_size,
const bts::spark::QuoteMuteZone& quote_mute_zone){
int ticks_moved = bts::securities::price_diff_to_ticks(current_price, new_price, tick_size);
bool toward_inside = quote_side==api::Side::SIDE_BUY ? ticks_moved >=0 : ticks_moved <= 0;
uint32_t throttle = toward_inside ? quote_mute_zone.inside_ticks() :
quote_mute_zone.outside_ticks();
uint32_t abs_ticks_moved = static_cast<uint32_t>(std::abs(ticks_moved));
// has to move outside of the zone
return abs_ticks_moved <= throttle;
}
// returns false if the param surpresses the quote
static bool is_quote_inside_market_reject(
double current_price,
double new_price,
double inside_market,
api::Side quote_side,
double tick_size,
const bts::spark::QuoteInsideMarketDistanceThrottle& imd){
bool toward = bts::securities::is_equal_or_better(quote_side,
Price(new_price),
Price(current_price));
if(toward && !os::is_nan(inside_market)){
int new_order_diff_market = bts::securities::order_diff_market(
new_price,
inside_market,
quote_side,
tick_size);
return new_order_diff_market > static_cast<int>(imd.on_toward_distance());
}
return false;
}
static bool conditional_quoting(Price quoting_price,
Quantity quoting_qty,
size_t leg_idx,
@@ -73,19 +73,31 @@ void QuoterWorkingStateWorker::_longpath_analyze_and_act ()
}
else{
// to adjust for what has already been filled
Quantity order_quantity = Quantity (computed_quantity.value() + _quoter->_price_quantity_state.aggregated_filled_quantity().value());
if (order_quantity.value() != _quoter->_price_quantity_state.out_quantity().value())
Quantity order_quantity = Quantity (
computed_quantity.value() +
_quoter->_price_quantity_state.aggregated_filled_quantity().value()
);
if (order_quantity != _quoter->_price_quantity_state.out_quantity())
// this is a size change, size changes always go through regardless of the
// quoting parameters
_modify_order (computed_price, order_quantity);
else{
bool reprice = true;
if (_quoter->_this_leg_data.leg_params().quote_params().has_quote_mute_zone())
reprice = SpreaderUtils::reprice_quote_mute_zone(_quoter->_price_quantity_state.out_price().value(), computed_price.value(), _quoter->_this_leg_data.trading_side(),
_quoter->_this_leg_data.tick_size(), _quoter->_this_leg_data.leg_params().quote_params().quote_mute_zone());
reprice=reprice && !(_quoter->_spreader.in_cache_warming_cycle());
if (reprice)
_modify_order (computed_price, order_quantity);
Price curr_price = _quoter->_price_quantity_state.out_price();
if(!bts::securities::is_same(curr_price,computed_price)){
const Book& book =
_quoter->_spreader.get_book(_quoter->_this_leg_data.leg_index());
bool throttle_reject = SpreaderUtils::is_throttle_reject(
curr_price.value(),
computed_price.value(),
book,
_quoter->_this_leg_data.trading_side(),
_quoter->_this_leg_data.tick_size(),
_quoter->_this_leg_data.leg_params().quote_params());
bool reprice = !throttle_reject;
reprice = reprice && !(_quoter->_spreader.in_cache_warming_cycle());
if (reprice)
_modify_order (computed_price, order_quantity);
}
}
}
}
+8 -1
View File
@@ -68,6 +68,7 @@ message QuoteParams
QuoteMaxOrderMove quote_max_order_move = 1040;
QuoteHidden quote_hidden = 1050;
ConditionalQuoting conditional_quoting=1060;
QuoteInsideMarketDistanceThrottle quote_inside_market_distance_throttle = 1070;
// default value 0, 1 means we will have one trailing
// order behind the top level
// (once we have ChildOrder's here, we can reture this
@@ -129,6 +130,11 @@ message ConditionalQuoting{
PriceType price_type=10;
}
message QuoteInsideMarketDistanceThrottle
{
uint32 on_toward_distance = 10;
}
/*************************************Quote RepriceThrottled (unused)*******************************
(Stellar's sensitivity)
This parameter defines regions (wrt their distance from the inside market)
@@ -174,7 +180,8 @@ THEN
message QuoteInsideMarketDistance
{
RulePriority priority = 1;
uint32 ticks_from_inside = 10;
uint32 on_toward_distance = 10;
uint32 on_away_distance = 20;
}
/******************************Quote InsideMarketDistanceWithTravel******************************
@@ -0,0 +1,13 @@
This test verifies that users can only see accounts that are not marked as deleted
1. Start a client and the admin gui, choose a user other than bts
2. In the admin gui, create an account whose name is Prada
3. For the designated user, add permission to see the Prada account
4. Verify the you can see the account in the client
5. In the admin gui, delete the account
6. Verify the gui no longer shows the Prada (or DELETED_Prada, etc) account
+4
View File
@@ -8,3 +8,7 @@ Make sure the following columns have filters when the window starts:
Cross an order, get a fill and make sure it shows up
Test sorting and filtering
Root algo:
Enter an iceberg order and force fills. Make sure the Root Algo column shows the Iceberg as the root algo.
Now enter a speader order and force fills. Make sure the Root Algo column shows the name of the spreader.
+16
View File
@@ -0,0 +1,16 @@
Manual Fills Test*
Manual fills should now show Admin fills
- Open the Trading -> Manual fills form
- Verify the form is completely empty as there should be no manual fills yet
- In the Admin UI, click on Accounts -> Position Management
- Add a fill for the bts user with a qty of 100
- Click Save
- Back in the Manual fills form, verify the form is still empty
- Open up Trading -> PnL
- Verify the PnL shows a row for the trade you entered
* = We need to flesh out this test as it currently only tests 1 narrow piece of functionality
+6
View File
@@ -64,6 +64,12 @@ Filter/Collapse interaction
- Now filter for order type = limit, you should only see the hedges and the currently working limit
- Now filter for order type = IB and you should see nothing
Parent/Child
------------
- Enter an algo fo your choice. Verify that the parent child algos show correctly.
- Enter a spreader. The spreader algo should show the name of the spreader as well. Verify
that the filter works for this (the filter only shows the spreader badge).
@@ -82,8 +82,15 @@ Prepare: open a ladder for a security that has steady market data activity (in t
- enter a limit order ACROSS the market to trigger an immediate fill.
- this time, the "When order is filled" sound should play; the "When order is canceled" sound should not be heard.
7) "Any Order" sent sounds:
Description:
You will be verifying that the order sent sound trigger works
- in the settings dialog, change "When order is filled" to "When order is sent", then click apply
- in the ladder, enter a limit order away from the market, so it is NOT liable to get filled.
- you should hear the sound you selected for "When order is sent"
7) Saved with workspace:
8) Saved with workspace:
----Note----------------------------------------------------------------------------------------------------------------------
- you have completed the sound-functionality testing, don't be alarmed by the 4 sound triggers you are about to add.
- this step just requires comparing 2 images of the settings dialog to confirm that they were retained with the workspace.
@@ -99,7 +106,7 @@ Prepare: open a ladder for a security that has steady market data activity (in t
- close the shell, then restart it with the "Where I left off on" workspace.
- open the OWSD to verify that the sound triggers were saved with the workspace.
8) Test shell does not crash when no sound card present
9) Test shell does not crash when no sound card present
- use a machine that does not have a sound card, such as eh-2 (check with Eric first)
- update config to turn off sounds
@@ -86,6 +86,16 @@ NOTES:
2005) Open a new ladder for any symbol and verify you can cross your own orders without being pestered by a confirmation dialog
-------------------------------------------------------------------------------
Left Panel Scroll test
-------------------------------------------------------------------------------
The scroll position of the left-panel area is now remembered as part of the ladder settings
Resize your ladder window to make it vertically small so that the left panel displays a scrollviewer.
Scroll the left panel down so that the qty box is the top-most visible control in the panel
Exit and relaunch Spark, loading your last saved workspace
Verify that the restored ladder window's left panel has been vertically scrolled so the the qty box is one again the top-most visble control
DONE!!!
@@ -0,0 +1,12 @@
This test makes sure the basic spreader works as child orders of different algos
Iceberg
-------
0. Follow the instructions from docs/spreader_common_setup_2.txt
1. In the spreader ladder enter a 5 lot sell IB with a show qty of 2 at -0.275.
2. Verify that it completely fills.
3. Verify that the Filled Qty and Avg Filled in the OB is correct
@@ -26,21 +26,22 @@ or more orders to get the unhedged delta < 1.
Force quoting fills based on the left most column (by lifting the quoting offer)
and verify that hedge orders of size "hedge_order_sent" (3rd column) are sent as a result.
The "unhedged_delta" column shows the unhedged delta immediately after the quoting fill
Compare the cumulative qty (cum) to the quote and hedge badges for easy visual inspection.
(The "unhedged_delta" column shows the unhedged delta immediately after the quoting fill
and the "remaining_unhedged_delta" column is the unhedged delta after the hedge order
has been sent.
You can check the hedge orders being sent easily by looking at the order reduction
happening on the order sent in step 2 (or look at the hedges in the orderbook).
has been sent)
------------------------------------------------------------------------------
quoting fill unhedged_delta hedge_order_sent remaining_unhedged_delta
quoting fill(cum) unhedged_delta hedge_order_sent (cum) remaining_unhedged_delta
------------------------------------------------------------------------------
1 2/3 0 2/3
1 4/3 1 1/3
1 1 1 0
4 8/3 2 2/3
5 4 4 0
2 4/3 1 1/3
1 1 1 0
1(1/15) 2/3 0(0/10) 2/3
1(2/15) 4/3 1(1/10) 1/3
1(3/15) 1 1(2/10) 0
4(7/17) 8/3 2(4/10) 2/3
5(12/15) 4 4(8/10) 0
2(14/15) 4/3 1(9/10) 1/3
1(15/15) 1 1(10/10) 0
6. At the end of this, the whole order should be filled. Check the order book
to make sure the fill price is -9.900, there are 10 total fills in SIF1 and
@@ -0,0 +1,12 @@
This test makes sure the basic spreader functionality works for 2 legged synthetic spreads with one quoting leg
Iceberg
-------
0. Follow the instructions from docs/spreader_common_setup_2.txt
1. In the spreader ladder enter a 5 lot sell IB with a show qty of 2 at -0.275.
2. Verify that it completely fills.
3. Verify that the Filled Qty and Avg Filled in the OB is correct
-2
View File
@@ -29,5 +29,3 @@ Sym,Mult,Exch
"BUB",1000,CBT
"FNU",1000,CBT
"SOM",500,CBT
"OJ",150,IFU
"KC",375,IFU
1 Sym Mult Exch
29 BUB 1000 CBT
30 FNU 1000 CBT
31 SOM 500 CBT
OJ 150 IFU
KC 375 IFU