Compare commits

..
2 Commits
307 changed files with 2383 additions and 9246 deletions

No files matched your search

@@ -3,10 +3,6 @@ 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"
+++
{{ youtube(id="yGQfr_puT3w") }}
<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>
## Report a Problem
If there is any issue at all, you can click here
@@ -18,14 +18,14 @@ page_description = "Report a problem or find the activity log from the Help modu
send, BTS will get an alert of everything you submitted and will be
in contact with you shortly.
{{ image(name="image58.png", alt="screenshot") }}
![](image58.png)
## 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.
{{ image(name="about_spark.png", alt="screenshot") }}
![](about_spark.png)
## Activity log
This is an overall log viewer which will show
@@ -34,4 +34,4 @@ page_description = "Report a problem or find the activity log from the Help modu
messages in a text box in the right top corner.
 
{{ image(name="image39.png", alt="screenshot") }}
![](image39.png)
@@ -1,15 +1,15 @@
+++
title = "Launcher"
title = "Module Launcher"
weight = 2
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "The basics of the BTS Spark launcher and user interface"
+++
{{ youtube(id="OxVqQBKFIDw") }}
<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>
- The module launcher is the first item that will come up after
logging in to Spark.
@@ -18,7 +18,7 @@ page_description = "The basics of the BTS Spark launcher and user interface"
each module
- It appears like the following:
{{ image(name="image38.png", alt="screenshot") }}
![](image38.png)
- If you right-click anywhere on the launcher you can modify
various display settings including:
@@ -29,7 +29,7 @@ page_description = "The basics of the BTS Spark launcher and user interface"
- 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:
{{ image(name="image68.png", alt="screenshot") }}
![](image68.png)
## Spark User interface
@@ -3,21 +3,17 @@ 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:
{{ image(name="image64.png", alt="BTS icon") }}
![](image64.png)
Simply double click or single click and hit enter to launch the
login screen:
{{ image(name="image3.png", alt="login screen") }}
![](image3.png)
Choose your username from the drop down, choose your initial
workspace, enter your password, and click Login
+22 -20
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:
{{ image(name="image53.png", alt="screenshot") }}
![](image53.png)
## Market View
{{ youtube(id="Y54S1uscT_g") }}
<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>
- This provides a grid of outright, spread, and synthetic products
showing real-time market data all in one place
@@ -29,7 +29,7 @@ page_description = "The BTS Spark Market module contains the Market View and Tim
- You choose which products you would like to view upon launching it,
the security browser will appear as shown below:
{{ image(name="image21.png", alt="security picker") }}
![](image21.png)
- 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 @@ page_description = "The BTS Spark Market module contains the Market View and Tim
- Below is an example of an outright, spread, all futures, and
synthetic chosen:
{{ image(name="image14.png", alt="screenshot") }}
![](image14.png)
- Clicking OK will bring up the Market View with the selected
securities you have chosen, in this example, it appears like the
following:
{{ image(name="market_view.png", alt="screenshot") }}
![](market_view.png)
- Once a market view is launched and you are viewing data, you can
customize the view further by the following:
@@ -61,19 +61,19 @@ page_description = "The BTS Spark Market module contains the Market View and Tim
- Filtering - by hovering over the column header, a filtering box
will appear like below:
{{ image(name="image66.png", alt="screenshot") }}
![](image66.png)
- All columns let you sort by that columns minimum and maximum
information in a box that appears like the following:
{{ image(name="image24.png", alt="screenshot") }}
![](image24.png)
- 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:
{{ image(name="image11.png", alt="screenshot") }}
![](image11.png)
- 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 @@ page_description = "The BTS Spark Market module contains the Market View and Tim
- Change securities
- 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.
- Click on the settings icon ![](change_securities.png) which will launch the market view security selection.  You can add or remove Securities you have already selected and are shown in the market view.
- Launch an order ticket
- This is done 1 of 3 ways:
@@ -96,7 +96,7 @@ page_description = "The BTS Spark Market module contains the Market View and Tim
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
{{ image(name="image23.png", alt="screenshot") }} icon
![](image23.png) icon
on the left-hand browser window of the market view
- Highlighting a single security and right clicking bringing up the
menu options to launch an order ticket.
@@ -121,7 +121,7 @@ page_description = "The BTS Spark Market module contains the Market View and Tim
- This is done by either highlighting 1 or many securities and then
clicking on the Fills icon
{{ image(name="image20.png", alt="screenshot") }} on
![](image20.png) on
the left-hand side to bring up a fill window for the selected
securities.  
@@ -132,14 +132,14 @@ page_description = "The BTS Spark Market module contains the Market View and Tim
- This is accomplished by highlighting 1 or many securities and then
clicking on the time and sales icon
{{ image(name="image49.png", alt="screenshot") }} on
![](image49.png) 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
{{ image(name="image34.png", alt="screenshot") }} on
![](image34.png) on
the lower left-hand side
- Please note - If only 1 security is chosen, a create synthetic
@@ -148,7 +148,9 @@ page_description = "The BTS Spark Market module contains the Market View and Tim
## Time and Sales
{{ youtube(id="nEkSGacjt0s") }}
<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>
- Provides a display of real-time fills as they happen for 1 or many
securities
@@ -159,9 +161,9 @@ page_description = "The BTS Spark Market module contains the Market View and Tim
- On launching a security browser will appear, allowing you to choose
the products you would like to view.
{{ image(name="time_and_sales.png", alt="screenshot") }}
![](time_and_sales.png)
- From time and sales, you can launch the same items as the market
view with the exception of a launching the synthetic window to
create a synthetic product
- 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.
- 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.
@@ -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"
+++
{{ youtube(id="pjm1JJ9Q3jM") }}
<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>
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.
{{ image(name="image16.png", alt="screenshot") }}
![](image16.png)
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.
{{ image(name="image15.png", alt="screenshot") }}
![](image15.png)
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.
{{ image(name="image37.png", alt="screenshot") }}
![](image37.png)
@@ -3,13 +3,12 @@ title = "Settings"
weight = 8
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "The BTS Spark Settings module and workspaces management"
+++
{{ youtube(id="yGQfr_puT3w") }}
<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>
## Workspaces
@@ -17,7 +16,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
{{ image(name="image9.png", alt="screenshot") }}
![](image9.png)
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
@@ -27,4 +26,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.
{{ image(name="image51.png", alt="screenshot") }}
![](image51.png)
+43 -27
View File
@@ -3,33 +3,47 @@ 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") }}
- Launching the spreader from the launcher will bring up the
<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
synthetic management browser which appears like below:
{{ image(name="image35.png", alt="screenshot") }}
![](image35.png)
- 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 {{ 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") }}
- 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)
@@ -49,7 +63,7 @@ page_description = "How to create and edit synthetic spreads in BTS Spark's spre
- An empty synthetic browser will look like below without any legs
chosen:
{{ image(name="image31.png", alt="screenshot") }}
![](image31.png)
- 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
@@ -63,13 +77,13 @@ page_description = "How to create and edit synthetic spreads in BTS Spark's spre
name your synthetic product.  By default, it will auto-populate
based on the symbols you have chosen:
{{ image(name="image29.png", alt="screenshot") }}
![](image29.png)
- 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:
{{ image(name="image19.png", alt="screenshot") }}
![](image19.png)
- Also included on this row are what ticks you would like this to be
priced in as well as the format of the pricing
@@ -78,7 +92,7 @@ page_description = "How to create and edit synthetic spreads in BTS Spark's spre
as the option to add additional legs through the add leg box to the
right of the symbols:
{{ image(name="image18.png", alt="screenshot") }}
![](image18.png)
- The following rows are all part of the main parameters which can
all be modified according to the traders style of designing the
@@ -90,14 +104,14 @@ page_description = "How to create and edit synthetic spreads in BTS Spark's spre
details of how that parameter functions.  Below is an example of the
throttle details displayed:
{{ image(name="image56.png", alt="screenshot") }}
![](image56.png)
- 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:
{{ image(name="image10.png", alt="screenshot") }}
![](image10.png)
- Quantity - This is the number of contracts of the security you
would like to be included in the calculation of the synthetic as
@@ -125,7 +139,7 @@ page_description = "How to create and edit synthetic spreads in BTS Spark's spre
displayed and click on such will display further details on how this
parameter is used:
{{ image(name="image72.png", alt="screenshot") }}
![](image72.png)
- Quote
@@ -134,20 +148,20 @@ page_description = "How to create and edit synthetic spreads in BTS Spark's spre
by defining a region on either side of the working quote order,
inside which quote updates will not be sent:
{{ image(name="image6.png", alt="screenshot") }}
![](image6.png)
- Pre-Hedge
- Pay Less - If enabled by checking the checkbox, the spreader will
attempt to pay less when the quantity at the level of the calculated
hedge price is less than the threshold defined.  The hedge order is
hedge price is more than the threshold defined.  The hedge order is
paid less by the “Payless Ticks” as defined per leg.
- Pay Up - If enabled by checking the checkbox, the spreader will pay
up when the quantity at the level of the calculated hedge price is
more than the threshold defined.  The hedge order will pay up by the
“Payup Ticks” as defined per leg.
{{ image(name="image54.png", alt="screenshot") }}
![](image54.png)
- Post-Hedge
@@ -158,7 +172,7 @@ page_description = "How to create and edit synthetic spreads in BTS Spark's spre
opposite inside market, it is re-priced to cross the opposite inside
market as defined by “cross ticks”
{{ image(name="image70.png", alt="screenshot") }}
![](image70.png)
- Time Based Cross - Basically the same as Ticks Away Cross,
however, this is based on time (in ms) on when to automatically cross
@@ -167,7 +181,7 @@ page_description = "How to create and edit synthetic spreads in BTS Spark's spre
away from the opposite inside market, it is re-priced to cross the
opposite inside market as defined by “cross ticks”
{{ image(name="image40.png", alt="screenshot") }}
![](image40.png)
- Size Based Cross - similar to ticks and time based cross, but based
on size instead.  If you define a threshold of the opposite inside
@@ -175,3 +189,5 @@ page_description = "How to create and edit synthetic spreads in BTS Spark's spre
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,10 +4,6 @@ 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
+62 -53
View File
@@ -3,9 +3,6 @@ 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"
+++
@@ -13,11 +10,13 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
Order Book, Fills, P&L, Ladder, and Order Ticket
- It appears like below:
{{ image( name = "image44.png", alt = "screenshot") }}
![](image44.png)
## Order Book
{{ youtube(id="ygMFkjBBGeU") }}
<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>
- This provides a list of all orders a user has placed, by default it
will display only working orders
@@ -27,7 +26,7 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
other windows within BTS
- Appears like the following:
{{ lightbox( name = "image5.png", alt = "screenshot") }}
![](image5.png)
- You can sort all columns by single left clicking on the column
header which will sort in either descending or ascending order, an
@@ -35,14 +34,14 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
 Below is a display of the security column sorting in descending
fashion.
{{ image( name = "image60.png", alt = "screenshot") }}
![](image60.png)
- 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
{{ image( name = "image52.png", alt = "screenshot") }}
![](image52.png)
- If the column is displaying time base information, a special style
box will appear when clicking on the filtering box providing more
@@ -54,45 +53,49 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
- Since Open
- A specific user-defined period of time
{{ image( name = "image25.png", alt = "screenshot") }}
![](image25.png)
- 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.
{{ image(name="spark_menu_order_book.png", alt="the spark menu in the order book", pull="right") }}
well as launch other modules.  It appears like the below:
- {{ icon(name="image48.png", alt="Fills icon") }} Fills -
![](spark_menu_order_book.png)
- ![](image48.png)Fills -
clicking on this will launch a fills window
- {{ icon(name="image57.png", alt = "modify icon") }} Modify -
- ![](image57.png) Modify -
clicking on this will launch an order ticket for a live order
highlighted in the Order Book.
- {{ icon(name="image73.png", alt = "cancel selected icon")}} Cancel
- ![](image73.png)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
- {{ icon(name="image74.png", alt = "cancel buys icon")}} Cancel
- ![](image74.png)Cancel
buys - clicking on this will cancel all the buy orders you have in
the order book
- {{ icon(name="image33.png", alt = "cancel all icon")}} Cancel
- ![](image33.png)Cancel
sells - clicking on this will cancel all the sell orders you have in
the order book
- {{ icon(name="image2.png", alt = "cancel all icon")}} Cancel
- ![](image2.png)Cancel
all - clicking on this will cancel all working orders you have the
order book
- {{ 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") }}
- ![](recreate_icon.png) Recreate - clicking this will recreate already cancelled orders, sending them back to market with the same price and quantity as they were previously at before being cancelled.
- ![](collapse_all_icon.png) Collapse all - This will collapse all trees inside the order book to show only the top level of the order.
- ![](expand_all_icon.png) Expand all - This will expand to show everything inside the order book.
- ![](settings_icon.png) Settings - This launches the settings for the orderbook.
- Furthermore, there is a “floating” or “mini orderbook” feature as well
integrated within the ladder itself.
- To access this, you need to center click with your mouse wheel over
working orders on the ladder.
- From here, you can change the price and quantity of any working order
within the mini orderbook straight from the window
- This looks like below:
![](mini_orderbook.png)
## Fills
- 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” time frame
period, by default it will open up with “since open” timeframe
selected showing you fills on the day thus far
- You can create multiple fill windows, filtered however you
like
@@ -103,7 +106,9 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
## P&amp;L
{{ youtube(id="xj6cEQ6By34") }}
<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>
- This provides a running ledger of a users account(s) profit and
loss, broken down per symbol
@@ -120,18 +125,20 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
- This is accomplished on the top bar, which when clicked on, displays
the choices for calculation, this appears like below:
{{ image( name = "pnl_calculation.png", alt = "screenshot") }}
![](pnl_calculation.png)
- Below is an example P&L window showing a user’s multiple accounts and
various positions in each:
{{ image( name = "pnl_window.png", alt = "screenshot") }}
![](pnl_window.png)
## Ladder
{{ youtube(id="Q0qsyc1nvYo") }}
<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>
- The ladder was designed to be the most logically produced ladder on
the market, engineered and designed by professional traders for
@@ -142,12 +149,12 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
market with a single click
- Below is an example ladder:
{{ image( name = "ladder.png", alt = "screenshot") }}
![](ladder.png)
- 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; {{ image( name = "image1.png", alt = "screenshot") }}
- The information section on the left contains the following: &nbsp; ![](image1.png)
- 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
@@ -160,7 +167,7 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
- The control section right underneath the information section on the
left-hand side offers the following:
{{ image( name = "image62.png", alt = "screenshot") }}
![](image62.png)
- The account in which the orders will flow into when trading this
security
@@ -195,7 +202,7 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
the stop price, by clicking on the “&lt;” to bring up the stop price
option:
{{ image( name = "image61.png", alt = "screenshot") }}
![](image61.png)
- Iceberg
@@ -206,7 +213,7 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
quantity.  You will need to define what type of order that will
be used for the child order to be sent.
{{ image( name = "image47.png", alt = "screenshot") }}
![](image47.png)
- Time Box
@@ -226,9 +233,9 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
- Below are images of the type of time frame that can be used within
the time box
{{ image( name = "image42.png", alt = "screenshot") }}
![](image42.png)
         
{{ image( name = "image50.png", alt = "screenshot") }}
![](image50.png)
- Lean
- This is a specialized algo order which allows the user to
@@ -237,14 +244,14 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
- “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
{{ image( name = "lean_algo_control.png", alt = "screenshot") }}
![](lean_algo_control.png)
- 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.
{{ image( name = "join_algo_control.png", alt = "screenshot") }}
![](join_algo_control.png)
- Top
- This is a specialized algo order which allows the user to
@@ -252,14 +259,14 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
- 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
{{ image( name = "top_algo_control.png", alt = "screenshot") }}
![](top_algo_control.png)
- Offset
- The goal of the offset order is to send your order X amount of ticks
from where you actually place it on the ladder. This is sometimes
known as a "fat finger" safety.
{{ image( name = "offset_algo_control.png", alt = "screenshot") }}
![](offset_algo_control.png)
- OCO
@@ -269,26 +276,26 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
of the fill. Composing this order with Cover simulates scalping
functionality.
{{ image( name = "oco_algo_control.png", alt = "screenshot") }}
![](oco_algo_control.png)
- Cover
- A Cover order’s purpose is to fire a hedge order (of any algo-type) in response to fills from it’s child order on the opposite side of the market N ticks away.
{{ image( name = "cover_algo_control.png", alt = "screenshot") }}
![](cover_algo_control.png)
- Trigger
- The Trigger order is exactly as it sounds, where it will trigger an order based on conditions defined.
- This order also has exit strategy built in where a user can define when the trigger order can auto-cancel based on time as well as price conditional.
{{ image( name = "trigger_algo_control.png", alt = "screenshot") }}
![](trigger_algo_control.png)
- The middle of the control section is where you can click on
predefined values to quickly populate the volume at which you wish
to enter the market.
{{ image( name = "image17.png", alt = "screenshot") }}
![](image17.png)
- You can manually type in a quantity here as well
- Furthermore, if you have a value in the volume bar, you can
@@ -298,7 +305,7 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
- By having the auto clear box checked, it will bring the value back
to zero after placing an order at the size chosen.
{{ image( name = "image59.png", alt = "screenshot") }}
![](image59.png)
-  The bottom section of the control area of a ladder has 4 aspects
to it
@@ -310,7 +317,7 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
- Cancel All, which will cancel all orders on the ladder
- Settings, which will launch the ladder settings window which
appears like below
{{ image( name = "ladder_settings.PNG", alt = "screenshot") }}
![](ladder_settings.png)
- Fonts and Colors - This is where you can customize the overall main
aspects of the ladder.
- Market Break Lines - These are where can control the viewing of what
@@ -332,7 +339,7 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
- The other, main part of the ladder, is the ladder itself as
displayed below
{{ image( name = "ladder_main_part.png", alt = "screenshot") }}
![](ladder_main_part.png)
- There are 5 main columns
@@ -349,7 +356,7 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
- 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
{{ image( name = "ladder_badges.png", alt = "screenshot") }}
![](ladder_badges.png)
- To change the price level you wish to work the order, you can right-click and hold on the badge to move it up or down the ladder for the
desired price
@@ -360,7 +367,9 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
## Order Ticket
{{ youtube(id="bdC5pZ6T35I") }}
<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>
- The order ticket was designed to provide another means other than
the ladder to enter orders into the market
@@ -369,7 +378,7 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
which security to populate
- The order ticket appears like below
{{ image( name = "image27.png", alt = "screenshot") }}
![](image27.png)
- On the top left, you have the price for the order as well as the
volume
@@ -407,7 +416,7 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
- This is all in place in a very user friendly GUI as shown below:
{{ image( name = "algo_studio.png", alt = "screenshot") }}
![](algo_studio.png)
- There are 3 main areas of the Algo Studio:
-3
View File
@@ -3,9 +3,6 @@ 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.
+50 -83
View File
@@ -3,51 +3,18 @@ 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
### Internal Changes
- Lots of permission / admin changes to facilitate ICE
## 2.11.0
### User Visible Changes
- Fixed fills form time filter to also apply to SOD fills
- Addressed an issue that could prevent the web Admin UI from displaying over slow HTTP connections
- Removed the option to enter manual spread fills
- 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
- 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
- A new, more powerful PnL form is now available
### API Changes
- Added security state information (pre open, halted, normal, closed, etc)
- Added security state information (pre open, halted, normal, closed, etc)
### Bug Fixes
- Brought back the "select all" check box in the mini order book
- Fixed an issue in the market data subsystem that on rare occasions could result in a startup crash
### Internal Changes
- Fixed an issue that was stopping us from interpreting SOD times as local rather than GMT
- Fixed an issue that was stopping us from interpreting SOD times as local rather than GMT
## 2.9.3
### Internal Changes
- Fixed system test scripts
@@ -63,7 +30,7 @@ page_description = "Release notes for BTS Spark"
- Avoided shipping production configuration defaults that lead to trying to download a position file for the development sftp server.
### Api
- C# API sample now uses synchronous calls.
## 2.9.0
### User Visible Changes
- Improved Workspace management User Interface
@@ -71,11 +38,11 @@ page_description = "Release notes for BTS Spark"
- The Spark UI Window group now has a title in the Windows task bar
- Added sound alerts for trade events, see Launcher -> Settings -> Alerts
- Allowed composite algos that are attached to existing orders to identify fills that were already present prior to attachment
- Added import facilities for GMI position files
- Added import facilities for GMI position files
- Added the ability for risk managers to enter manual fills
- Added "raw" P&L columns, that is columns that *do not* include spread legs in their accounting
### Bug Fixes
- Fixed the settings action menu (Load/Save/Reset to Factory)
- Fixed the settings action menu (Load/Save/Reset to Factory)
- Fixed the method used to determine if a security is expired to take into account the start of day time
### API Changes
- Fixed an issue in the Client SDK that could lead to occasional failures to connect to the spark server as well as excessive memory usage.
@@ -112,10 +79,10 @@ page_description = "Release notes for BTS Spark"
- Made it easier to generate simple UI's for new order types
### API Changes
- Dynamic orders can now use QuantityMeasure parameter types
## 2.7.2
### User Visible Changes
- Changed the login dialog to avoid leaking workspace names from other users.
- Changed the login dialog to avoid leaking workspace names from other users.
- Added new Splitter algo. From the manual: The splitter is useful
for pro-rata markets where you want to vary your exposure
independent of the order quantity that you want to fill. The
@@ -123,13 +90,13 @@ page_description = "Release notes for BTS Spark"
the order quantity but it will put exposed_qty total size on the
market. Splitter favors order size over queue position.
### Internal Changes
- Made spark more resilient to lack of connectivity to the CME web site for downloading of templates.
- Made spark more resilient to lack of connectivity to the CME web site for downloading of templates.
## 2.7.1
### User Visible Changes
- Fixed some issues with the Cover algo, where in some circumstances
it would erroneously cancel launched hedges on completion.
## 2.7.0
### User Visible Changes
- Allowed algos to process "in-flight" fills while shutting down
@@ -148,36 +115,36 @@ page_description = "Release notes for BTS Spark"
### User Visible Changes
- Fixed an issue in the ladder where in some cases the mini order book could not be brought up from a badge
- Fixed a rare race condition that could lead to erroneous limit enforcement
- Fixed an issue with the margin limits where an error could occur if an account name with more than 12 chars was used
- Fixed an issue with the margin limits where an error could occur if an account name with more than 12 chars was used
## 2.6.1
### User Visible Changes
- Fixed an issue in the ladder where an error could occur when trying to change ladder settings for the first time
## 2.6.0
### User Visible Changes
- 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 made last trade more distinctive when overlayed on the volumn 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
- Address a rare issue that could lead to an error report on UI exit
- Added margin risk limits
- Added the "Offset" algo. Offset prices a child algo at a parameterizable distance from the top of the book
- Avoided switching the current account and order type on open ladders when limits are changed
- Avoided switching the current account and order type on open ladders when limits are changed
### Internal Changes
- Ensure that admin logins are always allowed to login, regardless of keycode status.
## 2.5.0
### User Visible Changes
- Added loss limit risk check
- 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 volume column
- Improved accuracy of the directional arrow when displaying last trades in the volumn 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 level order's security rather then current row's
- 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
- 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
@@ -193,16 +160,16 @@ page_description = "Release notes for BTS Spark"
### 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 children:
- Made Cover order more independent from its hedging chidren:
- 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
- Changed Lean, Join and Cover algos to hide themselves in the ladder once they have completed their work
- Changed manual fills to appear in day PnL rather than in the SOD records
- Add buy/sell coloring to fill rows in fills window
- Streamlined the mini order book:
- Display of the mini order book popup is quicker
- Only exchange orders are displayed
- Update buttons are only displayed for orders that are under control of an algo with the update applying to the entire algo, not just the exchange order
- Update buttons are only displayed for orders that are under control of an algo with the update applying to the entire algo, not just the exchange order
- We can now select multiple rows to attach, detach, update or cancel multiple orders in a batch
- A checkbox column has been added to make clearer which rows are selected
### API Changes
@@ -212,7 +179,7 @@ page_description = "Release notes for BTS Spark"
- Added system requirements to Spark documentation
- Implemented keycodes
- Temporarily rejected stop and stop limit orders, pending bug fixes
## 2.2.0
### User Visible Changes
- Made our orders easier to spot in MBO display
@@ -223,41 +190,41 @@ page_description = "Release notes for BTS Spark"
- Improved handling of orders with undiscovered securities
- Added feedback to the ladder when the user attempts to middle click on bid/ask columns using an order that does not have parameters
- Fixed display of algo studio algo types in order book
- Hid net change setting in synthetic ladders
- Hid net change setting in synthetic ladders
- Fixed a number of problems with filtering on the Fills form
- On the Lean algo, OnToward and Away are now calculated with respect to the opposite inside market at time of the order launch
- Iceberg Spreaders with queue holders are not erroneously cancelled after the first fill
- Added security symbol column to fills window
- Re-organized ladder settings dialog to make it clearer and easier to use
## 2.1.4
### User Visible Changes
- Fixed handling of cancel button in security picker
## 2.1.3
### User Visible Changes
- Fixed a simulator bug in the handling of 0 sized orders
- Fixed a simulator bug in the handling of 0 sized orders
## 2.2.1
### User Visible Changes
- Fixed order book form exception when display a spreader order for which the legs were not present yet
- Fixed order book form exception when display a spreader order for whichs the legs were not present yet
## 2.1.1
### User Visible Changes
- Added PIQ to GLBX Iceberg orders
- Corrected handling of stop limit orders
## 2.1.0
### User Visible Changes
- 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 overlaid on the same ladder column
- Allowed the LTQ and VAP values to be overlayed 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
- Fixed a bug that could cause incorrect price displays for certain types of synthetic securities
## 2.0.5
### User Visible Changes
- Fixed a bug in the handling of rejected QHO orders
@@ -267,19 +234,19 @@ page_description = "Release notes for BTS Spark"
## 2.0.4
### User Visible Changes
- Fixed performance regression in order manager
### Internal changes
- Improved market data diagnostics for bad configurations
- Changed market data default buffer size
## 2.0.3
### Internal changes
- Improved backwards compatibility in UI settings
## 2.0.2
### User Visible Changes
- Middle click the ladder price column to show the orders for that price level
- Middle click the ladder price column to show the orders for that price level
- Middle click the bid or ask column in the ladder no longer sends an order
### Internal changes
@@ -307,8 +274,8 @@ page_description = "Release notes for BTS Spark"
- Improved settings manager tool output
- Added diagnostics in the event MD memory corruption is detected
- Added systems tests for join,lean, iceberg and spreader orders
- Made exchange order recovery more resilient in the presence of database loss
- Made exchange order recovery more resilient in the presence of database loss
## 1.6.2
### User Visible Changes
### Exchange simulator
@@ -333,7 +300,7 @@ page_description = "Release notes for BTS Spark"
- 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
@@ -347,21 +314,21 @@ page_description = "Release notes for BTS Spark"
- These columns can be restored by toggling their visibility within the PnL's Settings form
- A new Traded Today column has been added to the PnL
- Filtering on this column will allow a user to restrict the report to only show today's position
### Internal changes
- Improved organization of the shell notification architecture
### Internal changes
- Improved organization of the shell notification architecture
- Added logging to help troubleshoot shell exit hang
## 1.5.6
### User Visible Changes
- Fixed a bug that could cause limits to be erroneously enforced
- Fixed a bug that could cause limits to be erroneously enforced
- Improved algo documentation
- Changed how the spreader uses net change
- Simplified trigger algo user interface
### Internal changes
### Internal changes
- Defaulted to not recording sim market data
- Improved documentation build
- Improved documentation build
- Added additional performance tracing
## 1.5.5
- Fixed Market View 'stale working orders' bug
@@ -369,11 +336,11 @@ page_description = "Release notes for BTS Spark"
- Avoided logging when warming up cache
## 1.5.3
- Fixed sounds startup issue
- Fixed sounds startup issue
## 1.5.2
- Added better logging to AlgoDefinitionService
## 1.5.1
- Fixed missing securities in the Fills and P&L windows.
@@ -394,7 +361,7 @@ page_description = "Release notes for BTS Spark"
#### Bug Fixes
- Fixed the "Since Open" time filter to use the exchange open time rather than 8:30 AM
- Fixed a bug in the order book where sometimes parent orders would not update their last message column.
#### Algos
- Added OCO Algo
- An OCO order creates a relationship between a child order and a 'stop' or 'other' order such that when one of these is cancelled, both cancel, or when one is filled, the other order's size is debited in the amount of the fill. Composing this order with Cover simulates scalping functionality
@@ -402,14 +369,14 @@ page_description = "Release notes for BTS Spark"
- Improved SDK documentation and usability
- Dynamic orders are now properly identified in the order book
#### Documentation
- Information on the Spark API has been added to the Spark manual
- Information on the Spark API has been added to the Spark manual
- The documentation has been updated and reorganized to make information easier to find
### Internal Changes
- Improved robustness of limit system test
- 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 digits as required by some CME products
- Increased default order price precision to 8 digis as required by some CME products
## 1.4.11
### New Features
@@ -477,7 +444,7 @@ page_description = "Release notes for BTS Spark"
### 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 accommodate the entire form
- 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 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,9 +3,6 @@ title = "System Requirements"
weight = 4
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "Recommended and minimum systems requirements for BTS Spark"
+++
+2 -7
View File
@@ -1,20 +1,15 @@
#/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 /home/bts/doc/
cd 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
+9 -18
View File
@@ -1,4 +1,4 @@
.page, .menu-open .page {
.page {
position: absolute;
width: calc(100% - #{$sidebar-width});
height: 100%;
@@ -7,24 +7,15 @@
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-right: .5em;
margin-left: -1.5em;
padding-left: 10px;
text-decoration: none;
font-weight: initial;
float: left;
visibility: hidden;
&:hover {
text-decoration: none;
visibility: visible;
text-decoration: underline;
}
}
@@ -164,13 +155,13 @@
}
}
// @include min-screen(600px) {
// .page {
// left: $sidebar-width;
// }
// }
@include min-screen(600px) {
.page {
left: $sidebar-width;
}
}
.menu-closed .page {
.page-without-menu {
width: 100%;
left: 0;
+19 -46
View File
@@ -123,9 +123,27 @@
.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;
@@ -138,50 +156,5 @@
&: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;
}
+2 -17
View File
@@ -1,4 +1,4 @@
.menu, .menu-open .menu {
.menu {
height: 100%;
position: absolute;
left: 0;
@@ -39,7 +39,7 @@
}
}
.menu-closed .menu {
.menu-hidden {
width: 0;
}
@@ -47,19 +47,4 @@
.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;
}
}
+3 -98
View File
@@ -1,7 +1,6 @@
@charset "utf-8";
@import "normalize";
@import "colorbox";
* {
box-sizing: border-box;
@@ -22,10 +21,6 @@ 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) {
@@ -45,21 +40,9 @@ $sidebar-width: 300px;
@import "content";
@import "header";
@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;
img {
transform: scale(0.666);
transform-origin: top left;
}
.back-button {
@@ -75,82 +58,4 @@ $sidebar-width: 300px;
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
@@ -1,42 +0,0 @@
#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;}
+6 -15
View File
@@ -1,19 +1,10 @@
function initToggleMenu() {
$(".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");
}
var $menu = document.querySelector(".menu");
var $menuIcon = document.querySelector(".menu-icon");
var $page = document.querySelector(".page");
$menuIcon.addEventListener("click", function () {
$menu.classList.toggle("menu-hidden");
$page.classList.toggle("page-without-menu");
});
}
function debounce(func, wait) {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

+4 -58
View File
@@ -1,40 +1,11 @@
$(document).ready(function() {
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);
}
$("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);
});
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()});
@@ -43,29 +14,4 @@ $(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
@@ -1,6 +0,0 @@
/*!
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
@@ -1,131 +0,0 @@
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
@@ -1 +0,0 @@
<a class="zola-anchor" href="#{{ id }}" aria-label="Anchor link for: {{ id }}"><i class="fas fa-link"></i></a>
+13 -23
View File
@@ -5,12 +5,9 @@
<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="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 %}
<meta name="description" content="Documentation for Blue Trading Systems' BTS Spark" />
<title>{% block title %}{{ config.title }}{% endblock title %}</title>
@@ -39,8 +36,12 @@
}
</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 %}
@@ -59,9 +60,6 @@
{% 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") %}
@@ -118,14 +116,14 @@
<div class="page">
<div class="page__header">
<span class="menu-icon">
<i class="fas fa-bars"></i>
</span>
<div class="menu-icon">
<span></span>
<span></span>
<span></span>
</div>
{% if config.build_search_index %}
<span class="search-icon"><i class="fas fa-search"></i></span>
<span class="search-icon">🔎</span>
{% endif %}
<span class="font-icon"><i class="fas fa-font"></i></span>
</div>
<div class="page__content">
@@ -156,19 +154,11 @@
</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,13 +1,5 @@
{% 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,13 +1,5 @@
{% 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
@@ -1 +0,0 @@
<div style="clear:both;"></div>
-17
View File
@@ -1,17 +0,0 @@
{% 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
@@ -1,34 +0,0 @@
{% 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}}"
/>
@@ -1,52 +0,0 @@
{% 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>
@@ -1,6 +0,0 @@
<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>
@@ -1,8 +0,0 @@
<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>
+62 -53
View File
@@ -1,74 +1,83 @@
using bts;
using bts.api.admin;
using bts.api.core;
using bts.utils;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using bts.api.core;
using mbts.ui.shell;
namespace mbts.spark {
[Export]
class AdminModel : IDisposable {
public readonly UserTuple user;
IDisposable _dispose;
IReadOnlyDictionary<uint, string> _account_map = DictionaryHelper.empty<uint, string>();
readonly BehaviorSubject<IReadOnlyDictionary<uint, string>>
_accounts_subject = new BehaviorSubject<IReadOnlyDictionary<uint, string>>(new Dictionary<uint, string>()),
_users_subject = new BehaviorSubject<IReadOnlyDictionary<uint, string>>(new Dictionary<uint, string>());
IReadOnlyDictionary<uint, string>
_account_map = new Dictionary<uint, string>(),
_user_map = new Dictionary<uint, string>();
readonly BehaviorSubject<AdminModelWrapper> _wrapper_subject = new BehaviorSubject<AdminModelWrapper>(new AdminModelWrapper());
IDisposable _dispose;
[ImportingConstructor]
AdminModel(AdminService service, UserTuple user) {
AdminModel(AdminService service, [Import]UserTuple user) {
this.user = user;
// accounts
on_accounts = service.subscribei_admin_data()
.Select(up => up.all.TryGetFirst(out var ad) && ad.Users.Where(u => u.Name == user.name).TryGetSingle(out var usr) && usr.HasUserData && usr.UserData.HasAccountPermissions ?
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 && acc.Value.IsPresent)
.Select(acc => acc.Value)
.ToDictionary(acc => acc.Id, acc => acc.Name) :
new Dictionary<uint, string>(0))
.Replay(1)
.RefCount();
// users
on_users = Observable.Return(DictionaryHelper.wrap_value(user.id, user.name));
all_user_tuples = user.permissions == UserTuple.Permissions.Admin ?
service.subscribei_admin_data()
.Select(u => u.all.TryFirst())
.Where(ad => ad.HasValue)
.Select(ad => ad.Value.Users
.Where(u => user.can_access_user_data(u.Id))
.Select(u => new UserTuple(u.Id, u.Name))
.ToDictionary(UserTuple.key_fn))
.Table(UserTuple.key_fn)
.Cache() :
Observable.Return(TableUpdate<UserTuple, uint>.mk_empty(UserTuple.key_fn).with_added(new[] { user }));
_dispose = on_accounts.Subscribe(map => _account_map = map);
_dispose = service.subscribei_admin_data().Select(update => {
_wrapper_subject.OnNext(new AdminModelWrapper(update.all.TryFirst(), user.name));
IEnumerable<BTSUserImmutable> active_users =
from admin_data in update.all
from bts_user in admin_data.Users
where bts_user.Name == user.name && bts_user.HasUserData && bts_user.UserData.HasAccountPermissions
select bts_user;
IEnumerable<BTSAccountImmutable> active_accounts =
from admin_data in update.all
from bts_account in admin_data.Accounts
where bts_account.IsPresent && _wrapper_subject.Value.can_view_account(bts_account.Name)
join account_id in (
from bts_user in active_users
from account_id in bts_user.UserData.AccountPermissions.AccountPermissions.Keys
select account_id
) on bts_account.Id equals account_id
select bts_account;
var users = active_users.ToDictionary(u => u.Id, u => u.Name);
var accounts = active_accounts.ToDictionary(a => a.Id, a => a.Name);
return (users, accounts);
}).Subscribe(t => {
_account_map = t.accounts;
_user_map = t.users;
_users_subject.OnNext(_user_map = t.users);
_accounts_subject.OnNext(_account_map = t.accounts);
});
}
public IObservable<IReadOnlyDictionary<uint, string>> on_users { get; }
public IObservable<IReadOnlyDictionary<uint, string>> on_accounts { get; }
public IObservable<TableUpdate<UserTuple, uint>> all_user_tuples { get; }
public IObservable<IReadOnlyList<AccountTuple>> on_account_tuples => on_accounts.Select(d => d.Select(kv => new AccountTuple(kv.Key, kv.Value)).ToListSorted(a => a.name));
public IObservable<IReadOnlyDictionary<uint, string>> on_users => _users_subject.AsObservable();
public IObservable<IReadOnlyDictionary<uint, string>> on_accounts => _accounts_subject.AsObservable();
public IObservable<IReadOnlyList<UserTuple>> on_user_tuples => on_users.Select(d => d.Select(kv => new UserTuple(kv.Key, kv.Value)).ToList());
public IObservable<IReadOnlyList<AccountTuple>> on_account_tuples => on_accounts.Select(d => d.Select(kv => new AccountTuple(kv.Key, kv.Value)).ToList());
public IObservable<AdminModelWrapper> on_admin_data => _wrapper_subject.AsObservable();
public IObservable<Update<T, TKey>> permission_filter<T, TKey>(IObservable<Update<T, TKey>> positions, Func<T, uint> get_account_id) where TKey : IEquatable<TKey> =>
user.permissions == UserTuple.Permissions.Admin ?
positions :
positions.Filter(on_accounts.Select(map => new Func<T, bool>(p => map.ContainsKey(get_account_id(p)))));
public AccountTuple? get_default_account() {
foreach (var kv in _account_map) {
return new AccountTuple(kv.Key, kv.Value);
}
return null;
}
public uint? get_user_id(string user) => _id_for_name(user, _user_map);
public uint? get_account_id(string account) => _id_for_name(account, _account_map);
public void Dispose() => Disposable.dispose(ref _dispose);
public IObservable<Update<T, TKey>> permission_filter<T, TKey>(IObservable<Update<T, TKey>> positions, Func<T, string> get_account_name) where TKey : IEquatable<TKey> =>
user.permissions == UserTuple.Permissions.Admin ?
positions :
positions.Filter(on_accounts.Select(map => {
var hash = new HashSet<string>(map.Values);
return new Func<T, bool>(v => hash.Contains(get_account_name(v)));
}));
public AccountTuple? get_default_account() =>
_account_map.Select(kv => new AccountTuple(kv.Key, kv.Value)).TryFirst();
public void Dispose() =>
Disposable.dispose(ref _dispose);
static uint? _id_for_name(string name, IReadOnlyDictionary<uint, string> map) {
foreach (var kv in map) {
if (kv.Value == name)
return kv.Key;
}
return null;
}
}
}
+8 -3
View File
@@ -3,10 +3,15 @@ using mbts;
using mbts.config;
using mbts.spark;
using mbts.ui;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
@@ -18,8 +23,8 @@ namespace bts.ui {
IDisposable _dispose;
[ImportingConstructor]
public AboutDaemon(IBus bus, UserTuple user) {
_user = user.name;
public AboutDaemon(IBus bus, [Import(LoginContainer.Exports.UserName)]string username) {
_user = username;
_bus = bus;
_dispose = bus.subscribe(SparkTags.About).OnDispatcher().Subscribe(_ => show_about());
}
@@ -58,4 +63,4 @@ namespace bts.ui {
Disposable.dispose(ref _dispose);
}
}
}
}
+7 -44
View File
@@ -7,11 +7,13 @@ using mbts.ui;
using System;
using System.ComponentModel.Composition;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using System.Windows;
using System.Threading.Tasks;
using mbts.ui.shell;
namespace bts.spark {
namespace bts.spark
{
[Export, Export(typeof(IShellDaemon))]
public class ErrorReporter : IShellDaemon, IDisposable {
readonly ErrorReportService _svc;
@@ -19,9 +21,9 @@ namespace bts.spark {
IDisposable _dispose;
[ImportingConstructor]
public ErrorReporter(ErrorReportService svc, IBus bus, UserTuple user) {
public ErrorReporter(ErrorReportService svc, IBus bus, [Import(LoginContainer.Exports.UserName)] string username) {
_svc = svc;
_username = user.name;
_username = username;
_dispose = bus.subscribe(SparkTags.Support).OnDispatcher().Subscribe(msg => show_support_window(msg, "Report a problem"));
}
@@ -46,45 +48,6 @@ namespace bts.spark {
ErrorMessage = message,
});
}
public async Task report_silently(string contact, string description, string userdata) {
var request = append_logs(new ErrorReport {
User = _username,
ErrorMessage = description,
UserDescription = userdata,
Contact = contact
}.immutable());
var reply = await _svc.send_report_async(request);
}
public static ErrorReportImmutable append_logs(ErrorReportImmutable src) {
var result = src.CloneToMutable();
var windows_log = LogUtil.read_windows_event_log();
if (!windows_log.IsEmpty) {
result.Files.Add(new FileData {
Name = "Application.evtx.gz",
Contents = windows_log
});
}
var (data,date) = LogUtil.read_latest_crash_dump();
if (!data.IsEmpty) {
result.Files.Add(new FileData {
Name = $"spark-{date.ToString("s")}.dmp.gz",
Contents = data
});
}
var spark_log = LogUtil.read_logs_compressed();
if (!spark_log.IsEmpty) {
result.Files.Add(new FileData {
Name = "log.gz",
Contents = spark_log
});
}
return result.immutable();
}
}
static class ErrorReporterExtensions {
+21 -13
View File
@@ -1,6 +1,5 @@
using bts.api;
using bts.api.synthetic;
using bts.md;
using mbts;
using mbts.co2;
using mbts.spark;
@@ -8,10 +7,15 @@ using mbts.ui;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Linq;
using SecurityKey = System.UInt32;
using System.Collections.Concurrent;
using System.Threading;
using bts.api.user;
using bts.md;
using mbts.ui.shell;
namespace bts {
[Export(typeof(IInitializable))]
@@ -21,21 +25,17 @@ namespace bts {
readonly Lazy<SyntheticSecurityService> _ss;
readonly Dictionary<SecurityKey, SecurityMD> _deleted_synthetics = new Dictionary<SecurityKey, SecurityMD>();
static uint get_naggle() => mbts.config.Configuration.get<md.RelayOptions>().naggle_ms;
[ImportingConstructor]
public MDDaemon(Lazy<IBus> bus, Lazy<AuthTokenImmutable> token, Lazy<SyntheticSecurityService> ss, UserTuple user)
: base(mbts.config.Configuration.get<md.RelayOptions>().naggle_ms, MDSourceOptions.DeferInit, user) {
public MDDaemon(Lazy<IBus> bus, Lazy<AuthTokenImmutable> token, Lazy<SyntheticSecurityService> ss, [Import(LoginContainer.Exports.UserID)]uint user_id) :
base(get_naggle(), MDSourceOptions.DeferInit, user_id) {
_bus = bus;
_token = token;
_ss = ss;
_ss = ss;
}
public int priority => 55;
[Export]
MDConsumer consumer => _consumer;
IBus bus => _bus.Value;
public override void initialize() {
override public void initialize() {
_relay.set_token(_token.Value);
_dispose.Add(
_ss.Value.subscribei_metadata()
@@ -60,8 +60,16 @@ namespace bts {
// Post full dictionary of syntheics here
bus.post(SparkTags.SyntheticstrategiesTag, t);
}));
_dispose.Add(on_securities.Subscribe(md => bus.post(SparkTags.SecuritiesTag, md)));
_dispose.Add( this.on_securities.Subscribe( (md) => {
bus.post(SparkTags.SecuritiesTag, md);
}));
Task.Run(run);
}
[Export]
MDConsumer consumer => _consumer;
public int priority => 55;
IBus bus => _bus.Value;
}
}
-55
View File
@@ -1,55 +0,0 @@
using System;
using System.ComponentModel.Composition;
using mbts.ui;
using mbts;
using Newtonsoft.Json;
using System.Threading.Tasks;
namespace bts.spark {
[Export(typeof(IShellDaemon))]
class OopsDaemon : IShellDaemon, IDisposable {
[ImportingConstructor]
public OopsDaemon(ErrorReporter e) {
var pid = get_pid();
if (pid.HasValue) {
handle_unclean_shutdown(e, pid.Value);
}
save_pid();
}
public void Dispose() {
clear_pid();
}
void handle_unclean_shutdown(ErrorReporter e, int oldpid) {
string extra = "";
try {
var conf = mbts.config.Configuration.get<bts.config.System>();
extra = JsonConvert.SerializeObject(conf, Formatting.Indented);
}
catch { }
Task.Run(() => e.report_silently(
$"Host: {Environment.MachineName}",
$"pid: {oldpid}\n{extra}",
"Automated crash report")
);
}
const string _subkey = @"Software\BTS";
private void save_pid() {
using (var key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(_subkey)) {
key.SetValue("pid", System.Diagnostics.Process.GetCurrentProcess().Id, Microsoft.Win32.RegistryValueKind.DWord);
}
}
private void clear_pid() {
using (var key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(_subkey)) {
key.DeleteValue("pid",false);
}
}
private int? get_pid() {
using (var key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(_subkey)) {
return key.GetValue("pid") as int?;
}
}
}
}
@@ -43,11 +43,6 @@ namespace bts.shell {
void _show_order_window(Window window) {
foreach (var kv in AlgoStudioModelUtils.get_dynamic_resources(_model.algo_model.ui_configuration))
window.Resources[kv.Key] = kv.Value;
// the dynamic algo 'SecurityKey' template has a ContractPickerPopup that expects the security map to be available as a DynamicResource.
var sub = _bus.subscribe(SparkTags.SecuritiesTag)
.OnDispatcher()
.Subscribe(sec_map => window.Resources[ResourceKeys.Securities] = sec_map);
window.Closed += (s, e) => Disposable.dispose(ref sub);
window.Show(-200, -160, true);
}
@@ -3,13 +3,17 @@ using bts.metrics;
using mbts;
using mbts.config;
using mbts.ui;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading.Tasks;
namespace bts.perf {
[Export, Export(typeof(IShellDaemon))]
class ShellLatencyDaemon : IShellDaemon, IDisposable {
readonly ShellLatencyService _service;
@@ -20,10 +24,10 @@ namespace bts.perf {
Ping _pinger = new Ping();
[ImportingConstructor]
public ShellLatencyDaemon(ShellLatencyService service, UserTuple user) {
public ShellLatencyDaemon(ShellLatencyService service, [Import(LoginContainer.Exports.UserName)] string user) {
_service = service;
_version = Configuration.get<bts.config.System>().version;
_username = user.name;
_username = user;
_hostname = Environment.MachineName;
_forever_ping_host(_pinger);
@@ -101,4 +105,4 @@ namespace bts.perf {
Disposable.dispose(ref _subs);
}
}
}
}
@@ -5,6 +5,7 @@ using mbts.co2;
using mbts.persist;
using mbts.spark;
using mbts.ui;
using mbts.ui.shell;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
@@ -12,11 +13,13 @@ using System.Threading.Tasks;
using System.Windows.Media;
using static mbts.ui.shell.ProtoBufSerializationHelper;
namespace bts.shell {
namespace bts.shell
{
[Export(typeof(IShellModule))]
class MarketShellModule : IShellModule {
class MarketShellModule : IShellModule
{
[ImportingConstructor]
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model) {
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model, [Import(LoginContainer.Exports.UserName)]string user_name) {
_tsf = new TimeAndSalesWindowFactory(this, bus, consumer, settings_hub);
_mvf = new MarketViewWindowFactory(this, bus, settings_hub, orders_model);
}
@@ -26,7 +26,6 @@ namespace mbts.spark {
readonly ObservableFilterCollection<OrderRecordImmutable> _working_orders, _working_bids, _working_asks;
readonly string _security_text, _security_key_text;
readonly Property<double> _bid_price, _ask_price, _last_price, _day_change, _settlement, _session_high, _session_low;
readonly Property<bool> _last_price_from_spread;
readonly CellProperty<int> _position;
readonly CellProperty<uint> _last_size;
readonly CellProperty<ulong> _volume;
@@ -55,7 +54,6 @@ namespace mbts.spark {
_day_change = backing(nameof(day_change), double.NaN);
_session_low = backing(nameof(session_low), double.NaN);
_session_high = backing(nameof(session_high), double.NaN);
_last_price_from_spread = backing(nameof(last_price_from_spread), false);
_settlement = backing(nameof(settlement), double.NaN);
_volume = new CellProperty<ulong>(this, nameof(volume), 0);
_inside_bids = new CellProperty<bool>(this, nameof(inside_bids));
@@ -111,7 +109,6 @@ namespace mbts.spark {
public int position => _position.value;
public double raw_day_position => _raw_day_position;
public double last_price => _last_price.Value;
public bool last_price_from_spread => _last_price_from_spread.Value;
public uint last_size => _last_size.value;
public double bid_price => _bid_price.Value;
public uint bid_direct_size => _bid_direct_size.Value;
@@ -170,17 +167,15 @@ namespace mbts.spark {
_inside_bids.on_next_distinct(_bid_price.Value >= _working_best_bid.value, ref dirty);
_inside_asks.on_next_distinct(_ask_price.Value <= _working_best_ask.value, ref dirty);
}
if (md.md.get_last_trade(ref _last_ref, out NativePriceSize ps, out DateTime ts, out ulong v, out bool from_spread)) {
if (md.md.get_last_trade(ref _last_ref, out NativePriceSize ps, out DateTime ts, out ulong v)) {
_last_price.set(ps.price);
_last_price_from_spread.set(from_spread);
_last_size.on_next_distinct(ps.size, ref dirty);
_volume.on_next_distinct(v, ref dirty);
}
dirty |= _settlement.set_new(md.md.settlement_price);
dirty |= _session_low.set_new(md.md.session_low);
dirty |= _session_high.set_new(md.md.session_high);
dirty |= _day_change.set_new(last_price - settlement);
_volume.on_next_distinct(md.md.total_volume, ref dirty);
_settlement.set(md.md.settlement_price);
_session_low.set(md.md.session_low);
_session_high.set(md.md.session_high);
_day_change.set(last_price - settlement);
if (dirty)
invalidated?.Invoke();
@@ -38,8 +38,7 @@ namespace mbts.spark {
IMultiValueConverter price_converter = new ui.spark.converters.TickDisplayConverter();
add_col(Columns.Security, "Security", r => r.security.ToString(), nameof(Row.security), DefaultColumnWidths.Security, security_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.Volume, "Volume", r => r.volume.ToString(), nameof(Row.volume), DefaultColumnWidths.Volume, volume_filter, DataGridFilterKnownTemplates.ULongMinMax);
add_col(Columns.LastSpd, "Spd", r => r.last_price_from_spread ? "S" : "", nameof(Row.last_price_from_spread), DefaultColumnWidths.Side, last_price_from_spread_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.Last, "Last", r => r.format_price(r.last_price), nameof(Row.last_price), DefaultColumnWidths.Price, last_price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
add_col(Columns.Last, "Last", r => r.format_price(r.last_price), nameof(Row.last_price), DefaultColumnWidths.Price, last_price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UIntMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.Position, "Pos", r => r.position.ToString(), nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.RawDayPosition, "Raw Day Pos", r => SparkUtils.fmt_position(r.raw_day_position), nameof(Row.raw_day_position), DefaultColumnWidths.Size, raw_day_position_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
@@ -139,7 +138,6 @@ namespace mbts.spark {
ask_direct_size_filter.load_state(filters.ask_direct_size);
ask_implied_size_filter.load_state(filters.ask_implied_size);
last_price_filter.load_state(filters.last_price);
last_price_from_spread_filter.load_state(filters.last_price_from_spread);
last_size_filter.load_state(filters.last_size);
day_change_filter.load_state(filters.day_change);
settlement_filter.load_state(filters.settlement);
@@ -157,7 +155,7 @@ namespace mbts.spark {
}), DataGridFilterHelper.hook_filters(rows_view, security_filter,
bid_price_filter, bid_merged_size_filter, bid_direct_size_filter, bid_implied_size_filter,
ask_price_filter, ask_merged_size_filter, ask_direct_size_filter, ask_implied_size_filter,
last_price_filter, last_price_from_spread_filter, last_size_filter, day_change_filter, settlement_filter,
last_price_filter, last_size_filter, day_change_filter, settlement_filter,
session_low_filter, session_high_filter, position_filter, raw_day_position_filter, working_bid_volume_filter, working_ask_volume_filter, volume_filter, security_key_filter),
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
WorkLoop.Start("MarketView", _refresh)
@@ -220,7 +218,6 @@ namespace mbts.spark {
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_direct_size, nameof(Row.ask_direct_size));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_implied_size, nameof(Row.ask_implied_size));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> last_price_filter { get; } = new DoubleMinMax<Row>(r => r.last_price, nameof(Row.last_price));
public IDataGridColumnPersistentFilter<Row, string> last_price_from_spread_filter { get; } = new RegexFilterVM<Row>(r => r.last_price_from_spread.ToString(), nameof(Row.last_price_from_spread));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> last_size_filter { get; } = new UIntMinMax<Row>(r => r.last_size, nameof(Row.last_size));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> day_change_filter { get; } = new DoubleMinMax<Row>(r => r.day_change, nameof(Row.day_change));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> settlement_filter { get; } = new DoubleMinMax<Row>(r => r.settlement, nameof(Row.settlement));
@@ -253,7 +250,6 @@ namespace mbts.spark {
ask_direct_size = ask_direct_size_filter.get_state(),
ask_implied_size = ask_implied_size_filter.get_state(),
last_price = last_price_filter.get_state(),
last_price_from_spread = last_price_from_spread_filter.get_state(),
last_size = last_size_filter.get_state(),
day_change = day_change_filter.get_state(),
settlement = settlement_filter.get_state(),
@@ -12,7 +12,6 @@ namespace mbts.ui.spark {
Security,
Volume,
Last,
LastSpd,
LastSize,
Position,
RawDayPosition,
@@ -100,7 +100,6 @@ namespace bts.shell {
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
public static GridColumnSettings volume => new GridColumnSettings(col_id(Columns.Volume), DefaultColumnHeaders.Volume, width: DefaultColumnWidths.Volume);
public static GridColumnSettings last => new GridColumnSettings(col_id(Columns.Last), DefaultColumnHeaders.Last, width: DefaultColumnWidths.Price);
public static GridColumnSettings last_from_spread => new GridColumnSettings(col_id(Columns.LastSpd), DefaultColumnHeaders.S, width: DefaultColumnWidths.Price, is_visible: false);
public static GridColumnSettings last_size => new GridColumnSettings(col_id(Columns.LastSize), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings position => new GridColumnSettings(col_id(Columns.Position), DefaultColumnHeaders.Pos, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings raw_day_position => new GridColumnSettings(col_id(Columns.RawDayPosition), DefaultColumnHeaders.RawDayPos, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right, is_visible: false);
@@ -139,7 +138,6 @@ namespace bts.shell {
yield return security;
yield return volume;
yield return last;
yield return last_from_spread;
yield return last_size;
yield return position;
yield return raw_day_position;
@@ -226,8 +224,6 @@ namespace bts.shell {
[ProtoMember(25)]
public string strip_name;
[ProtoMember(26)]
public string last_price_from_spread;
[ProtoMember(28)]
public string mic_code;
}
@@ -227,7 +227,7 @@ namespace mbts.ui.spark {
}
DataGridColumn _column_factory(LegSpreaderParamsTuple l) {
var col = new DataGridColumnInternal(l.leg.Security, ParameterCellTemplateConverter, ParameterCellEditingTemplateConverter);
var col = new DataGridColumnInternal(l.leg.Security, ParameterCellTemplateConverter, ParameterCellEditingTemplateConverter) { Width = 75, };
col.HeaderTemplate = (DataTemplate)FindResource(IsNew ? RemovableColumnHeaderTemplateKey : NonRemovableColumnHeaderTemplateKey);
DataGridEx.SetRightBorder(col, new DataGridExVerticalLine(false, true, Colors.DarkGray, 1));
return col;
@@ -1,9 +1,7 @@
using bts;
using bts.api;
using bts.api.core;
using bts.api;
using bts.api.synthetic;
using bts.collections;
using bts.spark;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
@@ -20,36 +18,11 @@ namespace mbts.spark {
readonly uint _user_id;
[ImportingConstructor]
public SpreaderModel(IBus bus, SyntheticSecurityService service, AdminModel admin_model, ErrorReporter error, UserTuple user) {
public SpreaderModel(IBus bus, SyntheticSecurityService service, ErrorReporter error, [Import(LoginContainer.Exports.UserID)]uint user_id) {
_bus = bus;
_service = service;
_error = error;
_user_id = user.id;
// synthetic tuples
var strategy_source = _bus.subscribe(SparkTags.SyntheticstrategiesTag)
.Select(map => MappedDictionary.create<uint, ExecutionStrategyImmutable, (uint security_id, ExecutionStrategyImmutable strategy)>(map, (k, v) => (k, v)))
.Table(tup => tup.security_id);
var synthetic_source = _bus.subscribe(SparkTags.SecuritiesTag)
.Select(map => new FilteredDictionary<uint, SecurityMD>(map, smd => smd.security.ContractCase == Security.ContractOneofCase.Synthetic))
.Table(smd => smd.security.Id);
var synthetic_user_join = synthetic_source.LeftJoin(
admin_model.all_user_tuples,
SecurityMD.key_fn,
UserTuple.key_fn,
smd => smd.security.Synthetic.UserId,
(smd, sid, ut, uid) => new SyntheticTuple(smd.security, ut.name, null),
(smd, sid, uid) => new SyntheticTuple(smd.security, null, null),
SyntheticTuple.key_fn);
synthetic_tuples = synthetic_user_join.LeftJoin(
strategy_source,
SyntheticTuple.key_fn,
tup => tup.security_id,
(synth_tup, id, strat_tup) => new SyntheticTuple(synth_tup.security, synth_tup.user_name, strat_tup.strategy),
(synth_tup, id) => synth_tup,
SyntheticTuple.key_fn);
_user_id = user_id;
}
public IObservable<IReadOnlyDictionary<uint, ExecutionStrategyImmutable>> strategy => _bus.subscribe(SparkTags.SyntheticstrategiesTag);
@@ -63,8 +36,6 @@ namespace mbts.spark {
.LeftJoin(strategies, (security, strategy) => (new ExecutionStrategyImmutable?(strategy), security), security => (new ExecutionStrategyImmutable?(), security))
);
public IObservable<TableUpdate<SyntheticTuple, uint>> synthetic_tuples { get; }
public IObservable<ExecutionStrategyImmutable> security_strategy(uint security_key) =>
strategy.TrySelect(s => {
bool success = s.TryGetValue(security_key, out ExecutionStrategyImmutable result);
@@ -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,7 +113,6 @@ 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
@@ -7,15 +7,8 @@ namespace mbts.ui.spark {
/// Interaction logic for SyntheticManagementView.xaml
/// </summary>
partial class SyntheticManagementView : UserControl {
public enum Columns {
Name,
Description,
n_legs,
n_working_orders,
SecurityKey,
User
}
public enum Columns { Name, Description, n_legs, n_working_orders, SecurityKey}
public SyntheticManagementView() =>
InitializeComponent();
}
@@ -10,7 +10,6 @@ using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
@@ -28,7 +27,7 @@ namespace mbts.spark {
readonly IWindowSettingsManager<GridSettings> _settings_manager;
public SyntheticManagementWindow(SyntheticManagementWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state, IWindowSettingsManager<GridSettings> settings_manager)
public SyntheticManagementWindow(SyntheticManagementWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state, WindowSettingsManager<GridSettings> settings_manager)
: base("Synthetic Management", bus, factory, SparkCommands.ShowSyntheticManagement.icon) {
_orders_model = orders_model;
_state = state;
@@ -67,8 +66,8 @@ namespace mbts.spark {
), SparkCommand.create("Clone", Icons.CloneSynthetic, SparkTags.ShowSyntheticCloneTag).complete(
current_row_connectable_builder.create(observe_row_strategy),
rs => rs.HasValue, select_row_strategy_tuple
), SparkCommands.ShowLadderAutoLookup(SparkCommands.KeyGestures.ShowLadder).complete(
vm_connectable_builder.create(vm => vm.current_security_observable.Select(s => s.HasValue ? ImmutableHashSet<uint>.Empty.Add(s.Value.Id) : null)), id => id != null
), SparkCommands.ShowLadder(SparkCommands.KeyGestures.ShowLadder).complete(
vm_connectable_builder.create(vm => vm.current_security_md), md => md.HasValue, ConvertUtils.ValueOrDefault
), SparkCommands.ShowOrders.complete(() => {
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
ob_settings.filters = new bts.shell.orderbook.FilterStates {
@@ -1,5 +1,4 @@
using bts.api;
using bts.settings;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
@@ -8,7 +7,6 @@ using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Reactive.Linq;
using System.Threading.Tasks;
using static mbts.ui.controls.DIYGrid;
@@ -35,8 +33,13 @@ namespace bts.shell {
public async void show(SparkTags.Nothing nothing) =>
_bus.post(SparkTags.ShowWindow, await load(new StartupData()));
IShellWindow _create(StartupData state) =>
new SyntheticManagementWindow(this, _bus, _orders_model, state, _settings_hub.create_synth_settings_manager(state.grid));
IShellWindow _create(StartupData state) {
var settings_manager = new WindowSettingsManager<GridSettings>(_settings_hub,
state.grid,
get: us => us.synthetic_management_grid,
set: (us, gs) => us.with(provided_synthetic_management_gridsettings: gs));
return new SyntheticManagementWindow(this, _bus, _orders_model, state, settings_manager);
}
[ProtoContract]
public class StartupData {
@@ -64,7 +67,6 @@ namespace bts.shell {
public static GridColumnSettings n_legs => new GridColumnSettings(col_id(Columns.n_legs), DefaultColumnHeaders.N_Legs, width: DefaultColumnWidths.N_Legs);
public static GridColumnSettings n_working_orders => new GridColumnSettings(col_id(Columns.n_working_orders), DefaultColumnHeaders.N_Working_Orders, width: DefaultColumnWidths.N_Working_Orders);
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, is_visible: false, width: DefaultColumnWidths.SecurityKey);
public static GridColumnSettings user => new GridColumnSettings(col_id(Columns.User), DefaultColumnHeaders.User, is_visible: false, width: DefaultColumnWidths.User);
public static IEnumerable<SortingCriteria> default_sorting_criterion {
get {
@@ -79,7 +81,6 @@ namespace bts.shell {
yield return n_legs;
yield return n_working_orders;
yield return security_key;
yield return user;
}
}
@@ -100,15 +101,6 @@ namespace bts.shell {
public UIntMinMaxState n_working_orders;
[ProtoMember(6)]
public string security_key;
[ProtoMember(7)]
public uint[] user;
}
// migration rules
[Export(typeof(ISyntheticManagementMigrationRule))]
class AddMissingColumnsMigrationRule : ISyntheticManagementMigrationRule {
public bool apply(GridSettings settings, out GridSettings new_settings) =>
settings.add_missing_columns(StartupData.DefaultGridSettings.columns, out new_settings);
}
}
}
}
@@ -1,33 +0,0 @@
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);
}
}
}
}
@@ -1,39 +0,0 @@
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,11 +115,6 @@ 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,8 +190,6 @@ 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();
@@ -229,8 +227,6 @@ 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,
@@ -1,15 +1,12 @@
using bts;
using bts.api;
using bts.api;
using bts.collections;
using bts.spark;
using bts.utils;
using mbts.persist;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.settings;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
@@ -20,45 +17,43 @@ using static mbts.ui.controls.DIYGrid;
using static mbts.ui.spark.SyntheticManagementView;
namespace mbts.spark {
class SyntheticManagementDialogVM : SecurityGrid<SyntheticManagementDialogVM.Row, SyntheticTuple> {
class SyntheticManagementDialogVM : SecurityMDGrid<SyntheticManagementDialogVM.Row, (ExecutionStrategyImmutable? strategy, SecurityMD md)> {
readonly IBus _bus;
readonly OrdersModel _orders_model;
readonly IWindowSettingsManager<GridSettings> _settings_manager;
IWindowSettingsManager<GridSettings> _settings_manager;
public SyntheticManagementDialogVM(IBus bus, OrdersModel orders_model, IWindowSettingsManager<GridSettings> settings_manager) : this(bus, orders_model, settings_manager, null, null) { }
public SyntheticManagementDialogVM(IBus bus, OrdersModel orders_model, IWindowSettingsManager<GridSettings> settings_manager, IReadOnlyCollection<SortingCriteria> sorting, FilterStates filters)
: base(orders_model.spreader.synthetic_tuples.Select(t => t.state.Values), sorting) {
: base(orders_model.spreader.strategy_tuple.Select(map => map.Values), sorting) {
_bus = bus;
_orders_model = orders_model;
_settings_manager = settings_manager;
// init filters
user_filter = DiscreteValuesFilterVM.CreateTableUpdate<Row, UserTuple, uint>(r => r.user_tuple, UserTuple.key_fn, out var user_filter_items_observer, out var user_filter_items_view, nameof(Row.user_tuple));
user_filter_items_view.CustomSort = Comparer<string>.Default.wrap<string, UserTuple>(ut => ut.name).to_non_generic();
if (filters != null) {
create_time_filter.load_state(filters.create_time);
name_filter.load_state(filters.name);
description_filter.load_state(filters.description);
n_legs_filter.load_state(filters.n_legs);
n_working_orders_filter.load_state(filters.n_working_orders);
security_key_filter.load_state(filters.security_key);
user_filter.load_state(filters.user);
security_key_filter.load_state(filters.security_key);
}
_settings_manager = settings_manager;
// columns
add_col(Columns.Name, DefaultColumnHeaders.Name, r => r.name, nameof(Row.name), name_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.Security);
add_col(Columns.Description, DefaultColumnHeaders.Description, r => r.description, nameof(Row.description), description_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.Description);
add_col(Columns.n_legs, DefaultColumnHeaders.N_Legs, r => r.n_legs.ToString(), nameof(Row.n_legs), n_legs_filter, DataGridFilterKnownTemplates.IntMinMax, DefaultColumnWidths.N_Legs);
columns.Add(new HyperlinkElementColumn<Row, Columns>(
Columns.n_working_orders,
new Binding(nameof(Row.n_working_orders)),
Columns.n_working_orders,
new Binding(nameof(Row.n_working_orders)),
new Binding(nameof(Row.show_order_book)),
n_working_orders_filter,
DataGridFilterKnownTemplates.UIntMinMax) {
id = col_id(Columns.n_working_orders),
Header = DefaultColumnHeaders.N_Working_Orders,
Width = DefaultColumnWidths.N_Working_Orders
});
}
);
add_col(Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, r => r.security_key.ToString(), nameof(Row.security_key), security_key_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.SecurityKey);
add_col(Columns.User, DefaultColumnHeaders.User, r => r.user_name, nameof(Row.user_name), user_filter, DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.User);
//
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, IDataGridColumnFilter filter, string filter_template, double width) =>
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
@@ -68,39 +63,42 @@ namespace mbts.spark {
Width = width
});
//
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, DictionaryHelper.empty<string, GridColumnSettingsFeatureFlags>(), row_vms);
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, row_vms);
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
grid_settings_manager.load_from(settings_manager.current_settings());
_resources.AddRange(
orders_model.admin_model.all_user_tuples.OnDispatcher().Subscribe(user_filter_items_observer),
DataGridFilterHelper.hook_all<SyntheticManagementDialogVM, Row>(this, rows_view),
DataGridFilterHelper.hook_filters(rows_view, create_time_filter, name_filter, description_filter, n_legs_filter, n_working_orders_filter, security_key_filter),
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
);
}
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
protected override Row row_factory((ExecutionStrategyImmutable? strategy, SecurityMD md) row_model) =>
new Row(_bus, row_model.md, row_model.strategy, _orders_model);
protected override void on_row_removed(Row row) => row.Dispose();
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
public GridSettings get_grid_save_state() {
if (_settings_manager.instance_settings == null)
return null;
else
return grid_settings_manager.create();
}
public StartupData create() =>
new StartupData(rows_view.get_sorting_criteria(columns), _get_filter_states(), get_grid_save_state());
#region Filters
public IDataGridColumnPersistentFilter<Row, TimeFilterTuple> create_time_filter { get; } = new TimeFilterVM<Row>(r => r.create_time, nameof(Row.create_time));
public IDataGridColumnPersistentFilter<Row, string> name_filter { get; } = new RegexFilterVM<Row>(r => r.name, nameof(Row.name));
public IDataGridColumnPersistentFilter<Row, string> description_filter { get; } = new RegexFilterVM<Row>(r => r.description, nameof(Row.description));
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> n_legs_filter { get; } = new IntMinMax<Row>(r => r.n_legs, nameof(Row.n_legs));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> n_working_orders_filter { get; } = new UIntMinMax<Row>(r => r.n_working_orders, nameof(Row.n_legs));
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
public IDataGridColumnPersistentFilter<Row, uint[]> user_filter { get; }
public GridSettings get_grid_save_state() =>
_settings_manager.instance_settings == null ? null : grid_settings_manager.create();
public StartupData create() =>
new StartupData(rows_view.get_sorting_criteria(columns), _get_filter_states(), get_grid_save_state());
protected override Row row_factory(SyntheticTuple row_model) =>
new Row(_bus, row_model, _orders_model);
protected override void on_row_removed(Row row) =>
row.Dispose();
FilterStates _get_filter_states() =>
new FilterStates {
@@ -109,19 +107,19 @@ namespace mbts.spark {
name = name_filter.get_state(),
n_legs = n_legs_filter.get_state(),
n_working_orders = n_working_orders_filter.get_state(),
security_key = security_key_filter.get_state(),
user = user_filter.get_state()
security_key = security_key_filter.get_state()
};
#endregion
public class Row : SecurityRow, IEquatable<Row>, IRowVM, IActionableRowVM {
public readonly SyntheticTuple synthetic_tuple;
public class Row : SecurityMDRow, IEquatable<Row>, IRowVM, IActionableRowVM {
public readonly ExecutionStrategyImmutable? strategy;
readonly IBus _bus;
readonly Property<uint> _n_working_orders;
public Row(IBus bus, SyntheticTuple synthetic_tuple, OrdersModel orders_model) : base(synthetic_tuple.security) {
public Row(IBus bus, SecurityMD security_md, ExecutionStrategyImmutable? strategy, OrdersModel orders_model) : base(security_md) {
_bus = bus;
this.synthetic_tuple = synthetic_tuple;
this.strategy = strategy;
_n_working_orders = backing(nameof(n_working_orders),
orders_model.on_security_orders(security_key)
.Select(t => (uint)(t.state
@@ -135,23 +133,25 @@ namespace mbts.spark {
show_order_book = new DelegateCommand(_show_order_book);
}
public event Action invalidated;
public DateTime create_time => DateTime.MinValue; //TODO
public string name { get; }
public string user_name => synthetic_tuple.user_name;
public UserTuple user_tuple => new UserTuple(synthetic_tuple.security.Synthetic.UserId, user_name);
public int n_legs { get; }
public string description { get; }
public ExecutionStrategyImmutable? strategy => synthetic_tuple.strategy;
public bool strategy_present => strategy.HasValue;
public uint n_working_orders => _n_working_orders.Value;
public ICommand show_ladder { get; }
public ICommand show_order_book { get; }
public ICommand default_command => show_ladder;
public void raise_invalidated() =>
invalidated?.Invoke();
public ObservableCollection<RowAnimation> row_animations => new ObservableCollection<RowAnimation>();
public IReadOnlyList<IGridCellStore> cell_stores => new IGridCellStore[0];
public event Action invalidated;
public void raise_invalidated() => invalidated?.Invoke();
public bool Equals(Row other) =>
other != null &&
@@ -163,8 +163,10 @@ namespace mbts.spark {
public override int GetHashCode() =>
security_key.GetHashCode();
void _show_ladder() =>
_bus.post(SparkTags.ShowLadderAutoLookupTag, ImmutableHashSet<uint>.Empty.Add(security.Id));
_bus.post(SparkTags.ShowLadderTag, md);
void _show_order_book() {
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
@@ -175,4 +177,4 @@ namespace mbts.spark {
}
}
}
}
}
@@ -62,10 +62,7 @@ namespace mbts.spark {
_vm = new AlgoStudioVM(_model);
_vm.load_from(_state);
_view = new AlgoStudioView { DataContext = _vm };
_resources = new CompositeDisposable(
_bus.subscribe(SparkTags.SecuritiesTag)
.OnDispatcher()
.Subscribe(sec_map => _view.Resources[ResourceKeys.Securities] = sec_map));
_resources = new CompositeDisposable();
// add definition
_commands.Add(new SparkCommandCommand("Add Algo", Icons.AddBoxes, _vm.add_definition_cmd));
// clone definition
@@ -27,7 +27,7 @@ namespace mbts.spark {
_error_reporter = error_reporter;
definitions =
service.subscribei_algo_definitions()
.Filter(r => r.Metadata.IsActive && _admin_model.user.can_access_user_data(r.Metadata.CreatorUserId))
.Filter(r => r.Metadata.IsActive && r.Metadata.CreatorUserId == _admin_model.user.id)
.SelectKeyValue(_definition_data, d => d.key)
.Table()
.Cache();
@@ -13,9 +13,6 @@ namespace mbts.spark {
string _template_key;
ICommand _set_value_cmd;
public AlgoParamRowVM(IAlgoVM algo, IAlgoParamVM param, AlgoParamRowVM parent)
: this(algo, param, parent, (param as ITemplateSelectorKey)?.key) { }
public AlgoParamRowVM(IAlgoVM algo, IAlgoParamVM param, AlgoParamRowVM parent, string template_key = null) {
this.param = param;
this.algo = algo;
@@ -247,31 +247,6 @@
</TextBlock>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<u:TemplateSelectorKeyValue Key="{x:Static u:TemplateKeys.SecurityKey}">
<DataTemplate>
<Grid>
<!--Note: Ideally, this 'securities BindingProxy' would be a static resource, but it only seems to detect the dynamic securities when it is in the visual tree.-->
<!--<Grid.Resources>
<u:BindingProxy x:Key="SecuritiesBindingProxy" x:Shared="False" Value="{DynamicResource {x:Static u:ResourceKeys.Securities}}"/>
</Grid.Resources>-->
<u:BindingProxy x:Name="SecuritiesBindingProxy" Value="{DynamicResource {x:Static u:ResourceKeys.Securities}}"/>
<TextBlock>
<TextBlock.Text>
<MultiBinding>
<MultiBinding.Converter>
<cv:MultiValueConverterPipe>
<cv:DictionaryLookupConverter/>
<x:Static MemberType="cv:ValueConverters" Member="SecuritySelectorText"/>
</cv:MultiValueConverterPipe>
</MultiBinding.Converter>
<Binding Path="Value" ElementName="SecuritiesBindingProxy"/>
<Binding Path="param.value"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
</DataTemplate>
</u:TemplateSelectorKeyValue>
</usp:AlgoParamTemplateSelector.KeyedTemplates>
<!--Bool-->
<usp:AlgoParamTemplateSelector.BoolTemplate>
@@ -345,21 +320,6 @@
</usp:PriceEntryControl>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<u:TemplateSelectorKeyValue Key="{x:Static u:TemplateKeys.SecurityKey}">
<DataTemplate>
<c:ContractPickerPopup
CloseOnCancel="True"
CloseOnOK="True"
Header="{Binding SelectedSecurity, RelativeSource={RelativeSource Self}, Converter={x:Static cv:ValueConverters.SecuritySelectorText}}"
IsEditMode="False"
IsLazy="True"
SecurityMap="{DynamicResource {x:Static u:ResourceKeys.Securities}}"
SelectedSecurityKey="{Binding param.value}"
ShowDialogSkirt="True"
OKClick="{x:Static c:ContractPickerPopup.FlushSelectedSecurityKeyHandler}"
/>
</DataTemplate>
</u:TemplateSelectorKeyValue>
</usp:AlgoParamTemplateSelector.KeyedTemplates>
<!--Bool-->
<usp:AlgoParamTemplateSelector.BoolTemplate>
@@ -118,14 +118,13 @@ namespace mbts.spark {
add_col_expl(Columns.Side, new SideColumn<Row, Columns>(Columns.Side, r => r.side, side_filter) { id = col_id(Columns.Side) }, side_filter);
// all others (easily identified by the enum 'Columns.Foo')
add_col(Columns.FillTime, DefaultColumnHeaders.FillTime, r => r.fill_time.ToString("M/d h:mm:ss tt"), new TimeFilterVM<FillTuple>(r => r.fill.Timestamp.ToDateTimeOffset().LocalDateTime), DataGridFilterKnownTemplates.TimeFilter, DefaultColumnWidths.Time);
add_col(Columns.User, DefaultColumnHeaders.User, r => r.user, dv_filter_rx(_model.factory.subscribe_users().Select(d => d.Values.Ordered()), r => r.order?.user() ?? string.Empty), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.User);
add_col(Columns.Account, DefaultColumnHeaders.Account, r => r.account, dv_filter_rx(_model.factory.subscribe_accounts().Select(d => d.Values.Ordered()), r => r.account), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Account);
add_col(Columns.User, DefaultColumnHeaders.User, r => r.user, dv_filter_rx(_model.factory.subscribe_users().Select(d => d.Values), r => r.order?.user() ?? string.Empty), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.User);
add_col(Columns.Account, DefaultColumnHeaders.Account, r => r.account, dv_filter_rx(_model.factory.subscribe_accounts().Select(d => d.Values), r => r.account), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Account);
add_col(Columns.Exchange, DefaultColumnHeaders.Exchange, r => r.exchange.ToString(), dv_filter(EnumHelper.get_values<Venue>().ToArray(), r => r.security.exchange_or_default()), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Exchange);
add_col(Columns.TimeInForce, DefaultColumnHeaders.TimeInForce, r => r.time_in_force.ToString(), dv_filter(EnumHelper.get_values<TimeInForce>().ToArray(), r => r.order?.time_in_force() ?? TimeInForce.Undefined), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.TimeInForce);
add_col(Columns.Size, DefaultColumnHeaders.Size, r => r.size.ToString(), new DoubleMinMax<FillTuple>(r => r.fill.LastShares), DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.Size);
add_col(Columns.Price, DefaultColumnHeaders.Price, r => r.format_price(r.price), new DoubleMinMax<FillTuple>(r => r.fill.LastPrice), DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.Price);
add_col(Columns.FillKind, DefaultColumnHeaders.FillKind, r => r.fill_kind.ToString(), dv_filter(EnumHelper.get_values<FillKind>().ToArray(), r => r.fill.FillKind), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.FillKind);
add_col(Columns.FillStatus, DefaultColumnHeaders.FillStatus, r => r.fill_status.ToString(), dv_filter(EnumHelper.get_values<FillStatus>().ToArray(), r => r.fill.FillStatus), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.FillKind);
add_col(Columns.FillKey, DefaultColumnHeaders.FillKey, r => r.fill_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.FillKey), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.FillKey);
add_col(Columns.OrderKey, DefaultColumnHeaders.OrderKey, r => r.order_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.OrderKey.ToString()), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.OrderKey);
add_col(Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, r => r.security_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.SecurityKey.ToString()), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.SecurityKey);
@@ -147,7 +146,6 @@ namespace mbts.spark {
});
_dispose = new CompositeDisposable(
ObservableAutoWrapper.connect(col_tups, columns, ct => ct.col),
ObservableAutoWrapper.connect_item_hooks(col_tups, ct => ct.filter as IDisposable),
RXCollection.connect(rows, fills_buffer.Filter(filter_src, FillTuple.key_fn), comp_src, ft => new Row(ft, model), r => r.fill_tuple),
fills_source.OnDispatcher().Subscribe(t => {
last_unfiltered_fills = t;
@@ -229,7 +227,6 @@ namespace mbts.spark {
case Columns.Exchange: return wrap_default(ft => ft.security.exchange_or_default());
case Columns.FillKey: return wrap(ft => ft.fill.FillKey);
case Columns.FillKind: return wrap_default(ft => ft.fill.FillKind);
case Columns.FillStatus: return wrap_default(ft => ft.fill.FillStatus);
case Columns.FillTime: return wrap(ft => ft.fill.Timestamp.to_local_time());
case Columns.OrderKey: return wrap(ft => ft.fill.OrderKey);
case Columns.OrderType: return wrap_default(ft => ft.order?.order_type() ?? OrderType.None);
@@ -285,6 +282,7 @@ 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));
@@ -295,7 +293,6 @@ namespace mbts.spark {
public double price => _fill.LastPrice;
public double size => _fill.LastShares;
public FillKind fill_kind => _fill.FillKind;
public FillStatus fill_status => _fill.FillStatus;
public string fill_key => _fill.FillKey;
public string order_key => _order.Key.ToString();
@@ -303,7 +300,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 => fill_tuple.root_order_security?.display_name() ?? OrderType.None.display_name();
public string root_algo_display_name => _root_algo?.name ?? root_algo_order_type.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);
@@ -21,7 +21,6 @@ namespace mbts.ui.spark {
Price,
Size,
FillKind,
FillStatus,
FillKey,
OrderKey,
SecurityKey
@@ -37,8 +37,7 @@ namespace bts.shell {
{ nameof(FillsVM.Row.side), Columns.Side },
{ nameof(FillsVM.Row.size), Columns.Size },
{ nameof(FillsVM.Row.time_in_force), Columns.TimeInForce },
{ nameof(FillsVM.Row.user), Columns.User },
{ nameof(FillsVM.Row.fill_status ), Columns.FillStatus },
{ nameof(FillsVM.Row.user), Columns.User }
};
public FillsWindowFactory(TradingShellModule module, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model)
@@ -74,10 +74,6 @@ namespace bts.shell.fills {
[ProtoMember(15)]
[DIYGridColumnID(nameof(Columns.Symbol))]
public string[] symbol { get; set; }
[ProtoMember(17)]
[DIYGridColumnID(nameof(Columns.FillStatus))]
public Fill.Types.FillStatus[] fill_status { get; set; }
}
static class FillsFilterStatesExtensions {
@@ -100,7 +96,6 @@ namespace bts.shell.fills {
pb.add_discrete_values(fs.side, ft => ft.fill.Side);
pb.add_discrete_values(fs.fill_kind, ft => ft.fill.FillKind);
pb.add_discrete_values(fs.symbol, ft => ft.security?.Symbol ?? string.Empty);
pb.add_discrete_values(fs.fill_status, ft => ft.fill.FillStatus);
// numeric
pb.add_numeric(fs.size, ft => ft.fill.LastShares);
pb.add_numeric(fs.price, ft => ft.fill.LastPrice);
@@ -23,10 +23,6 @@ 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,
@@ -78,7 +74,14 @@ namespace mbts.spark {
other.show == show;
public override bool Equals(object obj) => Equals(obj as LadderColumnSettings);
public override int GetHashCode() => _type_hash;
public override int GetHashCode() =>
width.GetHashCode() +
background.GetHashCode() +
foreground.GetHashCode() +
font_family.GetHashCode() +
font_size.GetHashCode() +
font_weight.GetHashCode();
public static bool operator ==(LadderColumnSettings x, LadderColumnSettings y) => x.safe_equals(y);
public static bool operator !=(LadderColumnSettings x, LadderColumnSettings y) => !(x == y);
@@ -86,9 +89,6 @@ 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() => _type_hash;
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
bars.GetHashCode(),
show_numbers.GetHashCode());
}
[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,14 +149,19 @@ namespace mbts.spark {
show_ltq.Equals(other.show_ltq);
public override bool Equals(object obj) => Equals(obj as VolumeColumnSettings);
public override int GetHashCode() => _type_hash;
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
general.GetHashCode(),
buy.GetHashCode(),
sell.GetHashCode(),
passive.GetHashCode(),
total.GetHashCode(),
show_ltq.GetHashCode());
}
[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)]
@@ -179,7 +184,8 @@ namespace mbts.spark {
Equals(obj as LadderMarketBreakLine);
public override int GetHashCode() =>
_type_hash;
show.GetHashCode() +
(int)color;
}
[ProtoContract]
@@ -249,11 +255,6 @@ 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,
@@ -496,9 +497,6 @@ 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);
@@ -535,81 +533,112 @@ 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) =>
profile_equals(this, 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 &&
default_algo == other.default_algo &&
left_panel_scroll_position == other.left_panel_scroll_position;
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;
public override bool Equals(object obj) => Equals(obj as LadderSettings);
public override int GetHashCode() => _type_hash;
// 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 struct WindowSpecifics {
public readonly double order_entry_panel_width, zoom_factor;
@@ -1,5 +1,4 @@
using bts.api;
using bts.collections;
using bts.md;
using mbts;
using mbts.config;
@@ -51,7 +50,6 @@ namespace bts.shell {
_mbo = mbo;
_dispose = Disposable.Create(
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
bus.subscribe(SparkTags.ShowLadderAutoLookupTag).Subscribe(_show_windows),
bus.subscribe(SparkTags.ShowLadderTag).OnDispatcher().Subscribe(s => show_windows(new[] { s })),
bus.subscribe(SparkTags.ShowLaddersTag).OnDispatcher().Subscribe(show_windows)
);
@@ -81,9 +79,11 @@ namespace bts.shell {
if (!_settings_hub.current.ladder_profiles.TryGetFirst(lp => lp.id == id, out var result)) {
// profile was deleted
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;
} 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;
}
}
}
return new LadderSaveState { security_key = vm.security.Id, settings = s, settings_id = id };
@@ -147,7 +147,7 @@ namespace bts.shell {
var curr = vm.get_save_state();
if (settings_id == 0) {
last_value = curr;
} else if (!LadderSettings.profile_equals(curr, last_value)) {
} else if (!curr.Equals(last_value)) {
settings_id = 0;
last_value = curr;
}
@@ -161,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 (!LadderSettings.profile_equals(last_value, vm.get_save_state())) {
if (!last_value.Equals(vm.get_save_state())) {
settings_id = 0;
} else {
settings_subject.OnNext(last_value = profile.value);
@@ -209,7 +209,6 @@ namespace bts.shell {
_ => _,
vm.get_save_state(),
UserSettings.FileExtensions.LADDER,
LadderSettings.profile_equals,
settings_id);
dialog_vm.applied += apply;
// dialog
@@ -274,21 +273,7 @@ namespace bts.shell {
});
}
async void _show_windows(IImmutableSet<uint> securities) {
if (securities == null)
return;
var start_time = DateTime.Now;
do {
var found = securities.KeyJoin(_securities).ToList();
securities = securities.RemoveRange(found.Select(SecurityMD.key_fn));
show_windows(found);
if (securities.Count == 0)
break;
await Task.Delay(500);
} while ((DateTime.Now - start_time) < _ladder_conf.security_timeout);
}
void show_windows(IEnumerable<SecurityMD> securities) {
void show_windows(IReadOnlyCollection<SecurityMD> securities) {
int n_allowed = (int)_ladder_conf.max_open_ladders - _wm.list_windows().Where(w => w.window_type == _window_type).Count();
int n_opened = 0;
foreach (var s in securities) {
@@ -35,14 +35,7 @@ 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,
_left_panel_extent_height,
_left_panel_view_port_height,
_left_panel_scroll_position,
_left_panel_scroll_position_target;
double _position, _day_position, _raw_day_position;
bool _show_account_position, _show_day_position, _show_raw_day_position;
ulong _misc_time = 0;
ulong _last_trade_time = 0;
@@ -83,7 +76,6 @@ 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);
@@ -341,33 +333,6 @@ 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 {
@@ -399,10 +364,6 @@ 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();
@@ -433,7 +394,6 @@ 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,14 +301,7 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<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"
>
<ScrollViewer Grid.Row="0" Grid.RowSpan="2" VerticalScrollBarVisibility="Hidden">
<Grid RowSpan="2" Background="{mbts:BTSColor UI.spark_menu_background}">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
@@ -1,17 +1,11 @@
using bts;
using bts.collections;
using bts.utils;
using mbts.ui;
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;
@@ -86,8 +80,6 @@ namespace mbts.spark {
_ladder.visible_columns_changed += () => _column_headers_presenter.Columns.SetItems(
_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;
@@ -95,39 +87,7 @@ 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
@@ -62,15 +62,15 @@
Header="{Binding security}"
IsEditMode="False"
IsLazy="True"
SecurityMap="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Securities}}"
SecurityMap="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.SecuritiesKey}}"
SelectedSecurity="{Binding security, UpdateSourceTrigger=Explicit}"
ShowDialogSkirt="True"
ToolTip="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
OKClick="{x:Static c:ContractPickerPopup.FlushSelectedSecurityHandler}"
/>
<ComboBox Grid.Row="2" Grid.Column="2" ItemsSource="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Accounts}}" SelectedItem="{Binding account}"/>
<ComboBox Grid.Row="2" Grid.Column="4" ItemsSource="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Users}}" SelectedItem="{Binding user}"/>
<ComboBox Grid.Row="2" Grid.Column="6" ItemsSource="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Sides}}" SelectedItem="{Binding side}" Width="48"/>
<ComboBox Grid.Row="2" Grid.Column="2" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.AccountsKey}}" SelectedItem="{Binding account}"/>
<ComboBox Grid.Row="2" Grid.Column="4" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.UsersKey}}" SelectedItem="{Binding user}"/>
<ComboBox Grid.Row="2" Grid.Column="6" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.SidesKey}}" SelectedItem="{Binding side}" Width="48"/>
<c:TickUpDownUInt Grid.Row="2" Grid.Column="8" MinWidth="40" Value="{Binding size}"/>
<usp:PriceEntryControl Grid.Row="2" Grid.Column="10" MinWidth="55" Value="{Binding price}" ContractSpec="{Binding spec}"/>
</Grid>
@@ -6,6 +6,12 @@ namespace mbts.ui.spark {
/// Interaction logic for ManualFillRowEditor.xaml
/// </summary>
partial class ManualFillRowEditor : UserControl {
public const string
SecuritiesKey = nameof(SecuritiesKey),
AccountsKey = nameof(AccountsKey),
UsersKey = nameof(UsersKey),
SidesKey = nameof(SidesKey);
public static readonly RoutedCommand
CommitCommand = new RoutedCommand(),
CancelCommand = new RoutedCommand();
@@ -70,7 +70,7 @@ namespace bts.shell.manual_fills {
mfd => new ManualFillRow(mfd),
mfr => mfr.data),
_bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m => {
security_map = new FilteredDictionary<uint, SecurityMD>(m, smd => smd.security.ContractCase == Security.ContractOneofCase.Future);
security_map = m;
RaisePropertyChanged(nameof(security_map));
}),
ObservableAutoWrapper.connect_item_hooks(_persisted_rows, r => Disposable.Create(dirty_mon.join(r), selection_mon.join(r))),
@@ -50,11 +50,11 @@ namespace mbts.ui.spark {
row_editor.CommandBindings.Add(new CommandBinding(ManualFillRowEditor.CommitCommand, commit_executed));
row_editor.CommandBindings.Add(new CommandBinding(ManualFillRowEditor.CancelCommand, cancel_executed));
// dynamic resources
row_editor.Resources[ResourceKeys.Accounts] = vm.accounts;
row_editor.Resources[ResourceKeys.Users] = vm.users;
row_editor.Resources[ResourceKeys.Sides] = vm.sides;
row_editor.Resources[ManualFillRowEditor.AccountsKey] = vm.accounts;
row_editor.Resources[ManualFillRowEditor.UsersKey] = vm.users;
row_editor.Resources[ManualFillRowEditor.SidesKey] = vm.sides;
FocusManager.SetIsFocusScope(row_editor, true);
var sec_hook = vm.as_demuxer().subscribe(nameof(ManualFillsVM.security_map)).OnDispatcher().Subscribe(v => row_editor.Resources[ResourceKeys.Securities] = v);
var sec_hook = vm.as_demuxer().subscribe(nameof(ManualFillsVM.security_map)).OnDispatcher().Subscribe(v => row_editor.Resources[ManualFillRowEditor.SecuritiesKey] = v);
// popup adorner
var popup_adorner = new PopupAdorner(
data_grid, data_grid,
@@ -121,7 +121,7 @@ namespace mbts.spark {
r => r.user,
DefaultColumnWidths.User,
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, string>(
model.factory.subscribe_users().Select(d => d.Values.Ordered()),
model.factory.subscribe_users().Select(d => d.Values),
ort => ort.order.order.user(),
nameof(OrderBookRowVM.user)),
DataGridFilterKnownTemplates.DiscreteValues,
@@ -133,7 +133,7 @@ namespace mbts.spark {
r => r.account,
DefaultColumnWidths.Account,
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, string, string[]>(
model.factory.subscribe_accounts().Select(d => d.Values.Ordered()),
model.factory.subscribe_accounts().Select(d => d.Values),
ort => ort.order.order.account(),
vals => vals.ToArray(),
vals => Observable.Return(vals),
@@ -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.display_name();
public string root_algo_display_name => order_tuple.root.algo.name;
// Tree
public bool is_parent { get => _is_parent; set => Set(ref _is_parent, value); }
@@ -239,7 +239,7 @@ namespace mbts.spark {
grid_settings_manager,
order_mod_vm.algo_vm.connect_data(Observable.Return(model)),
order_mod_vm.algo_vm.connect_data(_selected_params_mon.subscribe_spreader_security()),
order_mod_vm,
order_mod_vm.algo_vm,
attach_vm,
_show_fills.observe_current(true)
.Subscribe(is_visible => {
@@ -735,13 +735,17 @@ namespace mbts.spark {
_size_permissions = AlgoParamPermissions.ReadOnly | AlgoParamPermissions.Write;
SecurityMD? _security_md;
public OrderParamsVM(OrderBookVM owner, AlgoFormats init_format, AlgoEditingPermissions init_permissions) {
public OrderParamsVM(OrderBookVM owner, AlgoFormats init_format, AlgoEditingPermissions init_permissions)
: this(owner,
AlgoDefinitionVM.create(
owner._model,
AlgoData.create_unpersisted(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg), CustomOrderDefinition.CustomOneofCase.Iceberg.display_name()),
init_format: init_format,
editing_permissions: init_permissions)) { }
OrderParamsVM(OrderBookVM owner, AlgoDefinitionVM algo_vm) {
_owner = owner;
algo_vm = AlgoDefinitionVM.create(
owner._model,
AlgoData.create_unpersisted(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg), CustomOrderDefinition.CustomOneofCase.Iceberg.display_name()),
init_format: init_format,
editing_permissions: init_permissions);
this.algo_vm = algo_vm;
_accounts = backing(
nameof(selected_account),
_model.factory.subscribe_account_tuples(),
@@ -780,8 +784,7 @@ namespace mbts.spark {
appended_param_rows.insert_front(new AlgoParamRowVM(root, _price_param, null, AlgoParamTemplateKeys.Price));
}
}),
ObservableAutoWrapper.connect_item_hooks(param_rows, dirty_params_mon.join),
algo_vm);
ObservableAutoWrapper.connect_item_hooks(param_rows, dirty_params_mon.join));
// payup methods
void payup_bid(int ticks) => payup(bbo.bid, -ticks);
void payup_ask(int ticks) => payup(bbo.ask, ticks);
@@ -66,10 +66,6 @@ namespace mbts.spark {
_vm = new OrderBookVM(_bus, _notification_service, _model, _settings_manager);
_vm.load_from(_state);
_view = new OrderBookView(_vm);
_vm.SubscribeDisposed(
_bus.subscribe(SparkTags.SecuritiesTag)
.OnDispatcher()
.Subscribe(sec_map => _view.Resources[ResourceKeys.Securities] = sec_map));
var show_fills_command = SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(() => new FillsSettings(_vm.selected_orders.Select(r => r.Key).ToArray()));
@@ -501,12 +501,6 @@ namespace bts.shell.pnl {
account_name == other.account_name &&
sod == other.sod &&
day == other.day &&
bid_price.Equals(other.bid_price) &&
ask_price.Equals(other.ask_price) &&
last_price.Equals(other.last_price) &&
mid_price.Equals(other.mid_price) &&
settlement_price.Equals(other.settlement_price) &&
calc_type.Equals(other.calc_type) &&
raw_day == other.raw_day &&
working.summary == other.working.summary;
@@ -97,22 +97,22 @@ namespace bts.shell.pnl {
if (security.HasValue && security.Value.ContractCase != Security.ContractOneofCase.Future) {
day_long = day_short = sod_long = sod_short = 0;
} else {
day_long = DoubleUtil.round_to_decimal(data.day.long_pos);
day_short = DoubleUtil.round_to_decimal(data.day.short_pos);
sod_long = DoubleUtil.round_to_decimal(data.sod.long_pos);
sod_short = DoubleUtil.round_to_decimal(data.sod.short_pos);
day_long = (decimal)DoubleUtil.nan_to_zero(data.day.long_pos);
day_short = (decimal)DoubleUtil.nan_to_zero(data.day.short_pos);
sod_long = (decimal)DoubleUtil.nan_to_zero(data.sod.long_pos);
sod_short = (decimal)DoubleUtil.nan_to_zero(data.sod.short_pos);
}
var nd = new PNLNormalizedData(
DoubleUtil.round_to_decimal(day_pnl.value),
(decimal)DoubleUtil.nan_to_zero(day_pnl.value),
day_long,
day_short,
DoubleUtil.round_to_decimal(data.raw_day.long_pos),
DoubleUtil.round_to_decimal(data.raw_day.short_pos),
DoubleUtil.round_to_decimal(sod_pnl.value),
(decimal)DoubleUtil.nan_to_zero(data.raw_day.long_pos),
(decimal)DoubleUtil.nan_to_zero(data.raw_day.short_pos),
(decimal)DoubleUtil.nan_to_zero(sod_pnl.value),
sod_long,
sod_short,
DoubleUtil.round_to_decimal(data.working_long_pos),
DoubleUtil.round_to_decimal(data.working_short_pos));
(decimal)DoubleUtil.nan_to_zero(data.working_long_pos),
(decimal)DoubleUtil.nan_to_zero(data.working_short_pos));
if (_normalized_data != nd) {
_normalized_data = nd;
updated?.Invoke(nd);
@@ -103,7 +103,7 @@ namespace bts.shell.pnl {
load_from(settings);
_dispose = Disposable.Create(
pnl_source.Operate(
_calculation_type.observe_current(true),
_calculation_type.observe_current(),
(data, calc) => data.with(calc_type: calc)
).FilterWithRaw(filter_src, PNLRowData.key_fn, null)
.OnDispatcher()
+6 -11
View File
@@ -1,5 +1,4 @@
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@@ -7,9 +6,8 @@ using System.Reactive.Linq;
using System.Runtime.CompilerServices;
namespace mbts.spark {
class AlgoParamVM<T> : IAlgoParamVM<T>, INotifyPropertyChanged, ITemplateSelectorKey {
class AlgoParamVM<T> : IAlgoParamVM<T>, INotifyPropertyChanged {
readonly IEqualityComparer<T> _comparer;
readonly string _template_key;
AlgoParamPermissions _permissions;
bool _is_enabled, _has_value;
T _committed_value, _value;
@@ -17,16 +15,15 @@ namespace mbts.spark {
public AlgoParamVM(string id, string display_name)
: this(id, display_name, default, EqualityComparer<T>.Default) { }
public AlgoParamVM(string id, string display_name, T value, string template_key = null)
: this(id, display_name, value, EqualityComparer<T>.Default, template_key) { }
public AlgoParamVM(string id, string display_name, T value)
: this(id, display_name, value, EqualityComparer<T>.Default) { }
public AlgoParamVM(string id, string display_name, T value, IEqualityComparer<T> comparer, string template_key = null) {
public AlgoParamVM(string id, string display_name, T value, IEqualityComparer<T> comparer) {
this.id = id;
this.display_name = display_name;
_value = value;
_committed_value = value;
_comparer = comparer;
_template_key = template_key;
}
public event PropertyChangedEventHandler PropertyChanged;
@@ -53,9 +50,7 @@ namespace mbts.spark {
public bool is_enabled { get => _is_enabled; set => _set(ref _is_enabled, value); }
public bool has_value { get => _has_value; set => _set(ref _has_value, value); }
public bool is_dirty { get; private set; }
object IAlgoParamVM.value { get => value; set => this.value = (T)value; }
string ITemplateSelectorKey.key => _template_key;
object IAlgoParamVM.value { get => value; set => this.value = (T)value; }
public void revert() {
if (is_dirty) {
+1 -3
View File
@@ -36,8 +36,6 @@ 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;
@@ -87,4 +85,4 @@ namespace mbts.spark {
public static string user(this FillTuple fill_tuple) =>
fill_tuple.order.HasValue ? fill_tuple.order.Value.user() : string.Empty;
}
}
}
@@ -67,6 +67,7 @@ namespace mbts.spark.utilities {
public IObservable<IReadOnlyDictionary<uint, string>> subscribe_accounts() => _admin_model.on_accounts;
public IObservable<IReadOnlyDictionary<uint, string>> subscribe_users() => _admin_model.on_users;
public IObservable<IReadOnlyList<UserTuple>> subscribe_user_tuples() => _admin_model.on_user_tuples;
public IObservable<IReadOnlyList<AccountTuple>> subscribe_account_tuples() => _admin_model.on_account_tuples;
#region Defaults
@@ -62,7 +62,6 @@ namespace mbts.spark {
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: param.AsInt32 = 0; break;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef: param.AsOrderDef = new CustomOrderDefinition(); break;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: param.AsString = string.Empty; break;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveSecurityKey:
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: param.AsUint32 = 0u; break;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveQuantity: param.AsQuantity = new bts.spark.QuantityMeasure(); break;
}
@@ -12,7 +12,7 @@ using System.Reactive.Linq;
namespace mbts.spark {
interface IDynamicOrderParamVM {
DynamicOrderParamDescriptor descriptor { get; }
DynamicOrderParam param { get; }
DynamicOrderParam param { get; set; }
}
interface IDynamicOrderParamVM<T> : IDynamicOrderParamVM {
@@ -21,16 +21,26 @@ namespace mbts.spark {
}
abstract class DynamicOrderParamVM : DisposableViewModelBase, IDynamicOrderParamVM {
DynamicOrderParam _param;
protected DynamicOrderParamVM(DynamicOrderParamDescriptor descriptor)
: this(descriptor, DynamicOrderHelper.init_param(descriptor)) { }
private DynamicOrderParamVM(DynamicOrderParamDescriptor descriptor, DynamicOrderParam param) {
this.descriptor = descriptor;
this.param = param;
_param = param;
}
public DynamicOrderParamDescriptor descriptor { get; set; }
public DynamicOrderParam param { get; }
public DynamicOrderParam param {
get => _param;
set {
_param = value;
on_param_changed();
}
}
protected abstract void on_param_changed();
}
class DynamicOrderParamVM<TView, TModel> : DynamicOrderParamVM, IDynamicOrderParamVM<TView> {
@@ -43,12 +53,14 @@ namespace mbts.spark {
_field_desc = field_descriptor;
_convert = convert;
_convert_back = convert_back;
value = _convert(ConvertUtils.try_convert<TModel>(_field_desc.Accessor.GetValue(param)));
}
public TView value {
get => _value;
set => Set(ref _value, value);
set {
_field_desc.Accessor.SetValue(param, _convert_back(_value = value));
RaisePropertyChanged();
}
}
public IObservable<TView> subscribe_value() =>
@@ -58,6 +70,11 @@ namespace mbts.spark {
void on_prop_changed(object sender, PropertyChangedEventArgs e) =>
o.OnNext(value);
});
protected override void on_param_changed() {
_value = _convert(ConvertUtils.try_convert<TModel>(_field_desc.Accessor.GetValue(param)));
RaisePropertyChanged(nameof(value));
}
}
class DynamicOrderEnumeratedVM : DynamicOrderParamVM, IDynamicOrderParamVM<string> {
@@ -88,6 +105,11 @@ namespace mbts.spark {
o.OnNext(selected_element);
});
protected override void on_param_changed() {
_sync_elements();
RaisePropertyChanged(nameof(selected_element));
}
static IEnumerable<string> _get_elements(DynamicOrderParamDescriptor descriptor) =>
descriptor.ParamType?.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Enumerated ?
descriptor.ParamType.Enumerated.Element : Enumerable.Empty<string>();
@@ -126,5 +148,10 @@ namespace mbts.spark {
o.OnNext(quantity);
});
}
protected override void on_param_changed()
{
RaisePropertyChanged(nameof(quantity));
}
}
}
@@ -20,7 +20,6 @@ namespace mbts.ui.spark
public DataTemplate StringTemplate { get; set; }
public DataTemplate OrderDefTemplate { get; set; }
public DataTemplate QuantityMeasureTemplate { get; set; }
public DataTemplate SecurityKeyTemplate { get; set; }
public DataTemplate EnumTemplate { get; set; }
@@ -39,7 +38,6 @@ namespace mbts.ui.spark
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveDouble: return DoubleTemplate;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: return IntTemplate;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: return UIntTemplate;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveSecurityKey: return SecurityKeyTemplate;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: return StringTemplate;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef: return OrderDefTemplate;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveQuantity: return QuantityMeasureTemplate;
@@ -4,7 +4,6 @@ using bts.collections;
using bts.spark;
using mbts.collections;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.utility;
using mbts.utils;
using System;
@@ -74,9 +73,9 @@ namespace mbts.spark {
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveDouble: return mk<double, double>(DynamicOrderParam.AsDoubleFieldNumber, v => v, v => v, double.NaN);
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: return mk<int?, int>(DynamicOrderParam.AsInt32FieldNumber, v => v, v => v.GetValueOrDefault());
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: return mk<uint?, uint>(DynamicOrderParam.AsUint32FieldNumber, v => v, v => v.GetValueOrDefault());
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveSecurityKey: return mk<uint?, uint>(DynamicOrderParam.AsUint32FieldNumber, v => v, v => v.GetValueOrDefault(), template_key: TemplateKeys.SecurityKey);
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: return mk<string, string>(DynamicOrderParam.AsStringFieldNumber, v => v, v => v);
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveQuantity: {
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveQuantity:
{
var qm = new DynamicOrderQuantityVM(descriptor);
return attached(qm, DynamicOrderHelper.get_quantity_value, DynamicOrderHelper.set_quantity_value, new QuantityMeasure { Ratio = 1.0, Absolute = 1 });
}
@@ -89,16 +88,16 @@ namespace mbts.spark {
default: throw ExceptionBuilder.Argument.unexpected_case(descriptor.ParamType.DescriptorCase, nameof(descriptor));
}
DynamicOrderParamVM<TView, TModel> mk<TView, TModel>(int field_number, Func<TModel, TView> convert, Func<TView, TModel> convert_back, TView default_value = default, string template_key = null) {
DynamicOrderParamVM<TView, TModel> mk<TView, TModel>(int field_number, Func<TModel, TView> convert, Func<TView, TModel> convert_back, TView default_value = default) {
var fdesc = DynamicOrderParam.Descriptor.FindFieldByNumber(field_number);
var pvm = new DynamicOrderParamVM<TView, TModel>(descriptor, fdesc, convert, convert_back);
return attached<TView, DynamicOrderParamVM<TView, TModel>>(pvm, get_from_param, assign_to_param, default_value, template_key);
return attached<TView, DynamicOrderParamVM<TView, TModel>>(pvm, get_from_param, assign_to_param, default_value);
TView get_from_param(DynamicOrderParam param) => convert(ConvertUtils.try_convert<TModel>(fdesc.Accessor.GetValue(param)));
void assign_to_param(DynamicOrderParam param, TView value) => fdesc.Accessor.SetValue(param, convert_back(value));
}
TParam attached<T, TParam>(TParam pvm, Func<DynamicOrderParam, T> selector, Action<DynamicOrderParam, T> assign_to_param, T default_value = default, string template_key = null) where TParam : DynamicOrderParamVM, IDynamicOrderParamVM<T> {
TParam attached<T, TParam>(TParam pvm, Func<DynamicOrderParam, T> selector, Action<DynamicOrderParam, T> assign_to_param, T default_value = default) where TParam : DynamicOrderParamVM, IDynamicOrderParamVM<T> {
add_property<T>(
pvm.descriptor.ParamName,
mdef => mdef.OrderParams.TryGetValue(pvm.descriptor.ParamName, out DynamicOrderParam p) ? selector(p) : default_value,
@@ -106,8 +105,7 @@ namespace mbts.spark {
() => pvm.value,
v => pvm.value = v,
pvm.subscribe_value(),
default_value,
template_key: template_key);
default_value);
return pvm;
}
}
@@ -89,7 +89,6 @@ namespace mbts.spark {
case AlgoType.Dynamic: vm = dynamic(key, name, name, editing_permissions); break;
case AlgoType.Splitter: vm = splitter(key,name,editing_permissions); break;
case AlgoType.Composite: vm = composite(key, name, editing_permissions); break;
case AlgoType.None: vm = new DefaultAlgoVM(); break;
default: vm = generic(key, name, algo_type, editing_permissions); break;
//default:
// Log.Ui.debug_fatal($"Failed to create {typeof(IAlgoVM)}: unknown algo type '{algo_type}.'");
@@ -167,8 +166,7 @@ namespace mbts.spark {
};
void subscribe_new_algo(IAlgoVM old_algo, IAlgoVM new_algo) {
RaisePropertyChanged(sd.prop_name);
Disposable.dispose(ref def_sub);
def_sub = new CompositeDisposable();
Disposable.replace(ref def_sub, new CompositeDisposable());
if (assert(new_algo != null, sd.prop_name, "No algo specified.")) {
def_sub.Add(new_algo.subscribe_definition().Subscribe(_ => _def_change_notifier.execute()));
if (new_algo is INotifyDataErrorInfo ndei)
@@ -257,14 +255,14 @@ namespace mbts.spark {
_security_changed += o.OnNext;
return Disposable.Create(() => _security_changed -= o.OnNext);
});
Disposable.dispose(ref _pending_load);
_pending_load = Disposable.Create(
Disposable.replace(ref _pending_load, Disposable.Create(
// load children
ObservableAutoWrapper.connect_item_hooks(
_child_algo_selector_data.AsCollection(),
sd => security_src.SubscribeEach(_ => ObservableAutoWrapper.connect_item_actions(_child_algo_selector_data.AsCollection(), selector_load_impl))),
// load regular props
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load(algom)));
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load(algom))));
}
void selector_load_impl(AlgoSelectorData sd) {
@@ -300,12 +298,11 @@ namespace mbts.spark {
_security_changed += o.OnNext;
return Disposable.Create(() => _security_changed -= o.OnNext);
});
Disposable.dispose(ref _pending_load);
_pending_load = Disposable.Create(
Disposable.replace(ref _pending_load, Disposable.Create(
ObservableAutoWrapper.connect_item_hooks(
_child_algo_selector_data.AsCollection(),
sd => security_src.Subscribe(_ => ObservableAutoWrapper.connect_item_actions(_child_algo_selector_data.AsCollection(), selector_load_impl))),
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load_many(algos)));
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load_many(algos))));
void selector_load_impl(AlgoSelectorData sd) {
// **assume that algos has more than 1 element
@@ -545,19 +542,8 @@ namespace mbts.spark {
}
}
protected void add_property<TValue>(
string prop_name,
Func<TMutable, TValue> get_from_def,
Action<TMutable, TValue> assign_to_def,
Func<TValue> get_local,
Action<TValue> set_local,
IObservable<TValue> subscribe_local,
TValue non_uniform_value = default,
AlgoParamPermissions permissions = AlgoParamPermissions.ReadWrite,
string template_key = null) {
var param = new AlgoParamVM<TValue>(prop_name, DisplayNameHelper.snake_to_title_case(prop_name), get_local(), template_key) {
permissions = permissions
};
protected void add_property<TValue>(string prop_name, Func<TMutable, TValue> get_from_def, Action<TMutable, TValue> assign_to_def, Func<TValue> get_local, Action<TValue> set_local, IObservable<TValue> subscribe_local, TValue non_uniform_value = default, AlgoParamPermissions permissions = AlgoParamPermissions.ReadWrite) {
var param = new AlgoParamVM<TValue>(prop_name, DisplayNameHelper.snake_to_title_case(prop_name), get_local()) { permissions = permissions };
_algo_params.Add(param);
_auto_props[prop_name] = new AutoProp(prop_name,
def => {
@@ -830,11 +816,12 @@ namespace mbts.spark {
var default_algo = AlgoKey.from_known_type(OrderDataHelper.get_default_algo(OrderDataHelper.get_algo_format(security.Value.ContractCase)));
foreach (var sd in _child_algo_selectors)
sd.load_algo_data_recursive(algo_data, default_algo);
Disposable.dispose(ref _selector_data_hooks);
_selector_data_hooks = ObservableAutoWrapper.connect_item_hooks(child_algo_selectors, s => {
s.load_algo_data_recursive(algo_data, default_algo);
return Disposable.empty;
});
Disposable.replace(
ref _selector_data_hooks,
ObservableAutoWrapper.connect_item_hooks(child_algo_selectors, s => {
s.load_algo_data_recursive(algo_data, default_algo);
return Disposable.empty;
}));
}
}
+2 -4
View File
@@ -158,9 +158,7 @@ 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, SecurityImmutable? security = null) {
if (order.order_type() == CustomOrderDefinition.CustomOneofCase.Spreader && security.HasValue)
return security.Value.ToString();
public static string display_name(OrderRecordImmutable order, AlgoData? algo) {
if (algo.HasValue) {
return algo.Value.name;
} else if (order.HasDef && order.Def.HasCustomDef) {
@@ -252,7 +250,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, order_sec_algo.security);
display_name(order_sec_algo.order, order_sec_algo.algo);
public static string display_name(this CustomOrderDefinitionImmutable algo) =>
algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic ? algo.Dynamic.DynamicOrderType : algo.CustomCase.display_name();
+15 -23
View File
@@ -59,30 +59,23 @@ namespace mbts.spark {
this.md_auxiliary_model = md_auxiliary_model;
var scheduler = ThreadPoolScheduler.Instance;
var interval = TimeSpan.FromMilliseconds(100);
on_orders = admin_model
.permission_filter(
_service.subscribei_orders()
.Naggle(interval, scheduler),
o => o.account_id()
).join(sstore, order => order.security_key())
on_orders =
_service
.subscribei_orders()
.Naggle(interval, scheduler)
.PermissionFilter(admin_model.on_admin_data)
.join(sstore, order => order.security_key())
.Table()
.Cache();
on_positions = admin_model
.permission_filter(
_position_service.subscribei_position_data().Naggle(interval, scheduler),
p => p.AccountId)
.join(sstore, pos => pos.SecurityId)
.Table()
.Cache();
on_pnl = admin_model
.permission_filter(
_position_service.subscribei_pnl_data().Naggle(interval, scheduler),
p => p.Pos.AccountId)
on_positions = _position_service.subscribei_position_data().Naggle(interval, scheduler).PermissionFilter(admin_model.on_admin_data).join(sstore, pos => pos.SecurityId).Table().Cache();
on_pnl = _position_service.subscribei_pnl_data()
.Naggle(interval, scheduler)
.join(sstore, p => p.Pos.SecurityId)
.TableCheckedAutoCorrect(EqualityComparer<(SecurityImmutable? sec, PNLUnitImmutable data)>.Default)
.Where(t => t.has_changes())
.Cache();
on_account_pnl = _position_service.subscribei_pnl_account_data().Naggle(interval, scheduler).Table().Cache();
var working_orders = on_orders.Filter(tup => tup.order.is_active());
this.working_orders =
on_orders.Scan(
@@ -181,11 +174,10 @@ namespace mbts.spark {
.Where(t => t.has_changes())
.Cache();
// fill tuples
var on_fills = admin_model
.permission_filter(
_service.subscribei_fills()
.Naggle(interval, scheduler),
f => f.Account)
var on_fills =
_service.subscribei_fills()
.Naggle(interval, scheduler)
.PermissionFilter(admin_model.on_admin_data)
.join(sstore, f => f.SecurityKey)
.Table().Cache();
var fill_order_tuples_join = on_fills.LeftJoin(
@@ -210,7 +202,6 @@ 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(
@@ -274,6 +265,7 @@ namespace mbts.spark {
public IObservable<IReadOnlyDictionary<uint, OrderSecurity>> working_orders { get; }
public IObservable<TableUpdate<(SecurityImmutable?, PNLUnitImmutable), int>> on_pnl { get; }
public IObservable<TableUpdate<PNLRowData, PNLKey>> security_account_pnl { get; }
public IObservable<TableUpdate<AccountPNLImmutable, uint>> on_account_pnl { get; }
public IObservable<TableUpdate<(SecurityImmutable?, PositionUnitImmutable), int>> on_positions { get; }
public IObservable<TableUpdate<(AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>> studio_algos_with_meta { get; }
public IObservable<TableUpdate<(SecurityImmutable?, ManualFillImmutable), int>> on_manual_fills_securities { get; }
@@ -1,80 +0,0 @@
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,9 +5,14 @@ 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 {
@@ -15,7 +20,6 @@ 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)) },
@@ -73,7 +77,6 @@ namespace mbts {
OrderFilledEventPredicate _filled_event;
OrderCompletedEventPredicate _completed_event;
OrderCanceledEventPredicate _canceled_event;
OrderSentEventPredicate _sent_event;
public enum PredicateTypeOneofCase {
None,
@@ -117,8 +120,6 @@ 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)]
@@ -175,8 +176,6 @@ 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)]
@@ -207,7 +206,6 @@ 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;
}
@@ -238,7 +236,6 @@ 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;
}
@@ -288,7 +285,6 @@ 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);
@@ -324,7 +320,6 @@ 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);
@@ -360,7 +355,6 @@ namespace mbts {
_filled_event = null;
_completed_event = null;
_canceled_event = null;
_sent_event = null;
_attached_target_order_key = null;
}
field = value;
@@ -8,7 +8,6 @@ using mbts.spark;
using mbts.ui;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Windows.Input;
namespace bts.spark {
@@ -42,9 +41,6 @@ namespace bts.spark {
SparkCommand.create("P&L (2.0)", Icons.Accounting, SparkTags.ShowPNLTag);
// Ladder
public static PartialCommand<IImmutableSet<uint>> ShowLadderAutoLookup(KeyGesture key_gesture = null) =>
SparkCommand.create("Ladder", Icons.Ladder, SparkTags.ShowLadderAutoLookupTag, key_gesture);
public static PartialCommand<SecurityMD> ShowLadder(KeyGesture key_gesture = null) =>
SparkCommand.create("Ladder", Icons.Ladder, SparkTags.ShowLadderTag, key_gesture);
-3
View File
@@ -7,7 +7,6 @@ using bts.spark;
using mbts.ui;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
namespace mbts.spark {
@@ -56,8 +55,6 @@ namespace mbts.spark {
// The owners of the various SparkWindows may remove dependencies on the former at their leasure.
// For now, both tags go through the same logic in the LadderWindowFactory anyways.
public static ITag<SecurityMD> ShowLadderTag { get; } = new SparkTag<SecurityMD>("Show Ladder");
// TODO: get rid of the SecurityMD ShowLadderTag. This has created burdensome dependency issues.
public static ITag<IImmutableSet<uint>> ShowLadderAutoLookupTag { get; } = new SparkTag<IImmutableSet<uint>>("Show Ladder Auto Lookup");
public static ITag<IReadOnlyList<SecurityMD>> ShowLaddersTag { get; } = new SparkTag<IReadOnlyList<SecurityMD>>(nameof(ShowLaddersTag));
public static ITag<IObservable<SecurityMD>> ShowLinkedLadder { get; } = new SparkTag<IObservable<SecurityMD>>("Linked Ladder");
public static ITag<SecurityCriteria> ShowMarketViewTag { get; } = new SparkTag<SecurityCriteria>("Show MarketView");
Loaded 100 of 307 files, more files were not shown because too many files have changed in this diff. Show more