Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5330000e12 | ||
|
|
1d51a063ea | ||
|
|
da3dc9f477 | ||
|
|
f437d50041 | ||
|
|
c7bbcc1738 | ||
|
|
3bdde95b42 | ||
|
|
52e6be5d60 | ||
|
|
bda76d0234 | ||
|
|
7cb08ba90a | ||
|
|
ee6d05c737 | ||
|
|
62a5ee94e8 | ||
|
|
f6e12d1664 | ||
|
|
d022080b80 | ||
|
|
44acb1caac | ||
|
|
28347f1074 | ||
|
|
7826a1d0b4 | ||
|
|
975edb48df | ||
|
|
c2a5c278e9 | ||
|
|
f895e69aa9 | ||
|
|
7c050a9e10 | ||
|
|
38087d21d8 | ||
|
|
4b2e49c4aa | ||
|
|
e31c0a8112 | ||
|
|
876d6e0981 | ||
|
|
cb2dac6126 | ||
|
|
a3cc1c652f | ||
|
|
7ab6fd273e | ||
|
|
7d47361446 | ||
|
|
9a95b5ed34 | ||
|
|
0070c1f1cc | ||
|
|
cd4664a012 | ||
|
|
b1b8d79706 | ||
|
|
002832a87d | ||
|
|
958212ea0c | ||
|
|
0013d529da | ||
|
|
23f8d0bbbb | ||
|
|
3c72760a3f | ||
|
|
8844f60cd0 | ||
|
|
62e01e43e2 | ||
|
|
7953cc1209 | ||
|
|
67227320c9 | ||
|
|
5dd2c14fba | ||
|
|
dcf116fe82 | ||
|
|
9c571cc16d | ||
|
|
aa8e32ee43 | ||
|
|
4d0a793ecc | ||
|
|
659bf6b208 | ||
|
|
33312a4bf8 | ||
|
|
929a93b1f7 | ||
|
|
f49e41c5c0 | ||
|
|
87f99d42bf | ||
|
|
e4f5531855 | ||
|
|
ae2712c9ac | ||
|
|
d62bde8158 | ||
|
|
53ea5ddf81 | ||
|
|
9e0b95a1ad | ||
|
|
5325c372ba | ||
|
|
c3f4183531 | ||
|
|
6de5416394 | ||
|
|
1a947097a5 | ||
|
|
50abbd6aea | ||
|
|
f324504df1 | ||
|
|
931d5dad01 | ||
|
|
ef094b891a | ||
|
|
d25d91038d | ||
|
|
700f1d97fd | ||
|
|
5b1cf890e8 | ||
|
|
c254248886 | ||
|
|
5aaa5a3360 | ||
|
|
54d31c345a | ||
|
|
ae3e721f62 | ||
|
|
0833dce43d | ||
|
|
47b2e917c1 | ||
|
|
c2864d1715 | ||
|
|
2769de6f7e | ||
|
|
ef3b39361a | ||
|
|
fa2a2e2107 | ||
|
|
3005dfdc0f | ||
|
|
4fd8d9dc5b | ||
|
|
c948b981cf | ||
|
|
8e51b05941 | ||
|
|
75568e6c75 | ||
|
|
6e3c880ea2 | ||
|
|
ded957a0ef | ||
|
|
05741ceab0 | ||
|
|
9e3b3879d0 | ||
|
|
a1b189fcb1 | ||
|
|
7a48e5dd5c | ||
|
|
2842aea933 | ||
|
|
f97d156020 | ||
|
|
d8922be0ad | ||
|
|
5634ada367 | ||
|
|
94d249edbd | ||
|
|
1c29ac0ec5 | ||
|
|
e7e280bad8 | ||
|
|
7ca11c3eee | ||
|
|
caf6ac029c | ||
|
|
4c0fa81963 | ||
|
|
720c1595e0 | ||
|
|
bedd0051a9 | ||
|
|
09bed1a713 | ||
|
|
ebb53c5035 | ||
|
|
243b82a6d7 | ||
|
|
bd4f840d27 | ||
|
|
de893d2175 | ||
|
|
33658f13f6 | ||
|
|
469ee033b2 | ||
|
|
451fb67195 | ||
|
|
a21739a866 | ||
|
|
7d694f5c04 | ||
|
|
14e08cf508 | ||
|
|
12349ef8b7 | ||
|
|
c99ee9e2d1 | ||
|
|
da71ec4c0f | ||
|
|
edd8fe7604 | ||
|
|
925bd72825 | ||
|
|
3ab2b132f6 | ||
|
|
f35a9662fd | ||
|
|
b20f8d1293 | ||
|
|
fabb06e87c | ||
|
|
0ce2b9f135 | ||
|
|
fea10e3578 | ||
|
|
b0fd113c0f | ||
|
|
a826543c07 | ||
|
|
7d3b1f3bd2 | ||
|
|
7877e44713 | ||
|
|
46387703fe | ||
|
|
ec9425f4e8 | ||
|
|
4489f78b35 | ||
|
|
9f25b12c29 | ||
|
|
eaacafca86 | ||
|
|
a1a591e162 | ||
|
|
a9edc05b25 | ||
|
|
3a70f58662 | ||
|
|
56a7e43425 | ||
|
|
9a22e7e1fc | ||
|
|
89bbf25335 | ||
|
|
2bfaf8305a | ||
|
|
b414289299 | ||
|
|
9623a015df | ||
|
|
f3266cbcec | ||
|
|
98e34aafcd | ||
|
|
aae0cba2f7 | ||
|
|
d4abac9d0d | ||
|
|
ac0d0d8dae | ||
|
|
6ff71980aa | ||
|
|
23352438cf | ||
|
|
76a519911d | ||
|
|
4447412e64 | ||
|
|
35b01451c4 | ||
|
|
bab2508299 | ||
|
|
defdcc5d44 | ||
|
|
b4a8cf0cff | ||
|
|
7e8ef4d273 | ||
|
|
3e9e4c0f12 | ||
|
|
9de3e2c597 | ||
|
|
3382fe22ca | ||
|
|
9eaa2eb5e4 | ||
|
|
451dcc3352 | ||
|
|
e490493ae7 | ||
|
|
fb8904fed4 | ||
|
|
4e7f3ed036 | ||
|
|
aa0080224d | ||
|
|
3537be6d98 | ||
|
|
6f72c2f268 | ||
|
|
2e6aa8b54d | ||
|
|
87ef164264 | ||
|
|
c4c8bb6f5e | ||
|
|
c53c12b5d8 | ||
|
|
f5c4eda4df | ||
|
|
55cb3592a1 | ||
|
|
8e00da5a72 | ||
|
|
c19b720121 | ||
|
|
93af8bc738 | ||
|
|
47cf8f1df8 | ||
|
|
79b011d934 | ||
|
|
9e9a90f27b | ||
|
|
7a319ed943 | ||
|
|
50724cec66 | ||
|
|
6de25c700f | ||
|
|
4256c51993 | ||
|
|
d56ea7df25 | ||
|
|
6ffb8b630f | ||
|
|
677d1aaa77 | ||
|
|
07874cad77 | ||
|
|
c53c843d54 | ||
|
|
5920b841c3 | ||
|
|
6d7fbe5c84 | ||
|
|
2ba33caae8 | ||
|
|
293dddc54f | ||
|
|
1ca250ccb1 | ||
|
|
bca8f754b6 | ||
|
|
146e555ad0 | ||
|
|
5424a07862 | ||
|
|
d6a6b2ca79 | ||
|
|
8e6b863d8f | ||
|
|
660dd71bad | ||
|
|
88ae39fa52 | ||
|
|
353a33a391 | ||
|
|
6a6e1b1139 | ||
|
|
44262c584c | ||
|
|
935e8455a3 | ||
|
|
d01441987d | ||
|
|
b24528ba2f | ||
|
|
e2b781c42c | ||
|
|
ff3facedb0 | ||
|
|
119a2dc6ad | ||
|
|
74defda5a9 | ||
|
|
9212e8d1da | ||
|
|
c88a004c27 | ||
|
|
0cab24523d | ||
|
|
c4500e8ac0 | ||
|
|
6d180156e2 | ||
|
|
46dd1b7445 | ||
|
|
bd8db00227 | ||
|
|
f4bd00ae77 | ||
|
|
38842a91c3 | ||
|
|
1f44425ac1 | ||
|
|
3581d05883 | ||
|
|
251775283b | ||
|
|
acabe59d8a | ||
|
|
efa941bdb1 | ||
|
|
f8028ab381 | ||
|
|
8a638d2e86 | ||
|
|
09d34b03ac | ||
|
|
3798d64403 | ||
|
|
fc7dcfad80 | ||
|
|
483a61426d | ||
|
|
8094b53402 | ||
|
|
837c3d5949 | ||
|
|
1a7b550e08 | ||
|
|
bbb1b8f147 | ||
|
|
5656f1685c | ||
|
|
cd04e27625 | ||
|
|
dc8480d778 | ||
|
|
5127b70fea | ||
|
|
f4a4a34567 | ||
|
|
f53b300cd4 | ||
|
|
1b99cff2c5 | ||
|
|
b1d799787a | ||
|
|
d019ae287c | ||
|
|
ffc316f50c | ||
|
|
fc5eb48d6a | ||
|
|
f1de94a218 | ||
|
|
e19907ad92 | ||
|
|
b5bdc18332 | ||
|
|
2864db23c9 | ||
|
|
c012a63e86 |
No files matched your search
@@ -22,4 +22,7 @@ node_modules
|
||||
paket-files
|
||||
project.lock.json
|
||||
src/server/sparkd/orderman/perf_analysis/data
|
||||
src/server/sparkd/orderman/perf_analysis/logs/
|
||||
src/server/sparkd/orderman/perf_analysis/trace_data/
|
||||
src/lib/orderman/perf_analysis/logs/latency_data/
|
||||
trace_data/
|
||||
Vendored
+12
-1
@@ -42,7 +42,18 @@
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "Client Tests",
|
||||
"type": "clr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
|
||||
"args": ["test","test_filter"],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "Client Attach",
|
||||
"type": "clr",
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ else ()
|
||||
-fPIC -Wall -pedantic -Wextra -Werror \
|
||||
-Wno-missing-braces -Wno-ignored-qualifiers \
|
||||
--std=c++17 -fdiagnostics-color")
|
||||
set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_DEBUG} -DNDEBUG -O3")
|
||||
set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -DNDEBUG -O3")
|
||||
add_subdirectory(src/server/sparkd)
|
||||
add_subdirectory(src/lib)
|
||||
add_subdirectory(src/web)
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
|
||||
<head>
|
||||
<!-- 2018-10-08 Mon 18:50 -->
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>‎</title>
|
||||
<meta name="generator" content="Org mode" />
|
||||
<meta name="author" content="ppinto" />
|
||||
<style type="text/css">
|
||||
<!--/*--><![CDATA[/*><!--*/
|
||||
.title { text-align: center;
|
||||
margin-bottom: .2em; }
|
||||
.subtitle { text-align: center;
|
||||
font-size: medium;
|
||||
font-weight: bold;
|
||||
margin-top:0; }
|
||||
.todo { font-family: monospace; color: red; }
|
||||
.done { font-family: monospace; color: green; }
|
||||
.priority { font-family: monospace; color: orange; }
|
||||
.tag { background-color: #eee; font-family: monospace;
|
||||
padding: 2px; font-size: 80%; font-weight: normal; }
|
||||
.timestamp { color: #bebebe; }
|
||||
.timestamp-kwd { color: #5f9ea0; }
|
||||
.org-right { margin-left: auto; margin-right: 0px; text-align: right; }
|
||||
.org-left { margin-left: 0px; margin-right: auto; text-align: left; }
|
||||
.org-center { margin-left: auto; margin-right: auto; text-align: center; }
|
||||
.underline { text-decoration: underline; }
|
||||
#postamble p, #preamble p { font-size: 90%; margin: .2em; }
|
||||
p.verse { margin-left: 3%; }
|
||||
pre {
|
||||
border: 1px solid #ccc;
|
||||
box-shadow: 3px 3px 3px #eee;
|
||||
padding: 8pt;
|
||||
font-family: monospace;
|
||||
overflow: auto;
|
||||
margin: 1.2em;
|
||||
}
|
||||
pre.src {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
padding-top: 1.2em;
|
||||
}
|
||||
pre.src:before {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
top: -10px;
|
||||
right: 10px;
|
||||
padding: 3px;
|
||||
border: 1px solid black;
|
||||
}
|
||||
pre.src:hover:before { display: inline;}
|
||||
/* Languages per Org manual */
|
||||
pre.src-asymptote:before { content: 'Asymptote'; }
|
||||
pre.src-awk:before { content: 'Awk'; }
|
||||
pre.src-C:before { content: 'C'; }
|
||||
/* pre.src-C++ doesn't work in CSS */
|
||||
pre.src-clojure:before { content: 'Clojure'; }
|
||||
pre.src-css:before { content: 'CSS'; }
|
||||
pre.src-D:before { content: 'D'; }
|
||||
pre.src-ditaa:before { content: 'ditaa'; }
|
||||
pre.src-dot:before { content: 'Graphviz'; }
|
||||
pre.src-calc:before { content: 'Emacs Calc'; }
|
||||
pre.src-emacs-lisp:before { content: 'Emacs Lisp'; }
|
||||
pre.src-fortran:before { content: 'Fortran'; }
|
||||
pre.src-gnuplot:before { content: 'gnuplot'; }
|
||||
pre.src-haskell:before { content: 'Haskell'; }
|
||||
pre.src-hledger:before { content: 'hledger'; }
|
||||
pre.src-java:before { content: 'Java'; }
|
||||
pre.src-js:before { content: 'Javascript'; }
|
||||
pre.src-latex:before { content: 'LaTeX'; }
|
||||
pre.src-ledger:before { content: 'Ledger'; }
|
||||
pre.src-lisp:before { content: 'Lisp'; }
|
||||
pre.src-lilypond:before { content: 'Lilypond'; }
|
||||
pre.src-lua:before { content: 'Lua'; }
|
||||
pre.src-matlab:before { content: 'MATLAB'; }
|
||||
pre.src-mscgen:before { content: 'Mscgen'; }
|
||||
pre.src-ocaml:before { content: 'Objective Caml'; }
|
||||
pre.src-octave:before { content: 'Octave'; }
|
||||
pre.src-org:before { content: 'Org mode'; }
|
||||
pre.src-oz:before { content: 'OZ'; }
|
||||
pre.src-plantuml:before { content: 'Plantuml'; }
|
||||
pre.src-processing:before { content: 'Processing.js'; }
|
||||
pre.src-python:before { content: 'Python'; }
|
||||
pre.src-R:before { content: 'R'; }
|
||||
pre.src-ruby:before { content: 'Ruby'; }
|
||||
pre.src-sass:before { content: 'Sass'; }
|
||||
pre.src-scheme:before { content: 'Scheme'; }
|
||||
pre.src-screen:before { content: 'Gnu Screen'; }
|
||||
pre.src-sed:before { content: 'Sed'; }
|
||||
pre.src-sh:before { content: 'shell'; }
|
||||
pre.src-sql:before { content: 'SQL'; }
|
||||
pre.src-sqlite:before { content: 'SQLite'; }
|
||||
/* additional languages in org.el's org-babel-load-languages alist */
|
||||
pre.src-forth:before { content: 'Forth'; }
|
||||
pre.src-io:before { content: 'IO'; }
|
||||
pre.src-J:before { content: 'J'; }
|
||||
pre.src-makefile:before { content: 'Makefile'; }
|
||||
pre.src-maxima:before { content: 'Maxima'; }
|
||||
pre.src-perl:before { content: 'Perl'; }
|
||||
pre.src-picolisp:before { content: 'Pico Lisp'; }
|
||||
pre.src-scala:before { content: 'Scala'; }
|
||||
pre.src-shell:before { content: 'Shell Script'; }
|
||||
pre.src-ebnf2ps:before { content: 'ebfn2ps'; }
|
||||
/* additional language identifiers per "defun org-babel-execute"
|
||||
in ob-*.el */
|
||||
pre.src-cpp:before { content: 'C++'; }
|
||||
pre.src-abc:before { content: 'ABC'; }
|
||||
pre.src-coq:before { content: 'Coq'; }
|
||||
pre.src-groovy:before { content: 'Groovy'; }
|
||||
/* additional language identifiers from org-babel-shell-names in
|
||||
ob-shell.el: ob-shell is the only babel language using a lambda to put
|
||||
the execution function name together. */
|
||||
pre.src-bash:before { content: 'bash'; }
|
||||
pre.src-csh:before { content: 'csh'; }
|
||||
pre.src-ash:before { content: 'ash'; }
|
||||
pre.src-dash:before { content: 'dash'; }
|
||||
pre.src-ksh:before { content: 'ksh'; }
|
||||
pre.src-mksh:before { content: 'mksh'; }
|
||||
pre.src-posh:before { content: 'posh'; }
|
||||
/* Additional Emacs modes also supported by the LaTeX listings package */
|
||||
pre.src-ada:before { content: 'Ada'; }
|
||||
pre.src-asm:before { content: 'Assembler'; }
|
||||
pre.src-caml:before { content: 'Caml'; }
|
||||
pre.src-delphi:before { content: 'Delphi'; }
|
||||
pre.src-html:before { content: 'HTML'; }
|
||||
pre.src-idl:before { content: 'IDL'; }
|
||||
pre.src-mercury:before { content: 'Mercury'; }
|
||||
pre.src-metapost:before { content: 'MetaPost'; }
|
||||
pre.src-modula-2:before { content: 'Modula-2'; }
|
||||
pre.src-pascal:before { content: 'Pascal'; }
|
||||
pre.src-ps:before { content: 'PostScript'; }
|
||||
pre.src-prolog:before { content: 'Prolog'; }
|
||||
pre.src-simula:before { content: 'Simula'; }
|
||||
pre.src-tcl:before { content: 'tcl'; }
|
||||
pre.src-tex:before { content: 'TeX'; }
|
||||
pre.src-plain-tex:before { content: 'Plain TeX'; }
|
||||
pre.src-verilog:before { content: 'Verilog'; }
|
||||
pre.src-vhdl:before { content: 'VHDL'; }
|
||||
pre.src-xml:before { content: 'XML'; }
|
||||
pre.src-nxml:before { content: 'XML'; }
|
||||
/* add a generic configuration mode; LaTeX export needs an additional
|
||||
(add-to-list 'org-latex-listings-langs '(conf " ")) in .emacs */
|
||||
pre.src-conf:before { content: 'Configuration File'; }
|
||||
|
||||
table { border-collapse:collapse; }
|
||||
caption.t-above { caption-side: top; }
|
||||
caption.t-bottom { caption-side: bottom; }
|
||||
td, th { vertical-align:top; }
|
||||
th.org-right { text-align: center; }
|
||||
th.org-left { text-align: center; }
|
||||
th.org-center { text-align: center; }
|
||||
td.org-right { text-align: right; }
|
||||
td.org-left { text-align: left; }
|
||||
td.org-center { text-align: center; }
|
||||
dt { font-weight: bold; }
|
||||
.footpara { display: inline; }
|
||||
.footdef { margin-bottom: 1em; }
|
||||
.figure { padding: 1em; }
|
||||
.figure p { text-align: center; }
|
||||
.inlinetask {
|
||||
padding: 10px;
|
||||
border: 2px solid gray;
|
||||
margin: 10px;
|
||||
background: #ffffcc;
|
||||
}
|
||||
#org-div-home-and-up
|
||||
{ text-align: right; font-size: 70%; white-space: nowrap; }
|
||||
textarea { overflow-x: auto; }
|
||||
.linenr { font-size: smaller }
|
||||
.code-highlighted { background-color: #ffff00; }
|
||||
.org-info-js_info-navigation { border-style: none; }
|
||||
#org-info-js_console-label
|
||||
{ font-size: 10px; font-weight: bold; white-space: nowrap; }
|
||||
.org-info-js_search-highlight
|
||||
{ background-color: #ffff00; color: #000000; font-weight: bold; }
|
||||
.org-svg { width: 90%; }
|
||||
/*]]>*/-->
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
/*
|
||||
@licstart The following is the entire license notice for the
|
||||
JavaScript code in this tag.
|
||||
|
||||
Copyright (C) 2012-2018 Free Software Foundation, Inc.
|
||||
|
||||
The JavaScript code in this tag is free software: you can
|
||||
redistribute it and/or modify it under the terms of the GNU
|
||||
General Public License (GNU GPL) as published by the Free Software
|
||||
Foundation, either version 3 of the License, or (at your option)
|
||||
any later version. The code is distributed WITHOUT ANY WARRANTY;
|
||||
without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
|
||||
|
||||
As additional permission under GNU GPL version 3 section 7, you
|
||||
may distribute non-source (e.g., minimized or compacted) forms of
|
||||
that code without the copy of the GNU GPL normally required by
|
||||
section 4, provided you include this license notice and a URL
|
||||
through which recipients can access the Corresponding Source.
|
||||
|
||||
|
||||
@licend The above is the entire license notice
|
||||
for the JavaScript code in this tag.
|
||||
*/
|
||||
<!--/*--><![CDATA[/*><!--*/
|
||||
function CodeHighlightOn(elem, id)
|
||||
{
|
||||
var target = document.getElementById(id);
|
||||
if(null != target) {
|
||||
elem.cacheClassElem = elem.className;
|
||||
elem.cacheClassTarget = target.className;
|
||||
target.className = "code-highlighted";
|
||||
elem.className = "code-highlighted";
|
||||
}
|
||||
}
|
||||
function CodeHighlightOff(elem, id)
|
||||
{
|
||||
var target = document.getElementById(id);
|
||||
if(elem.cacheClassElem)
|
||||
elem.className = elem.cacheClassElem;
|
||||
if(elem.cacheClassTarget)
|
||||
target.className = elem.cacheClassTarget;
|
||||
}
|
||||
/*]]>*///-->
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="content">
|
||||
<p>
|
||||
We are going to experiment with our development process, getting away
|
||||
from cycles and towards more continuous development. The main idea is
|
||||
to get rid of the prod and QA branches and instead deploy directly
|
||||
from master, which we keep deployment ready at all times. Development
|
||||
occurs on private branches, each branch mapped to a separately
|
||||
deploy-able project. Each of projects will be managed more or less
|
||||
independently and will include specification, development and
|
||||
validation stages. At any particular time we might have anywhere from
|
||||
1 to 6 projects in flight each of which continuously pulls from master,
|
||||
but does not push back to master until the code is ready to be
|
||||
deployed to production. The point at which a project is ready to be
|
||||
deployed then takes roughly the place of the QA phase of our old cycle
|
||||
mode. At this time, we will do a PR, and go through a series of
|
||||
validation steps, to make sure the work meets our standards. This
|
||||
validation includes:
|
||||
</p>
|
||||
|
||||
<ul class="org-ul">
|
||||
<li>Code Reviews</li>
|
||||
<li>Acceptance Testing (including UI and business tests)</li>
|
||||
<li>Automated System testing (to be developed)</li>
|
||||
<li>A mini-qa, focused on the feature, by someone not directly involved
|
||||
in its implementation</li>
|
||||
<li>If possible, deployment to a beta customer.</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Once these QA gates are passed, we will update the release notes, tag
|
||||
a new version, integrate to master and then ideally immediately deploy
|
||||
to a customer.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The main goal with this change is to reduce the latency from feature
|
||||
specification to production roll out. We also hope to improve
|
||||
throughput by making more efficient use of QA time and reducing idle
|
||||
cycles. We expect people to participate in more than one project
|
||||
depending on their area of expertise and work load. This does not mean
|
||||
people will work on two things at the same time, rather that people
|
||||
come into and leave projects as they free up and not when the project
|
||||
starts or ends.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This new scheme will come into full effect when we finish 1.3
|
||||
although to some degree 1.3 was already setup like this. At the
|
||||
moment, we have 3 major projects, vap/settlments/implied, grid
|
||||
settings and speed work, which are in branches of prod. We will merge
|
||||
these back to prod and then master this week. We have 3 other projects
|
||||
that are in master, the excel API, the server side SDK and the MIC
|
||||
administration and enforcement components. These we hope to stabilize
|
||||
as soon as possible so that we can merge them to prod and deploy them
|
||||
along with the rest. Once we have all this, prod and master should be
|
||||
identical and we will delete prod and operate exclusively out of
|
||||
master. Please avoid committing anything new to master so we can
|
||||
complete this transition as soon as possible.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Let me know if you have any question
|
||||
</p>
|
||||
</div>
|
||||
<div id="postamble" class="status">
|
||||
<p class="author">Author: ppinto</p>
|
||||
<p class="date">Created: 2018-10-08 Mon 18:50</p>
|
||||
<p class="validation"><a href="http://validator.w3.org/check?uri=referer">Validate</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,55 @@
|
||||
We are going to experiment with our development process, getting away
|
||||
from cycles and towards more continuous development. The main idea is
|
||||
to get rid of the prod and QA branches and instead deploy directly
|
||||
from master, which we keep deployment ready at all times. Development
|
||||
occurs on private branches, each branch mapped to a separately
|
||||
deploy-able project. Each of projects will be managed more or less
|
||||
independently and will include specification, development and
|
||||
validation stages. At any particular time we might have anywhere from
|
||||
1 to 6 projects in flight each of which continuously pulls from master,
|
||||
but does not push back to master until the code is ready to be
|
||||
deployed to production. The point at which a project is ready to be
|
||||
deployed then takes roughly the place of the QA phase of our old cycle
|
||||
mode. At this time, we will do a PR, and go through a series of
|
||||
validation steps, to make sure the work meets our standards. This
|
||||
validation includes:
|
||||
|
||||
- Code Reviews
|
||||
- Acceptance Testing (including UI and business tests)
|
||||
- Automated System testing (to be developed)
|
||||
- A mini-qa, focused on the feature, by someone not directly involved
|
||||
in its implementation
|
||||
- If possible, deployment to a beta customer.
|
||||
|
||||
Once these QA gates are passed, we will update the release notes, tag
|
||||
a new version, integrate to master and then ideally immediately deploy
|
||||
to a customer.
|
||||
|
||||
The main goal with this change is to reduce the latency from feature
|
||||
specification to production roll out. We also hope to improve
|
||||
throughput by making more efficient use of QA time and reducing idle
|
||||
cycles. We expect people to participate in more than one project
|
||||
depending on their area of expertise and work load. This does not mean
|
||||
people will work on two things at the same time, rather that people
|
||||
come into and leave projects as they free up and not when the project
|
||||
starts or ends.
|
||||
|
||||
This new scheme will come into full effect when we finish 1.3
|
||||
although to some degree 1.3 was already setup like this. At the
|
||||
moment, we have 3 major projects, vap/settlments/implied, grid
|
||||
settings and speed work, which are in branches of prod. We will merge
|
||||
these back to prod and then master this week. We have 3 other projects
|
||||
that are in master, the excel API, the server side SDK and the MIC
|
||||
administration and enforcement components. These we hope to stabilize
|
||||
as soon as possible so that we can merge them to prod and deploy them
|
||||
along with the rest. Once we have all this, prod and master should be
|
||||
identical and we will delete prod and operate exclusively out of
|
||||
master. Please avoid committing anything new to master so we can
|
||||
complete this transition as soon as possible.
|
||||
|
||||
Let me know if you have any question
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
{% if page and page.is_homepage %}<meta name="description" content="{{ config['site_description'] }}">{% endif %}
|
||||
<meta name="keywords" content="Futures, Options, Derivatives, Trade, Trading, Professional, options trading, futures trading, Software" />
|
||||
<meta name="rights" content="© Blue Trading Systems" />
|
||||
<meta name="description" content="Documentation for Blue Trading Systems' BTS Spark" />
|
||||
{% if config.site_author %}<meta name="author" content="{{ config.site_author }}">{% endif %}
|
||||
{% if page and page.canonical_url %}<link rel="canonical" href="{{ page.canonical_url }}">{% endif %}
|
||||
{% if config.site_favicon %}<link rel="shortcut icon" href="{{ base_url }}/{{ config.site_favicon }}">
|
||||
@@ -25,41 +27,54 @@
|
||||
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="{{ base_url }}/css/local.css?1529096332" type="text/css" />
|
||||
<!--link rel="stylesheet" href="https://www.bluetradingsystems.com/templates/bluetradesys/scripts/colorbox-master/example5/colorbox.css" type="text/css" /-->
|
||||
{%- for path in extra_css %}
|
||||
<link href="{{ path }}" rel="stylesheet">
|
||||
{%- endfor %}
|
||||
{%- endblock %}
|
||||
|
||||
{%- block libs %}
|
||||
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
|
||||
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
|
||||
<script src="{{ base_url }}/js/jquery-1.10.2.min.js" defer></script>
|
||||
<script src="{{ base_url }}/js/bootstrap-3.0.3.min.js" defer></script>
|
||||
<script src="{{ base_url }}/js/highlight.pack.js" defer></script>
|
||||
{%- for path in extra_css %}
|
||||
<link href="{{ path }}" rel="stylesheet">
|
||||
{%- endfor %}
|
||||
|
||||
{%- endblock %}
|
||||
|
||||
{%- block analytics %}
|
||||
{%- if config.google_analytics %}
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', '{{ config.google_analytics[0] }}', '{{ config.google_analytics[1] }}');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
{%- endif %}
|
||||
{%- endblock %}
|
||||
|
||||
{%- block extrahead %} {% endblock %}
|
||||
|
||||
<script>
|
||||
dataLayer = [];
|
||||
</script>
|
||||
<script>
|
||||
debug_mode = location.hostname !== "www.bluetradingsystems.com";
|
||||
|
||||
if(debug_mode)
|
||||
{
|
||||
console.log("debug mode enabled (no analytics)");
|
||||
|
||||
if(location.hostname !== "www.bluetradingsystems.com")
|
||||
{
|
||||
console.log("Hostname != www.bluetradingsystems.com");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
||||
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
||||
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
||||
})(window,document,'script','dataLayer','GTM-56FHG8R');
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<script src="{{ base_url }}/js/jquery-1.10.2.min.js" defer></script>
|
||||
<script src="{{ base_url }}/js/bootstrap-3.0.3.min.js" defer></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body{% if page and page.is_homepage %} class="homepage"{% endif %}>
|
||||
<!-- Google Tag Manager (noscript) -->
|
||||
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-56FHG8R"
|
||||
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
|
||||
<!-- End Google Tag Manager (noscript) -->
|
||||
|
||||
{% include "nav.html" %}
|
||||
|
||||
@@ -91,13 +106,16 @@
|
||||
{%- include "keyboard-modal.html" %}
|
||||
|
||||
{% if page and page.is_homepage %}
|
||||
<!--
|
||||
MkDocs version : {{ mkdocs_version }}
|
||||
-->
|
||||
<p style="float:right; padding: 1em; color:#bbb;">
|
||||
|
||||
<!--p style="float:right; padding: 1em; color:#bbb;">
|
||||
Build Date UTC : {{ build_date_utc }}
|
||||
</p>
|
||||
</p-->
|
||||
{% endif %}
|
||||
|
||||
<script src="{{ base_url }}/js/highlight.pack.js" defer></script>
|
||||
|
||||
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>
|
||||
<!--script src="https://www.bluetradingsystems.com/templates/bluetradesys/scripts/colorbox-master/jquery.colorbox-min.js"></script-->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
|
||||
<h4 class="modal-title" id="exampleModalLabel">Keyboard Shortcuts</h4>
|
||||
<h4 class="modal-title">Keyboard Shortcuts</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table class="table">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
|
||||
<h4 class="modal-title" id="exampleModalLabel">Search</h4>
|
||||
<h4 class="modal-title">Search</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
|
||||
@@ -7,13 +7,13 @@ If you need specific information quickly use the Search button in the top right
|
||||
<ol>
|
||||
<li><a href="Logging-In-to-the-System">Login</a></li>
|
||||
<li><a href="Launcher">Module Launcher</a></li>
|
||||
<li><a href="Market">Market
|
||||
<li><a href="Market">Market</a>
|
||||
<ul>
|
||||
<li><a href="Market/#market-view">Market View</a></li>
|
||||
<li><a href="Market/#time-and-sales">Time and Sales</a></li>
|
||||
</ul>
|
||||
</a></li>
|
||||
<li><a href="Trading">Trading
|
||||
</li>
|
||||
<li><a href="Trading">Trading</a>
|
||||
<ul>
|
||||
<li><a href="Trading/#order-book">Order Book</a></li>
|
||||
<li><a href="Trading/#fills">Fills</a></li>
|
||||
@@ -21,17 +21,17 @@ If you need specific information quickly use the Search button in the top right
|
||||
<li><a href="Trading/#ladder">Ladder</a></li>
|
||||
<li><a href="Trading/#order-ticket">Order Ticket</a></li>
|
||||
</ul>
|
||||
</a></li>
|
||||
</li>
|
||||
<li><a href="Spreader">Spreader</a></li>
|
||||
<li><a href="My-Windows">My Windows</a></li>
|
||||
<li><a href="Settings">Settings
|
||||
<li><a href="Settings">Settings</a>
|
||||
<ul><li><a href="Settings/#workspaces">Workspaces</a></li></ul>
|
||||
</a></li>
|
||||
<li><a href="Help">Help
|
||||
</li>
|
||||
<li><a href="Help">Help</a>
|
||||
<ul>
|
||||
<li><a href="Help/#report-a-problem">Report a Problem</a></li>
|
||||
<li><a href="Help/#about-spark">About Spark</a></li>
|
||||
<li><a href="Help/#activity-log">Activity Log</a></li>
|
||||
</ul>
|
||||
</a></li>
|
||||
</li>
|
||||
</ol>
|
||||
@@ -0,0 +1,61 @@
|
||||
# 1.2
|
||||
|
||||
### Market View
|
||||
- Synthetic future leg working orders are now shown
|
||||
|
||||
### Order Book
|
||||
- Many visual facets of the Order Book grid can now be customized
|
||||
- The Order Book settings page can be access from the gears icon in the Order Book toolbar or from the right-click menu
|
||||
- The Expand/Collapse All button has been separated into two different buttons on the toolbar
|
||||
|
||||
### Sound Alerts
|
||||
- Sounds alerts can now be configured for various order events
|
||||
- Configure sounds under the Sound/Notification Triggers on the Order Book settings page
|
||||
|
||||
### Fills
|
||||
- Newly launched fill windows will filter out start-of-day records by default
|
||||
|
||||
### PnL
|
||||
- Prices are now formatted with penny precision
|
||||
- Day PnL has been added to the Totals footer at the bottom of the window
|
||||
|
||||
### Ladder
|
||||
- Order badges now have been improved to communicate a variety of order state
|
||||
- Background color now indicates order side
|
||||
- Child orders will display a one-character abbreviation to their parent order enclosed in a rectangle; the rectangle will have a fill color matching the parent order
|
||||
- Border color around badge will match the associated algo color
|
||||
- Exchange orders will present with a solid dot in the badge's top-right corner
|
||||
- When multiple orders reside at the same level, a solid triangle will be present in badge's bottom-right corner; all other badge decorations will not be visible in this state
|
||||
- Hedge orders will present with a vertical line inside the badge's left border
|
||||
- Trigger orders, once triggered, will no longer be shown in the ladder
|
||||
- Session High and Low visual indicators have been added to the Price column
|
||||
- Session range is now indicated in the Price column
|
||||
- A button reflecting position has been added above the quantity field
|
||||
- This button reflects the position for the currently selected account
|
||||
- Pressing the button will copy the position size to the quantity field
|
||||
- Order quantity button size values are now customizable
|
||||
- Default size preset values can now be changed, as well as added or removed
|
||||
- This can be configured per ladder instance (i.e. per security) by making use of the Custom tab
|
||||
- Synthetic ladders that were saved as part of a workspace are now restored on startup
|
||||
|
||||
### Synthetics
|
||||
- Hedge Ratio parameter has been renamed to Lean Qty Factor
|
||||
- Spreader now includes a Hidden mode parameter
|
||||
- A quoting leg can be hidden, ie an order will not sent out until the market crosses the computed quoting price. When that happens, the quote is sent out as an IOC order.
|
||||
|
||||
### Algos
|
||||
- Trigger orders now can contain a Trigger Exit parameter based on time or price; once the exit condition has been met the Trigger order will cancel
|
||||
- IOC/FAK order type added
|
||||
- Cover order type added
|
||||
- Cancelling a synthetic order no longer cancels any working hedge orders
|
||||
|
||||
### General
|
||||
- A manual page has been added to the launcher Help menu
|
||||
- Various help buttons and tooltips were added throughout the application
|
||||
- Various cash per point values updated
|
||||
- Time-based grid filters now have tick up/down buttons
|
||||
- Numerous performance improvements and stability enhancements
|
||||
- Automated position and orderbook maintenance during daily recycle
|
||||
- Individual fills be compacted into start of day position records
|
||||
- Orders from the day before the recycle will be deleted
|
||||
|
||||
+1
-1
Submodule docker/linux updated: aedfd79e02...8ee9692597.
+1
-1
Submodule externals updated: 83046b27ef...5f21fd13e7.
@@ -1,3 +1,6 @@
|
||||
1.3.4
|
||||
- Changed default logging config for Windows. If Logging config is overridden for a system it should be adjusted to override only linux.
|
||||
|
||||
1.1.0
|
||||
- With this release we dropped wild cards from limits, so any user
|
||||
that has limits expressed in terms of wild cards will have to have
|
||||
|
||||
@@ -1,3 +1,24 @@
|
||||
1.3.4
|
||||
- Significantly reduced client memory use
|
||||
- Improved OrderBook performance
|
||||
- Improved workspace load time
|
||||
|
||||
1.3.3
|
||||
- Reduced server memory usage
|
||||
|
||||
1.3.2
|
||||
- Improved OrderBook performance
|
||||
- Fix some memory leaks when switching workspaces
|
||||
|
||||
1.3.1
|
||||
- Improved server side logging
|
||||
- Fixed leaking of market data subscriptions on market view form closing
|
||||
|
||||
1.3.0
|
||||
- Significantly improved order execution latency
|
||||
- Added latency monitoring tools
|
||||
- Provided core binding controls to system admins so as to reduce latency jitter
|
||||
|
||||
1.1.22
|
||||
- Orderbook: Orders in Error status can now be recreated
|
||||
- Orderbook: Orders containing child orders will now be collapsed by default.
|
||||
|
||||
@@ -11,18 +11,22 @@ namespace mbts.spark {
|
||||
[Export]
|
||||
class AdminModel : IDisposable {
|
||||
readonly string _user_name;
|
||||
public string user_name => _user_name;
|
||||
|
||||
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>());
|
||||
_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>();
|
||||
IDisposable _dispose;
|
||||
readonly BehaviorSubject<AdminModelWrapper> _wrapper_subject = new BehaviorSubject<AdminModelWrapper>(null);
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
AdminModel(AdminService service, [Import("username")]string user_name) {
|
||||
_user_name = user_name;
|
||||
_dispose = service.subscribei_admin_data().Select(update => {
|
||||
_wrapper_subject.OnNext(new AdminModelWrapper(update.all.FirstOrDefault(), _user_name));
|
||||
IEnumerable<BTSUserImmutable> active_users =
|
||||
from admin_data in update.all
|
||||
from bts_user in admin_data.Users
|
||||
@@ -52,9 +56,10 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> on_users => _users_subject.AsObservable();
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> on_accounts => _accounts_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 UserTuple? get_current_user() {
|
||||
foreach (var kv in _user_map) {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
using bts;
|
||||
using bts.api.admin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
class AdminModelWrapper
|
||||
{
|
||||
readonly AdminDataImmutable? _admin_data;
|
||||
readonly string _user_name;
|
||||
readonly List<BTSAccountImmutable> _allowed_accounts;
|
||||
readonly Dictionary<string, string> _allowed_account_dict = new Dictionary<string, string>();
|
||||
public AdminModelWrapper(AdminDataImmutable? admin_data = null, string user_name = null)
|
||||
{
|
||||
_admin_data = admin_data;
|
||||
_user_name = user_name;
|
||||
_allowed_accounts = search_admin(u => u.Name == _user_name).Select(x => x.Item1).ToList();
|
||||
foreach(var val in _allowed_accounts)
|
||||
_allowed_account_dict[val.Name] = val.Name;
|
||||
}
|
||||
public IEnumerable<Tuple<BTSAccountImmutable, TradingPermissionUnitImmutable>> get_trading_permissions(UInt32 user_id)
|
||||
{
|
||||
return search_admin(u => u.Id == user_id);
|
||||
}
|
||||
public IEnumerable<BTSAccountImmutable> viewable_accounts()
|
||||
{
|
||||
return _allowed_accounts;
|
||||
}
|
||||
public bool can_view_account(string account)
|
||||
{
|
||||
return _allowed_account_dict.ContainsKey(account);
|
||||
}
|
||||
|
||||
IEnumerable<Tuple<BTSAccountImmutable, TradingPermissionUnitImmutable>> search_admin(Func<BTSUserImmutable, bool> predicate)
|
||||
{
|
||||
var rv = new List<Tuple<BTSAccountImmutable, TradingPermissionUnitImmutable>>();
|
||||
if (_admin_data.HasValue)
|
||||
{
|
||||
var users = _admin_data.Value.Users.Where(predicate);
|
||||
if (users != null && users.Count() == 1)
|
||||
{
|
||||
var user = users.ElementAt(0);
|
||||
if (user.HasUserData && user.UserData.HasAccountPermissions)
|
||||
{
|
||||
foreach (var kv in user.UserData.AccountPermissions.AccountPermissions)
|
||||
{
|
||||
var account_id = kv.Key;
|
||||
var accounts = _admin_data.Value.Accounts.Where(a => a.Id == account_id);
|
||||
if (accounts != null && accounts.Count() == 1)
|
||||
{
|
||||
var account = accounts.ElementAt(0);
|
||||
var tp = kv.Value.TradingPermission;
|
||||
var rv_val = new TradingPermissionUnitImmutable(tp);
|
||||
rv.Add(Tuple.Create(account, rv_val));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.api.position;
|
||||
using bts.spark;
|
||||
using mbts.collections;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace bts.api.core
|
||||
{
|
||||
|
||||
static class PermissionFilters
|
||||
{
|
||||
public static IObservable<Update<PositionUnitImmutable, int>> PermissionFilter(this IObservable<Update<PositionUnitImmutable, int>> source, AdminModel am)
|
||||
{
|
||||
var pred = Observable.Create<Func<PositionUnitImmutable, bool>>( observer =>
|
||||
{
|
||||
return am.on_admin_data.Subscribe(x =>
|
||||
{
|
||||
Func<PositionUnitImmutable, bool> filter = (z => x.can_view_account(z.Account));
|
||||
observer.OnNext(filter);
|
||||
});
|
||||
});
|
||||
|
||||
return source.Filter(pred);
|
||||
}
|
||||
|
||||
public static IObservable<Update<OrderRecordImmutable, uint>> PermissionFilter(this IObservable<Update<OrderRecordImmutable, uint>> source, AdminModel am)
|
||||
{
|
||||
var pred = Observable.Create<Func<OrderRecordImmutable, bool>>(observer =>
|
||||
{
|
||||
return am.on_admin_data.Subscribe(x =>
|
||||
{
|
||||
Func<OrderRecordImmutable, bool> filter = (z => x.can_view_account(z.account()));
|
||||
observer.OnNext(filter);
|
||||
});
|
||||
});
|
||||
|
||||
return source.Filter(pred);
|
||||
}
|
||||
|
||||
public static IObservable<Update<FillImmutable, string>> PermissionFilter(this IObservable<Update<FillImmutable, string>> source, AdminModel am)
|
||||
{
|
||||
var pred = Observable.Create<Func<FillImmutable, bool>>(observer =>
|
||||
{
|
||||
return am.on_admin_data.Subscribe(x =>
|
||||
{
|
||||
Func<FillImmutable, bool> filter = (z => x.can_view_account(z.Account));
|
||||
observer.OnNext(filter);
|
||||
});
|
||||
});
|
||||
|
||||
return source.Filter(pred);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using System.Globalization;
|
||||
using mbts.ui.native;
|
||||
using bts;
|
||||
|
||||
namespace mbts.ui.shell
|
||||
{
|
||||
@@ -90,13 +91,13 @@ namespace mbts.ui.shell
|
||||
else {
|
||||
// Second instance. Activate the main window and return
|
||||
NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, (WM)NativeMethods.WM_SHOWME, IntPtr.Zero, IntPtr.Zero);
|
||||
Logger.log(bts.log.Level.Info, bts.log.Subsystem.Ui, "Starting a second shell is not allowed. Attempting to bring the original shell to the foreground.");
|
||||
Log.Ui.info("Starting a second shell is not allowed. Attempting to bring the original shell to the foreground.");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
catch (AbandonedMutexException ame) {
|
||||
Logger.log(bts.log.Level.Warn, bts.log.Subsystem.Ui, "Shell mutex was abandoned. Releasing the mutex and trying again. \n" + ame.ToString());
|
||||
Log.Ui.warn("Shell mutex was abandoned. Releasing the mutex and trying again. \n" + ame.ToString());
|
||||
_mutex.ReleaseMutex();
|
||||
// Try agai until stack overflow
|
||||
return Run();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using bts.api;
|
||||
using bts.log;
|
||||
using bts.spark;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
@@ -28,13 +28,13 @@ namespace bts {
|
||||
async void _delete_synthetic(uint id) {
|
||||
var (orders, orders_success) = await _orders_model.on_orders.FirstAsyncTask(SparkUtils.DefaultTimeOut);
|
||||
if (!orders_success) {
|
||||
Logger.warn(Subsystem.Ui, $"Ignoring request to delete synthetic security ({id}) because the orders lookup timed out (must confirm no working orders).");
|
||||
Log.Ui.warn($"Ignoring request to delete synthetic security ({id}) because the orders lookup timed out (must confirm no working orders).");
|
||||
} else if (orders.state.Values.Any(o => o.Def.SecurityKey == id && o.is_non_terminal())) {
|
||||
Logger.warn(Subsystem.Ui, $"Ignoring request to delete synthetic security ({id}) because non-terminal-orders were detected.");
|
||||
Log.Ui.warn($"Ignoring request to delete synthetic security ({id}) because non-terminal-orders were detected.");
|
||||
} else {
|
||||
var (strategy, strategy_success) = await _orders_model.spreader.lookup_strategy_async(id);
|
||||
if (strategy_success) {
|
||||
var deleted_strategy = strategy.with(deleted: true);
|
||||
ExecutionStrategyImmutable? strategy = await _orders_model.spreader.lookup_strategy_async(id);
|
||||
if (strategy.HasValue) {
|
||||
ExecutionStrategyImmutable deleted_strategy = strategy.Value.with(deleted: true);
|
||||
await _orders_model.spreader.save_strategy(id, deleted_strategy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using bts.api;
|
||||
using bts.api.synthetic;
|
||||
using bts.log;
|
||||
using mbts;
|
||||
using mbts.co2;
|
||||
using mbts.spark;
|
||||
@@ -12,29 +11,52 @@ using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using SecurityKey = System.UInt32;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
namespace bts {
|
||||
[Export(typeof(IShellDaemon))]
|
||||
class MDSource : IObserver<List<SecurityAndHandle>>, IShellDaemon, IDisposable {
|
||||
readonly IBus _bus;
|
||||
[Export(typeof(IInitializable))]
|
||||
class MDSource : IObserver<List<SecurityAndHandle>>, IInitializable, IDisposable {
|
||||
readonly Lazy<IBus> _bus;
|
||||
readonly Lazy<SyntheticSecurityService> _ss;
|
||||
readonly MDConsumer _consumer = new MDConsumer();
|
||||
readonly Co2RelayControl _relay;
|
||||
HashSet<SecurityKey> _deleted_synthetics = new HashSet<SecurityKey>();
|
||||
readonly Dictionary<SecurityKey, SecurityMD> _deleted_synthetics = new Dictionary<SecurityKey, SecurityMD>();
|
||||
long _version = 0;
|
||||
readonly ConcurrentDictionary<SecurityKey, SecurityMD> _exchange_sec_map = new ConcurrentDictionary<SecurityKey, SecurityMD>();
|
||||
List<IDisposable> _dispose = new List<IDisposable>();
|
||||
IReadOnlyDictionary<SecurityKey, SecurityMD> _exchange_sec_map = new Dictionary<SecurityKey, SecurityMD>();
|
||||
|
||||
[ImportingConstructor]
|
||||
public MDSource(IBus bus, SyntheticSecurityService ss) {
|
||||
public MDSource(Lazy<IBus> bus, Lazy<SyntheticSecurityService> ss) {
|
||||
_bus = bus;
|
||||
_ss = ss;
|
||||
_relay = new Co2RelayControl(_consumer);
|
||||
_relay.preload_securities((co2.MDRequest.Types.Flags)0xff).ignore();
|
||||
_dispose.Add(_consumer.on_security.Subscribe(this));
|
||||
}
|
||||
public void initialize() {
|
||||
_dispose.Add(
|
||||
ss.subscribei_metadata()
|
||||
_ss.Value.subscribei_metadata()
|
||||
.Select(upd => upd.all.ToDictionary(i => i.SecurityKey, i => i.Strategy))
|
||||
.Subscribe(t => {
|
||||
_deleted_synthetics = new HashSet<uint>(t.Where(kv => kv.Value.Deleted).Select(kv => kv.Key));
|
||||
_bus.post(SparkTags.SyntheticstrategiesTag, t);
|
||||
var deleted_synthetic_keys = new HashSet<uint>(t.Where(kv => kv.Value.Deleted).Select(kv => kv.Key));
|
||||
// add back 'undeleted' synthetics
|
||||
foreach (var kvp in _deleted_synthetics) {
|
||||
if (!deleted_synthetic_keys.Contains(kvp.Key)) {
|
||||
_exchange_sec_map.TryAdd(kvp.Key, kvp.Value);
|
||||
_deleted_synthetics.Remove(kvp.Key);
|
||||
_version += 1;
|
||||
}
|
||||
}
|
||||
// Remove any newly deleted synthetics and store them for potential undelete
|
||||
foreach (var key in deleted_synthetic_keys) {
|
||||
if (_exchange_sec_map.TryRemove(key, out SecurityMD deleted)) {
|
||||
_deleted_synthetics[key] = deleted;
|
||||
_version += 1;
|
||||
}
|
||||
}
|
||||
// Post full dictionary of syntheics here
|
||||
bus.post(SparkTags.SyntheticstrategiesTag, t);
|
||||
}));
|
||||
Task.Run(run);
|
||||
}
|
||||
@@ -42,38 +64,39 @@ namespace bts {
|
||||
[Export]
|
||||
MDConsumer consumer => _consumer;
|
||||
|
||||
public int priority => 55;
|
||||
IBus bus => _bus.Value;
|
||||
|
||||
public void OnCompleted() { }
|
||||
public void OnError(Exception error) => Logger.error(Subsystem.gen, error.ToString());
|
||||
public void OnError(Exception error) => Log.Md.error(error.ToString());
|
||||
public void OnNext(List<SecurityAndHandle> values) {
|
||||
if (values.Count > 0) {
|
||||
var temp = _exchange_sec_map.ToDictionary(k => k.Key, k => k.Value);
|
||||
foreach (var v in values) {
|
||||
temp.Add(v.security.Id, new SecurityMD(v.security, v.handle));
|
||||
var smd = new SecurityMD(v.security, v.handle);
|
||||
_exchange_sec_map.AddOrUpdate(smd.security.Id, smd, (_, __) => smd);
|
||||
}
|
||||
_exchange_sec_map = temp;
|
||||
_version += 1;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() => Disposable.dispose(_dispose);
|
||||
|
||||
async Task run() {
|
||||
int exch_sec_count = 0, deleted_synth_count = 0;
|
||||
long run_version = 0;
|
||||
while (true) {
|
||||
var exch = _exchange_sec_map;
|
||||
var deleted_synths = _deleted_synthetics;
|
||||
if (exch.Count > exch_sec_count || deleted_synths.Count != deleted_synth_count) {
|
||||
exch_sec_count = exch.Count;
|
||||
deleted_synth_count = deleted_synths.Count;
|
||||
exch = exch.Where(kv => !deleted_synths.Contains(kv.Key)).ToDictionary();
|
||||
long current_version = _version;
|
||||
if (current_version > run_version) {
|
||||
run_version = current_version;
|
||||
try {
|
||||
Logger.log(Level.Info, Subsystem.gen, String.Format("MDSource publishing {0} securities", exch.Count));
|
||||
_bus.post(SparkTags.SecuritiesTag, exch);
|
||||
Log.Md.info($"MDSource publishing {exch.Count} securities");
|
||||
bus.post(SparkTags.SecuritiesTag, exch);
|
||||
} catch (Exception e) {
|
||||
Logger.error(Subsystem.md, $"Error in publish_securities: {e}");
|
||||
Log.Md.error($"Error in publish_securities: {e}");
|
||||
}
|
||||
}
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using bts.api;
|
||||
using bts.api;
|
||||
using bts.spark;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using mbts;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using bts.spark;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
|
||||
namespace bts {
|
||||
[Export, Export(typeof(IShellDaemon))]
|
||||
@@ -29,102 +24,57 @@ namespace bts {
|
||||
bus.subscribe(SparkTags.ShowAndEditPickMultiple).OnDispatcher().Subscribe(show_and_edit_pick_multiple)
|
||||
);
|
||||
}
|
||||
|
||||
void show_pick_one(PartialCommand<SecurityMD> message) {
|
||||
var vm = new ContractPickerModel(_bus, false, null);
|
||||
var view = new ContractPickerView(vm);
|
||||
|
||||
Window picker = new Window {
|
||||
Content = view,
|
||||
Title = $"Select a Security for {message.title}",
|
||||
Icon = message.icon,
|
||||
Height = view.Height,
|
||||
Width = view.Width
|
||||
};
|
||||
picker.Style = picker.TryFindResource("BTSWindow") as Style;
|
||||
view.Height = Double.NaN;
|
||||
view.Width = Double.NaN;
|
||||
|
||||
vm.ok_action = () => {
|
||||
picker.Close();
|
||||
message.complete(vm.result).execute(_bus);
|
||||
};
|
||||
picker.move_to_mouse();
|
||||
picker.Show();
|
||||
}
|
||||
|
||||
void show_filtered_pick_one((PartialCommand<SecurityMD> message, SecurityCriteria global_filter) message_and_filter) {
|
||||
var vm = new ContractPickerModel(_bus, false, null, message_and_filter.global_filter);
|
||||
var view = new ContractPickerView(vm);
|
||||
|
||||
Window picker = new Window {
|
||||
Content = view,
|
||||
Title = $"Select a Security for {message_and_filter.message.title}",
|
||||
Icon = message_and_filter.message.icon,
|
||||
Height = view.Height,
|
||||
Width = view.Width
|
||||
};
|
||||
picker.Style = picker.TryFindResource("BTSWindow") as Style;
|
||||
view.Height = Double.NaN;
|
||||
view.Width = Double.NaN;
|
||||
|
||||
vm.ok_action = () => {
|
||||
picker.Close();
|
||||
message_and_filter.message.complete(vm.result).execute(_bus);
|
||||
};
|
||||
picker.move_to_mouse();
|
||||
picker.Show();
|
||||
}
|
||||
|
||||
void show_pick_multiple(PartialCommand<SecurityCriteria> message) {
|
||||
var vm = new ContractPickerModel(_bus, true, null);
|
||||
var view = new ContractPickerView(vm);
|
||||
|
||||
Window picker = new Window
|
||||
{
|
||||
Content = view,
|
||||
Title = $"Select Securities for {message.title}",
|
||||
Icon = message.icon,
|
||||
Height = view.Height,
|
||||
Width = view.Width,
|
||||
};
|
||||
picker.Style = picker.TryFindResource("BTSWindow") as Style;
|
||||
view.Height = Double.NaN;
|
||||
view.Width = Double.NaN;
|
||||
|
||||
vm.ok_action = () => {
|
||||
picker.Close();
|
||||
message.complete(vm.result_criteria).execute(_bus);
|
||||
};
|
||||
picker.move_to_mouse();
|
||||
picker.Show();
|
||||
}
|
||||
|
||||
void show_and_edit_pick_multiple((PartialCommand<SecurityCriteria> message, SecurityCriteria existing_criteria) message_and_data) {
|
||||
var vm = new ContractPickerModel(_bus, true, message_and_data.existing_criteria);
|
||||
var view = new ContractPickerView(vm);
|
||||
|
||||
Window picker = new Window {
|
||||
Content = view,
|
||||
Title = $" {message_and_data.message.title} security selection",
|
||||
Icon = message_and_data.message.icon,
|
||||
Height = view.Height,
|
||||
Width = view.Width,
|
||||
};
|
||||
picker.Style = picker.TryFindResource("BTSWindow") as Style;
|
||||
view.Height = Double.NaN;
|
||||
view.Width = Double.NaN;
|
||||
|
||||
vm.ok_action = () => {
|
||||
picker.Close();
|
||||
message_and_data.message.complete(vm.result_criteria).execute(_bus);
|
||||
};
|
||||
picker.move_to_mouse();
|
||||
picker.Show();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
|
||||
void show_pick_one(PartialCommand<SecurityMD> message) =>
|
||||
_show_picker(message, _fmt_pick_one_title, new ContractPickerModel(_bus, false, null), vm => vm.result);
|
||||
|
||||
void show_filtered_pick_one((PartialCommand<SecurityMD> message, SecurityCriteria global_filter) message_and_filter) =>
|
||||
_show_picker(message_and_filter.message, _fmt_pick_one_title, new ContractPickerModel(_bus, false, null, message_and_filter.global_filter), vm => vm.result);
|
||||
|
||||
void show_pick_multiple(PartialCommand<SecurityCriteria> message) =>
|
||||
_show_picker(message, _fmt_pick_multiple_title, new ContractPickerModel(_bus, true, null), vm => vm.result_criteria);
|
||||
|
||||
void show_and_edit_pick_multiple((PartialCommand<SecurityCriteria> message, SecurityCriteria existing_criteria) message_and_data) =>
|
||||
_show_picker(message_and_data.message, _fmt_pick_multiple_title, new ContractPickerModel(_bus, true, message_and_data.existing_criteria), vm => vm.result_criteria);
|
||||
|
||||
static string _fmt_pick_one_title(string message_title) => $"Select a Security for {message_title}";
|
||||
static string _fmt_pick_multiple_title(string message_title) => $"Select Securities for {message_title}";
|
||||
|
||||
void _show_picker<T>(PartialCommand<T> message, Func<string, string> fmt_title, ContractPickerModel vm, Func<ContractPickerModel, T> select_result) {
|
||||
var icon = message.icon;
|
||||
if (icon == null) {
|
||||
// not neccessary grind to hault just bc the icon is null, but it is most likely a dev-mistake that should be addressed.
|
||||
handle_bad_icon("a null icon");
|
||||
} else if (!icon.IsFrozen) {
|
||||
handle_bad_icon("an icon that wasn't frozen (not threadsafe)");
|
||||
}
|
||||
//
|
||||
void handle_bad_icon(string icon_error_desc) {
|
||||
Log.Api.warn($"{nameof(SecurityPickerDaemon)} received a message '{message.title}' with {icon_error_desc}, will use {nameof(Icons.Admin)}.");
|
||||
icon = Icons.Admin;
|
||||
}
|
||||
//
|
||||
var cpv = new ContractPickerView(vm);
|
||||
var window = new Window {
|
||||
Content = cpv,
|
||||
Title = fmt_title(message.title),
|
||||
Icon = message.icon,
|
||||
Height = cpv.Height,
|
||||
Width = cpv.Width,
|
||||
};
|
||||
window.Style = window.TryFindResource("BTSWindow") as Style;
|
||||
cpv.Width = double.NaN;
|
||||
cpv.Height = double.NaN;
|
||||
|
||||
vm.ok_action = () => {
|
||||
window.Close();
|
||||
message.complete(select_result(vm)).execute(_bus);
|
||||
};
|
||||
window.move_to_mouse();
|
||||
window.Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using bts;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
@@ -7,9 +9,14 @@ using System.Windows.Media.Imaging;
|
||||
namespace mbts.ui {
|
||||
public class Icons {
|
||||
public const string
|
||||
PaymentGeometryKey = nameof(PaymentGeometryKey),
|
||||
GearGeometryKey = nameof(GearGeometryKey),
|
||||
OrdersGeometryKey = nameof(OrdersGeometryKey),
|
||||
ToolsGeometryKey = nameof(ToolsGeometryKey);
|
||||
PaymentGeometryKey = nameof(PaymentGeometryKey),
|
||||
PlayGeometryKey = nameof(PlayGeometryKey),
|
||||
ToolsGeometryKey = nameof(ToolsGeometryKey),
|
||||
DraggableGeometryKey = nameof(DraggableGeometryKey);
|
||||
|
||||
public static Geometry PlayGeometry => Geometry.Parse(_get_geometric_path(PlayGeometryKey).FirstOrDefault() ?? string.Empty);
|
||||
|
||||
static readonly string base_uri = "pack://application:,,,/Images/Icons/";
|
||||
private static ResourceDictionary _resources = (ResourceDictionary)Application.LoadComponent(new Uri("/Images/Vector/Icons.xaml", UriKind.RelativeOrAbsolute));
|
||||
@@ -20,39 +27,43 @@ namespace mbts.ui {
|
||||
|
||||
[Obsolete("Please only add new vector icons using icons.xaml")]
|
||||
private static ImageSource load_image(string filename) {
|
||||
return new BitmapImage(uri(filename));
|
||||
return new BitmapImage(uri(filename)).frozen();
|
||||
}
|
||||
|
||||
private static ImageSource load_resource_and_freeze(string resource_name) {
|
||||
return ((DrawingImage)_resources[resource_name]).frozen();
|
||||
}
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
public static ImageSource Logo { get { return load_image("logo.ico"); } }
|
||||
public static ImageSource Admin { get { return (DrawingImage)_resources["Admin"]; } }
|
||||
public static ImageSource Analytics { get { return (DrawingImage)_resources["Analytics"]; } }
|
||||
public static ImageSource Sheets { get { return (DrawingImage)_resources["Sheets"]; } }
|
||||
public static ImageSource Trades { get { return (DrawingImage)_resources["Trades"]; } }
|
||||
public static ImageSource Depth { get { return (DrawingImage)_resources["Depth"]; } }
|
||||
public static ImageSource Ladder { get { return (DrawingImage)_resources["Ladder"]; } }
|
||||
public static ImageSource Admin { get { return load_resource_and_freeze("Admin"); } }
|
||||
public static ImageSource Analytics { get { return load_resource_and_freeze("Analytics"); } }
|
||||
public static ImageSource Sheets { get { return load_resource_and_freeze("Sheets"); } }
|
||||
public static ImageSource Trades { get { return load_resource_and_freeze("Trades"); } }
|
||||
public static ImageSource Depth { get { return load_resource_and_freeze("Depth"); } }
|
||||
public static ImageSource Ladder { get { return load_resource_and_freeze("Ladder"); } }
|
||||
public static ImageSource SheetsRed { get { return load_image("SheetsRed.png"); } }
|
||||
public static ImageSource SheetsGreen { get { return load_image("SheetsGreen.png"); } }
|
||||
public static ImageSource Spreads { get { return (DrawingImage)_resources["Spreads"]; } }
|
||||
public static ImageSource Spreads { get { return load_resource_and_freeze("Spreads"); } }
|
||||
public static ImageSource BlueSpreads { get { return load_image("BlueSpread.ico"); } }
|
||||
public static ImageSource Reports { get { return (DrawingImage)_resources["Reports"]; } }
|
||||
public static ImageSource Risk { get { return (DrawingImage)_resources["Risk"]; } }
|
||||
public static ImageSource Eye { get { return (DrawingImage)_resources["Eye"]; } }
|
||||
public static ImageSource Coins { get { return (DrawingImage)_resources["Coins"]; } }
|
||||
public static ImageSource Reports { get { return load_resource_and_freeze("Reports"); } }
|
||||
public static ImageSource Risk { get { return load_resource_and_freeze("Risk"); } }
|
||||
public static ImageSource Eye { get { return load_resource_and_freeze("Eye"); } }
|
||||
public static ImageSource Coins { get { return load_resource_and_freeze("Coins"); } }
|
||||
|
||||
|
||||
public static ImageSource DataCopy { get { return load_image("DataCopy.ico"); } }
|
||||
public static ImageSource Refresh { get { return load_image("Refresh.png"); } }
|
||||
public static ImageSource FavoriteWindow { get { return load_image("FavoriteWindow.png"); } }
|
||||
|
||||
public static ImageSource Browser => (DrawingImage)_resources["Browser"];
|
||||
public static ImageSource Browser => load_resource_and_freeze("Browser");
|
||||
|
||||
public static ImageSource Date { get { return load_image("Date.png"); } }
|
||||
|
||||
public static ImageSource Cube3d { get { return (DrawingImage)_resources["Cube"]; } }
|
||||
public static ImageSource Cube3d { get { return load_resource_and_freeze("Cube"); } }
|
||||
public static ImageSource Algo { get { return Cube3d; } }
|
||||
public static ImageSource Quote { get { return Math; } }
|
||||
public static ImageSource Math { get { return (DrawingImage)_resources["Surface"]; } }
|
||||
public static ImageSource Math { get { return load_resource_and_freeze("Surface"); } }
|
||||
public static ImageSource Abacus { get { return load_image("Abacus.png"); } }
|
||||
|
||||
public static ImageSource Delete { get { return load_image("Delete.png"); } }
|
||||
@@ -64,75 +75,90 @@ namespace mbts.ui {
|
||||
#pragma warning restore CS0618
|
||||
|
||||
// Vector icons
|
||||
public static ImageSource Bookmarks { get { return (DrawingImage)_resources["Bookmarks"]; } }
|
||||
public static ImageSource Window { get { return (DrawingImage)_resources["Window"]; } }
|
||||
public static ImageSource ActiveWindow { get { return (DrawingImage)_resources["ActiveWindow"]; } }
|
||||
public static ImageSource Trash { get { return (DrawingImage)_resources["Trash"]; } }
|
||||
public static ImageSource Trash2 { get { return (DrawingImage)_resources["Trash2"]; } }
|
||||
public static ImageSource Crucible { get { return (DrawingImage)_resources["Crucible"]; } }
|
||||
public static ImageSource Bookmarks { get { return load_resource_and_freeze("Bookmarks"); } }
|
||||
public static ImageSource Window { get { return load_resource_and_freeze("Window"); } }
|
||||
public static ImageSource ActiveWindow { get { return load_resource_and_freeze("ActiveWindow"); } }
|
||||
public static ImageSource Trash { get { return load_resource_and_freeze("Trash"); } }
|
||||
public static ImageSource Trash2 { get { return load_resource_and_freeze("Trash2"); } }
|
||||
public static ImageSource Crucible { get { return load_resource_and_freeze("Crucible"); } }
|
||||
|
||||
public static ImageSource Accounting { get { return (DrawingImage)_resources["Accounting"]; } }
|
||||
public static ImageSource Add { get { return (DrawingImage)_resources["Add"]; } }
|
||||
public static ImageSource AddEncircled { get { return (DrawingImage)_resources["AddEncircled"]; } }
|
||||
public static ImageSource Beaker { get { return (DrawingImage)_resources["Beaker"]; } }
|
||||
public static ImageSource Book { get { return (DrawingImage)_resources["Book"]; } }
|
||||
public static ImageSource Accounting { get { return load_resource_and_freeze("Accounting"); } }
|
||||
public static ImageSource Add { get { return load_resource_and_freeze("Add"); } }
|
||||
public static ImageSource AddEncircled { get { return load_resource_and_freeze("AddEncircled"); } }
|
||||
public static ImageSource Beaker { get { return load_resource_and_freeze("Beaker"); } }
|
||||
public static ImageSource Book { get { return load_resource_and_freeze("Book"); } }
|
||||
|
||||
public static ImageSource Orders => create_icon(OrdersGeometryKey);
|
||||
public static ImageSource AddOrders { get { return (DrawingImage)_resources["AddOrders"]; } }
|
||||
public static ImageSource CancelOrders { get { return (DrawingImage)_resources["CancelOrders"]; } }
|
||||
public static ImageSource CancelBuys { get { return (DrawingImage)_resources["CancelBuys"]; } }
|
||||
public static ImageSource CancelSells { get { return (DrawingImage)_resources["CancelSells"]; } }
|
||||
public static ImageSource CancelSelected { get { return (DrawingImage)_resources["CancelSelected"]; } }
|
||||
public static ImageSource AddOrders { get { return load_resource_and_freeze("AddOrders"); } }
|
||||
public static ImageSource CancelOrders { get { return load_resource_and_freeze("CancelOrders"); } }
|
||||
public static ImageSource CancelBuys { get { return load_resource_and_freeze("CancelBuys"); } }
|
||||
public static ImageSource CancelSells { get { return load_resource_and_freeze("CancelSells"); } }
|
||||
public static ImageSource CancelSelected { get { return load_resource_and_freeze("CancelSelected"); } }
|
||||
|
||||
public static ImageSource LadderCancelOrders { get { return (DrawingImage)_resources["LadderCancelOrders"]; } }
|
||||
public static ImageSource LadderCancelBuys { get { return (DrawingImage)_resources["LadderCancelBuys"]; } }
|
||||
public static ImageSource LadderCancelSells { get { return (DrawingImage)_resources["LadderCancelSells"]; } }
|
||||
public static ImageSource LadderCancelOrders { get { return load_resource_and_freeze("LadderCancelOrders"); } }
|
||||
public static ImageSource LadderCancelBuys { get { return load_resource_and_freeze("LadderCancelBuys"); } }
|
||||
public static ImageSource LadderCancelSells { get { return load_resource_and_freeze("LadderCancelSells"); } }
|
||||
|
||||
public static ImageSource Synthetics { get { return (DrawingImage)_resources["Synthetics"]; } }
|
||||
public static ImageSource AddSynthetic { get { return (DrawingImage)_resources["AddSynthetic"]; } }
|
||||
public static ImageSource RemoveSynthetic { get { return (DrawingImage)_resources["RemoveSynthetic"]; } }
|
||||
public static ImageSource ModifySynthetic { get { return (DrawingImage)_resources["ModifySynthetic"]; } }
|
||||
public static ImageSource CloneSynthetic { get { return (DrawingImage)_resources["CloneSynthetic"]; } }
|
||||
public static ImageSource Synthetics { get { return load_resource_and_freeze("Synthetics"); } }
|
||||
public static ImageSource AddSynthetic { get { return load_resource_and_freeze("AddSynthetic"); } }
|
||||
public static ImageSource RemoveSynthetic { get { return load_resource_and_freeze("RemoveSynthetic"); } }
|
||||
public static ImageSource ModifySynthetic { get { return load_resource_and_freeze("ModifySynthetic"); } }
|
||||
public static ImageSource CloneSynthetic { get { return load_resource_and_freeze("CloneSynthetic"); } }
|
||||
|
||||
public static ImageSource ModifyOrder { get { return (DrawingImage)_resources["ModifyOrder"]; } }
|
||||
public static ImageSource Clock { get { return (DrawingImage)_resources["Clock"]; } }
|
||||
public static ImageSource Defrag { get { return (DrawingImage)_resources["Defrag"]; } }
|
||||
public static ImageSource Display { get { return (DrawingImage)_resources["Display"]; } }
|
||||
public static ImageSource Export => (DrawingImage)_resources["Export"];
|
||||
public static ImageSource Filter { get { return (DrawingImage)_resources["Filter"]; } }
|
||||
public static ImageSource FilterActive { get { return (DrawingImage)_resources["FilterActive"]; } }
|
||||
public static ImageSource FilterError { get { return (DrawingImage)_resources["FilterError"]; } }
|
||||
public static ImageSource Import => (DrawingImage)_resources["Import"];
|
||||
public static ImageSource Knight { get { return (DrawingImage)_resources["Knight"]; } }
|
||||
public static ImageSource ModifyOrder { get { return load_resource_and_freeze("ModifyOrder"); } }
|
||||
public static ImageSource Clock { get { return load_resource_and_freeze("Clock"); } }
|
||||
public static ImageSource Defrag { get { return load_resource_and_freeze("Defrag"); } }
|
||||
public static ImageSource Display { get { return load_resource_and_freeze("Display"); } }
|
||||
public static ImageSource Draggable => create_icon(DraggableGeometryKey);
|
||||
public static ImageSource Export => load_resource_and_freeze("Export");
|
||||
public static ImageSource Filter { get { return load_resource_and_freeze("Filter"); } }
|
||||
public static ImageSource FilterActive { get { return load_resource_and_freeze("FilterActive"); } }
|
||||
public static ImageSource FilterError { get { return load_resource_and_freeze("FilterError"); } }
|
||||
public static ImageSource Gear => create_icon(GearGeometryKey);
|
||||
public static ImageSource Import => load_resource_and_freeze("Import");
|
||||
public static ImageSource Knight { get { return load_resource_and_freeze("Knight"); } }
|
||||
public static ImageSource Payment => create_icon(PaymentGeometryKey);
|
||||
public static ImageSource Spawn { get { return (DrawingImage)_resources["Spawn"]; } }
|
||||
public static ImageSource Target { get { return (DrawingImage)_resources["Target"]; } }
|
||||
public static ImageSource Tetris { get { return (DrawingImage)_resources["Tetris"]; } }
|
||||
public static ImageSource Spawn { get { return load_resource_and_freeze("Spawn"); } }
|
||||
public static ImageSource Target { get { return load_resource_and_freeze("Target"); } }
|
||||
public static ImageSource Tetris { get { return load_resource_and_freeze("Tetris"); } }
|
||||
public static ImageSource Tools => create_icon(ToolsGeometryKey);
|
||||
public static ImageSource ViewSettings => (DrawingImage)_resources["ViewSettings"];
|
||||
public static ImageSource Wrench { get { return (DrawingImage)_resources["Wrench"]; } }
|
||||
public static ImageSource UpArrow { get { return (DrawingImage)_resources["UpArrow"]; } }
|
||||
public static ImageSource ViewSettings => load_resource_and_freeze("ViewSettings");
|
||||
public static ImageSource Wrench { get { return load_resource_and_freeze("Wrench"); } }
|
||||
public static ImageSource RightArrow { get { return (load_resource_and_freeze("RightArrow")); } }
|
||||
public static ImageSource DownArrow { get { return (load_resource_and_freeze("DownArrow")); } }
|
||||
public static ImageSource Support => load_resource_and_freeze("SupportIcon");
|
||||
|
||||
public static ImageSource Support => (DrawingImage)_resources["SupportIcon"];
|
||||
public static ImageSource create_icon(string geometric_path_key) =>
|
||||
create_icon(geometric_path_key, Brushes.Black);
|
||||
|
||||
public static ImageSource create_icon(string geometric_path_key) => create_icon(geometric_path_key, Brushes.Black);
|
||||
public static ImageSource create_icon(string geometric_path_key, Brush brush, Pen pen = null) {
|
||||
object obj = _resources[geometric_path_key];
|
||||
if (obj is string path) {
|
||||
if (string.IsNullOrEmpty(path))
|
||||
throw new ArgumentException($"Resource lookup of '{geometric_path_key}' did not return a valid string.", nameof(geometric_path_key));
|
||||
return _create_icon(new[] { path }, brush, pen);
|
||||
} else if (obj is string[] paths) {
|
||||
return _create_icon(paths, brush, pen);
|
||||
} else return null;
|
||||
}
|
||||
public static ImageSource create_icon(string geometric_path_key, Brush brush, Pen pen = null) =>
|
||||
new DrawingImage(create_drawing(geometric_path_key, brush, pen));
|
||||
|
||||
static ImageSource _create_icon(IEnumerable<string> paths, Brush brush, Pen pen = null) {
|
||||
public static Drawing create_drawing(string geometric_path_key) =>
|
||||
create_drawing(geometric_path_key, Brushes.Black);
|
||||
|
||||
public static Drawing create_drawing(string geometric_path_key, Brush brush, Pen pen = null) =>
|
||||
create_drawing(_get_geometric_path(geometric_path_key), brush, pen);
|
||||
|
||||
public static Drawing create_drawing(IEnumerable<string> paths, Brush brush, Pen pen = null) {
|
||||
var drawing_group = new DrawingGroup();
|
||||
foreach (string p in paths) {
|
||||
drawing_group.Children.Add(new GeometryDrawing(brush, pen, Geometry.Parse(p)));
|
||||
}
|
||||
return new DrawingImage(drawing_group);
|
||||
return drawing_group;
|
||||
}
|
||||
|
||||
static IEnumerable<string> _get_geometric_path(string geometric_path_key) {
|
||||
object obj = _resources[geometric_path_key];
|
||||
if (obj is string path) {
|
||||
if (string.IsNullOrEmpty(path)) {
|
||||
Log.Api.error($"Resource lookup of '{geometric_path_key}' did not return a valid string.", nameof(geometric_path_key));
|
||||
path = string.Empty;
|
||||
}
|
||||
return new[] { path };
|
||||
} else if (obj is string[] paths) {
|
||||
return paths.Where(p => !string.IsNullOrEmpty(p)).ToArray();
|
||||
} else return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,10 @@
|
||||
xmlns:mbts="clr-namespace:mbts.ui"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
>
|
||||
<sys:String x:Key="{x:Static mbts:Icons.GearGeometryKey}">
|
||||
M16.000015,9.6000061C12.500012,9.6000061 9.5999844,12.5 9.5999844,16 9.5999844,19.5 12.500012,22.399994 16.000015,22.399994 19.500019,22.399994 22.400015,19.5 22.400015,16 22.400015,12.5 19.500019,9.6000061 16.000015,9.6000061z M14.199995,0L17.800005,0C18.199999,0,18.599993,0.29998779,18.599993,0.79998779L18.599993,4.7999878C19.900013,5.1000061,21.000019,5.6000061,22.099997,6.2999878L25.000023,3.3999939C25.099999,3.2999878 25.300011,3.1999817 25.500025,3.1999817 25.700007,3.1999817 25.900019,3.2999878 26.000025,3.3999939L28.600033,6C28.900021,6.2999878,28.900021,6.7999878,28.600033,7.1000061L25.700007,10C26.400019,11.100006,26.900019,12.299988,27.200007,13.5L31.20001,13.5C31.600037,13.5,31.999998,13.799988,31.999998,14.299988L31.999998,17.899994C31.999998,18.299988,31.700012,18.699982,31.20001,18.699982L27.200007,18.699982C26.900019,20,26.400019,21.100006,25.700007,22.199982L28.600033,25.100006C28.700009,25.199982 28.799984,25.399994 28.799984,25.600006 28.799984,25.799988 28.700009,26 28.600033,26.100006L26.000025,28.600006C25.900019,28.699982 25.700007,28.799988 25.500025,28.799988 25.300011,28.799988 25.099999,28.699982 25.000023,28.600006L22.099997,25.699982C21.000019,26.399994,19.800007,26.899994,18.599993,27.199982L18.599993,31.199982C18.599993,31.600006,18.300005,32,17.800005,32L14.199995,32C13.8,32,13.400006,31.699982,13.400006,31.199982L13.400006,27.199982C12.099987,26.899994,11.000011,26.399994,9.9000027,25.699982L7.0000064,28.600006C6.9000003,28.699982 6.6999881,28.799988 6.5000059,28.799988 6.2999937,28.799988 6.099981,28.699982 6.0000054,28.600006L3.3999967,26C3.0999784,25.699982,3.0999784,25.199982,3.3999967,24.899994L6.2999937,22C5.5999805,20.899994,5.0999801,19.699982,4.7999923,18.5L0.79998828,18.5C0.39999409,18.5,-1.933513E-07,18.199982,0,17.699982L0,14.100006C-1.933513E-07,13.699982,0.2999879,13.299988,0.79998828,13.299988L4.7999923,13.299988C5.0999801,12,5.5999805,10.899994,6.2999937,9.7999878L3.3999967,7C3.2999908,6.8999939 3.1999845,6.6999817 3.1999845,6.5 3.1999845,6.2999878 3.2999908,6.1000061 3.3999967,6L6.0000054,3.3999939C6.099981,3.2999878 6.2999937,3.1999817 6.5000059,3.1999817 6.6999881,3.1999817 6.9000003,3.2999878 7.0000064,3.3999939L9.9000027,6.2999878C11.000011,5.6000061,12.199993,5.1000061,13.400006,4.7999878L13.400006,0.79998779C13.400006,0.29998779,13.8,0,14.199995,0z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.OrdersGeometryKey}">
|
||||
M50.14375,33.389126C50.416863,33.394688,50.653496,33.587773,50.705998,33.864617L50.015995,45.114388C49.810295,46.291462,49.130394,45.097489,49.130394,45.097489L47.65659,42.640538C42.747776,49.635197 34.633152,54.221001 25.433924,54.221001 14.884193,54.221001 5.7437552,48.204125 1.2449617,39.419205 0.80087067,38.552021 0.21358696,37.705738 0.052086769,36.736958 -0.49735315,33.490925 3.4507786,32.901036 4.8049124,34.432404 5.3674143,35.067692 6.034036,36.16407 6.5079177,37.026051 10.106778,43.799416 17.2321,48.413819 25.433924,48.413819 32.656544,48.413819 39.026364,44.826695 42.903977,39.351506L40.229367,38.854115C40.229367,38.854115,38.677464,38.647118,39.742466,37.996033L50.024895,33.398526C50.064972,33.391365,50.104733,33.388333,50.14375,33.389126z M23.030291,9.0310004L27.889918,9.0310004 27.889918,13.028299C30.539786,13.205498,32.324932,13.958096,33.464161,14.528294L33.964176,14.775793 32.417435,20.038878 31.706417,19.68068C30.579987,19.11958 28.839241,18.244583 26.149073,18.244583 24.006916,18.244583 22.723183,19.122181 22.723183,20.590876 22.723183,21.894272 23.943314,22.804369 27.551308,24.187165 32.706444,26.124659 34.911,28.702853 34.911,32.817341 34.911,36.892929 32.090527,40.05172 27.673611,40.989117L27.673611,45.540001 22.767485,45.540001 22.767485,41.330414C20.252919,41.155815,17.695551,40.424118,16.101811,39.404721L15.683999,39.137619 17.221739,33.731438 17.969059,34.180634C19.088988,34.851235 21.402748,35.97873 24.340325,35.97873 26.734886,35.97873 28.276728,34.921635 28.276728,33.291336 28.276728,31.986641 27.547409,30.853846 23.986016,29.57255 20.083815,28.144253 16.095511,26.04406 16.095511,21.239174 16.095511,17.280985 18.737379,14.291395 23.030291,13.329098z M25.268765,0C35.819447,2.7347414E-07 44.961449,6.0155495 49.458798,14.804573 49.902695,15.669173 50.490094,16.516972 50.651287,17.48427 51.198281,20.734265 47.255726,21.318962 45.901341,19.789065 45.336346,19.153567 44.669552,18.051968 44.19566,17.19517 40.5957,10.421881 33.473275,5.8072398 25.268765,5.8072393 18.047545,5.8072398 11.678013,9.3918336 7.8003562,14.867073L10.474627,15.367072C10.474627,15.367072,12.026909,15.574172,10.962921,16.223872L0.67933281,20.826762C0.36037083,20.880062,0.057029126,20.669165,0.00099972012,20.354063L0.69093342,9.1118043C0.89552094,7.9296158,1.5738537,9.124924,1.5738539,9.124924L3.0452772,11.583379C7.9566038,4.584462,16.073666,2.7347414E-07,25.268765,0z
|
||||
</sys:String>
|
||||
@@ -13,10 +17,40 @@
|
||||
M28.000001,6.0000026L28.000001,15 6.0010006,15 6.0010006,17.000003 30.001,17.000003 30.001,6.0000026z M14.231,2.9540056L14.979999,2.9540056 14.979999,3.987005C15.704998,4.009005,16.200997,4.178005,16.550998,4.3600049L16.248998,5.0620046C15.994998,4.9340047 15.499998,4.7010047 14.725999,4.7010047 13.796,4.7010047 13.445001,5.1900046 13.445001,5.6160046 13.445001,6.1710043 13.893,6.4470041 14.943999,6.8310037 16.187998,7.2780037 16.816998,7.8320036 16.816998,8.7800031 16.816998,9.6210027 16.151998,10.409003 14.907999,10.591002L14.907999,11.698002 14.146,11.698002 14.146,10.622003C13.445001,10.611002,12.720001,10.420002,12.297002,10.154003L12.587001,9.4400029C13.022001,9.696003 13.651,9.8980026 14.328,9.8980026 15.185999,9.8980026 15.765998,9.4620028 15.765998,8.8540034 15.765998,8.2680035 15.293999,7.9060035 14.4,7.5870035 13.167001,7.1610038 12.406001,6.6710041 12.406001,5.7440041 12.406001,4.8600047 13.119,4.189005 14.231,4.0290052z M2,2.0000002L2,13 26.000001,13 26.000001,2.0000002z M0,0L28.000001,0 28.000001,4.0000031 32.001,4.0000031 32.001,19.000003 4.0010006,19.000003 4.0010006,15 0,15z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.PlayGeometryKey}">
|
||||
M0,0L25.194,16 0,32z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.ToolsGeometryKey}">
|
||||
M4.9629898,25.440168C4.564003,25.440168 4.0650101,25.640182 3.7659989,25.939165 3.0680008,26.638176 3.0680008,27.635191 3.7659989,28.3342 4.4639969,29.032204 5.4620133,29.032204 6.1600113,28.3342 6.8589859,27.635191 6.8589859,26.638176 6.1600113,25.939165 5.760994,25.640182 5.3620072,25.440168 4.9629898,25.440168z M18.432014,18.457121L22.422005,22.447138 24.816994,22.946165 29.107002,29.431192 27.509987,31.027212 21.025002,26.737175 20.526985,24.343178 16.535988,20.352123z M8.1560135,18.357115L13.643013,23.84415C11.048988,26.638176 8.9539872,28.932196 7.9560013,29.930189 6.359993,31.526207 3.6659928,31.626215 2.070015,29.930189 0.47400668,28.3342 0.47400668,25.640182 2.070015,24.044165z M2.967995,6.7840408C2.768013,6.7840408 2.5690079,6.8840479 2.4690018,6.9840545 2.1699906,7.2830371 2.1699906,7.7820638 2.4690018,8.0810469L11.746986,17.359123C12.047005,17.659113 12.544991,17.659113 12.845009,17.359123 13.14399,17.06011 13.14399,16.561113 12.845009,16.2621L3.5659867,6.9840545C3.3670121,6.8840479,3.1669998,6.7840408,2.967995,6.7840408z M5.2620011,4.4890431C5.0629959,4.4890431 4.8630143,4.5890192 4.7640152,4.6890262 4.3649979,4.9880394 4.3649979,5.4870356 4.7640152,5.8860554L14.042,15.164101C14.341011,15.46409 14.840004,15.46409 15.139015,15.164101 15.439003,14.865087 15.439003,14.366092 15.139015,14.067109L5.8610001,4.6890262C5.661995,4.5890192,5.4620133,4.4890431,5.2620011,4.4890431z M4.3649979,2.3940283C5.4620133,2.3940281,6.5589981,2.7930175,7.3580094,3.5920337L19.328987,15.46409 13.342995,21.450153 1.2720107,9.6770657C-0.42400357,7.9810704 -0.42400357,5.2880291 1.2720107,3.6910337 2.1699906,2.7930175 3.267006,2.3940281 4.3649979,2.3940283z M24.217995,0C25.31501,-8.5992724E-08,26.312996,0.20001344,27.211006,0.59900296L23.021004,4.7890328C22.223,5.5870422,22.223,6.7840408,23.021004,7.4820436L24.617012,9.079069C25.414986,9.8770789,26.612007,9.8770789,27.311012,9.079069L31.501015,4.9880394C31.799995,5.8860554 32.000008,6.7840408 32.000008,7.7820638 32.000008,12.07207 28.508003,15.564097 24.217995,15.564097 19.927986,15.564097 16.436012,12.07207 16.436012,7.7820638 16.436012,3.4920272 19.927986,-8.5992724E-08 24.217995,0z
|
||||
</sys:String>
|
||||
|
||||
<!--
|
||||
'y' for equilateral triangle of side length x:
|
||||
(x/2)^2 + Y^2 = x^2
|
||||
Y^2 = x^2 - (x^2)/4
|
||||
Y = root((0.75) * x^2)
|
||||
|
||||
'y' for equilateral triangle of side length 6:
|
||||
(6 * 0.5)^2 + Y^2 = 6^2
|
||||
9 + Y^2 = 36
|
||||
y^2 = 27
|
||||
y = root(27)
|
||||
y = 5.19615242271
|
||||
|
||||
use 1/2 of y to add some space between the up/down triangles.
|
||||
begin next triangle at (3/2)y ~= 7.79422863406
|
||||
-->
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.DraggableGeometryKey}">
|
||||
M0,5.19615242271 L3,0 6,5.19615242271
|
||||
M0,7.79422863406 L3,12.9903810568 6,7.79422863406 Z
|
||||
</sys:String>
|
||||
|
||||
<Geometry x:Key="arrow_geometry">
|
||||
F1 M 78.1398,324.18L 52.5865,298.626C 51.6892,297.728 50.3385,297.459 49.1678,297.946C 47.9971,298.43 47.2305,299.574 47.2305,300.843L 60.6385,326.398L 47.2305,351.952C 47.2305,353.223 47.9971,354.364 49.1678,354.848C 49.5558,355.011 49.9638,355.09 50.3678,355.09C 51.1838,355.09 51.9852,354.77 52.5865,354.171L 78.1398,328.616C 78.7238,328.032 79.0598,327.224 79.0598,326.398C 79.0598,325.572 78.7238,324.764 78.1398,324.18 Z
|
||||
</Geometry>
|
||||
|
||||
<DrawingImage x:Key="Bookmarks">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
@@ -488,17 +522,6 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
|
||||
<DrawingImage x:Key="PlayIcon">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing Brush="Black"
|
||||
Geometry="M0,0L25.194,16 0,32z" />
|
||||
</DrawingGroup.Children>
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
|
||||
<!-- help / support -->
|
||||
<DrawingImage x:Key="SupportIcon">
|
||||
<DrawingImage.Drawing>
|
||||
@@ -759,20 +782,39 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
|
||||
<DrawingImage x:Key="UpArrow" x:Shared="False">
|
||||
<DrawingImage x:Key="RightArrow" x:Shared="False">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing
|
||||
Brush="Black"
|
||||
Geometry="M16,0L32,19.745 0,19.745z"
|
||||
/>
|
||||
<GeometryDrawing Geometry="{StaticResource arrow_geometry}">
|
||||
<GeometryDrawing.Pen>
|
||||
<Pen Thickness="3" Brush="Black"/>
|
||||
</GeometryDrawing.Pen>
|
||||
</GeometryDrawing>
|
||||
</DrawingGroup.Children>
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
|
||||
<DrawingImage x:Key="DownArrow" x:Shared="False">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing Geometry="{StaticResource arrow_geometry}">
|
||||
<GeometryDrawing.Pen>
|
||||
<Pen Thickness="3" Brush="Black"/>
|
||||
</GeometryDrawing.Pen>
|
||||
</GeometryDrawing>
|
||||
</DrawingGroup.Children>
|
||||
<DrawingGroup.Transform>
|
||||
<RotateTransform Angle="90"/>
|
||||
</DrawingGroup.Transform>
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
|
||||
<!-- Pin
|
||||
|
||||
<!-- Pin
|
||||
<Path Data="F1M3,11L3,7 0,7 0,6 1,6 1,1 1,0 6,0 6,1 6,6 7,6 7,7 4,7 4,11 3,11z M2,1L2,6 4,6 4,1 2,1z" />
|
||||
Unpin
|
||||
<Path Data="F1M0,3L4,3 4,0 5,0 5,1 10,1 11,1 11,6 10,6 5,6 5,7 4,7 4,4 0,4 0,3z M10,2L5,2 5,4 10,4 10,2z" />
|
||||
|
||||
@@ -9,6 +9,7 @@ using mbts.ui.settings;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using bts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
@@ -17,6 +18,7 @@ using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Input;
|
||||
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class MarketViewVM {
|
||||
public class Row : SecurityMDRow, IDisposable, IEquatable<Row>, DIYGrid.IRowVM, DIYGrid.IActionableRowVM {
|
||||
@@ -27,9 +29,9 @@ 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<ushort> _bid_size, _ask_size;
|
||||
readonly Property<uint> _bid_size, _ask_size;
|
||||
readonly CellProperty<int> _position;
|
||||
readonly CellProperty<ushort> _last_size;
|
||||
readonly CellProperty<uint> _last_size;
|
||||
readonly CellProperty<ulong> _volume;
|
||||
readonly CellProperty<bool> _inside_bids, _inside_asks;
|
||||
readonly CellProperty<int> _working_bid_volume, _working_ask_volume;
|
||||
@@ -47,10 +49,10 @@ namespace mbts.spark {
|
||||
_position = new CellProperty<int>(0);
|
||||
_bid_price = backing(nameof(bid_price), double.NaN);
|
||||
_ask_price = backing(nameof(ask_price), double.NaN);
|
||||
_bid_size = backing<ushort>(nameof(bid_size), 0);
|
||||
_ask_size = backing<ushort>(nameof(ask_size), 0);
|
||||
_bid_size = backing<uint>(nameof(bid_size), 0);
|
||||
_ask_size = backing<uint>(nameof(ask_size), 0);
|
||||
_last_price = backing(nameof(last_price), double.NaN);
|
||||
_last_size = new CellProperty<ushort>(0);
|
||||
_last_size = new CellProperty<uint>(0);
|
||||
_day_change = backing(nameof(day_change), double.NaN);
|
||||
_session_low = backing(nameof(session_low), double.NaN);
|
||||
_session_high = backing(nameof(session_high), double.NaN);
|
||||
@@ -95,11 +97,11 @@ namespace mbts.spark {
|
||||
public bool inside_asks => _inside_asks.value;
|
||||
public int position => _position.value;
|
||||
public double last_price => _last_price.Value;
|
||||
public ushort last_size => _last_size.value;
|
||||
public uint last_size => _last_size.value;
|
||||
public double bid_price => _bid_price.Value;
|
||||
public ushort bid_size => _bid_size.Value;
|
||||
public uint bid_size => _bid_size.Value;
|
||||
public double ask_price => _ask_price.Value;
|
||||
public ushort ask_size => _ask_size.Value;
|
||||
public uint ask_size => _ask_size.Value;
|
||||
public ulong volume => _volume.value;
|
||||
public double settlement => _settlement.Value;
|
||||
public double day_change => _day_change.Value;
|
||||
|
||||
@@ -5,9 +5,10 @@ using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using System.Reactive.Linq;
|
||||
using static bts.shell.MarketViewWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using static mbts.ui.spark.MarketViewView;
|
||||
@@ -18,7 +19,11 @@ namespace mbts.spark {
|
||||
readonly OrdersModel _orders_model;
|
||||
|
||||
public MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state)
|
||||
: base(RXCollection.Create(state.SelectMany(s => s.security_criteria.subscribe_md(bus)), SecurityDataHelper.EqualityComparer.MDComparer), null) {
|
||||
: this(bus, orders_model, state, state.SelectManyInSeries(s => s.security_criteria.subscribe_md(bus))) { }
|
||||
|
||||
// securities_connectable should NOT be connected already.
|
||||
MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IConnectableObservable<IEnumerable<SecurityMD>> securities_connectable)
|
||||
: base(RXCollection.Create(securities_connectable, SecurityDataHelper.EqualityComparer.MDComparer), null) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
|
||||
@@ -107,6 +112,7 @@ namespace mbts.spark {
|
||||
}), DataGridFilterHelper.hook_filters(rows_view,
|
||||
security_filter, bid_price_filter, bid_size_filter, ask_price_filter, ask_size_filter, last_price_filter, last_size_filter, day_change_filter, settlement_filter,
|
||||
session_low_filter, session_high_filter, position_filter, working_bid_volume_filter, working_ask_volume_filter, volume_filter, security_key_filter),
|
||||
securities_connectable.Connect(),
|
||||
WorkLoop.Start("MarketView", _refresh)
|
||||
);
|
||||
}
|
||||
@@ -116,11 +122,11 @@ namespace mbts.spark {
|
||||
#region Filters
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<Row>(r => r.bid_price, nameof(Row.bid_price));
|
||||
public IDataGridColumnPersistentFilter<Row, UShortMinMaxState> bid_size_filter { get; } = new UShortMinMax<Row>(r => r.bid_size, nameof(Row.bid_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_size, nameof(Row.bid_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> ask_price_filter { get; } = new DoubleMinMax<Row>(r => r.ask_price, nameof(Row.ask_price));
|
||||
public IDataGridColumnPersistentFilter<Row, UShortMinMaxState> ask_size_filter { get; } = new UShortMinMax<Row>(r => r.ask_size, nameof(Row.ask_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_size, nameof(Row.ask_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> last_price_filter { get; } = new DoubleMinMax<Row>(r => r.last_price, nameof(Row.last_price));
|
||||
public IDataGridColumnPersistentFilter<Row, UShortMinMaxState> last_size_filter { get; } = new UShortMinMax<Row>(r => r.last_size, nameof(Row.last_size));
|
||||
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));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_low_filter { get; } = new DoubleMinMax<Row>(r => r.session_low, nameof(Row.session_low));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.ui;
|
||||
@@ -12,7 +13,7 @@ using System.Windows;
|
||||
|
||||
namespace mbts.spark {
|
||||
class MarketViewWindow : SparkWindow<MarketViewWindowFactory.StartupData>, ISharedWindow {
|
||||
const string _window_type = "MarketView", _thread_id = "MarketView";
|
||||
const string _window_type = "MarketView", _thread_id = "MarketView", _window_type_display_name = "Market View";
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly AllowOnce _init_guard = new AllowOnce("MarketView");
|
||||
MarketViewVM _vm;
|
||||
@@ -39,14 +40,12 @@ namespace mbts.spark {
|
||||
|
||||
public void thread_init() {
|
||||
_init_guard.check_access();
|
||||
Logger.notnull(_state);
|
||||
Log.notnull(_state);
|
||||
var state_src = Observable.FromEvent<Action<MarketViewWindowFactory.StartupData>, MarketViewWindowFactory.StartupData>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
|
||||
_vm = new MarketViewVM(_bus, _orders_model, state_src);
|
||||
_view = new MarketViewView { DataContext = _vm };
|
||||
var update_securities_tag = new SparkTags.SparkTag<SecurityCriteria>("MarketView Callback");
|
||||
var update_securities_cmd = SparkCommand.create("Update Market View", Icon, update_securities_tag);
|
||||
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
|
||||
_commands = new List<SparkCommand> {
|
||||
SparkCommand.create("Change Securities", Icons.Admin, SparkTags.ShowAndEditPickMultiple, () => (update_securities_cmd, _state.security_criteria)),
|
||||
SparkCommands.ShowLadders.complete(_vm.selected_security_mds_observable(), smds => smds.Count > 0),
|
||||
SparkCommands.ShowOrderTicketFromSecurity.complete(_vm.current_security_md, md => md.HasValue, ObjectHelper.ValueOrDefault),
|
||||
SparkCommands.ShowFills.complete(_vm.selected_securities_observable(), _ => true,
|
||||
@@ -55,9 +54,10 @@ namespace mbts.spark {
|
||||
})
|
||||
),
|
||||
SparkCommands.ShowTimeAndSales.complete(_vm.selected_securities_observable(), _ => true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria),
|
||||
SparkCommands.ShowSyntheticCreate.complete(_vm.selected_securities_observable(), secs => secs.Count > 0 && secs.Count <= 24, secs => new SecurityCriteria(secs))
|
||||
SparkCommands.ShowSyntheticCreate.complete(_vm.selected_securities_observable(), secs => secs.Count > 0 && secs.Count <= 24, secs => new SecurityCriteria(secs)),
|
||||
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria))
|
||||
};
|
||||
_subscriptions = _bus.subscribe(update_securities_tag).OnDispatcher().Subscribe(c =>
|
||||
_subscriptions = _bus.subscribe(update_securities_cmd.tag).OnDispatcher().Subscribe(c =>
|
||||
_state_changed?.Invoke(_state = new MarketViewWindowFactory.StartupData(c, _vm.rows_view.GetSortingCriteria(), _vm.get_filter_states())));
|
||||
}
|
||||
|
||||
@@ -67,4 +67,4 @@ namespace mbts.spark {
|
||||
Commands.OfType<IDisposable>().DisposeAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,15 +57,15 @@ namespace bts.shell {
|
||||
[ProtoMember(2)]
|
||||
public DoubleMinMaxState bid_price;
|
||||
[ProtoMember(3)]
|
||||
public UShortMinMaxState bid_size;
|
||||
public UIntMinMaxState bid_size;
|
||||
[ProtoMember(4)]
|
||||
public DoubleMinMaxState ask_price;
|
||||
[ProtoMember(5)]
|
||||
public UShortMinMaxState ask_size;
|
||||
public UIntMinMaxState ask_size;
|
||||
[ProtoMember(6)]
|
||||
public DoubleMinMaxState last_price;
|
||||
[ProtoMember(7)]
|
||||
public UShortMinMaxState last_size;
|
||||
public UIntMinMaxState last_size;
|
||||
[ProtoMember(8)]
|
||||
public DoubleMinMaxState day_change;
|
||||
[ProtoMember(9)]
|
||||
|
||||
@@ -18,21 +18,23 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
class TimeAndSalesModel {
|
||||
class TimeAndSalesModel : IDisposable {
|
||||
static readonly TimeSpan _poll_interval = TimeSpan.FromSeconds(0.4);
|
||||
Lazy<IObservable<IReadOnlyList<TradeInfo>>> _trades;
|
||||
IDisposable _dispose;
|
||||
|
||||
public TimeAndSalesModel(IBus bus, MDConsumer consumer, SecurityCriteria security_criteria) {
|
||||
trades = security_criteria.subscribe_md_map(bus)
|
||||
.Scan(new Dictionary<ChannelKey, ChannelConsumer>(),
|
||||
(cache, md_map) => {
|
||||
var copy = new Dictionary<ChannelKey, ChannelConsumer>(cache);
|
||||
copy.sync_with_demux(md_map, get_channel_key, get_channel_consumer, update_channel_consumer);
|
||||
return copy;
|
||||
}
|
||||
).Select(cache =>
|
||||
Observable.Interval(_poll_interval)
|
||||
.Select(_ => new List<TradeInfo>(cache.Values.SelectMany(c => c.read_trades())))
|
||||
).SelectMany(_ => _);
|
||||
var curr_channels = new Dictionary<ChannelKey, ChannelConsumer>();
|
||||
_trades = new Lazy<IObservable<IReadOnlyList<TradeInfo>>>(() => Observable.Interval(_poll_interval).Select(_ => {
|
||||
var copy = curr_channels;
|
||||
return copy.Values.SelectMany(c => c.read_trades()).ToList();
|
||||
}));
|
||||
_dispose = security_criteria.subscribe_md_map(bus).Subscribe(
|
||||
md_map => {
|
||||
var copy = new Dictionary<ChannelKey, ChannelConsumer>(curr_channels);
|
||||
copy.sync_with_demux(md_map, get_channel_key, get_channel_consumer, update_channel_consumer);
|
||||
curr_channels = copy;
|
||||
});
|
||||
|
||||
ChannelKey get_channel_key(KeyValuePair<uint, SecurityMD> md_kv) =>
|
||||
new ChannelKey(md_kv.Value.security.Symbol, md_kv.Value.security.security_type(), md_kv.Value.security.Exchange);
|
||||
@@ -48,7 +50,9 @@ namespace mbts.spark {
|
||||
channel_kv.Value.set_securities(security_kvs);
|
||||
}
|
||||
|
||||
public IObservable<IReadOnlyList<TradeInfo>> trades { get; }
|
||||
public IObservable<IReadOnlyList<TradeInfo>> trades => _trades.Value;
|
||||
|
||||
public void Dispose() => Disposable.dispose(ref _dispose);
|
||||
|
||||
private class ChannelKey : IEquatable<ChannelKey> {
|
||||
public readonly string symbol;
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace mbts.spark {
|
||||
|
||||
public Row(TimeAndSalesVM parent, SecurityImmutable security, TradeMessageImmutable trade) : base(security) {
|
||||
_trade_key = trade.Key;
|
||||
time = trade.Time.lldatetime();
|
||||
time = ((ulong)trade.Time).lldatetime().ToLocalTime();
|
||||
price = trade.Mantissa * Math.Pow(10, trade.Exponent);
|
||||
size = trade.Size;
|
||||
aggressor_side = trade.AggressorSide;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.co2;
|
||||
@@ -16,10 +17,11 @@ using static mbts.ui.controls.DIYGrid;
|
||||
|
||||
namespace mbts.spark {
|
||||
class TimeAndSalesWindow : SparkWindow<StartupData>, ISlowWindow {
|
||||
const string _window_type = "TimeAndSales";
|
||||
const string _window_type = "TimeAndSales", _window_type_display_name = "Time And Sales";
|
||||
readonly MDConsumer _consumer;
|
||||
readonly TimeAndSalesWindowFactory _factory;
|
||||
SingleValuePump<TimeAndSalesVM> _vm;
|
||||
TimeAndSalesModel _model;
|
||||
StartupData _state;
|
||||
TimeAndSalesView _view;
|
||||
SparkCommand[] _commands;
|
||||
@@ -27,7 +29,7 @@ namespace mbts.spark {
|
||||
|
||||
public TimeAndSalesWindow(TimeAndSalesWindowFactory factory, IBus bus, MDConsumer consumer, StartupData state, ImageSource icon)
|
||||
: base("Time And Sales", bus, factory, SparkCommands.ShowTimeAndSales.icon) {
|
||||
Logger.assert(state != null);
|
||||
Log.assert(state != null);
|
||||
_consumer = consumer;
|
||||
_factory = factory;
|
||||
_state = state;
|
||||
@@ -55,18 +57,20 @@ namespace mbts.spark {
|
||||
public void thread_init() {
|
||||
_dispose_resources();
|
||||
_vm = new SingleValuePump<TimeAndSalesVM>();
|
||||
var update_securities_tag = new SparkTags.SparkTag<SecurityCriteria>("TimeAndSales Callback");
|
||||
var update_securities_cmd = SparkCommand.create("Update Time and Sales", Icon, update_securities_tag);
|
||||
var current_smd_source = _vm.observable.SelectMany(vm => vm.current_security_md_observable(_bus)).OnDispatcher();
|
||||
var selected_secs_source = _vm.observable.SelectMany(vm => vm.selected_securities_observable()).OnDispatcher();
|
||||
var connectable_builder = new ConnectableSeriesBuilder<TimeAndSalesVM>(_vm.observable);
|
||||
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
|
||||
var current_smd_source = connectable_builder.create(vm => vm.current_security_md_observable(_bus)).OnDispatcher();
|
||||
var selected_secs_source = connectable_builder.create(vm => vm.selected_securities_observable()).OnDispatcher();
|
||||
_commands = new SparkCommand[] {
|
||||
SparkCommand.create("Change Securities", Icons.Admin, SparkTags.ShowAndEditPickMultiple, () => (update_securities_cmd, _state.security_criteria)),
|
||||
SparkCommands.ShowLadder.complete(current_smd_source, md => md.HasValue, ObjectHelper.ValueOrDefault),
|
||||
SparkCommands.ShowOrderTicketFromSecurity.complete(current_smd_source, md => md.HasValue, ObjectHelper.ValueOrDefault),
|
||||
SparkCommands.ShowFills.complete(selected_secs_source, always_true, get_fills_startup_data),
|
||||
SparkCommands.ShowMarketView.complete(selected_secs_source, always_true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria)
|
||||
SparkCommands.ShowMarketView.complete(selected_secs_source, always_true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria),
|
||||
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria))
|
||||
};
|
||||
_subscriptions = _bus.subscribe(update_securities_tag).OnDispatcher().Subscribe(update_criteria);
|
||||
_subscriptions = Disposable.Create(
|
||||
_bus.subscribe(update_securities_cmd.tag).OnDispatcher().Subscribe(update_criteria),
|
||||
connectable_builder.connect_all());
|
||||
_initialize();
|
||||
|
||||
bool always_true<T>(T _) => true;
|
||||
@@ -83,6 +87,7 @@ namespace mbts.spark {
|
||||
void _dispose_resources() {
|
||||
Disposable.dispose(ref _subscriptions);
|
||||
Disposable.dispose(ref _vm);
|
||||
Disposable.dispose(ref _model);
|
||||
if (_commands != null) {
|
||||
_commands.OfType<IDisposable>().DisposeAll();
|
||||
_commands = null;
|
||||
@@ -90,8 +95,10 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
void _initialize() {
|
||||
var model = new TimeAndSalesModel(_bus, _consumer, _state.security_criteria);
|
||||
_vm.value = new TimeAndSalesVM(_bus, model, _state.sorting_criteria, _state.filters);
|
||||
Disposable.dispose(ref _model);
|
||||
_model = new TimeAndSalesModel(_bus, _consumer, _state.security_criteria);
|
||||
// the previous _vm.value is automatically disposed when _vm.value is set - no need to dispose it here
|
||||
_vm.value = new TimeAndSalesVM(_bus, _model, _state.sorting_criteria, _state.filters);
|
||||
if (_view == null)
|
||||
_view = new TimeAndSalesView();
|
||||
_view.DataContext = _vm.value;
|
||||
|
||||
@@ -12,6 +12,7 @@ using bts.api;
|
||||
using System.ComponentModel.Composition;
|
||||
using mbts.ui.spark;
|
||||
using mbts.persist;
|
||||
using mbts.config;
|
||||
|
||||
namespace bts.ui {
|
||||
[Export(typeof(IShellModule))]
|
||||
@@ -25,6 +26,11 @@ namespace bts.ui {
|
||||
public SettingsShellModule(IBus bus, UserSettingsHub settings_hub, WorkspacesModel ws) {
|
||||
// todo: settings dialog
|
||||
ws_model = ws;
|
||||
var actions_list = new List<SparkCommand>(2) { SparkCommands.ShowWorkspaces };
|
||||
if (Configuration.get<bts.config.toggles.ToastNotifications>().enabled) {
|
||||
actions_list.Add(SparkCommands.ShowSettings);
|
||||
}
|
||||
actions = actions_list;
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.ShowWorkspaces).Subscribe(_ => show_workspaces_dialog()),
|
||||
bus.subscribe(SparkTags.ShowSettings).Subscribe(_ => {
|
||||
@@ -42,10 +48,7 @@ namespace bts.ui {
|
||||
|
||||
public int priority => 40;
|
||||
|
||||
public IEnumerable<SparkCommand> actions => new SparkCommand[]{
|
||||
SparkCommands.ShowWorkspaces,
|
||||
SparkCommands.ShowSettings
|
||||
};
|
||||
public IEnumerable<SparkCommand> actions { get; }
|
||||
|
||||
public Task<IShellWindow> create(Stream load_stream) {
|
||||
throw new NotImplementedException();
|
||||
|
||||
@@ -32,13 +32,13 @@ namespace mbts.ui.spark {
|
||||
AddColumnPlaceholderKey = "AddColumnPlaceholder";
|
||||
public const double GroupLevelIndent = 18;
|
||||
|
||||
static ExecutionStrategyEditor() =>
|
||||
static ExecutionStrategyEditor() {
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(ExecutionStrategyEditor), new FrameworkPropertyMetadata(typeof(ExecutionStrategyEditor)));
|
||||
|
||||
public ExecutionStrategyEditor() {
|
||||
AdornerHelper.attach_continuously_on_loaded(() => new ExecutionStrategyErrorAdorner(this), this);
|
||||
ToolTipService.ShowDurationProperty.OverrideMetadata(typeof(DependencyObject), new FrameworkPropertyMetadata(Int32.MaxValue));
|
||||
}
|
||||
|
||||
public ExecutionStrategyEditor() =>
|
||||
AdornerHelper.attach_continuously_on_loaded(() => new ExecutionStrategyErrorAdorner(this), this);
|
||||
|
||||
#region SyntheticName
|
||||
public static readonly DependencyProperty SyntheticNameProperty = DependencyProperty.Register("SyntheticName", typeof(string), typeof(ExecutionStrategyEditor),
|
||||
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace bts.shell {
|
||||
case Security.ContractOneofCase.Future: _show_synthetic_create(new ContractLeg[] { SyntheticDataHelper.Defaults.ContractLeg(sec) }); break;
|
||||
case Security.ContractOneofCase.Spread: _show_synthetic_create(sec.Spread.Legs.Select(l => l.CloneToMutable())); break;
|
||||
case Security.ContractOneofCase.Synthetic: _show_synthetic_create(sec.Synthetic.Legs.Select(l => l.CloneToMutable())); break;
|
||||
default: Logger.warn(log.Subsystem.Ui, $"{GetType()} unable to process security: {sec}"); break;
|
||||
default: Log.Ui.warn($"{GetType()} unable to process security: {sec}"); break;
|
||||
}
|
||||
} else
|
||||
_show_synthetic_create(securities.Select(s => new ContractLeg(SyntheticDataHelper.Defaults.ContractLeg(s))));
|
||||
@@ -61,15 +61,15 @@ namespace bts.shell {
|
||||
|
||||
void _show_synthetic_strategy(SecurityImmutable security) {
|
||||
if (security.ContractCase != Security.ContractOneofCase.Synthetic)
|
||||
Logger.warn(log.Subsystem.Ui, $"{GetType()} observed a non-synthetic-security posted on {nameof(SparkTags.ShowSyntheticStrategyTag)}: {security}");
|
||||
Log.Ui.warn($"{GetType()} observed a non-synthetic-security posted on {nameof(SparkTags.ShowSyntheticStrategyTag)}: {security}");
|
||||
else
|
||||
_show_synthetic_strategy_async(security).ignore();
|
||||
}
|
||||
|
||||
async Task _show_synthetic_strategy_async(SecurityImmutable verified_synthetic) {
|
||||
var lookup = await _model.spreader.lookup_strategy_async(verified_synthetic);
|
||||
if (lookup.success)
|
||||
_show_synthetic_editor(new SyntheticEditorVM(_model.spreader, verified_synthetic, lookup.result, _model.security_has_working_orders(verified_synthetic.Id)));
|
||||
ExecutionStrategyImmutable? strategy = await _model.spreader.lookup_strategy_async(verified_synthetic);
|
||||
if (strategy.HasValue)
|
||||
_show_synthetic_editor(new SyntheticEditorVM(_model.spreader, verified_synthetic, strategy.Value, _model.security_has_working_orders(verified_synthetic.Id)));
|
||||
}
|
||||
|
||||
void _show_synthetic_clone((SecurityImmutable security, ExecutionStrategyImmutable strategy) args) {
|
||||
|
||||
@@ -39,18 +39,18 @@ namespace mbts.spark {
|
||||
return (result, success);
|
||||
});
|
||||
|
||||
public async Task<(ExecutionStrategyImmutable result, bool success)> lookup_strategy_async(SecurityImmutable security) =>
|
||||
public async Task<ExecutionStrategyImmutable?> lookup_strategy_async(SecurityImmutable security) =>
|
||||
await lookup_strategy_async(security.Id);
|
||||
|
||||
public async Task<(ExecutionStrategyImmutable result, bool success)> lookup_strategy_async(uint id) {
|
||||
public async Task<ExecutionStrategyImmutable?> lookup_strategy_async(uint id) {
|
||||
var result = await _service.query_security_metadata_async(new MetadataQuery { SecurityKey = { id } });
|
||||
if (result.success) {
|
||||
var v = result.value.Data.FirstOrDefault(i => i.SecurityKey == id);
|
||||
if (v != null) {
|
||||
return (v.Strategy, true);
|
||||
var metadata = result.value.Data.FirstOrDefault(i => i.SecurityKey == id);
|
||||
if (metadata != null) {
|
||||
return metadata.Strategy;
|
||||
}
|
||||
}
|
||||
return (null, false);
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<uint?> create_synthetic(SecurityImmutable synthetic) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.ui;
|
||||
@@ -18,6 +19,7 @@ namespace mbts.spark {
|
||||
StartupData _state;
|
||||
OrdersModel _orders_model;
|
||||
SyntheticManagementView _view;
|
||||
IDisposable _subscriptions;
|
||||
|
||||
public static SyntheticManagementWindow create(SyntheticManagementWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state) {
|
||||
var window = new SyntheticManagementWindow(factory, bus, orders_model);
|
||||
@@ -28,20 +30,23 @@ namespace mbts.spark {
|
||||
SyntheticManagementWindow(SyntheticManagementWindowFactory factory, IBus bus, OrdersModel orders_model)
|
||||
: base("Synthetic Management", bus, factory, SparkCommands.ShowSyntheticManagement.icon) {
|
||||
_orders_model = orders_model;
|
||||
var vm_connectable_builder = new ConnectableSeriesBuilder<SyntheticManagementDialogVM>(_vm.observable);
|
||||
var current_row_source = vm_connectable_builder.create(vm => vm.current_row_observable);
|
||||
var current_row_connectable_builder = new ConnectableSeriesBuilder<SyntheticManagementDialogVM.Row>(current_row_source);
|
||||
Commands = new SparkCommand[] {
|
||||
SparkCommand.create("Add", Icons.AddSynthetic, SparkTags.ShowSyntheticCreateTag).complete(
|
||||
() => new SecurityCriteria(), () => true
|
||||
), SparkCommand.create("Remove", Icons.RemoveSynthetic, SparkTags.SyntheticDeleteTag).complete(
|
||||
_vm.observable.SelectMany(vm => vm.current_row_observable.SelectMany(observe_row_mod_params)),
|
||||
current_row_connectable_builder.create(observe_row_mod_params),
|
||||
can_execute, select_security_id
|
||||
), SparkCommand.create("Change", Icons.ModifySynthetic, SparkTags.ShowSyntheticStrategyTag).complete(
|
||||
_vm.observable.SelectMany(vm => vm.current_row_observable),
|
||||
current_row_source,
|
||||
r => r != null, r => r?.security ?? SecurityDataHelper.Defaults.SecurityImmutable
|
||||
), SparkCommand.create("Clone", Icons.CloneSynthetic, SparkTags.ShowSyntheticCloneTag).complete(
|
||||
_vm.observable.SelectMany(vm => vm.current_row_observable.SelectMany(observe_row_strategy)),
|
||||
current_row_connectable_builder.create(observe_row_strategy),
|
||||
rs => rs.HasValue, select_row_strategy_tuple
|
||||
), SparkCommands.ShowLadder.complete(
|
||||
_vm.observable.SelectMany(vm => vm.current_security_md), md => md.HasValue, ObjectHelper.ValueOrDefault
|
||||
vm_connectable_builder.create(vm => vm.current_security_md), md => md.HasValue, ObjectHelper.ValueOrDefault
|
||||
), SparkCommands.ShowOrders.complete(() => new OrderBookWindowFactory.OrderBookStartupData {
|
||||
filters = new OrderBookWindowFactory.FilterStates {
|
||||
security_criteria = new SecurityCriteria(_vm.value.row_vms.Select(r => r.security))
|
||||
@@ -49,6 +54,10 @@ namespace mbts.spark {
|
||||
})
|
||||
};
|
||||
|
||||
_subscriptions = Disposable.Create(
|
||||
vm_connectable_builder.connect_all(),
|
||||
current_row_connectable_builder.connect_all());
|
||||
|
||||
IObservable<RowModificationParams?> observe_row_mod_params(SyntheticManagementDialogVM.Row r) =>
|
||||
r == null ? Observable.Return(new RowModificationParams?()) :
|
||||
_orders_model.security_has_working_orders(r.security_key).Select(hwo => RowModificationParams.create(r.security, hwo));
|
||||
@@ -63,7 +72,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public void initialize(StartupData state) {
|
||||
Logger.notnull(state);
|
||||
Log.notnull(state);
|
||||
_state = state;
|
||||
_vm.value = new SyntheticManagementDialogVM(_bus, _orders_model, state.sorting_criteria, state.filters ?? new FilterStates());
|
||||
if (_view == null) _view = new SyntheticManagementView { DataContext = _vm.value };
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace mbts.spark {
|
||||
IDictionary<uint, bool> _value_store = new Dictionary<uint, bool>();
|
||||
|
||||
public override string display_name { get; } = "Hidden";
|
||||
public override string help_text { get; } = string.Empty;
|
||||
public override string help_text { get; } = "A hidden quote is not sent out until the market crosses the computed quoting price. When that happens, the quote is sent out as an IOC order. This continues until the synthetic spreader order is fully filled, or cancelled.";
|
||||
public override string man_page { get; } = string.Empty;
|
||||
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote };
|
||||
public FontWeight font_weight => FontWeights.SemiBold;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.spark;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace bts.shell {
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.ShowActivityLog).Subscribe(show_log),
|
||||
messages.Connect(),
|
||||
messages.Subscribe(msg => Logger.info(log.Subsystem.Ui, msg.message))
|
||||
messages.Subscribe(msg => Log.Ui.info(msg.message))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ namespace mbts.spark {
|
||||
#region Filters
|
||||
public IDataGridColumnPersistentFilter<Row, TimeFilterTuple> fill_time_filter { get; } = new TimeFilterVM<Row>(r => r.fill_time, nameof(Row.fill_time));
|
||||
public IDataGridColumnPersistentFilter<Row, TimeInForce[]> time_in_force_filter { get; } = DiscreteValuesFilterVM.Create<Row, TimeInForce>(EnumHelper.get_values<TimeInForce>().ToLArray(), r => r.time_in_force, nameof(Row.time_in_force));
|
||||
public IDataGridColumnPersistentFilter<Row, OrderType[]> order_type_filter { get; } = DiscreteValuesFilterVM.Create<Row, OrderType>(EnumHelper.get_values<OrderType>().ToLArray(), r => r.order_type, nameof(Row.order_type));
|
||||
public IDataGridColumnPersistentFilter<Row, OrderType[]> order_type_filter { get; } = DiscreteValuesFilterVM.Create<Row, OrderType>(OrderFactory.all_order_types, r => r.order_type, nameof(Row.order_type));
|
||||
public IDataGridColumnPersistentFilter<Row, string[]> account_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, string[]> user_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, Venue[]> exchange_filter { get; } = DiscreteValuesFilterVM.Create<Row, Venue>(EnumHelper.get_values<Venue>().ToLArray(), r => r.exchange, nameof(Row.exchange));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.ui;
|
||||
@@ -30,7 +31,7 @@ namespace mbts.spark {
|
||||
|
||||
public void thread_init() {
|
||||
_init_guard.check_access();
|
||||
Logger.notnull(_state);
|
||||
Log.notnull(_state);
|
||||
_vm = new FillsVM(_bus, _orders_model, _state);
|
||||
_view = new FillsView { DataContext = _vm };
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ using LadderSettings = mbts.spark.LadderSettings;
|
||||
namespace bts.shell {
|
||||
class LadderWindowFactory : ShellWindowFactory<LadderSaveState> {
|
||||
const string _window_type = "ladder";
|
||||
readonly TimeSpan _security_timeout;
|
||||
readonly IBus _bus;
|
||||
readonly IWindowManager _wm;
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
@@ -30,7 +31,9 @@ namespace bts.shell {
|
||||
_wm = wm;
|
||||
_settings_hub = settings_hub;
|
||||
_orders_model = orders_model;
|
||||
_max_ladders_open = (int)(Configuration.get<bts.config.LadderKnobs>().max_open_ladders);
|
||||
var knobs = Configuration.get<bts.config.LadderKnobs>();
|
||||
_max_ladders_open = (int)(knobs.max_open_ladders);
|
||||
_security_timeout = TimeSpan.FromSeconds(Math.Max(15, knobs.security_timeout_secs));
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
|
||||
bus.subscribe(SparkTags.ShowLadderTag).OnDispatcher().Subscribe(s => show_windows(new[] { s })),
|
||||
@@ -39,7 +42,7 @@ namespace bts.shell {
|
||||
}
|
||||
|
||||
public async override Task<IShellWindow> load(LadderSaveState state) {
|
||||
await Wait.until_true(() => _securities.ContainsKey(state.security_key), TimeSpan.FromSeconds(15));
|
||||
await Wait.until_true(() => _securities.ContainsKey(state.security_key), _security_timeout);
|
||||
if (_securities.TryGetValue(state.security_key, out SecurityMD security)) {
|
||||
return create_ladder_window(security, state.settings);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.ui;
|
||||
using bts.utils;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
@@ -46,7 +47,7 @@ namespace mbts.spark {
|
||||
where T : struct =>
|
||||
new OrderBookCellStore<T?>(row, column, prop_name, get_value, select_other, format, CompareUtils.nullable_comparison(compare), EqualityComparer.CreateNullable<T>(EqualityComparer<T>.Default.Equals));
|
||||
|
||||
class OrderBookCellStore<T> : IOrderBookCellStore<T> {
|
||||
class OrderBookCellStore<T> : IOrderBookCellStore<T>, INotifyPropertyChanged {
|
||||
readonly OrderBookRowVM _row;
|
||||
readonly ICVOrderBookColumn _column;
|
||||
readonly string _prop_name;
|
||||
@@ -57,6 +58,10 @@ namespace mbts.spark {
|
||||
readonly IEqualityComparer<T> _eq_comparer;
|
||||
OrderBookCell<T> _current;
|
||||
|
||||
// Property changed handler to avoid WPF binding leak
|
||||
// https://blog.jetbrains.com/dotnet/2014/09/04/fighting-common-wpf-memory-leaks-with-dotmemory/
|
||||
public event PropertyChangedEventHandler PropertyChanged = (_,__) => { };
|
||||
|
||||
public OrderBookCellStore(
|
||||
OrderBookRowVM row,
|
||||
ICVOrderBookColumn column,
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.utils;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.utility;
|
||||
using mbts.ui.utility.text;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
@@ -23,7 +22,6 @@ using static mbts.spark.OrderBookRowVM;
|
||||
using static mbts.spark.OrderBookView;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
|
||||
//using GridSettingsManager = mbts.ui.GridSettingsManager<mbts.spark.OrderBookRowVM, mbts.spark.OrderRecordTuple, bts.shell.INotificationServiceDataCache, mbts.OrderRecordTuplePredicate>;
|
||||
|
||||
namespace mbts.spark {
|
||||
// This interface is used to hide ColumnBase.SortDirection, and offer a SortDirection member that does NOT require a DependencyProperty lookup.
|
||||
@@ -44,8 +42,8 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
partial class OrderBookVM {
|
||||
public class CVOrderBookColumnCollection : ObservableCollection<ColumnBase>, IValueFactory<FilterStates>, IDisposable {
|
||||
public CVOrderBookColumnCollection(IBus bus, OrdersModel model, IObservable<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)> order_securities) {
|
||||
public class OrderBookColumnCollection : ObservableCollection<ColumnBase>, IValueFactory<FilterStates>, IDisposable {
|
||||
public OrderBookColumnCollection(IBus bus, OrdersModel model, IObservable<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)> order_securities) {
|
||||
// security
|
||||
Add(security_column = new SecurityColumn(
|
||||
DiscreteValuesFilterVM.Create<OrderBookRowVM, SecurityImmutable, SecurityCriteria>(
|
||||
@@ -129,7 +127,7 @@ namespace mbts.spark {
|
||||
// order type
|
||||
Add(order_type_column = new OrderBookOrderTypeColumn(
|
||||
DiscreteValuesFilterVM.Create<OrderBookRowVM, OrderType>(
|
||||
EnumHelper.get_values_except(OrderType.None).ToList(),
|
||||
utilities.OrderFactory.all_order_types,
|
||||
r => r.order_type.value,
|
||||
value_path(nameof(OrderBookRowVM.order_type)))));
|
||||
// order sub type
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.spark;
|
||||
using mbts.ui;
|
||||
@@ -14,7 +15,7 @@ using OrderStatus = bts.api.orderman.OrderStatus;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class OrderBookRowVM : DisposableViewModelBase, IEquatable<OrderBookRowVM>, DIYGrid.IRowVM<OrderRecordTuple>, DIYGrid.IActionableRowVM {
|
||||
public readonly PredicateAnimator<OrderBookRowVM, OrderRecordTuple> animator;
|
||||
//public readonly PredicateAnimator<OrderBookRowVM, OrderRecordTuple> animator;
|
||||
|
||||
const string _unknown_security_price_format_string = "0.#########";
|
||||
const int _unknown_security_price_num_fractional_digits = 9;
|
||||
@@ -29,31 +30,66 @@ namespace mbts.spark {
|
||||
string _symbol;
|
||||
OrderBookRowVM _parent;
|
||||
OrderBookVM.BranchFilterSource _branch_filter_source;
|
||||
IDisposable _branch_filter_sub;
|
||||
// Core
|
||||
IOrderBookCellStore<uint> _order_key_store;
|
||||
IOrderBookCellStore<uint> _parent_order_key_store;
|
||||
IOrderBookCellStore<uint> _security_key_store;
|
||||
IOrderBookCellStore<SecurityImmutable?> _security_store;
|
||||
IOrderBookCellStore<Venue> _exchange_store;
|
||||
IOrderBookCellStore<string> _symbol_store;
|
||||
IOrderBookCellStore<uint> _order_key_store_backing;
|
||||
IOrderBookCellStore<uint> _parent_order_key_store_backing;
|
||||
IOrderBookCellStore<uint> _security_key_store_backing;
|
||||
IOrderBookCellStore<SecurityImmutable?> _security_store_backing;
|
||||
IOrderBookCellStore<Venue> _exchange_store_backing;
|
||||
IOrderBookCellStore<string> _symbol_store_backing;
|
||||
// Def
|
||||
IOrderBookCellStore<CustomOrderDefinition.CustomOneofCase> _order_type_store;
|
||||
IOrderBookCellStore<Side> _side_store;
|
||||
IOrderBookCellStore<string> _user_store;
|
||||
IOrderBookCellStore<string> _account_store;
|
||||
IOrderBookCellStore<TimeInForce> _time_in_force_store;
|
||||
IOrderBookCellStore<DateTime> _time_created_store;
|
||||
IOrderBookCellStore<CustomOrderDefinition.CustomOneofCase> _order_type_store_backing;
|
||||
IOrderBookCellStore<Side> _side_store_backing;
|
||||
IOrderBookCellStore<string> _user_store_backing;
|
||||
IOrderBookCellStore<string> _account_store_backing;
|
||||
IOrderBookCellStore<TimeInForce> _time_in_force_store_backing;
|
||||
IOrderBookCellStore<DateTime> _time_created_store_backing;
|
||||
// State
|
||||
IOrderBookCellStore<DateTime> _last_change_store;
|
||||
IOrderBookCellStore<OrderStatus> _status_store;
|
||||
IOrderBookCellStore<double> _price_store;
|
||||
IOrderBookCellStore<uint> _quantity_store;
|
||||
IOrderBookCellStore<uint> _filled_quantity_store;
|
||||
IOrderBookCellStore<double> _last_price_store;
|
||||
IOrderBookCellStore<double> _avg_price_store;
|
||||
IOrderBookCellStore<string> _last_msg_store;
|
||||
IOrderBookCellStore<OrderSubType> _order_sub_type_store;
|
||||
IOrderBookCellStore<DateTime> _last_change_store_backing;
|
||||
IOrderBookCellStore<OrderStatus> _status_store_backing;
|
||||
IOrderBookCellStore<double> _price_store_backing;
|
||||
IOrderBookCellStore<uint> _quantity_store_backing;
|
||||
IOrderBookCellStore<uint> _filled_quantity_store_backing;
|
||||
IOrderBookCellStore<double> _last_price_store_backing;
|
||||
IOrderBookCellStore<double> _avg_price_store_backing;
|
||||
IOrderBookCellStore<string> _last_msg_store_backing;
|
||||
IOrderBookCellStore<OrderSubType> _order_sub_type_store_backing;
|
||||
// Core
|
||||
IOrderBookCellStore<uint> _order_key_store => _order_key_store_backing ?? (_order_key_store_backing = CellFactory.create(this, _book_vm.columns.order_key_column, nameof(order_key), r => r._order.Key, r => r._order_key_store));
|
||||
IOrderBookCellStore<uint> _parent_order_key_store => _parent_order_key_store_backing ?? (_parent_order_key_store_backing = CellFactory.create(this, _book_vm.columns.parent_key_column, nameof(parent_order_key), r => r._order.ParentKey, r => r._parent_order_key_store));
|
||||
IOrderBookCellStore<uint> _security_key_store => _security_key_store_backing ?? (_security_key_store_backing = CellFactory.create(this, _book_vm.columns.security_key_column, nameof(security_key), r => r._order.security_key(), r => r._security_key_store));
|
||||
IOrderBookCellStore<SecurityImmutable?> _security_store => _security_store_backing ?? (_security_store_backing = CellFactory.create_nullable(this, _book_vm.columns.security_column, nameof(security), r => r._security, r => r._security_store, v => v.display_name()));
|
||||
IOrderBookCellStore<Venue> _exchange_store => _exchange_store_backing ?? (_exchange_store_backing = CellFactory.create(this, _book_vm.columns.exchange_column, nameof(exchange), r => r._security.HasValue ? _security.Value.Exchange : Venue.Unset, r => r._exchange_store));
|
||||
IOrderBookCellStore<string> _symbol_store => _symbol_store_backing ?? (_symbol_store_backing = CellFactory.create(this, _book_vm.columns.symbol_column, nameof(symbol), r => r._symbol, r => r._symbol_store, v => !String.IsNullOrEmpty(v) ? v : "unknown symbol"));
|
||||
// Def
|
||||
IOrderBookCellStore<CustomOrderDefinition.CustomOneofCase> _order_type_store => _order_type_store_backing ?? (_order_type_store_backing = CellFactory.create(this, _book_vm.columns.order_type_column, nameof(order_type), r => r._order.order_type(), r => r._order_type_store));
|
||||
IOrderBookCellStore<Side> _side_store => _side_store_backing ?? (_side_store_backing = CellFactory.create(this, _book_vm.columns.side_column, nameof(side), r => r._order.side(), r => r._side_store));
|
||||
IOrderBookCellStore<string> _user_store => _user_store_backing ?? (_user_store_backing = CellFactory.create(this, _book_vm.columns.user_column, nameof(user), r => _order.user(), r => r._user_store));
|
||||
IOrderBookCellStore<string> _account_store => _account_store_backing ?? (_account_store_backing = CellFactory.create(this, _book_vm.columns.account_column, nameof(account), r => r._order.account(), r => r._account_store));
|
||||
IOrderBookCellStore<TimeInForce> _time_in_force_store => _time_in_force_store_backing ?? (_time_in_force_store_backing = CellFactory.create(this, _book_vm.columns.time_in_force_column, nameof(time_in_force), r => r._order.time_in_force(), r => r._time_in_force_store));
|
||||
IOrderBookCellStore<DateTime> _time_created_store => _time_created_store_backing ?? (_time_created_store_backing = CellFactory.create(this, _book_vm.columns.time_created_column, nameof(time_created), r => r._order.created(), r => r._time_created_store, format_date));
|
||||
// State
|
||||
IOrderBookCellStore<DateTime> _last_change_store => _last_change_store_backing ?? (_last_change_store_backing = CellFactory.create(this, _book_vm.columns.last_change_column, nameof(last_change), r => _order.last_change(), r => r._last_change_store, format_date));
|
||||
IOrderBookCellStore<OrderStatus> _status_store => _status_store_backing ?? (_status_store_backing = CellFactory.create(this, _book_vm.columns.status_column, nameof(status), r => _order.status(), r => r._status_store));
|
||||
IOrderBookCellStore<double> _price_store => _price_store_backing ?? (_price_store_backing = CellFactory.create(this, _book_vm.columns.price_column, nameof(price), r => _order.price(), r => r._price_store, format_price));
|
||||
IOrderBookCellStore<uint> _quantity_store => _quantity_store_backing ?? (_quantity_store_backing = CellFactory.create(this, _book_vm.columns.qty_column, nameof(quantity), r => _order.quantity(), r => r._quantity_store));
|
||||
IOrderBookCellStore<uint> _filled_quantity_store => _filled_quantity_store_backing ?? (_filled_quantity_store_backing = CellFactory.create(this, _book_vm.columns.filled_qty_column, nameof(filled_quantity), r => _order.filled(), r => r._filled_quantity_store));
|
||||
IOrderBookCellStore<double> _last_price_store => _last_price_store_backing ?? (_last_price_store_backing = CellFactory.create(this, _book_vm.columns.last_price_column, nameof(last_price), r => _order.last_price(), r => r._last_price_store, format_price));
|
||||
IOrderBookCellStore<double> _avg_price_store => _avg_price_store_backing ?? (_avg_price_store_backing = CellFactory.create(this, _book_vm.columns.avg_price_column, nameof(avg_price), r => _order.avg_price(), r => r._avg_price_store, format_price));
|
||||
IOrderBookCellStore<string> _last_msg_store => _last_msg_store_backing ?? (_last_msg_store_backing = CellFactory.create(this, _book_vm.columns.last_message_column, nameof(last_msg), r => _order.last_msg(), r => r._last_msg_store));
|
||||
IOrderBookCellStore<OrderSubType> _order_sub_type_store => _order_sub_type_store_backing ?? (_order_sub_type_store_backing = CellFactory.create(this, _book_vm.columns.sub_type_column, nameof(order_sub_type), r => r._order.sub_type(), r => r._order_sub_type_store, format_sub_type));
|
||||
|
||||
static string format_date(DateTime v) => $"{v:M/d} {v:h:mm:ss.fff tt}";
|
||||
string format_price(double v) => _security.HasValue ?
|
||||
_security.Value.ContractSpec.format(v) :
|
||||
Math.Round(v, _unknown_security_price_num_fractional_digits, MidpointRounding.AwayFromZero).ToString(_unknown_security_price_format_string);
|
||||
static string format_sub_type(OrderSubType v) {
|
||||
switch (v) {
|
||||
case OrderSubType.HedgingLeg: return "H";
|
||||
case OrderSubType.QuotingLeg: return "Q";
|
||||
default: return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public OrderBookRowVM(IBus bus, OrderBookVM source_vm, OrdersModel model, OrderRecordImmutable record, SecurityMD? s) {
|
||||
_book_vm = source_vm;
|
||||
@@ -61,43 +97,7 @@ namespace mbts.spark {
|
||||
_order = record;
|
||||
_security_md = s;
|
||||
_symbol = _security_md.HasValue ? _security_md.Value.security.Symbol : null;
|
||||
animator = new PredicateAnimator<OrderBookRowVM,OrderRecordTuple>(this);
|
||||
// cells
|
||||
cell_stores = new DIYGrid.IGridCellStore[] {
|
||||
_security_store = CellFactory.create_nullable(this, _book_vm.columns.security_column, nameof(security), r => r._security, r => r._security_store, v => v.display_name()),
|
||||
_security_key_store = CellFactory.create(this, _book_vm.columns.security_key_column, nameof(security_key), r => r._order.security_key(), r => r._security_key_store),
|
||||
_parent_order_key_store = CellFactory.create(this, _book_vm.columns.parent_key_column, nameof(parent_order_key), r => r._order.ParentKey, r => r._parent_order_key_store),
|
||||
_order_key_store = CellFactory.create(this, _book_vm.columns.order_key_column, nameof(order_key), r => r._order.Key, r => r._order_key_store),
|
||||
_symbol_store = CellFactory.create(this, _book_vm.columns.symbol_column, nameof(symbol), r => r._symbol, r => r._symbol_store, v => !String.IsNullOrEmpty(v) ? v : "unknown symbol"),
|
||||
_time_created_store = CellFactory.create(this, _book_vm.columns.time_created_column, nameof(time_created), r => r._order.created(), r => r._time_created_store, format_date),
|
||||
_user_store = CellFactory.create(this, _book_vm.columns.user_column, nameof(user), r => _order.user(), r => r._user_store),
|
||||
_account_store = CellFactory.create(this, _book_vm.columns.account_column, nameof(account), r => r._order.account(), r => r._account_store),
|
||||
_exchange_store = CellFactory.create(this, _book_vm.columns.exchange_column, nameof(exchange), r => r._security.HasValue ? _security.Value.Exchange : Venue.Unset, r => r._exchange_store),
|
||||
_time_in_force_store = CellFactory.create(this, _book_vm.columns.time_in_force_column, nameof(time_in_force), r => r._order.time_in_force(), r => r._time_in_force_store),
|
||||
_order_type_store = CellFactory.create(this, _book_vm.columns.order_type_column, nameof(order_type), r => r._order.order_type(), r => r._order_type_store),
|
||||
_order_sub_type_store = CellFactory.create(this, _book_vm.columns.sub_type_column, nameof(order_sub_type), r => r._order.sub_type(), r => r._order_sub_type_store, format_sub_type),
|
||||
_side_store = CellFactory.create(this, _book_vm.columns.side_column, nameof(side), r => r._order.side(), r => r._side_store),
|
||||
_last_change_store = CellFactory.create(this, _book_vm.columns.last_change_column, nameof(last_change), r => _order.last_change(), r => r._last_change_store, format_date),
|
||||
_price_store = CellFactory.create(this, _book_vm.columns.price_column, nameof(price), r => _order.price(), r => r._price_store, format_price),
|
||||
_quantity_store = CellFactory.create(this, _book_vm.columns.qty_column, nameof(quantity), r => _order.quantity(), r => r._quantity_store),
|
||||
_filled_quantity_store = CellFactory.create(this, _book_vm.columns.filled_qty_column, nameof(filled_quantity), r => _order.filled(), r => r._filled_quantity_store),
|
||||
_avg_price_store = CellFactory.create(this, _book_vm.columns.avg_price_column, nameof(avg_price), r => _order.avg_price(), r => r._avg_price_store, format_price),
|
||||
_last_price_store = CellFactory.create(this, _book_vm.columns.last_price_column, nameof(last_price), r => _order.last_price(), r => r._last_price_store, format_price),
|
||||
_status_store = CellFactory.create(this, _book_vm.columns.status_column, nameof(status), r => _order.status(), r => r._status_store),
|
||||
_last_msg_store = CellFactory.create(this, _book_vm.columns.last_message_column, nameof(last_msg), r => _order.last_msg(), r => r._last_msg_store),
|
||||
};
|
||||
//
|
||||
string format_price(double v) => _security.HasValue ? _security.Value.ContractSpec.format(v) :
|
||||
Math.Round(v, _unknown_security_price_num_fractional_digits, MidpointRounding.AwayFromZero).ToString(_unknown_security_price_format_string);
|
||||
string format_date(DateTime v) => $"{v:M/d} {v:h:mm:ss.fff tt}";
|
||||
string format_sub_type(OrderSubType v) {
|
||||
switch (v) {
|
||||
case OrderSubType.HedgingLeg: return "H";
|
||||
case OrderSubType.QuotingLeg: return "Q";
|
||||
default: return string.Empty;
|
||||
}
|
||||
}
|
||||
//
|
||||
//animator = new PredicateAnimator<OrderBookRowVM,OrderRecordTuple>(this);
|
||||
_visible = backing(nameof(is_visible), true);
|
||||
_expanded = backing(nameof(is_expanded), false);
|
||||
_depth_level = backing(nameof(depth_level), () => {
|
||||
@@ -113,15 +113,11 @@ namespace mbts.spark {
|
||||
_show_order_modify_cmd = new ObservantCommand(() => bus.post(SparkTags.ShowModifyOrderTag, _order.Key));
|
||||
_show_order_modify_cmd.OnNext(status.value == OrderStatus.Working);
|
||||
row_passes_filter = _book_vm.columns.filter(this);
|
||||
_passes_filter = backing(nameof(passes_filter),
|
||||
Observable.FromEvent<Action<IObservable<bool>>, IObservable<bool>>(
|
||||
cb => _branch_filter_source_changed += cb, cb => _branch_filter_source_changed -= cb)
|
||||
.SelectMany(o => o));
|
||||
_passes_filter = backing(nameof(passes_filter), row_passes_filter);
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
public event Action<(OrderRecordTuple old_val, OrderRecordTuple new_val)> updated;
|
||||
event Action<IObservable<bool>> _branch_filter_source_changed;
|
||||
|
||||
public OrderRecordImmutable order => _order;
|
||||
public OrderMetadata order_metadata => _model.factory.get_metadata(order_type.value);
|
||||
@@ -216,7 +212,31 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public ObservableCollection<RowAnimation> row_animations { get; } = new ObservableCollection<RowAnimation>();
|
||||
public IReadOnlyList<DIYGrid.IGridCellStore> cell_stores { get; }
|
||||
|
||||
DIYGrid.IGridCellStore[] _cell_stores_backing;
|
||||
public IReadOnlyList<DIYGrid.IGridCellStore> cell_stores => _cell_stores_backing ?? (_cell_stores_backing = new DIYGrid.IGridCellStore[] {
|
||||
_security_store,
|
||||
_security_key_store,
|
||||
_parent_order_key_store,
|
||||
_order_key_store,
|
||||
_symbol_store,
|
||||
_time_created_store,
|
||||
_user_store,
|
||||
_account_store,
|
||||
_exchange_store,
|
||||
_time_in_force_store,
|
||||
_order_type_store,
|
||||
_order_sub_type_store,
|
||||
_side_store,
|
||||
_last_change_store,
|
||||
_price_store,
|
||||
_quantity_store,
|
||||
_filled_quantity_store,
|
||||
_avg_price_store,
|
||||
_last_price_store,
|
||||
_status_store,
|
||||
_last_msg_store
|
||||
});
|
||||
|
||||
public static Stack<OrderBookRowVM> lineage(OrderBookRowVM node) {
|
||||
var rv = new Stack<OrderBookRowVM>();
|
||||
@@ -227,11 +247,15 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public void join_branch_filter_source(OrderBookVM.BranchFilterSource new_branch_filter_source) {
|
||||
if (_branch_filter_source != null) {
|
||||
_branch_filter_source.passes_filter_changed -= _passes_filter.set;
|
||||
}
|
||||
_branch_filter_source = new_branch_filter_source;
|
||||
_branch_filter_source.set_row_passes_filter(_order.Key, row_passes_filter);
|
||||
_branch_filter_source_changed?.Invoke(_branch_filter_source);
|
||||
foreach (var child_row in _child_rows)
|
||||
child_row.join_branch_filter_source(_branch_filter_source);
|
||||
_passes_filter.set(_branch_filter_source.passes_filter);
|
||||
_branch_filter_source.passes_filter_changed += _passes_filter.set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -266,8 +290,8 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public void update_order(OrderRecordImmutable order) {
|
||||
Logger.assert(order.ParentKey == _order.ParentKey, "Parent Keys can change?");
|
||||
Logger.assert(order.Key == _order.Key, "Keys can change?");
|
||||
Log.assert(order.ParentKey == _order.ParentKey, "Parent Keys can change?");
|
||||
Log.assert(order.Key == _order.Key, "Keys can change?");
|
||||
var old_data = current_data;
|
||||
_order = order;
|
||||
if (_update_partial(_dynamic_render_cells))
|
||||
@@ -310,7 +334,13 @@ namespace mbts.spark {
|
||||
public override int GetHashCode() => (int)_order.Key;
|
||||
public override string ToString() => $"{order_key} {last_change} {parent_order_key}";
|
||||
|
||||
protected override void Dispose(bool disposing) => Disposable.dispose(ref _branch_filter_sub);
|
||||
protected override void Dispose(bool disposing) {
|
||||
// idempotent
|
||||
if (_branch_filter_source != null) {
|
||||
_branch_filter_source.passes_filter_changed -= _passes_filter.set;
|
||||
_branch_filter_source = null;
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerable<OrderBookRowVM> _descendents(OrderBookRowVM root) {
|
||||
foreach (var child in root.child_orders) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
@@ -27,10 +28,7 @@ namespace mbts.spark {
|
||||
partial class OrderBookVM : DisposableViewModelBase, IHandleRowSelection {
|
||||
readonly LiveSortingColumKeys _live_sorting_columns;
|
||||
readonly Property<OrderBookRowVM> _current_row;
|
||||
readonly Subject<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)> _order_securities_subject =
|
||||
new Subject<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)>();
|
||||
OrderBookStartupData _state;
|
||||
|
||||
Dictionary<uint, OrderBookRowVM> _order_rows_cache = new Dictionary<uint, OrderBookRowVM>();
|
||||
Dictionary<uint, BranchFilterSource> _branch_filter_roots = new Dictionary<uint, BranchFilterSource>();
|
||||
ObservableCollection<OrderBookRowVM> _order_rows = new ObservableCollection<OrderBookRowVM>();
|
||||
@@ -39,7 +37,8 @@ namespace mbts.spark {
|
||||
SetMap<uint, uint> _security_orders = new SetMap<uint, uint>();
|
||||
|
||||
public OrderBookVM(IObservable<string> owner_title, IBus bus, INotificationService notification_service, OrdersModel model) {
|
||||
columns = new CVOrderBookColumnCollection(bus, model, _order_securities_subject);
|
||||
var order_securities_subject = new Subject<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)>();
|
||||
columns = new OrderBookColumnCollection(bus, model, order_securities_subject);
|
||||
rows_view = CollectionViewSource.GetDefaultView(_order_rows);
|
||||
rows_view.Filter = obj => obj is OrderBookRowVM vm && vm.is_visible && vm.passes_filter;
|
||||
columns.filter_changed += () => {
|
||||
@@ -52,13 +51,15 @@ namespace mbts.spark {
|
||||
cvls.LiveFilteringProperties.AddRange(nameof(OrderBookRowVM.is_visible), nameof(OrderBookRowVM.passes_filter));
|
||||
_current_row = backing<OrderBookRowVM>(nameof(current_row));
|
||||
|
||||
bool expand_collapse_all_status = false;
|
||||
expand_collapse_all_rows_command = new DelegateCommand(
|
||||
collapse_all_rows_command = new DelegateCommand(
|
||||
() => {
|
||||
foreach (var r in _order_rows.Where(r => r.is_visible && r.passes_filter)) {
|
||||
r.is_expanded = !expand_collapse_all_status;
|
||||
}
|
||||
expand_collapse_all_status = !expand_collapse_all_status;
|
||||
foreach (var r in _order_rows.Where(r => r.is_visible && r.passes_filter))
|
||||
r.is_expanded = false;
|
||||
});
|
||||
expand_all_rows_command = new DelegateCommand(
|
||||
() => {
|
||||
foreach (var r in _order_rows.Where(r => r.is_visible && r.passes_filter))
|
||||
r.is_expanded = true;
|
||||
});
|
||||
|
||||
grid_settings_manager = _create_grid_settings_manager(owner_title, bus, notification_service, model, _order_rows, rows_view, columns);
|
||||
@@ -69,11 +70,12 @@ namespace mbts.spark {
|
||||
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(on_securities),
|
||||
model.on_orders.OnDispatcher().AddRemoveOnly().Subscribe(on_orders),
|
||||
model.on_orders.OnDispatcher().Subscribe(on_orders),
|
||||
grid_settings_manager,
|
||||
columns);
|
||||
columns,
|
||||
order_securities_subject);
|
||||
|
||||
void on_orders(LegacyTableUpdate<OrderRecordImmutable, uint> table) {
|
||||
void on_orders(TableUpdate<OrderRecordImmutable, uint> table) {
|
||||
// clear everything (most likely the server restarted)
|
||||
if (table.removed.Count() == _order_rows.Count && !table.added.Any()) {
|
||||
_selected_rows.Clear();
|
||||
@@ -84,18 +86,14 @@ namespace mbts.spark {
|
||||
_parent_to_children.clear();
|
||||
_security_orders.clear();
|
||||
missing_security_orders.clear();
|
||||
Log.Ui.warn("OrderBook received reset message.");
|
||||
return;
|
||||
}
|
||||
var changed_keys = new HashSet<uint>();
|
||||
var added_securities = new List<SecurityImmutable>(1);
|
||||
var removed_securities = new List<SecurityImmutable>(1);
|
||||
|
||||
foreach (var o in table.removed) {
|
||||
// if state has it, then it wasn't actually removed, just changed
|
||||
if (table.state.TryGetValue(o.Key, out OrderRecordImmutable changed_order)) {
|
||||
changed_keys.Add(o.Key);
|
||||
_order_rows_cache[o.Key].update_order(changed_order);
|
||||
} else if (_order_rows_cache.TryPopValue(o.Key, out OrderBookRowVM row)) {
|
||||
if (_order_rows_cache.TryPopValue(o.Key, out OrderBookRowVM row)) {
|
||||
row.Dispose();
|
||||
_order_rows.Remove(row);
|
||||
if (_security_orders.removes_key(o.Def.SecurityKey, o.Key)) {
|
||||
@@ -122,10 +120,10 @@ namespace mbts.spark {
|
||||
missing_security_orders.remove(o.Key);
|
||||
}
|
||||
}
|
||||
foreach (var ch in table.changes)
|
||||
_order_rows_cache[ch.New.Key].update_order(ch.New);
|
||||
var orphans = new Dictionary<uint, OrderBookRowVM>();
|
||||
foreach (var o in table.added) {
|
||||
if (changed_keys.Contains(o.Key))
|
||||
continue;
|
||||
SecurityMD? security_md;
|
||||
if (security_dict.TryGetValue(o.Def.SecurityKey, out SecurityMD smd)) {
|
||||
security_md = smd;
|
||||
@@ -168,7 +166,7 @@ namespace mbts.spark {
|
||||
_order_rows.Add(row);
|
||||
}
|
||||
if (added_securities.Count > 0 || removed_securities.Count > 0)
|
||||
_order_securities_subject.OnNext((added_securities, removed_securities));
|
||||
order_securities_subject.OnNext((added_securities, removed_securities));
|
||||
|
||||
void try_add_children(OrderBookRowVM row) {
|
||||
if (_parent_to_children.try_lookup(row.order.Key, out ISet<uint> children))
|
||||
@@ -204,13 +202,13 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
if (added_securities.Count > 0)
|
||||
_order_securities_subject.OnNext((added_securities, new List<SecurityImmutable>(0)));
|
||||
order_securities_subject.OnNext((added_securities, new List<SecurityImmutable>(0)));
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<OrderRecordImmutable?> selected_order_changed;
|
||||
|
||||
public CVOrderBookColumnCollection columns { get; }
|
||||
public OrderBookColumnCollection columns { get; }
|
||||
public ISet<Columns> live_sorting_columns => _live_sorting_columns;
|
||||
public ICollectionView rows_view { get; }
|
||||
public OrderBookRowVM current_row { get => _current_row.Value; set => _current_row.Value = value ?? _selected_rows.FirstOrDefault(); }
|
||||
@@ -218,7 +216,8 @@ namespace mbts.spark {
|
||||
public IEnumerable<OrderRecordImmutable> selected_orders => _selected_rows.Select(r => r.order);
|
||||
|
||||
public GridSettingsManager<OrderBookRowVM, OrderRecordTuple, INotificationServiceDataCache, OrderRecordTuplePredicate> grid_settings_manager { get; }
|
||||
public ICommand expand_collapse_all_rows_command { get; }
|
||||
public ICommand collapse_all_rows_command { get; }
|
||||
public ICommand expand_all_rows_command { get; }
|
||||
|
||||
public void row_selection_changed(SelectionChangedEventArgs e) {
|
||||
RowSelectionHelper.apply_changes(_selected_rows, e);
|
||||
@@ -240,6 +239,9 @@ namespace mbts.spark {
|
||||
internal void remove_branch_filter_root(uint key) =>
|
||||
_branch_filter_roots.Remove(key);
|
||||
|
||||
protected override void Dispose(bool disposing) =>
|
||||
Disposable.dispose(_order_rows);
|
||||
|
||||
static GridSettingsManager<OrderBookRowVM, OrderRecordTuple, INotificationServiceDataCache, OrderRecordTuplePredicate> _create_grid_settings_manager(
|
||||
IObservable<string> owner_title, IBus bus, INotificationService notification_service, OrdersModel model,
|
||||
ObservableCollection<OrderBookRowVM> rows, ICollectionView rows_view, ObservableCollection<DIYGrid.ColumnBase> columns) {
|
||||
@@ -294,24 +296,21 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
internal class BranchFilterSource : IObservable<bool> {
|
||||
internal class BranchFilterSource {
|
||||
readonly HashSet<uint> _orders_pass_filter = new HashSet<uint>();
|
||||
event Action<bool> _passes_filter_changed;
|
||||
|
||||
public event Action<bool> passes_filter_changed;
|
||||
|
||||
public bool passes_filter => _orders_pass_filter.Count > 0;
|
||||
|
||||
public void set_row_passes_filter(uint key, bool value) {
|
||||
if (value) {
|
||||
if (_orders_pass_filter.Add(key) && _orders_pass_filter.Count == 1)
|
||||
_passes_filter_changed?.Invoke(value);
|
||||
passes_filter_changed?.Invoke(value);
|
||||
} else if (_orders_pass_filter.Remove(key) && _orders_pass_filter.Count == 0) {
|
||||
_passes_filter_changed?.Invoke(value);
|
||||
passes_filter_changed?.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(IObserver<bool> observer) {
|
||||
_passes_filter_changed += observer.OnNext;
|
||||
observer.OnNext(_orders_pass_filter.Count > 0);
|
||||
return Disposable.Create(() => _passes_filter_changed -= observer.OnNext);
|
||||
}
|
||||
}
|
||||
|
||||
class LiveSortingColumKeys : HashSet<Columns> {
|
||||
|
||||
@@ -15,11 +15,9 @@ namespace mbts.spark {
|
||||
CVRowTemplateKey = "CVRowTemplateKey";
|
||||
|
||||
public const double RowHeight = 17;
|
||||
OrderBookVM _vm;
|
||||
|
||||
public OrderBookView(OrderBookVM vm) {
|
||||
InitializeComponent();
|
||||
_vm = vm;
|
||||
DataContext = vm;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,10 +28,10 @@ namespace mbts.spark {
|
||||
, _cancel_all_orders_command
|
||||
, _cancel_selected_orders_command
|
||||
, _recreate_selected_orders_command
|
||||
, _toggle_collapse_expand_all_command
|
||||
, _toggle_collapse_all_command
|
||||
, _toggle_expand_all_command
|
||||
, _show_grid_settings_command;
|
||||
|
||||
|
||||
public OrderBookWindow(OrdersModel model, OrderBookWindowFactory factory, IBus bus, INotificationService notification_service, OrderBookWindowFactory.OrderBookStartupData state, ImageSource icon)
|
||||
: base("Order Book", bus, factory, SparkCommands.ShowOrders.icon) {
|
||||
_model = model;
|
||||
@@ -98,11 +98,15 @@ namespace mbts.spark {
|
||||
can_execute: () => _vm.selected_orders.Any(x => _passes_recreate_search_criteria(x))
|
||||
);
|
||||
|
||||
_toggle_collapse_expand_all_command = new SparkCommandCommand("Expand / Collapse All",
|
||||
Icons.UpArrow,
|
||||
_vm.expand_collapse_all_rows_command);
|
||||
_toggle_collapse_all_command = new SparkCommandCommand("Collapse All",
|
||||
Icons.RightArrow,
|
||||
_vm.collapse_all_rows_command);
|
||||
|
||||
_show_grid_settings_command = SparkCommands.grid_settings_command(_vm.grid_settings_manager.launch_settings_editor);
|
||||
_toggle_expand_all_command = new SparkCommandCommand("Expand All",
|
||||
Icons.DownArrow,
|
||||
_vm.expand_all_rows_command);
|
||||
|
||||
_show_grid_settings_command = SparkCommands.window_settings_command(_vm.grid_settings_manager.launch_settings_editor);
|
||||
|
||||
_commands = new SparkCommand[] {
|
||||
_show_fills_command,
|
||||
@@ -112,7 +116,8 @@ namespace mbts.spark {
|
||||
_cancel_sell_orders_command,
|
||||
_cancel_all_orders_command,
|
||||
_recreate_selected_orders_command,
|
||||
_toggle_collapse_expand_all_command,
|
||||
_toggle_collapse_all_command,
|
||||
_toggle_expand_all_command,
|
||||
_show_grid_settings_command
|
||||
};
|
||||
_vm.selected_order_changed += _vm_selected_order_changed;
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
using bts.api;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.collections.extensions;
|
||||
using mbts.config;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.ui.utility;
|
||||
using NAudio.Wave;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -50,7 +53,7 @@ namespace mbts.ui {
|
||||
ToastNotificationController _toast_controller;
|
||||
SoundNotificationPlayer _sound_player;
|
||||
Dispatcher _dispatcher;
|
||||
bool _toast_notifications_enabled, _sound_notifications_enabled;
|
||||
bool _toast_notifications_enabled, _sound_notifications_enabled = NotificationSettings.Default.sound_notification.enabled;
|
||||
|
||||
[ImportingConstructor]
|
||||
public Notifier(IBus bus, UserSettingsHub settings_hub) {
|
||||
@@ -68,27 +71,29 @@ namespace mbts.ui {
|
||||
if (_disposer.has_dispose_started)
|
||||
return;
|
||||
_sound_player = SoundNotificationPlayer.create();
|
||||
_toast_controller = new ToastNotificationController(bus);
|
||||
_settings_sub = settings_hub.subscribe_default_settings().StartWith(settings_hub.current).OnDispatcher().Subscribe(s => {
|
||||
if (s == null || _disposer.has_dispose_started)
|
||||
return;
|
||||
_sound_notifications_enabled = s.notification.sound_notification.enabled;
|
||||
switch (s.notification.toast_notification.mode) {
|
||||
case ToastNotificationSettings.Mode.Disabled:
|
||||
_toast_notifications_enabled = false;
|
||||
_toast_controller.hide();
|
||||
break;
|
||||
case ToastNotificationSettings.Mode.Flash:
|
||||
_toast_notifications_enabled = true;
|
||||
_toast_controller.flash_duration = s.notification.toast_notification.duration ?? _fallback_flash_duration;
|
||||
break;
|
||||
case ToastNotificationSettings.Mode.ManualClose:
|
||||
_toast_notifications_enabled = true;
|
||||
_toast_controller.flash_duration = null;
|
||||
break;
|
||||
}
|
||||
_toast_controller.max_height = Math.Max(s.notification.toast_notification.max_height, _toast_notification_box_min_height);
|
||||
});
|
||||
if (Configuration.get<bts.config.toggles.ToastNotifications>().enabled) {
|
||||
_toast_controller = new ToastNotificationController(bus);
|
||||
_settings_sub = settings_hub.subscribe_default_settings().StartWith(settings_hub.current).OnDispatcher().Subscribe(s => {
|
||||
if (s == null || _disposer.has_dispose_started)
|
||||
return;
|
||||
_sound_notifications_enabled = s.notification.sound_notification.enabled;
|
||||
switch (s.notification.toast_notification.mode) {
|
||||
case ToastNotificationSettings.Mode.Disabled:
|
||||
_toast_notifications_enabled = false;
|
||||
_toast_controller.hide();
|
||||
break;
|
||||
case ToastNotificationSettings.Mode.Flash:
|
||||
_toast_notifications_enabled = true;
|
||||
_toast_controller.flash_duration = s.notification.toast_notification.duration ?? _fallback_flash_duration;
|
||||
break;
|
||||
case ToastNotificationSettings.Mode.ManualClose:
|
||||
_toast_notifications_enabled = true;
|
||||
_toast_controller.flash_duration = null;
|
||||
break;
|
||||
}
|
||||
_toast_controller.max_height = Math.Max(s.notification.toast_notification.max_height, _toast_notification_box_min_height);
|
||||
});
|
||||
}
|
||||
// it is possible that dispose was called on another thread while we were initializing things above.
|
||||
if (_disposer.has_dispose_started) {
|
||||
Disposable.dispose(ref _settings_sub);
|
||||
@@ -97,9 +102,8 @@ namespace mbts.ui {
|
||||
(_dispatcher = Dispatcher.CurrentDispatcher).UnhandledException += _on_dispatcher_UnhandledException;
|
||||
Dispatcher.Run();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Logger.assert(false, $"Exception in {Thread.CurrentThread.Name} thread: {ex}");
|
||||
} catch (Exception ex) {
|
||||
Log.assert(false, $"Exception in {Thread.CurrentThread.Name} thread: {ex}");
|
||||
_disposer.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -144,7 +148,7 @@ namespace mbts.ui {
|
||||
static void _on_dispatcher_UnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) {
|
||||
BTSApplicationBootstrapper.CurrentDispatcher_HandleClipboardError(sender, e);
|
||||
if (!e.Handled) {
|
||||
Logger.error(bts.log.Subsystem.Ui, $"Unhandled Exception in ToastNotification dispatcher: {e.Exception}");
|
||||
Log.Ui.error($"Unhandled Exception in ToastNotification dispatcher: {e.Exception}");
|
||||
if (!e.Dispatcher.HasShutdownStarted) {
|
||||
e.Dispatcher.BeginInvokeShutdown(DispatcherPriority.Send);
|
||||
}
|
||||
@@ -170,16 +174,7 @@ namespace mbts.ui {
|
||||
}
|
||||
|
||||
static IReadOnlyDictionary<SoundNotification.SystemSound, NAudioPlayer> _all_players() =>
|
||||
new Dictionary<SoundNotification.SystemSound, NAudioPlayer> {
|
||||
{ SoundNotification.SystemSound.GameCorrect, new NAudioPlayer(bts.Properties.Resources.game_sound_correct) },
|
||||
{ SoundNotification.SystemSound.GameIncorrect, new NAudioPlayer(bts.Properties.Resources.game_sound_wrong) },
|
||||
{ SoundNotification.SystemSound.GameOverArcade, new NAudioPlayer(bts.Properties.Resources.game_over_arcade) },
|
||||
{ SoundNotification.SystemSound.Whistle, new NAudioPlayer(bts.Properties.Resources.whistle) },
|
||||
{ SoundNotification.SystemSound.BellCounter, new NAudioPlayer(bts.Properties.Resources.bell_counter) },
|
||||
{ SoundNotification.SystemSound.Powerup, new NAudioPlayer(bts.Properties.Resources.powerup) },
|
||||
{ SoundNotification.SystemSound.CashRegister, new NAudioPlayer(bts.Properties.Resources.cash_register_purchase) },
|
||||
{ SoundNotification.SystemSound.Reload, new NAudioPlayer(bts.Properties.Resources.reload_1911) }
|
||||
};
|
||||
EnumHelper.get_values<SoundNotification.SystemSound>().ToReadOnlyDictionary(s => s, NAudioPlayer.create);
|
||||
}
|
||||
|
||||
// This class adopts to role of view-controller in the mvc paradigm, rather than view-model in mvvm.
|
||||
@@ -287,7 +282,7 @@ namespace mbts.ui {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Logger.warn(bts.log.Subsystem.Ui, $"ToastNotifier had an unexpected {nameof(_notification_box)}.CollectionChanged Action ({e.Action}), will clear all notifications to get a clean slate.");
|
||||
Log.Ui.warn($"ToastNotifier had an unexpected {nameof(_notification_box)}.CollectionChanged Action ({e.Action}), will clear all notifications to get a clean slate.");
|
||||
_notifications.Clear();
|
||||
_notification_box.Visibility = Visibility.Hidden;
|
||||
break;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.shell;
|
||||
using mbts.ui.utility;
|
||||
using NAudio.Wave;
|
||||
using System;
|
||||
using System.IO;
|
||||
@@ -9,21 +10,26 @@ namespace mbts.ui {
|
||||
// number of milliseconds to delay playing sound,
|
||||
// during which time all subsequent requests to play the same sound will be ignored.
|
||||
const int _delay_ms = 75;
|
||||
readonly int _start_position;
|
||||
WaveOutEvent _wave_out = new WaveOutEvent();
|
||||
WaveFileReader _wave_reader;
|
||||
bool _is_playing, _is_play_requested;
|
||||
|
||||
public NAudioPlayer(Stream uwave_stream) {
|
||||
public NAudioPlayer(Stream uwave_stream, int start_position = 0) {
|
||||
var ms = new MemoryStream();
|
||||
uwave_stream.CopyTo(ms);
|
||||
ms.Position = 0;
|
||||
_wave_reader = new WaveFileReader(ms);
|
||||
_wave_reader.Position = _start_position = (start_position < _wave_reader.Length ? start_position : 0);
|
||||
_wave_out.Init(_wave_reader);
|
||||
_wave_out.PlaybackStopped += _on_playback_stopped;
|
||||
}
|
||||
|
||||
public static NAudioPlayer create(SoundNotification.SystemSound system_sound) =>
|
||||
new NAudioPlayer(_get_stream(system_sound));
|
||||
public static NAudioPlayer create(SoundNotification.SystemSound system_sound) {
|
||||
var stream = _get_stream(system_sound);
|
||||
var sb = EnumHelper.get_attribute<SoundNotification.SystemSound, SilenceBufferAttribute>(system_sound);
|
||||
return sb != null ? new NAudioPlayer(stream, sb.start_position) : new NAudioPlayer(stream);
|
||||
}
|
||||
|
||||
public void play() {
|
||||
if (_is_playing) {
|
||||
@@ -36,7 +42,7 @@ namespace mbts.ui {
|
||||
}
|
||||
|
||||
void _on_playback_stopped(object sender, StoppedEventArgs e) {
|
||||
_wave_reader.Position = 0;
|
||||
_wave_reader.Position = _start_position;
|
||||
if (_is_play_requested) {
|
||||
_is_play_requested = false;
|
||||
_play_core();
|
||||
|
||||
@@ -25,6 +25,13 @@ namespace bts.shell {
|
||||
}
|
||||
}
|
||||
|
||||
class SilenceBufferAttribute : Attribute {
|
||||
public readonly int start_position;
|
||||
|
||||
public SilenceBufferAttribute(int start_position) =>
|
||||
this.start_position = start_position;
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class SoundNotification {
|
||||
SystemSound? _system_sound;
|
||||
@@ -37,10 +44,12 @@ namespace bts.shell {
|
||||
GameIncorrect,
|
||||
[License(LicenseType.PublicDomain, "Game Over Arcade", "myfox14", link: "https://freesound.org/people/myfox14/sounds/")]
|
||||
GameOverArcade,
|
||||
[SilenceBuffer(100000)]
|
||||
[License(LicenseType.PublicDomain, "Whistle", "zstewart09", link: "https://freesound.org/people/zstewart09/sounds")]
|
||||
Whistle,
|
||||
[License(LicenseType.PublicDomain, "Powerup", "kianda", link: "https://freesound.org/people/kianda/sounds/")]
|
||||
Powerup,
|
||||
[SilenceBuffer(130000)]
|
||||
[License(LicenseType.AttributionLicense, "Bell, Counter, A", "Inspectorj of Freesound.org", link: "https://www.jshaw.co.uk")]
|
||||
BellCounter,
|
||||
[License(LicenseType.PublicDomain, "Cash Register Purchase", "Zott820", link: "https://freesound.org/people/Zott820/sounds/")]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.utils;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
|
||||
@@ -19,14 +20,12 @@ namespace mbts.spark {
|
||||
key = new FillTupleKey(fill.FillKey, order.HasValue ? order.Value.Key : 0u, security_md.HasValue ? security_md.Value.security.Id : 0u);
|
||||
}
|
||||
|
||||
// the TrimEnd is very important b/c the server currently does not trim,
|
||||
// but the account is part of the group, so this is necessary
|
||||
public string account {
|
||||
get {
|
||||
var trimmed_account_string = string.Empty;
|
||||
if (fill.FillKind != FillKind.Sod && order.HasValue && order.Value.HasDef) trimmed_account_string = order.Value.Def.Account;
|
||||
else trimmed_account_string = fill.Account;
|
||||
return trimmed_account_string == null ? string.Empty : trimmed_account_string.Trim();
|
||||
var account_string = string.Empty;
|
||||
if (fill.FillKind != FillKind.Sod && order.HasValue && order.Value.HasDef) account_string = order.Value.Def.Account;
|
||||
else account_string = fill.Account;
|
||||
return account_string == null ? string.Empty : account_string;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace bts.spark {
|
||||
IReadOnlyDictionary<uint, SecurityMD> _security = new Dictionary<uint, SecurityMD>();
|
||||
IDisposable _dispose;
|
||||
static readonly IEqualityComparer<OrderRecordImmutable> _id_comparer =
|
||||
mbts.utils.EqualityComparer.Create<OrderRecordImmutable, uint>(a => a.Key);
|
||||
bts.utils.EqualityComparer.Create<OrderRecordImmutable, uint>(a => a.Key);
|
||||
|
||||
public OrderActivitySummarizer(IBus bus, IObservable<TableUpdate<OrderRecordImmutable, uint>> orders) {
|
||||
_bus = bus;
|
||||
@@ -134,7 +134,9 @@ namespace bts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (neworder, oldorder) in update.changes) {
|
||||
foreach (var order in update.changes) {
|
||||
var neworder = order.New;
|
||||
var oldorder = order.Old;
|
||||
(var security, var spec) = get_security(neworder.Def.SecurityKey);
|
||||
//
|
||||
// description
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
|
||||
|
||||
@@ -24,13 +24,15 @@ namespace mbts.spark
|
||||
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
|
||||
cross_threshold = new InlineUserProperty<bts.spark.QuantityMeasure>(
|
||||
new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1}, false);
|
||||
max_chase_ticks = new InlineUserProperty<uint>(10, false);
|
||||
max_chase_ticks = new InlineUserProperty<uint>(2, false);
|
||||
trade_toward_behavior = new InlineUserProperty<LeanOrderDefinition.Types.OnToward>(LeanOrderDefinition.Types.OnToward.Cancel, false);
|
||||
}
|
||||
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
|
||||
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
|
||||
|
||||
public readonly InlineUserProperty<bts.spark.QuantityMeasure> cross_threshold;
|
||||
public readonly InlineUserProperty<uint> max_chase_ticks;
|
||||
public readonly InlineUserProperty<LeanOrderDefinition.Types.OnToward> trade_toward_behavior;
|
||||
}
|
||||
|
||||
class LeanOrderVM : OrderTypeVMBase<LeanOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
|
||||
@@ -44,7 +46,8 @@ namespace mbts.spark
|
||||
IDisposable _dispose, _child_order_errors_sub;
|
||||
|
||||
public bts.spark.QuantityMeasure cross_threshold { get; set; } = new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1 };
|
||||
public uint max_chase_ticks { get; set; } = 10;
|
||||
public uint max_chase_ticks { get; set; } = 2;
|
||||
public LeanOrderDefinition.Types.OnToward trade_toward_behavior { get; set; } = LeanOrderDefinition.Types.OnToward.Cancel;
|
||||
|
||||
public LeanOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
|
||||
: this(model, security_md_source, security_md_source.Select(smd =>
|
||||
@@ -77,6 +80,11 @@ namespace mbts.spark
|
||||
max_chase_ticks = p.max_chase_ticks.value;
|
||||
RaisePropertyChanged(nameof(max_chase_ticks));
|
||||
}
|
||||
if (p.trade_toward_behavior.force_override)
|
||||
{
|
||||
trade_toward_behavior = p.trade_toward_behavior.value;
|
||||
RaisePropertyChanged(nameof(trade_toward_behavior));
|
||||
}
|
||||
_inline_props = p;
|
||||
_validate();
|
||||
})
|
||||
@@ -94,10 +102,13 @@ namespace mbts.spark
|
||||
var i = factory.lean_order();
|
||||
i.CrossThreshold = cross_threshold;
|
||||
i.MaxChaseTicks = max_chase_ticks;
|
||||
i.TradeTowardBehavior = trade_toward_behavior;
|
||||
child_order.assign_to(i.Child);
|
||||
value.Lean = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<LeanOrderDefinition.Types.OnToward> trade_toward_types => EnumHelper.get_values_except<LeanOrderDefinition.Types.OnToward>();
|
||||
|
||||
public ICollection<IOrderTypeVM> child_order_types
|
||||
{
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace mbts.spark {
|
||||
readonly UserProperty<int> _payup_ticks, _timeout_secs;
|
||||
readonly UserProperty<bool> _use_fixed_price, _is_timeout_checked, _is_priceout_checked;
|
||||
SecurityMD? _security_md;
|
||||
SecurityImmutable? _security => _security_md.HasValue ? _security_md.Value.security : null;
|
||||
SecurityImmutable? _security => _security_md.security();
|
||||
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
|
||||
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
|
||||
InlineTriggerOrderProperties _inline_props;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.api.position;
|
||||
@@ -31,7 +32,7 @@ namespace mbts.spark {
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
private OrdersModel(IBus bus, OrderFactory factory, OrderService service, PositionService position_service, SpreaderModel spreader, ErrorReporter error) {
|
||||
private OrdersModel(IBus bus, OrderFactory factory, OrderService service, PositionService position_service, SpreaderModel spreader, ErrorReporter error, AdminModel admin_model) {
|
||||
_bus = bus;
|
||||
_factory = factory;
|
||||
_service = service;
|
||||
@@ -40,15 +41,15 @@ namespace mbts.spark {
|
||||
_error = error;
|
||||
var scheduler = ThreadPoolScheduler.Instance;
|
||||
var interval = TimeSpan.FromMilliseconds(100);
|
||||
on_orders = _service.subscribei_orders().Naggle(interval, scheduler).Table().Cache();
|
||||
on_positions = _position_service.subscribei_position_data().Table().Cache();
|
||||
on_orders = _service.subscribei_orders().Naggle(interval, scheduler).PermissionFilter(admin_model).Table().Cache();
|
||||
on_positions = _position_service.subscribei_position_data().Naggle(interval, scheduler).PermissionFilter(admin_model).Table().Cache();
|
||||
working_orders =
|
||||
on_orders.Scan(
|
||||
ImmutableDictionary<uint,OrderRecordImmutable>.Empty,
|
||||
(workers, table) => _update_working_orders(workers, table))
|
||||
.Replay(1).RefCount();
|
||||
on_order_tuples = OrderRecordTupleSource.create_observable(bus.subscribe(SparkTags.SecuritiesTag), on_orders);
|
||||
on_fills = _service.subscribei_fills().Naggle(interval, scheduler).Table().Cache();
|
||||
on_fills = _service.subscribei_fills().Naggle(interval, scheduler).PermissionFilter(admin_model).Table().Cache();
|
||||
_activity = new OrderActivitySummarizer(bus, on_orders);
|
||||
on_fill_tuples = FillTupleSource.create_observable(bus.subscribe(SparkTags.SecuritiesTag), on_orders, on_fills);
|
||||
_security_orders_demuxer = new TableUpdateDemuxer<OrderRecordImmutable, uint, uint>(o => o.Key, o => o.security_key());
|
||||
@@ -204,11 +205,26 @@ namespace mbts.spark {
|
||||
.StartWith(false);
|
||||
|
||||
public static IObservable<double> security_total_position(this OrdersModel orders_model, uint security_key, string account) =>
|
||||
// PositionUnit.Account is actually the clearing account; this code assumes that clearing account and account are equivalent.
|
||||
// However, it still must match account to PositionUnit.Account.Trimmed() because the exchange likes to slap spaces on the end.
|
||||
orders_model.on_security_positions(security_key).filter(p => p.Account == account).Scan(0.0, (pos, table) =>
|
||||
// the state should always have 1 element, since the PositionUnit-identity is security + account
|
||||
pos + (table.added.Sum(p => p.total_pos()) - table.removed.Sum(p => p.total_pos())));
|
||||
orders_model.on_security_positions(security_key)
|
||||
.Select(t => {
|
||||
foreach (var kv in t.state) {
|
||||
if (kv.Value.Account == account)
|
||||
return kv.Value.total_pos();
|
||||
}
|
||||
return double.NaN;
|
||||
}).DistinctUntilChanged()
|
||||
.StartWith(double.NaN);
|
||||
|
||||
// DO NOT REMOVE (BEGIN)
|
||||
//public static IObservable<double> security_total_position(this OrdersModel orders_model, uint security_key, string account) =>
|
||||
// // PositionUnit.Account is actually the clearing account; this code assumes that clearing account and account are equivalent.
|
||||
// // However, it still must match account to PositionUnit.Account.Trimmed() because the exchange likes to slap spaces on the end.
|
||||
// orders_model.on_security_positions(security_key)
|
||||
// .filter(p => p.Account == account)
|
||||
// // the state should always have 1 element, since the PositionUnit-identity is security + account
|
||||
// .Scan(0.0, (pos, table) => pos + (table.added.Sum(p => p.total_pos()) - table.removed.Sum(p => p.total_pos())))
|
||||
// .StartWith(double.NaN);
|
||||
// DO NOT REMOVE (END)
|
||||
|
||||
public static IObservable<ITableUpdateTransform<OrderRecordImmutable, uint>> on_security_root_orders(this OrdersModel orders_model, uint security_key) =>
|
||||
orders_model.on_security_orders(security_key).Scan(new RootOrderData(), (rod, table) => {
|
||||
@@ -269,7 +285,7 @@ namespace mbts.spark {
|
||||
}
|
||||
// ** [continue comment at top of method] this call would otherwise be:
|
||||
// rod.last_update = rod.last_update.update(removed_dict.Values.ToList(), added_dict.Values.ToList(), o => o.Key) **
|
||||
//rod.last_update = rod.last_update.update(removed_roots, added_roots, o => o.Key);
|
||||
rod.last_update = rod.last_update.update(removed_roots, added_roots, o => o.Key);
|
||||
return rod;
|
||||
}).Select(r => r.last_update);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.spark;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.ui.settings;
|
||||
using mbts.utils;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.ui.settings;
|
||||
using mbts.utils;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
@@ -19,7 +20,7 @@ namespace mbts {
|
||||
public static bool check_order_transition_trigger(OrderRecordTuple order, Func<OrderRecordImmutable, bool> order_predicate_core, IPredicate<SecurityImmutable> security_predicate, INotificationServiceDataCache data_cache, out OrderRecordTuple oldval) {
|
||||
if (check_security_predicate(order, security_predicate) && order_predicate_core(order.order)) {
|
||||
oldval = data_cache.get_previous_order_state(order.order.Key);
|
||||
return oldval == null ? !data_cache.is_first_order_update : order_predicate_core(oldval.order);
|
||||
return oldval == null ? !data_cache.is_first_order_update : !order_predicate_core(oldval.order);
|
||||
}
|
||||
oldval = null;
|
||||
return false;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
@@ -119,7 +120,7 @@ namespace mbts {
|
||||
OrderKey,
|
||||
[PredicateCase(is_toastable:false)]
|
||||
ParentKey,
|
||||
[PredicateCase("When order is filled", PredicateCategory.Event)]
|
||||
[PredicateCase("When order gets a fill", PredicateCategory.Event)]
|
||||
FilledEvent,
|
||||
[PredicateCase("When order is completed", PredicateCategory.Event)]
|
||||
CompletedEvent,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.proto;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.spark;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.spark;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.proto;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.spark;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.proto;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.spark;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.proto;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.spark;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.proto;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.spark;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.shell;
|
||||
using bts.utils;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.collections.extensions;
|
||||
using mbts.config;
|
||||
using mbts.spark;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
@@ -31,6 +32,8 @@ namespace mbts.ui.settings {
|
||||
}
|
||||
|
||||
static class PredicateBuilderVMWrapper {
|
||||
static readonly bool _toast_notifications_enabled_global = Configuration.get<bts.config.toggles.ToastNotifications>().enabled;
|
||||
|
||||
public static ITemplatedPropertyWrapperVM<TSource> wrap_child_oneof<TSource, TChild, TChildOneof>(
|
||||
Func<TSource, TChild> get, Action<TSource, TChild> set, Func<TChild, TChildOneof> get_child_oneof, Dictionary<TChildOneof, ITemplatedPropertyWrapperVM<TChild>> oneof_vms)
|
||||
where TChildOneof : struct
|
||||
@@ -104,8 +107,9 @@ namespace mbts.ui.settings {
|
||||
}
|
||||
}
|
||||
public ITemplatedPropertyWrapperVM<TPredicate> selected_predicate => _selected_predicate.Value;
|
||||
public bool can_enable_toast_notifications => _can_enable_toast_notifications.Value;
|
||||
public bool can_enable_toast_notifications => _toast_notifications_enabled_global && _can_enable_toast_notifications.Value;
|
||||
public bool toast_notifications_enabled { get => _toast_notifications_enabled.Value; set => _toast_notifications_enabled.set(value); }
|
||||
public bool is_root => true;
|
||||
string ITemplateSelectorKey.key => PredicateTemplateKeys.Recursive;
|
||||
|
||||
protected override void Dispose(bool disposing) =>
|
||||
@@ -161,6 +165,7 @@ namespace mbts.ui.settings {
|
||||
}
|
||||
}
|
||||
public ITemplatedPropertyWrapperVM<TChild> selected_predicate => _selected_predicate.Value;
|
||||
public bool is_root => false;
|
||||
string ITemplateSelectorKey.key => _template_key;
|
||||
|
||||
protected override void Dispose(bool disposing) {
|
||||
|
||||
@@ -15,6 +15,7 @@ using mbts.ui.shell;
|
||||
using NUnit.Framework;
|
||||
using mbts.config;
|
||||
using bts.core;
|
||||
using bts;
|
||||
|
||||
namespace mbts.ui.tests
|
||||
{
|
||||
@@ -43,7 +44,7 @@ namespace mbts.ui.tests
|
||||
finally {
|
||||
gonow.Set();
|
||||
}
|
||||
Logger.log(bts.log.Level.Info, bts.log.Subsystem.Ui, "Init Application");
|
||||
Log.Ui.info("Init Application");
|
||||
Dispatcher.Run();
|
||||
});
|
||||
t.Name = "Application Thread";
|
||||
@@ -72,7 +73,7 @@ namespace mbts.ui.tests
|
||||
Assert.IsNotNull(Application.Current);
|
||||
// Run the logic of this test on the "Main" thread
|
||||
Application.Current.Dispatcher.Invoke(() => {
|
||||
Logger.log(bts.log.Level.Info, bts.log.Subsystem.Ui, "Begin: test_windows_disposed");
|
||||
Log.Ui.info("Begin: test_windows_disposed");
|
||||
// Make sure that an IShellWindow and ISlowWindow added to the Windows collection is eventually disposed
|
||||
var thread_factory = new ShellThreadFactory();
|
||||
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), null, new SimpleBus());
|
||||
@@ -97,7 +98,7 @@ namespace mbts.ui.tests
|
||||
public void test_slow_windows_disposed() {
|
||||
Assert.IsNotNull(Application.Current);
|
||||
Application.Current.Dispatcher.Invoke(() => {
|
||||
Logger.log(bts.log.Level.Info, bts.log.Subsystem.Ui, "Begin: test_slow_windows_disposed");
|
||||
Log.Ui.info("Begin: test_slow_windows_disposed");
|
||||
// Make sure that an IShellWindow and ISlowWindow added to the Windows collection is eventually disposed
|
||||
var thread_factory = new ShellThreadFactory();
|
||||
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), null, new SimpleBus());
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace bts.core
|
||||
subject<T>(tag).OnNext(data);
|
||||
}
|
||||
catch(Exception e) {
|
||||
Logger.error(log.Subsystem.Ui, $"Exception posting to '{tag}': {e}");
|
||||
Log.Ui.error($"Exception posting to '{tag}': {e}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ namespace bts.spark
|
||||
return _get_parameter();
|
||||
}
|
||||
catch (Exception e) {
|
||||
Logger.warn(log.Subsystem.Ui, $"Error retrieving parameter for '{title}' - Exception: '{e}'");
|
||||
Log.Ui.warn($"Error retrieving parameter for '{title}' - Exception: '{e}'");
|
||||
return default;
|
||||
}
|
||||
}
|
||||
@@ -151,7 +151,7 @@ namespace bts.spark
|
||||
bus.post(tag, arg);
|
||||
}
|
||||
catch (Exception e) {
|
||||
Logger.warn(log.Subsystem.Ui, $"Error invoking command '{title}' - Exception: '{e}'");
|
||||
Log.Ui.warn($"Error invoking command '{title}' - Exception: '{e}'");
|
||||
}
|
||||
}
|
||||
public override bool canExecute() {
|
||||
@@ -205,7 +205,7 @@ namespace bts.spark
|
||||
try {
|
||||
bus.post(_tag, _parameter);
|
||||
} catch (Exception e) {
|
||||
Logger.warn(log.Subsystem.Ui, $"Error invoking command '{title}' - Exception: '{e}'");
|
||||
Log.Ui.warn($"Error invoking command '{title}' - Exception: '{e}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,14 @@ namespace bts.spark
|
||||
public static PartialCommand<IEnumerable<(OrderDefinitionImmutable, double, uint)>> RecreateSelectedOrders =>
|
||||
SparkCommand.create("Recreate", Icons.Beaker, SparkTags.RecreateOrder);
|
||||
|
||||
public static SparkCommand grid_settings_command(IDialogCommand launch_settings_editor) =>
|
||||
new SparkCommandWrapper("Window Settings", Icons.Tools, launch_settings_editor);
|
||||
// TODO: remove this method, eventually want all windows to use window_settings_command()
|
||||
public static SparkCommand change_securities_command(Func<(PartialCommand<SecurityCriteria>, SecurityCriteria)> get_param) =>
|
||||
SparkCommand.create("Settings", Icons.Admin, SparkTags.ShowAndEditPickMultiple, get_param);
|
||||
|
||||
public static PartialCommand<SecurityCriteria> change_securities_callback(string window_name) =>
|
||||
SparkCommand.create(window_name, Icons.Admin, new SparkTags.SparkTag<SecurityCriteria>($"Change {window_name} securities callback"));
|
||||
|
||||
public static SparkCommand window_settings_command(IDialogCommand launch_settings_editor) =>
|
||||
new SparkCommandWrapper("Settings", Icons.Admin, launch_settings_editor);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using mbts.collections.extensions;
|
||||
using System.Collections;
|
||||
using bts.collections;
|
||||
|
||||
namespace mbts.ui.utility
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using bts;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
@@ -186,7 +187,7 @@ namespace mbts.expression
|
||||
if (t[x] is CompareOperator) {
|
||||
CompareOperator b = (CompareOperator)t[x];
|
||||
if (b.Left != null) {
|
||||
Logger.assert(b.Right != null);
|
||||
Log.assert(b.Right != null);
|
||||
continue;
|
||||
}
|
||||
b.Left = t[x - 1];
|
||||
@@ -202,7 +203,7 @@ namespace mbts.expression
|
||||
if (t[x] is JoinOperator) {
|
||||
JoinOperator j = (JoinOperator)t[x];
|
||||
if (j.Left != null) {
|
||||
Logger.assert(j.Right != null);
|
||||
Log.assert(j.Right != null);
|
||||
continue;
|
||||
}
|
||||
j.Left = t[x - 1];
|
||||
@@ -213,7 +214,7 @@ namespace mbts.expression
|
||||
}
|
||||
}
|
||||
|
||||
Logger.assert(t.Count == 1);
|
||||
Log.assert(t.Count == 1);
|
||||
return t.First();
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Windows;
|
||||
using System.Windows.Markup;
|
||||
using System.Windows.Media;
|
||||
using System.Reflection;
|
||||
using bts;
|
||||
|
||||
namespace mbts.ui
|
||||
{
|
||||
@@ -114,7 +115,7 @@ namespace mbts.ui
|
||||
}
|
||||
else
|
||||
{ // if the color is not in the dictionary then throw an exception
|
||||
Logger.assert_d(false, "The color '{0}' could not be found".fmt(Name));
|
||||
Log.assert_d(false, "The color '{0}' could not be found".fmt(Name));
|
||||
throw new ColorNotFoundException();
|
||||
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ namespace mbts.ui {
|
||||
public static BTSColor bid { get; }
|
||||
public static BTSColor bid_alt { get { return BTSCoreColors.blue_light; } }
|
||||
public static BTSColor ask { get; }
|
||||
public static BTSColor ask_alt { get { return BTSCoreColors.red_light_dark; } }
|
||||
public static BTSColor ask_alt { get { return new BTSColor(255,180,180); } }
|
||||
public static BTSColor last { get { return BTSCoreColors.black; } }
|
||||
public static BTSColor bid_ask_alt_blend => BTSCoreColors.purple_light;
|
||||
|
||||
@@ -207,11 +207,15 @@ namespace mbts.ui {
|
||||
public static BTSColor straddle_theovsmarket_positive { get { return BTSCoreColors.green_light; } }
|
||||
|
||||
// Order Types
|
||||
public static BTSColor tick_order { get { return BTSColors.purple.rgb_color();}}
|
||||
public static BTSColor lean_order { get { return BTSColors.purple.rgb_color();}}
|
||||
public static BTSColor join_order { get { return BTSColors.purple.rgb_color();}}
|
||||
public static BTSColor top_order { get { return BTSColors.purple.rgb_color();}}
|
||||
}
|
||||
public static BTSColor tick_order { get { return BTSColors.purple.rgb_color(); } }
|
||||
public static BTSColor lean_order { get { return new BTSColor(255, 170, 210); } }
|
||||
public static BTSColor join_order { get { return new BTSColor(200, 171, 232); } }
|
||||
public static BTSColor cover_order { get { return new BTSColor(134, 232, 224); } }
|
||||
public static BTSColor trigger_order { get { return new BTSColor(172, 255, 179); } }
|
||||
public static BTSColor off_order { get { return new BTSColor(0x8a, 0x00, 0x5f); } }//8a005f
|
||||
public static BTSColor top_order { get { return new BTSColor(0xdd, 0x7a, 0x27); } }//dd7a27
|
||||
public static BTSColor spreader_order { get { return new BTSColor(0xff, 0xff, 0x80); } } //ffff80
|
||||
}
|
||||
|
||||
public static class AW2 {
|
||||
public static BTSColor collapsed { get { return BTSCoreColors.blue_light_dark; } }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.utils;
|
||||
using bts.utils;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
@@ -665,44 +666,6 @@ namespace mbts.collections.extensions {
|
||||
}
|
||||
}
|
||||
|
||||
public static class ListExtensions
|
||||
{
|
||||
#region RemoveWhere
|
||||
public static void RemoveWhere<T>(this IList<T> list, Func<T, bool> where) { foreach (var r in list.Where(where).ToList()) list.Remove(r); }
|
||||
|
||||
public static void RemoveWhere<T>(this IList<T> list, Func<T, bool> where, Action<T> on_remove) {
|
||||
int count = list.Count;
|
||||
for (int i = 0; i < count; i++) {
|
||||
var t = list[i];
|
||||
if (where(t)) {
|
||||
list.RemoveAt(i--);
|
||||
--count;
|
||||
on_remove(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Random rng = new Random();
|
||||
|
||||
public static void Shuffle<T>(this IList<T> list) {
|
||||
int n = list.Count;
|
||||
while (n > 1) {
|
||||
n--;
|
||||
int k = rng.Next(n + 1);
|
||||
T value = list[k];
|
||||
list[k] = list[n];
|
||||
list[n] = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public static void RemoveAt<T>(this IList<T> list, int index, int count) {
|
||||
for (int i = 0; i < count; i++)
|
||||
list.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DictionaryExtensions
|
||||
{
|
||||
#region GetValueOrDefault
|
||||
|
||||
@@ -15,6 +15,7 @@ using System.Windows.Threading;
|
||||
using System.Collections.Specialized;
|
||||
using mbts.spark;
|
||||
using bts.api;
|
||||
using bts;
|
||||
|
||||
namespace mbts
|
||||
{
|
||||
@@ -155,7 +156,7 @@ namespace mbts
|
||||
//this overloaded declaration, (as opposed to one function with comp = null) allows observable_collection.sync_with to be passed into functions as a parameterless Action
|
||||
public static void sync_with<T>(this IList<T> dest, IEnumerable<T> src) { dest.sync_with(src, EqualityComparer<T>.Default); }
|
||||
public static void sync_with<T>(this IList<T> dest, IEnumerable<T> src, IEqualityComparer<T> comp) {
|
||||
Logger.notnull(dest);
|
||||
Log.notnull(dest);
|
||||
if (src == null) {
|
||||
dest.Clear();
|
||||
return;
|
||||
@@ -189,8 +190,8 @@ namespace mbts
|
||||
public static bool sync_with_setwise<T>(this ObservableCollection<T> dest, IEnumerable<T> src) { return dest.sync_with_setwise(src, EqualityComparer<T>.Default); }
|
||||
public static bool sync_with_setwise<T>(this ObservableCollection<T> dest, IEnumerable<T> src, IEqualityComparer<T> comp) {
|
||||
if (!src.is_distinct(comp)) {
|
||||
Logger.assert_d(false);
|
||||
Logger.error(bts.log.Subsystem.Ui, "sync_with_setwise requires input to be distinct. Error in input of type {0}.".fmt(typeof(T).Name));
|
||||
Log.assert_d(false);
|
||||
Log.Ui.error($"sync_with_setwise requires input to be distinct. Error in input of type {typeof(T).Name}.");
|
||||
}
|
||||
IEnumerable<T> added, removed;
|
||||
dest.difference(src, out added, out removed, comp);
|
||||
@@ -333,6 +334,20 @@ namespace System.Reactive.Linq
|
||||
public static IObservable<(TSource prev, TSource curr)> PairWithPrevious<TSource>(this IObservable<TSource> source, TSource default_value) =>
|
||||
source.Scan<TSource, (TSource prev, TSource curr)>((default_value, default_value), (last, next) => (last.curr, next));
|
||||
|
||||
/// <summary>
|
||||
/// Uses each element of a source observable to create an inner observable of <typeparamref name="TResult"/>, unlike <see cref="Observable.SelectMany{TSource, TResult}(IObservable{TSource}, Func{TSource, IObservable{TResult}})"/>,
|
||||
/// each inner sequence is terminated when the next one begins.
|
||||
/// <para/>In other words, observers are notified by the inner observerables in "series," rather than "parallel."
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource"></typeparam>
|
||||
/// <typeparam name="TResult"></typeparam>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="select_next"></param>
|
||||
/// <param name="source_sub"></param>
|
||||
/// <returns></returns>
|
||||
public static IConnectableObservable<TResult> SelectManyInSeries<TSource, TResult>(this IObservable<TSource> source, Func<TSource, IObservable<TResult>> select_next) =>
|
||||
new ConnectableSeriesSource<TSource>(source).create_connectable(select_next);
|
||||
|
||||
public static async Task<(T result, bool success)> FirstAsyncTask<T>(this IObservable<T> source, TimeSpan timeout) where T : class =>
|
||||
await source.Select(val => (val, true)).Merge(Observable.Return((default(T), false)).Delay(timeout)).FirstAsync();
|
||||
|
||||
@@ -350,9 +365,5 @@ namespace System.Reactive.Linq
|
||||
public static IObservable<T> OnDispatcher<T>(this IObservable<T> source) {
|
||||
return DispatcherObservable.ObserveOnDispatcher(source, DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
using mbts.user.settings;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using static mbts.spark.utilities.MathUtils;
|
||||
using bts;
|
||||
using static bts.utils.MathUtils;
|
||||
|
||||
namespace mbts {
|
||||
static class TickExtensions {
|
||||
@@ -273,7 +274,7 @@ namespace mbts {
|
||||
}
|
||||
}
|
||||
catch (OverflowException ex) {
|
||||
Logger.warn(bts.log.Subsystem.Ui, $"Arithmetic overflow exception in format_tick (tick:{tick}, value:{value}):{ex}");
|
||||
Log.Ui.warn($"Arithmetic overflow exception in format_tick (tick:{tick}, value:{value}):{ex}");
|
||||
return "Overflow";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using bts;
|
||||
|
||||
namespace mbts.ui.native
|
||||
{
|
||||
@@ -211,7 +212,7 @@ namespace mbts.ui.native
|
||||
// Since we want to do that during shutdown this could cause a long delay.
|
||||
bool result = SetWorkArea_SystemParametersInfo(SPI.SETWORKAREA, 1, rect, SPIF_UPDATEINIFILE);
|
||||
if (!result) {
|
||||
Logger.log(bts.log.Level.Error, bts.log.Subsystem.Ui, "Failed to set work area. {0} - {1}".fmt(Marshal.GetLastWin32Error(), new System.ComponentModel.Win32Exception().Message));
|
||||
Log.Ui.error("Failed to set work area. {0} - {1}".fmt(Marshal.GetLastWin32Error(), new System.ComponentModel.Win32Exception().Message));
|
||||
}
|
||||
SendNotifyMessage(HWND_BROADCAST, WM.SETTINGCHANGE, (Int64)SPI.SETWORKAREA, 0);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using bts;
|
||||
using mbts.ui.shell;
|
||||
|
||||
namespace mbts.ui.settings
|
||||
@@ -28,6 +29,7 @@ namespace mbts.ui.settings
|
||||
}
|
||||
|
||||
public Task<IShellWindow> restore(WindowState window) {
|
||||
Log.Ui.info("Loading window '{0}'".fmt(window.title));
|
||||
bool changed;
|
||||
int count = 0;
|
||||
do {
|
||||
@@ -36,7 +38,7 @@ namespace mbts.ui.settings
|
||||
} while (count < 100 && changed);
|
||||
|
||||
if (count == 100) {
|
||||
Logger.log(bts.log.Level.Error, bts.log.Subsystem.Persist, "Error: Window state migration still changing after 100 iterations. Please check IShellWindowMigrationRule logic.");
|
||||
Log.Persist.error("Error: Window state migration still changing after 100 iterations. Please check IShellWindowMigrationRule logic.");
|
||||
}
|
||||
|
||||
return restore(window.owner, window.app_data, window.position);
|
||||
@@ -55,7 +57,7 @@ namespace mbts.ui.settings
|
||||
}
|
||||
}
|
||||
catch(Exception e) {
|
||||
Logger.log(bts.log.Level.Error, bts.log.Subsystem.Persist, "Exception running compat rule {0}.\n{1}".fmt(test.GetType().Name, e.ToString()));
|
||||
Log.Persist.error("Exception running compat rule {0}.\n{1}".fmt(test.GetType().Name, e.ToString()));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -64,7 +66,7 @@ namespace mbts.ui.settings
|
||||
private async Task<IShellWindow> restore(string owner_name, byte[] state, WindowPosition windowPosition) {
|
||||
IShellModule owner = _modules.SingleOrDefault(m => m.key() == owner_name);
|
||||
if (owner == null) {
|
||||
Logger.log(bts.log.Level.Warn, bts.log.Subsystem.Ui, "Could not locate module '{0}'".fmt(owner_name));
|
||||
Log.Ui.warn("Could not locate module '{0}'".fmt(owner_name));
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -74,31 +76,15 @@ namespace mbts.ui.settings
|
||||
IShellWindow window = await task;
|
||||
if (window != null) {
|
||||
window.set_start_position(windowPosition);
|
||||
Logger.log(bts.log.Level.Info, bts.log.Subsystem.Ui, $"Restoring window {owner_name} - {window.diagnostic_name()}.");
|
||||
Log.Ui.info($"Restoring window {owner_name} - {window.diagnostic_name()}.");
|
||||
}
|
||||
return window;
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Logger.log(bts.log.Level.Error, bts.log.Subsystem.Ui,
|
||||
"Failure to re-create window for module {0}. Error: {1}".fmt(owner_name, e.Message));
|
||||
Log.Ui.error("Failure to re-create window for module {0}. Error: {1}".fmt(owner_name, e.Message));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//private static async Task<T> await_with_timeout<T>(Task<T> task) {
|
||||
// if (task.IsCompleted) {
|
||||
// return task.Result;
|
||||
// }
|
||||
// else {
|
||||
// if (await Task.WhenAny(task, Task.Delay(4096)) == task) {
|
||||
// return task.Result;
|
||||
// }
|
||||
// else {
|
||||
// Logger.log(bts.log.Level.Warn, bts.log.Subsystem.ui, "Timed out waiting to load saved window");
|
||||
// }
|
||||
// }
|
||||
// return default(T);
|
||||
//}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using bts.log;
|
||||
using bts;
|
||||
using mbts.ui.shell;
|
||||
|
||||
namespace mbts.ui.settings
|
||||
@@ -80,7 +80,7 @@ namespace mbts.ui.settings
|
||||
return state;
|
||||
}
|
||||
catch (Exception e) {
|
||||
Logger.log(Level.Error, Subsystem.Ui, "Error saving window state for type {0}.\n {1}".fmt(window.GetType(), e.Message));
|
||||
Log.Ui.error("Error saving window state for type {0}.\n {1}".fmt(window.GetType(), e.Message));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@ namespace mbts.spark {
|
||||
|
||||
[ProtoMember(1)]
|
||||
LadderSettings _ladder;
|
||||
[ProtoMember(2)]
|
||||
// removing NotificationSettings these from proto-serialization, since they are disabled by default for now.
|
||||
// This way, when they are enabled, there won't be any backwards compatability issues to worry about, as no clients will have persisted them yet.
|
||||
NotificationSettings _notification;
|
||||
|
||||
public UserSettings() { }
|
||||
|
||||
@@ -2,8 +2,8 @@ using System;
|
||||
using System.Windows;
|
||||
using System.Collections.Generic;
|
||||
using mbts.ui.native;
|
||||
using bts.log;
|
||||
using System.Windows.Threading;
|
||||
using bts;
|
||||
|
||||
namespace mbts.ui
|
||||
{
|
||||
@@ -26,9 +26,9 @@ namespace mbts.ui
|
||||
|
||||
static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) {
|
||||
var exception = e.ExceptionObject as Exception;
|
||||
Logger.log(Level.Error, Subsystem.Ui, $"Unhandled Exception: {e.ExceptionObject} {exception?.StackTrace}");
|
||||
Log.Ui.error($"Unhandled Exception: {e.ExceptionObject} {exception?.StackTrace}");
|
||||
if (e.IsTerminating) {
|
||||
Logger.system_is_crashing("Unhandled exception", e.ExceptionObject);
|
||||
Log.system_is_crashing("Unhandled exception", e.ExceptionObject);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using bts;
|
||||
using bts.api;
|
||||
|
||||
namespace mbts.ui.utility
|
||||
@@ -83,7 +84,7 @@ namespace mbts.ui.utility
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
mbts.Logger.log(bts.log.Level.Error, bts.log.Subsystem.Ui, String.Format("Exception message = {0}", ex.Message));
|
||||
Log.Ui.error(String.Format("Exception message = {0}", ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
|
||||
namespace mbts.utils {
|
||||
/// <summary>
|
||||
/// Observes a sequence of <typeparamref name="TKey"/>s which are used to create inner sequences of <typeparamref name="TValue"/>s.
|
||||
/// An internal subscription made to each inner sequence drives the notifications sent to outside observers.
|
||||
/// When the next key arrives, the previous internal subscription is disposed.
|
||||
/// <para/>Note: this behaves differently from <see cref="Observable.SelectMany{TSource, TResult}(IObservable{TSource}, Func{TSource, int, IObservable{TResult}})"/>,
|
||||
/// which merges each inner sequence together, without terminating the previous one when the next key arrives.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <seealso cref="Observable.SelectMany"/>
|
||||
class KeyDrivenObservableSeries<TKey, TValue> : ISubject<TKey, TValue>, IDisposable {
|
||||
readonly Func<TKey, IObservable<TValue>> _selector;
|
||||
object _lock = new object();
|
||||
Subject<TValue> _value_subject = new Subject<TValue>();
|
||||
IDisposable _current_key_sub;
|
||||
|
||||
public KeyDrivenObservableSeries(Func<TKey, IObservable<TValue>> selector) =>
|
||||
_selector = selector;
|
||||
|
||||
public void OnCompleted() => Disposable.dispose(ref _current_key_sub);
|
||||
public void OnError(Exception error) { }
|
||||
public void OnNext(TKey key) {
|
||||
lock (_lock) {
|
||||
// need to lock bc simultaneous callbacks on diff threads could generate 2 subscriptions at once,
|
||||
// only 1 of which would get disposed in the next callback.
|
||||
Disposable.dispose(ref _current_key_sub);
|
||||
_current_key_sub = _selector(key)?.Subscribe(_value_subject);
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(IObserver<TValue> observer) => _value_subject.Subscribe(observer);
|
||||
|
||||
public void Dispose() {
|
||||
Disposable.dispose(ref _current_key_sub);
|
||||
Disposable.dispose(ref _value_subject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Facilitates the creation of <see cref="IConnectableObservable{T}"/> sequences that are the product of multiple inner sequences, created by each <typeparamref name="TSource"/> element in the source.
|
||||
/// <para/>Note: The inner sequences will be played "series," as opposed to "parallel," meaning the previous inner sequence is terminated when the next <typeparamref name="TSource"/> element arrives.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">The type of elements used to create a series inner sequences.</typeparam>
|
||||
class ConnectableSeriesSource<TSource> {
|
||||
readonly IObservable<TSource> _source;
|
||||
readonly List<IObserver<TSource>> _observers = new List<IObserver<TSource>>();
|
||||
|
||||
public ConnectableSeriesSource(IObservable<TSource> source) =>
|
||||
_source = source;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an IConnectableObservable<TResult> sequence that is the product of multiple inner sequences created by each <typeparamref name="TSource"/> element in "series," terminating each inner sequence when the next one begins.
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult"></typeparam>
|
||||
/// <param name="select_next"></param>
|
||||
/// <returns></returns>
|
||||
public IConnectableObservable<TResult> create_connectable<TResult>(Func<TSource, IObservable<TResult>> select_next) =>
|
||||
new AnonymousConnectable<TResult>(_source, select_next);
|
||||
|
||||
class AnonymousConnectable<TResult> : IConnectableObservable<TResult> {
|
||||
readonly IObservable<TSource> _source;
|
||||
readonly Func<TSource, IObservable<TResult>> _select_next;
|
||||
List<LazySubscription> _lazy_subs = new List<LazySubscription>();
|
||||
KeyDrivenObservableSeries<TSource, TResult> _driver;
|
||||
IDisposable _driver_sub;
|
||||
bool _is_connected = false;
|
||||
|
||||
public AnonymousConnectable(IObservable<TSource> source, Func<TSource, IObservable<TResult>> select_next) {
|
||||
_source = source;
|
||||
_select_next = select_next;
|
||||
}
|
||||
|
||||
// idempotent
|
||||
public IDisposable Connect() {
|
||||
lock (this) {
|
||||
if (!_is_connected) {
|
||||
_driver = new KeyDrivenObservableSeries<TSource, TResult>(_select_next);
|
||||
foreach (var ls in _lazy_subs)
|
||||
ls.attach();
|
||||
_lazy_subs.Clear();
|
||||
_driver_sub = _source.Subscribe(_driver);
|
||||
_is_connected = true;
|
||||
}
|
||||
}
|
||||
return Disposable.Create(_deconnect);
|
||||
}
|
||||
|
||||
void _deconnect() {
|
||||
lock (this) {
|
||||
if (_is_connected) {
|
||||
_driver_sub.Dispose();
|
||||
_driver.Dispose();
|
||||
_is_connected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(IObserver<TResult> observer) {
|
||||
lock (this) {
|
||||
if (_is_connected)
|
||||
return _driver.Subscribe(observer);
|
||||
var ls = new LazySubscription(this, observer);
|
||||
_lazy_subs.Add(ls);
|
||||
return ls;
|
||||
}
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
class LazySubscription : IDisposable {
|
||||
readonly IObserver<TResult> _observer;
|
||||
readonly AnonymousConnectable<TResult> _owner;
|
||||
IDisposable _sub;
|
||||
|
||||
public LazySubscription(AnonymousConnectable<TResult> owner, IObserver<TResult> observer) {
|
||||
_owner = owner;
|
||||
_observer = observer;
|
||||
}
|
||||
|
||||
public void attach() =>
|
||||
_sub = _owner._driver.Subscribe(_observer);
|
||||
|
||||
public void Dispose() {
|
||||
lock (_owner) {
|
||||
Disposable.dispose(ref _sub);
|
||||
_owner._lazy_subs.Remove(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ConnectableSeriesBuilder<TSource> {
|
||||
readonly ConnectableSeriesSource<TSource> _connectable_series_source;
|
||||
readonly List<IConnectConnectable> _connect_connectables = new List<IConnectConnectable>();
|
||||
|
||||
public ConnectableSeriesBuilder(IObservable<TSource> source) =>
|
||||
_connectable_series_source = new ConnectableSeriesSource<TSource>(source);
|
||||
|
||||
public IObservable<TResult> create<TResult>(Func<TSource, IObservable<TResult>> select_next) {
|
||||
var connectable = _connectable_series_source.create_connectable(select_next);
|
||||
_connect_connectables.Add(new ConnectConnectable<TResult>(connectable));
|
||||
return connectable;
|
||||
}
|
||||
|
||||
public IDisposable connect_all() =>
|
||||
Disposable.Create(_connect_connectables.Select(c => c.connect()).ToArray());
|
||||
|
||||
interface IConnectConnectable {
|
||||
IDisposable connect();
|
||||
}
|
||||
|
||||
class ConnectConnectable<T> : IConnectConnectable {
|
||||
readonly IConnectableObservable<T> _connectable;
|
||||
|
||||
public ConnectConnectable(IConnectableObservable<T> connectable) =>
|
||||
_connectable = connectable;
|
||||
|
||||
public IDisposable connect() => _connectable.Connect();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Threading;
|
||||
using bts;
|
||||
using System.Threading;
|
||||
|
||||
namespace mbts.utils {
|
||||
class ThreadSafeCounter {
|
||||
@@ -14,9 +15,9 @@ namespace mbts.utils {
|
||||
public AllowOnce(string name) => _name = name;
|
||||
|
||||
/// <summary>
|
||||
/// After this method is called once, subsequent calls will result in the Application being terminated via Logger.assert.
|
||||
/// After this method is called once, subsequent calls will result in the Application being terminated via Log.assert.
|
||||
/// </summary>
|
||||
public void check_access() =>
|
||||
Logger.assert(_counter.increment() == 1, $"AllowOnce '{_name}' check_access was called twice.");
|
||||
Log.assert(_counter.increment() == 1, $"AllowOnce '{_name}' check_access was called twice.");
|
||||
}
|
||||
}
|
||||
@@ -446,9 +446,6 @@ namespace mbts.ui.shell
|
||||
public IEnumerable<string> AllErrors { get { return _errors.Values; } }
|
||||
#endregion
|
||||
|
||||
protected Property<T> backing<T>(string name, T default_value, bool validate_now = false, params Func<T, bool>[] validation_steps) =>
|
||||
backing(name, default_value, validation_steps.Select(v => new PropertyValidation<T>(v)).ToLArray(), validate_now);
|
||||
|
||||
protected Property<T> backing<T>(string name, T default_value, IEnumerable<PropertyValidation<T>> validation_steps, bool validate_now = false) {
|
||||
void run_validation(T val) {
|
||||
foreach (var pv in validation_steps) {
|
||||
@@ -649,9 +646,12 @@ namespace mbts.ui.shell
|
||||
}
|
||||
} else {
|
||||
_items.sync_with(value);
|
||||
if (_is_user_selected_item)
|
||||
if (_is_user_selected_item) {
|
||||
_is_user_selected_item = _items.Contains(_selected_item);
|
||||
_try_auto_select();
|
||||
}
|
||||
if (!_is_user_selected_item) {
|
||||
_try_auto_select();
|
||||
}
|
||||
}
|
||||
}
|
||||
void IObserver<IEnumerable<T>>.OnError(Exception error) { }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.ui;
|
||||
using bts.utils;
|
||||
using mbts.ui;
|
||||
using mbts.utils;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.utils;
|
||||
using bts.utils;
|
||||
using mbts.utils;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.spark;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.utils;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.utils;
|
||||
using bts.utils;
|
||||
using mbts.utils;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts.utils;
|
||||
using mbts.utils;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.ui;
|
||||
using bts.utils;
|
||||
using mbts.ui;
|
||||
using mbts.utils;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.utils;
|
||||
using bts.utils;
|
||||
using mbts.utils;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.utils;
|
||||
using bts.utils;
|
||||
using mbts.utils;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.utils;
|
||||
using bts.utils;
|
||||
using mbts.utils;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
Loaded 100 of 490 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user