Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
883272b05b | ||
|
|
a5c37712cf | ||
|
|
fd5ea2f864 | ||
|
|
415d03a156 | ||
|
|
36eadea650 | ||
|
|
7d45de95f6 | ||
|
|
03b7031af2 | ||
|
|
368e07ede4 | ||
|
|
ce7f347256 | ||
|
|
344c201a94 | ||
|
|
e9a4edda41 | ||
|
|
1c92171165 | ||
|
|
240e2e57bf | ||
|
|
24d68f28da | ||
|
|
2eb8f4f1e0 | ||
|
|
23cb40f83a | ||
|
|
9ee3eca0ba | ||
|
|
c84961452b | ||
|
|
202e08f3d0 | ||
|
|
2e0780f5e8 | ||
|
|
43f4b33366 | ||
|
|
4bcc73ece9 | ||
|
|
015ed0849c | ||
|
|
79e0713349 | ||
|
|
7785bd1538 | ||
|
|
2dff9799bc | ||
|
|
1f794c9f80 | ||
|
|
a8d2df01ca | ||
|
|
5ceeadf01e | ||
|
|
31baa56223 | ||
|
|
268f8a16a7 | ||
|
|
3232f010b9 | ||
|
|
251a835411 | ||
|
|
e815cbcdaa | ||
|
|
5571d73365 | ||
|
|
e70e3d2a2f | ||
|
|
96016d427d | ||
|
|
8c9ab5743c | ||
|
|
ca3bb8b99b | ||
|
|
2e45216e24 | ||
|
|
8d1761c5d8 | ||
|
|
32b2b8a702 | ||
|
|
76a489cc67 | ||
|
|
81488824db | ||
|
|
c47575067f | ||
|
|
26a4f5d2ed | ||
|
|
1bbe4fcbdf | ||
|
|
3859528670 | ||
|
|
d212383f56 | ||
|
|
dad7a65e92 | ||
|
|
acb2d1dfc5 | ||
|
|
d8d72d2052 | ||
|
|
f1b50b4160 | ||
|
|
6a90e71efe | ||
|
|
c98144daa0 | ||
|
|
a08eb90b1a | ||
|
|
215740a011 | ||
|
|
3bf6e81a4d | ||
|
|
0112bdbc3d | ||
|
|
547e06c33f | ||
|
|
35d53391da |
No files matched your search
@@ -33,4 +33,5 @@ trace_data/
|
||||
/src/lib/orderman/perf_analysis/log.csv
|
||||
/doc/user/public/*
|
||||
.ionide/*
|
||||
*.ionide
|
||||
/.ignore
|
||||
@@ -1,8 +1,8 @@
|
||||
<Window
|
||||
<Window
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
Width="400" Height="265"
|
||||
xmlns:ui="clr-namespace:mbts.ui"
|
||||
xmlns:mbts="clr-namespace:mbts.ui" WindowStartupLocation="CenterScreen"
|
||||
@@ -25,7 +25,7 @@
|
||||
<Hyperlink Command="ApplicationCommands.Open" CommandParameter="/doc/manual/index.html">Manual</Hyperlink-->
|
||||
<Hyperlink Command="ApplicationCommands.Open" CommandParameter="https://www.bluetradingsystems.com">www.bluetradingsystems.com</Hyperlink>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.ColumnSpan="2" VerticalAlignment="Bottom" HorizontalAlignment="Center" Margin="0,0,0,10">© Blue Trading Systems 2013 - 2018</TextBlock>
|
||||
<TextBlock Grid.ColumnSpan="2" VerticalAlignment="Bottom" HorizontalAlignment="Center" Margin="0,0,0,10">© Blue Trading Systems</TextBlock>
|
||||
<Button HorizontalAlignment="Right" VerticalAlignment="Bottom" Grid.ColumnSpan="2" Margin="10" IsDefault="True" Command="ApplicationCommands.Close" MinWidth="60">OK</Button>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -105,7 +105,7 @@ KeyModel = namedtuple("KeyModel", "type field")
|
||||
DefaultKeyModel = KeyModel("int", "id")
|
||||
def transform_service(service, keymap):
|
||||
return ServiceModel(
|
||||
service.name,
|
||||
service.name,
|
||||
option(service.options, apioptions_pb2.service_description, ""),
|
||||
[transform_method(m, keymap.get(m.output_type, DefaultKeyModel)) for m in service.method],
|
||||
service.name.lower())
|
||||
@@ -158,7 +158,7 @@ def to_type_model(field, msg):
|
||||
elif field.type == types.TYPE_DOUBLE: return TypeModel("double", islist, "double?", jstype='number')
|
||||
elif field.type == types.TYPE_FLOAT: return TypeModel("float", islist, "float?", jstype='number', fstype='float32')
|
||||
elif field.type == types.TYPE_STRING: return TypeModel("string", islist, cpptype="std::string")
|
||||
elif field.type == types.TYPE_BYTES: return TypeModel("ByteString", islist, cpptype="std::string", jstype='Uint8Array', fstype='Fable.Import.JS.Uint8Array')
|
||||
elif field.type == types.TYPE_BYTES: return TypeModel("ByteString", islist, cpptype="std::string", jstype='Uint8Array', fstype='uint8[]')
|
||||
elif field.type == types.TYPE_BOOL: return TypeModel("bool", islist, "bool?", jstype='boolean', natural_key=True)
|
||||
elif field.type == types.TYPE_ENUM:
|
||||
nested_enums = [x.name for x in msg.enum_type]
|
||||
@@ -185,8 +185,8 @@ def to_type_model(field, msg):
|
||||
map_value = to_type_model([f for f in t.field if f.name == 'value'][0], msg)
|
||||
itype = map_value.cstype+immutable_postfix
|
||||
return TypeModel(
|
||||
map_value.cstype,
|
||||
cpptype=map_value.cpptype,
|
||||
map_value.cstype,
|
||||
cpptype=map_value.cpptype,
|
||||
jstype=map_value.jstype,
|
||||
fstype=map_value.fstype,
|
||||
isMessage=True,
|
||||
@@ -234,7 +234,7 @@ def enum_short_name(enum_name, entry):
|
||||
return camel_case(entry[len(enum_name)+1:])
|
||||
return camel_case(entry)
|
||||
|
||||
def camel_case(item):
|
||||
def camel_case(item):
|
||||
if item.isupper() and not '_' in item: return item
|
||||
return item.title().replace('_', '')
|
||||
return d
|
||||
@@ -307,7 +307,7 @@ def generate_code(request, response):
|
||||
|
||||
#### MAIN #########
|
||||
if __name__ == "__main__":
|
||||
if DEBUG:
|
||||
if DEBUG:
|
||||
data = open("data", mode='rb').read()
|
||||
else:
|
||||
data = sys.stdin.read()
|
||||
@@ -317,7 +317,7 @@ if __name__ == "__main__":
|
||||
response = plugin.CodeGeneratorResponse()
|
||||
generate_code(request, response)
|
||||
|
||||
if DEBUG:
|
||||
if DEBUG:
|
||||
for f in response.file:
|
||||
print f.content
|
||||
else:
|
||||
|
||||
@@ -6,12 +6,12 @@ open google.protobuf
|
||||
|
||||
{{#each enum_type}}
|
||||
[<StringEnum>]
|
||||
type {{name}} =
|
||||
type {{name}} =
|
||||
{{#each values}}
|
||||
| [<CompiledName("{{name}}")>] {{short_name}}
|
||||
{{/each}}
|
||||
module {{name}} =
|
||||
let values =
|
||||
let values =
|
||||
[|
|
||||
{{#each values}}
|
||||
{{../name}}.{{short_name}}
|
||||
@@ -51,7 +51,7 @@ type {{name}} =
|
||||
module {{name}} =
|
||||
{{#each nested_enum}}
|
||||
[<StringEnum>]
|
||||
type {{name}} =
|
||||
type {{name}} =
|
||||
{{#each values}}
|
||||
| [<CompiledName("{{name}}")>] {{short_name}}
|
||||
{{/each}}
|
||||
@@ -95,7 +95,7 @@ module {{name}} =
|
||||
"{{name}}", {{@index}}., "{{output_type_full}}"
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
|]
|
||||
|]
|
||||
)
|
||||
|> unbox
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ function gen_method(service:IService, method_id : number, request_type : string,
|
||||
assertValid(request_proto, r)
|
||||
const buffer = request_proto.encode(r).finish()
|
||||
const result = await service.context.call(service.name, service.qualifier, method_id, buffer)
|
||||
let temp = result_proto.decode(result)
|
||||
// let temp = result_proto.decode(result)
|
||||
// There is a built in library function do do this but it doesn't work recursively
|
||||
// https://github.com/dcodeIO/protobuf.js/issues/1041
|
||||
// result_proto.toObject(temp, { enums: String, default: true })
|
||||
|
||||
@@ -4,19 +4,19 @@ import { assert, assertValid } from 'bts_assert'
|
||||
function default_ws_uri() {
|
||||
if (typeof window == 'undefined') return 'ws://sparkvm:8000/'
|
||||
const loc = window.location
|
||||
if (loc.hostname === "localhost" && loc.port === '8000') {
|
||||
if (loc.hostname === "localhost" && loc.port === '8080') {
|
||||
return "ws://sparkvm:8000/"
|
||||
}
|
||||
let ws_uri = "ws:";
|
||||
if (loc.protocol === "https:") {
|
||||
ws_uri = "wss:";
|
||||
}
|
||||
}
|
||||
ws_uri += "//" + loc.host;
|
||||
ws_uri += loc.pathname + "/";
|
||||
return ws_uri;
|
||||
}
|
||||
|
||||
// Message types
|
||||
// Message types
|
||||
const GatewayLogin = root.lookupType("bts.api.gateway.GatewayLogin")
|
||||
const GatewayRequest = root.lookupType("bts.api.gateway.GatewayRequest")
|
||||
const GatewayResponse = root.lookupType("bts.api.gateway.GatewayResponse")
|
||||
@@ -105,7 +105,7 @@ class PrivateContext {
|
||||
// },3000);
|
||||
}
|
||||
onmessage(e){
|
||||
const raw_buffer = new Uint8Array(e.data)
|
||||
const raw_buffer = new Uint8Array(e.data)
|
||||
const response = GatewayResponse.decode(raw_buffer)
|
||||
if (response && response.callResult && response.callResult.length > 0) {
|
||||
const call_buffer = response.callResult
|
||||
@@ -176,7 +176,7 @@ export class ServiceContext {
|
||||
});
|
||||
}
|
||||
|
||||
subscribe(serviceType: string, serviceQualifier:string,
|
||||
subscribe(serviceType: string, serviceQualifier:string,
|
||||
methodId: number, methodName : string,
|
||||
streamQualifier : string, callback: (x:RawUpdate[])=>void) : number {
|
||||
// Send a subscription request to the server
|
||||
@@ -209,7 +209,7 @@ export class ServiceContext {
|
||||
unsubscribe(subscriptionId : number, serviceType : string, serviceQualifier : string) {
|
||||
let req = GatewayUnsubscribe.create({subscriptionId, serviceType, serviceQualifier});
|
||||
let message = GatewayRequest.create({
|
||||
unsubscribe: req
|
||||
unsubscribe: req
|
||||
});
|
||||
this.send(message);
|
||||
}
|
||||
@@ -239,10 +239,10 @@ export class ServiceContext {
|
||||
if (update.id && this.subscriptions) {
|
||||
const s = this.subscriptions[update.id];
|
||||
if (s) {
|
||||
console.log("on tick");
|
||||
console.log(update);
|
||||
// console.log("on tick");
|
||||
// console.log(update);
|
||||
assert(update.id == s.id);
|
||||
s.callback(update.updates);
|
||||
s.callback(update.updates);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +108,8 @@ body {
|
||||
}
|
||||
|
||||
.is-launcher-sidebar-menu {
|
||||
position:relative;
|
||||
height:100vh;
|
||||
padding: 0;
|
||||
background: $primary;
|
||||
max-width: 7rem;
|
||||
@@ -123,6 +125,11 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.logout-icon {
|
||||
position:absolute;
|
||||
bottom:0;
|
||||
}
|
||||
|
||||
.launcher-item {
|
||||
text-align:center;
|
||||
width:100%;
|
||||
@@ -376,3 +383,35 @@ body {
|
||||
.is-100-width {
|
||||
width: 100px
|
||||
}
|
||||
|
||||
.filter-card {
|
||||
position:absolute;
|
||||
top:10px;
|
||||
left:10px;
|
||||
width: 13em;
|
||||
|
||||
.delete {
|
||||
float:right;
|
||||
margin: -10px -10px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.clear-background {
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
.table-header .header-icon-hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.table-header:hover .header-icon-hidden {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.yellow {
|
||||
color:yellow;
|
||||
}
|
||||
|
||||
.header-filter-icon {
|
||||
margin-left:.5ex;
|
||||
}
|
||||
@@ -123,13 +123,13 @@ let init (context:ServiceContext.ServiceContext) =
|
||||
this_username = context.username
|
||||
ShowLoading = false
|
||||
ManualFillData = {
|
||||
fills = { manualFill = (Array.empty: bts.api.position.ManualFill [])}
|
||||
newFills = list.Empty
|
||||
updatedFills = list.Empty
|
||||
deletedFills = { manualFill = (Array.empty: bts.api.position.ManualFill [])}
|
||||
tempSODFillData = emptyTempManualFill
|
||||
tempDayFillData = emptyTempManualFill
|
||||
}
|
||||
fills = { manualFill = (Array.empty: bts.api.position.ManualFill [])}
|
||||
newFills = list.Empty
|
||||
updatedFills = list.Empty
|
||||
deletedFills = { manualFill = (Array.empty: bts.api.position.ManualFill [])}
|
||||
tempSODFillData = emptyTempManualFill
|
||||
tempDayFillData = emptyTempManualFill
|
||||
}
|
||||
}, Access.getAdminData admin UpdateAdminData NoAdminData
|
||||
|
||||
let positionSub model dispatch =
|
||||
@@ -182,7 +182,6 @@ let selectSub (tab:Tab option) model =
|
||||
match model.unsub with
|
||||
| Some (unsub) -> unsub.Dispose()
|
||||
| None ->
|
||||
System.Console.WriteLine "...?"
|
||||
()
|
||||
Cmd.none
|
||||
|
||||
@@ -409,14 +408,13 @@ let update msg (model:Model) : Model * Cmd<Msg> =
|
||||
| SelectAccount account ->
|
||||
{ (clearSelection model) with previousScratch = model.scratch
|
||||
scratch = account
|
||||
ManualFillData = {
|
||||
fills = { manualFill = (Array.empty: bts.api.position.ManualFill [])}
|
||||
newFills = list.Empty
|
||||
updatedFills = list.Empty
|
||||
deletedFills = { manualFill = (Array.empty: bts.api.position.ManualFill [])}
|
||||
tempSODFillData = emptyTempManualFill
|
||||
tempDayFillData = emptyTempManualFill
|
||||
}
|
||||
ManualFillData = { fills = { manualFill = (Array.empty: bts.api.position.ManualFill [])}
|
||||
newFills = list.Empty
|
||||
updatedFills = list.Empty
|
||||
deletedFills = { manualFill = (Array.empty: bts.api.position.ManualFill [])}
|
||||
tempSODFillData = emptyTempManualFill
|
||||
tempDayFillData = emptyTempManualFill
|
||||
}
|
||||
marginScratch = emptyMargin
|
||||
}, selectSub None model
|
||||
| GetMarginData ->
|
||||
|
||||
@@ -287,12 +287,12 @@ let clipLimitsView model dispatch =
|
||||
isHeader = false
|
||||
colSpan = 5
|
||||
}
|
||||
TableControl.Cell (Fields.text
|
||||
TableControl.Cell (Fields.uncheckedUint32
|
||||
""
|
||||
(sprintf "%i" (match (List.tryHead model.SelectedClipLimits) with
|
||||
| Some r -> r.clipSize
|
||||
| None -> 0u
|
||||
))
|
||||
((match (List.tryHead model.SelectedClipLimits) with
|
||||
| Some r -> r.clipSize
|
||||
| None -> 0u
|
||||
))
|
||||
[ ]
|
||||
((fun value -> (value, (Array.ofList model.SelectedClipLimits))) >> ChangeMultipleClipLimits >> dispatch)
|
||||
)
|
||||
@@ -467,21 +467,21 @@ let productLimits model dispatch =
|
||||
isHeader = false
|
||||
colSpan = 5
|
||||
}
|
||||
TableControl.Cell (Fields.text
|
||||
TableControl.Cell (Fields.uncheckedUint32
|
||||
""
|
||||
(sprintf "%i" (match (List.tryHead model.SelectedPositionLimits) with
|
||||
| Some r -> r.longPositionLimit
|
||||
| None -> 0u
|
||||
))
|
||||
((match (List.tryHead model.SelectedPositionLimits) with
|
||||
| Some r -> r.longPositionLimit
|
||||
| None -> 0u
|
||||
))
|
||||
[ ]
|
||||
((fun value -> ("long", value, (Array.ofList model.SelectedPositionLimits))) >> ChangeMultipleProductLimits >> dispatch)
|
||||
)
|
||||
TableControl.Cell (Fields.text
|
||||
TableControl.Cell (Fields.uncheckedUint32
|
||||
""
|
||||
(sprintf "%i" (match (List.tryHead model.SelectedPositionLimits) with
|
||||
| Some r -> r.shortPositionLimit
|
||||
| None -> 0u
|
||||
))
|
||||
( (match (List.tryHead model.SelectedPositionLimits) with
|
||||
| Some r -> r.shortPositionLimit
|
||||
| None -> 0u
|
||||
))
|
||||
[ ]
|
||||
((fun value -> ("short", value, (Array.ofList model.SelectedPositionLimits))) >> ChangeMultipleProductLimits >> dispatch)
|
||||
)
|
||||
@@ -789,6 +789,7 @@ let venueToString (venue:bts.api.Venue) =
|
||||
| bts.api.Venue.CBOECFE -> "CBOECFE"
|
||||
| bts.api.Venue.GLBX -> "GLBX"
|
||||
| bts.api.Venue.ICE -> "ICE"
|
||||
| bts.api.Venue.SIMICE -> "SIMICE"
|
||||
|
||||
let exchange_display expirationNameList =
|
||||
let s = sprintf "%O" expirationNameList.product.exchange
|
||||
|
||||
@@ -34,6 +34,7 @@ let root model dispatch =
|
||||
match model.page with
|
||||
| UsersModel m -> Users.View.root m (UsersMsg>>dispatch) |> topLevel model.context model.page dispatch
|
||||
| AccountsModel m -> Accounts.View.root m (AccountsMsg>>dispatch) |> topLevel model.context model.page dispatch
|
||||
| ReportsModel m -> Reports.View.root m (ReportsMsg>>dispatch) |> topLevel model.context model.page dispatch
|
||||
|
||||
open Elmish.React
|
||||
open Elmish.Debug
|
||||
@@ -42,8 +43,8 @@ open Elmish.HMR
|
||||
// App
|
||||
Program.mkProgram init update root
|
||||
#if DEBUG
|
||||
|> Program.withConsoleTrace
|
||||
|> Program.withDebugger
|
||||
//|> Program.withConsoleTrace
|
||||
//|> Program.withDebugger
|
||||
|> Program.withHMR
|
||||
#endif
|
||||
|> Program.withReactUnoptimized "elmish-app"
|
||||
|
||||
@@ -31,7 +31,22 @@ let text (lbl:string) (dv:string) (attrs:IHTMLProp list) onchange =
|
||||
ClassName "input"
|
||||
Type "text"
|
||||
Value dv
|
||||
OnChange (fun ev -> !!ev.target?value |> onchange)
|
||||
OnChange (fun ev -> !!ev.target?value |> string |> onchange)
|
||||
] @ attrs)
|
||||
]
|
||||
]
|
||||
|
||||
let dvText (lbl:string) (dv:string) (attrs:IHTMLProp list) onchange =
|
||||
// floating point numbers require using DefaultValue instead of Value
|
||||
div [ ClassName "field" ]
|
||||
[
|
||||
Label lbl
|
||||
div [ ClassName "control" ]
|
||||
[ input ([
|
||||
ClassName "input"
|
||||
Type "text"
|
||||
DefaultValue dv
|
||||
OnChange (fun ev -> !!ev.target?value |> string |> onchange)
|
||||
] @ attrs)
|
||||
]
|
||||
]
|
||||
@@ -150,7 +165,7 @@ let password (lbl:string) (dv:string) (attrs:IHTMLProp list) onchange =
|
||||
] @ attrs)
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
|
||||
let number (lbl:string) (dv:string) (attrs:IHTMLProp list) (validate: string -> bool) onchange =
|
||||
let errorClass = match dv |> validate with
|
||||
@@ -180,17 +195,25 @@ let positiveIntNumber (lbl:string) (dv:string) (attrs:IHTMLProp list) onchange =
|
||||
let validate value =
|
||||
let itp = System.Int32.TryParse value
|
||||
fst itp && snd itp >= 0
|
||||
|
||||
number lbl dv attrs validate onchange
|
||||
|
||||
|
||||
|
||||
let number_novalidate (lbl:string) (dv) (attrs:IHTMLProp list) onchange =
|
||||
text lbl (string dv) attrs (unbox>>onchange)
|
||||
|
||||
let uncheckedInt (lbl:string) (dv:int) (attrs:IHTMLProp list) onchange =
|
||||
text lbl (string dv) attrs (int>>onchange)
|
||||
|
||||
let double (lbl:string) (dv:double) (attrs:IHTMLProp list) onchange =
|
||||
text lbl (string dv) attrs (double>>onchange)
|
||||
let uncheckedUint32 (lbl:string) (dv:uint32) (attrs:IHTMLProp list) onchange =
|
||||
text lbl (string dv) attrs (uint32>>onchange)
|
||||
|
||||
let uncheckedInt64 (lbl:string) (dv:int64) (attrs:IHTMLProp list) onchange =
|
||||
text lbl (string dv) attrs (int64>>onchange)
|
||||
|
||||
let uncheckedDouble (lbl:string) (dv:double) (attrs:IHTMLProp list) onchange =
|
||||
match dv with
|
||||
| System.Double.MinValue | System.Double.MaxValue ->
|
||||
dvText lbl "" attrs (double>>onchange)
|
||||
| _ ->
|
||||
dvText lbl (string dv) attrs (double>>onchange)
|
||||
|
||||
let enumSelect<'a> (lbl:string) (dv:'a) (values:'a[]) (attrs:IHTMLProp list) (onchange:'a->unit) =
|
||||
let vals = match (Array.tryFindIndex (fun elm -> unbox elm = unbox dv) values) with
|
||||
|
||||
@@ -109,4 +109,26 @@ let createSelectorTableWithStyle (headers:row option) (rl:row list) className st
|
||||
If a callback is provided add a checkbox as the first column and set a background color if it is checked... in addition to the callback
|
||||
*)
|
||||
|
||||
let createSelectorTable (headers:row option) (rl:row list) = createSelectorTableWithStyle headers rl "" []
|
||||
let createSelectorTable (headers:row option) (rl:row list) = createSelectorTableWithStyle headers rl "" []
|
||||
|
||||
let showFilter (content:ReactElement) (onApplyClick: MouseEvent -> Unit) (onClearClick: MouseEvent -> Unit) (onCloseClick: MouseEvent -> Unit) top left =
|
||||
div [][
|
||||
div [ Class "modal-background clear-background"; OnClick onCloseClick ][ ]
|
||||
div [ Class "card filter-card"; Style [ Top top; Left left ] ]
|
||||
[ div [ Class "card-content" ]
|
||||
[ content
|
||||
]
|
||||
footer [ Class "card-footer" ]
|
||||
[ p [ Class "card-footer-item"; OnClick onApplyClick ]
|
||||
[ span [ ]
|
||||
[ a [ ]
|
||||
[ str "Apply" ] ] ]
|
||||
p [ Class "card-footer-item"; OnClick onClearClick ]
|
||||
[ span [ ]
|
||||
[ a [ ]
|
||||
[ str "Clear" ] ] ] ] ]
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
module Unsub
|
||||
open System
|
||||
open Fable.Helpers.React
|
||||
open Fable.Helpers.React.Props
|
||||
open Fable.Import.React
|
||||
|
||||
// This is kind of a hack...
|
||||
// Use the react lifecycle methods to implement a deterministric dispose of a subscription
|
||||
type Unsubscribe(props) =
|
||||
inherit Component<IDisposable,unit>(props)
|
||||
override this.componentWillUnmount() =
|
||||
printfn "Disposing subscription"
|
||||
this.props.Dispose()
|
||||
override this.render() =
|
||||
null
|
||||
|
||||
let unsub props =
|
||||
ofType<Unsubscribe,IDisposable,unit> props []
|
||||
+10
-3
@@ -24,6 +24,7 @@ let isCurrentPage page currentPage =
|
||||
match page, currentPage with
|
||||
| UsersModel _, UsersModel _ -> true
|
||||
| AccountsModel _, AccountsModel _ -> true
|
||||
| ReportsModel _, ReportsModel _ -> true
|
||||
| _ -> false
|
||||
|
||||
let navItem label icon state currentPage dispatch =
|
||||
@@ -48,16 +49,22 @@ let root (currentPage:Page) (sc:ServiceContext) dispatch =
|
||||
li [ Class "launcher-item" ]
|
||||
[ navItem "Accounts" "server" (tmap (Accounts.State.init sc) (AccountsModel,Cmd.map AccountsMsg)) currentPage dispatch ]
|
||||
li [ Class "launcher-item" ]
|
||||
[ navItem "Reports" "line-chart" (tmap (Reports.State.init sc) (ReportsModel,Cmd.map ReportsMsg)) currentPage dispatch ]
|
||||
li [ Class "launcher-item logout-icon" ]
|
||||
[ a [ Href "/" ]
|
||||
[ div [ ClassName "icon is-large" ] [
|
||||
i [ ClassName "fa fa-3x fa-sign-out" ] []
|
||||
i [ ClassName "fa fa-2x fa-sign-out" ] []
|
||||
]
|
||||
div [] [str ("Logout \n" + sc.username)]
|
||||
div [] [
|
||||
str "Logout"
|
||||
br []
|
||||
str sc.username
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
let (navRoot: Page -> ServiceContext -> Dispatch<Msg> -> ReactElement) =
|
||||
let (navRoot: Page -> ServiceContext -> Dispatch<Msg> -> ReactElement) =
|
||||
lazyView3With (fun b c -> isCurrentPage (fst b) (fst c)) root
|
||||
@@ -0,0 +1,691 @@
|
||||
module Reports.State
|
||||
|
||||
open Elmish
|
||||
open Types
|
||||
open JSMap
|
||||
open System
|
||||
open System.Runtime.InteropServices
|
||||
open Fable.Import
|
||||
open Fable.Core
|
||||
open Fable.Core.JsInterop
|
||||
open System.Xml
|
||||
open Fable.PowerPack.PromiseImpl
|
||||
open bts.api.admin
|
||||
open bts.api.position
|
||||
open bts.api.persistence
|
||||
open bts.co2
|
||||
open Global
|
||||
|
||||
let getLoggedInID model =
|
||||
match model.adminData with
|
||||
| Some adata -> (Array.find (fun (usr:BTSUser) -> usr.name = model.this_username) adata.users).id
|
||||
| None -> 0u
|
||||
|
||||
let venueToString (venue:bts.api.Venue) =
|
||||
match venue with
|
||||
| bts.api.Venue.UNSET -> "Unset"
|
||||
| bts.api.Venue.BTS -> "BTS"
|
||||
| bts.api.Venue.SIM -> "SIM"
|
||||
| bts.api.Venue.CBOE -> "CBOE"
|
||||
| bts.api.Venue.CBOECFE -> "CBOECFE"
|
||||
| bts.api.Venue.GLBX -> "GLBX"
|
||||
| bts.api.Venue.ICE -> "ICE"
|
||||
| bts.api.Venue.SIMICE -> "SIMICE"
|
||||
|
||||
let securityTypeToString (venue:bts.api.SecurityType) =
|
||||
match venue with
|
||||
| bts.api.SecurityType.UNSET -> "Unset"
|
||||
| bts.api.SecurityType.STOCK -> "Stock"
|
||||
| bts.api.SecurityType.OPTION -> "Option"
|
||||
| bts.api.SecurityType.FUTURE -> "Future"
|
||||
| bts.api.SecurityType.SPREAD -> "Spread"
|
||||
| bts.api.SecurityType.SYNTHETIC -> "Synthetic"
|
||||
|
||||
let getAdminData (admin:IAdminService) (token:string) =
|
||||
Cmd.ofPromise
|
||||
admin.get_filtered_admin_data ()
|
||||
UpdateAdminData
|
||||
(fun _ -> RpcResponse "Error getting admin data")
|
||||
|
||||
let getPersistenceData model =
|
||||
let nameSpace = sprintf "%i%s" (model |> getLoggedInID) model.persisenceBaseNamespace
|
||||
Cmd.ofPromise
|
||||
model.persistenceService.retrieve {ns = nameSpace ; key = [|"pnlColumns"; "pnlFilterValues"; "pnlSorting" |]}
|
||||
UpdatePersistenceData
|
||||
(fun _ -> RpcResponse "Error getting Persistence data")
|
||||
|
||||
let PnLSub model dispatch =
|
||||
match model.unsub with
|
||||
| Some (unsub) -> unsub.Dispose()
|
||||
| None -> ()
|
||||
let svc = model.positionService
|
||||
svc.subscribe_pnl_data "" (
|
||||
fun (subUpdate:StreamUtils.IUpdate<bts.api.position.PNLUnit>) ->
|
||||
match subUpdate.added |> isEmpty, subUpdate.changed |> isEmpty with
|
||||
| true, true -> ()
|
||||
| _, _ ->
|
||||
subUpdate.all
|
||||
|> JSMap.toArray
|
||||
|> Array.map snd
|
||||
|> SetPositionData
|
||||
|> dispatch
|
||||
)
|
||||
|
||||
|> SetUnsub
|
||||
|> dispatch
|
||||
|
||||
let selectSub (tab:Tab option) model =
|
||||
match tab, model.currentTab with
|
||||
| Some PnL, _ | None, PnL -> Cmd.ofSub (PnLSub model)
|
||||
// | _ ->
|
||||
// match model.unsub with
|
||||
// | Some (unsub) -> unsub.Dispose()
|
||||
// | None ->
|
||||
// ()
|
||||
// Cmd.none
|
||||
|
||||
let requestProducts (mdRelay:IMarketDataRelay) = promise {
|
||||
let! result = mdRelay.request_products()
|
||||
return UpdateProducts result.products
|
||||
}
|
||||
|
||||
let requestExpNames (mdRelay:IMarketDataRelay) = promise {
|
||||
let! result = mdRelay.request_names()
|
||||
return UpdateExpirationNames result.securities
|
||||
}
|
||||
|
||||
let encodeUint8 (source:Object) =
|
||||
[| for ch in (toJson source) -> (uint8 ch) |]
|
||||
|
||||
let inline decodeUint8<'a> (source:uint8 []) =
|
||||
[| for ch in (source) -> (char ch) |]
|
||||
|> String
|
||||
|> ofJson<'a>
|
||||
|
||||
let saveSettings (persServ:IPersistenceService) (model:Model) = promise {
|
||||
let! result = persServ.store
|
||||
{ ns = sprintf "%i%s" (getLoggedInID model) model.persisenceBaseNamespace
|
||||
record = [|
|
||||
{ key = "pnlColumns"
|
||||
value = (model.PnLColumns |> encodeUint8) }
|
||||
{ key = "pnlFilterValues"
|
||||
value = (model.PnLFilterValues |> encodeUint8) }
|
||||
{ key = "pnlSorting"
|
||||
value = (model.PnLSortDirection |> encodeUint8) }
|
||||
|]
|
||||
}
|
||||
match result.result with
|
||||
| StoreResponse.Result.OK ->
|
||||
return PersistenceUpdateSuccess result
|
||||
| _ ->
|
||||
return ErrorOccured (sprintf "Error saving settings" )
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
let defaultPnLColumns = [
|
||||
("Account", true)
|
||||
("Symbol", false)
|
||||
("Exchange", false)
|
||||
("Type", true)
|
||||
("Security", true)
|
||||
("Security Key", false)
|
||||
("Total PnL", false)
|
||||
("Open PnL", false)
|
||||
("Day PnL", true)
|
||||
("Total Pos", false)
|
||||
("Total Long", false)
|
||||
("Total Long Avg", false)
|
||||
("Total Short", false)
|
||||
("Total Short Avg", false)
|
||||
("Open Pos", false)
|
||||
("Open Long", false)
|
||||
("Open Long Avg", false)
|
||||
("Open Short", false)
|
||||
("Open Short Avg", false)
|
||||
("Day Pos", true)
|
||||
("Day Long", false)
|
||||
("Day Long Avg", false)
|
||||
("Day Short", false)
|
||||
("Day Short Avg", false)
|
||||
("Raw Day Long", false)
|
||||
("Raw Day Short", false)
|
||||
("Raw Day Pos", false)
|
||||
("Traded Today", true)
|
||||
("Bid", false)
|
||||
("Ask", false)
|
||||
("Mid", false)
|
||||
("Last", false)
|
||||
("Settlement", false)
|
||||
("CPP", false)]
|
||||
|
||||
let getSec (securityList:Map<uint32, PositionSecurity>) ndx =
|
||||
match securityList.Count with
|
||||
| 0 -> {
|
||||
securityName = ""
|
||||
venue = bts.api.Venue.UNSET
|
||||
symbol = ""
|
||||
securityType = bts.api.SecurityType.UNSET
|
||||
}
|
||||
| _ -> securityList.[ndx]
|
||||
|
||||
|
||||
let init (context:ServiceContext.ServiceContext) : Model * Cmd<Msg> =
|
||||
let admin = AdminService.Create context ""
|
||||
let posServ = PositionService.Create context ""
|
||||
let basePosServ = BasePositionService.Create context ""
|
||||
let persServ = PersistenceService.Create context "settings"
|
||||
let mdr = MarketDataRelay.Create context ""
|
||||
{
|
||||
currentTab = Tab.PnL
|
||||
admin = admin
|
||||
adminData = None
|
||||
positionData = [||]
|
||||
basePositionService = basePosServ
|
||||
positionService = posServ
|
||||
persistenceService = persServ
|
||||
persisenceBaseNamespace = "#admin_ui"
|
||||
unsub = None
|
||||
ConfirmModal = "", (Msg.SwitchTab Tab.PnL)
|
||||
token = context.token
|
||||
this_username = context.username
|
||||
mdRelay = mdr
|
||||
mdRelayExpNameData = Array.empty<ExpirationNameList>
|
||||
mdRelayProductData = [||]
|
||||
securityList = Map.empty
|
||||
PnLColumns = defaultPnLColumns
|
||||
PnLSortFun = (fun (left:PNLUnit) (right:PNLUnit) -> (left.pos.securityId - right.pos.securityId) |> int)
|
||||
PnLSortDirection = "Security Key", true
|
||||
PnLFilter = Map.empty
|
||||
PnLFilterValues = Map.empty
|
||||
FilterOpen = false, Location (0.0,0.0)
|
||||
TempFilter = "", Empty
|
||||
GridSettingsOpen = false
|
||||
}, getAdminData admin context.token
|
||||
|
||||
let calcPnL (bid:double) (ask:double) (longPrice:double) (shortPrice:double) longPos shortPos (cashValue:double) =
|
||||
match bid, ask with
|
||||
| 0.0, _ | _,0.0 -> 0.0
|
||||
| _ ,_ ->
|
||||
let mid = (bid + ask) / 2.0
|
||||
let longDelta:double = mid - longPrice
|
||||
let shortDelta:double = shortPrice - mid
|
||||
|
||||
(
|
||||
(
|
||||
(longDelta * longPos)
|
||||
+ ( shortDelta * shortPos)
|
||||
)
|
||||
* cashValue
|
||||
)
|
||||
|
||||
let calcTotalPnL data =
|
||||
(calcPnL
|
||||
data.bidPrice
|
||||
data.askPrice
|
||||
data.pos.sod.longAvgPrice
|
||||
data.pos.sod.shortAvgPrice
|
||||
data.pos.sod.longPos
|
||||
data.pos.sod.shortPos
|
||||
data.cashValue)
|
||||
+ (calcPnL
|
||||
data.bidPrice
|
||||
data.askPrice
|
||||
data.pos.day.longAvgPrice
|
||||
data.pos.day.shortAvgPrice
|
||||
data.pos.day.longPos
|
||||
data.pos.day.shortPos
|
||||
data.cashValue)
|
||||
|
||||
let calcSODPnL data =
|
||||
(calcPnL
|
||||
data.bidPrice
|
||||
data.askPrice
|
||||
data.pos.sod.longAvgPrice
|
||||
data.pos.sod.shortAvgPrice
|
||||
data.pos.sod.longPos
|
||||
data.pos.sod.shortPos
|
||||
data.cashValue)
|
||||
|
||||
let calcDayPnL data =
|
||||
calcPnL
|
||||
data.bidPrice
|
||||
data.askPrice
|
||||
data.pos.day.longAvgPrice
|
||||
data.pos.day.shortAvgPrice
|
||||
data.pos.day.longPos
|
||||
data.pos.day.shortPos
|
||||
data.cashValue
|
||||
|
||||
let getPnLFilterType header =
|
||||
match header = "Symbol" || header = "Account"|| header = "Exchange" || header = "Type" || header = "Security" || header = "Traded Today" with
|
||||
| true -> StringSearch ""
|
||||
| false -> NumberRange (Double.MinValue, Double.MaxValue)
|
||||
|
||||
let getValFromPNLUnit header (data:PNLUnit) (securityList: Map<uint32, PositionSecurity> option) =
|
||||
let sec = match securityList with
|
||||
| Some secL -> getSec <| secL <| uint32 data.pos.securityId
|
||||
| None -> { securityName = ""; venue = bts.api.Venue.UNSET; symbol = ""; securityType = bts.api.SecurityType.UNSET}
|
||||
|
||||
match header with
|
||||
| "Symbol" -> sec.symbol, 0.0
|
||||
| "Account" -> data.pos.account, 0.0
|
||||
| "Exchange" -> venueToString sec.venue, 0.0
|
||||
| "Type" -> securityTypeToString <| sec.securityType, 0.0
|
||||
| "Security" -> sec.securityName, 0.0
|
||||
| "Security Key" -> "", data.pos.securityId |> double
|
||||
| "Total PnL" -> "", calcTotalPnL data
|
||||
| "Open PnL" -> "", calcSODPnL data
|
||||
| "Day PnL" -> "", calcDayPnL data
|
||||
| "Total Pos" -> "", ((data.pos.day.longPos + data.pos.sod.longPos) - (data.pos.day.shortPos + data.pos.sod.shortPos))
|
||||
| "Total Long" -> "", (data.pos.day.longPos + data.pos.sod.longPos)
|
||||
| "Total Long Avg" -> "", ((data.pos.day.longAvgPrice + data.pos.sod.longAvgPrice) / 2.0 )
|
||||
| "Total Short" -> "", data.pos.day.shortPos + data.pos.sod.shortPos
|
||||
| "Total Short Avg" -> "", (data.pos.day.shortAvgPrice + data.pos.sod.shortAvgPrice) / 2.0
|
||||
| "Open Pos" -> "", data.pos.sod.longPos - data.pos.sod.shortPos
|
||||
| "Open Long" -> "", data.pos.sod.longPos
|
||||
| "Open Long Avg" -> "", data.pos.sod.longAvgPrice
|
||||
| "Open Short" -> "", data.pos.sod.shortPos
|
||||
| "Open Short Avg" -> "", data.pos.sod.shortAvgPrice
|
||||
| "Day Pos" -> "", data.pos.day.longPos - data.pos.day.shortPos
|
||||
| "Day Long" -> "", data.pos.day.longPos
|
||||
| "Day Long Avg" -> "", data.pos.day.longAvgPrice
|
||||
| "Day Short" -> "", data.pos.day.shortPos
|
||||
| "Day Short Avg" -> "", data.pos.day.shortAvgPrice
|
||||
| "Raw Day Long" -> "", data.pos.rawDay.longPos
|
||||
| "Raw Day Short" -> "", data.pos.rawDay.shortPos
|
||||
| "Raw Day Pos" -> "", data.pos.rawDay.longPos - data.pos.rawDay.shortPos
|
||||
| "Traded Today" -> (if (data.pos.day.longPos > 0.0) || (data.pos.day.shortPos > 0.0) then "yes" else "no"), 0.0
|
||||
| "Bid" -> "", data.bidPrice
|
||||
| "Ask" -> "", data.askPrice
|
||||
| "Mid" -> "", (data.askPrice + data.bidPrice) / 2.0
|
||||
| "Last" -> "", data.lastPrice
|
||||
| "Settlement" -> "", data.settlementPrice
|
||||
| "CPP" -> "", data.cashValue
|
||||
| _ -> "", 0.0
|
||||
|
||||
let filterIsDefault filter =
|
||||
match filter with
|
||||
| StringSearch s -> s = ""
|
||||
| NumberRange (min, max) -> min = Double.MinValue && max = Double.MaxValue
|
||||
| Empty -> true
|
||||
|
||||
let update msg (model:Reports.Types.Model) : Model * Cmd<Msg> =
|
||||
match msg with
|
||||
| UpdateAdminData adm ->
|
||||
let newModel =
|
||||
{ model with
|
||||
adminData = Some adm
|
||||
}
|
||||
|
||||
newModel, Cmd.batch [
|
||||
selectSub (Some PnL) model
|
||||
Cmd.ofExistingPromise (requestProducts model.mdRelay) id (box>>ErrorOccured)
|
||||
getPersistenceData newModel
|
||||
]
|
||||
| SaveSettings ->
|
||||
model, Cmd.ofExistingPromise (saveSettings model.persistenceService model) id (box>>ErrorOccured)
|
||||
|
||||
| PersistenceUpdateSuccess sr ->
|
||||
model, Cmd.Empty
|
||||
|
||||
| UpdatePersistenceData pd ->
|
||||
match pd.result with
|
||||
| RetrieveResponse.Result.OK ->
|
||||
let pnlCols = Array.tryFind (fun (v:bts.api.persistence.Record) -> v.key = "pnlColumns") pd.record
|
||||
let pnlFilterValues = Array.tryFind (fun (v:bts.api.persistence.Record) -> v.key = "pnlFilterValues") pd.record
|
||||
let pnlSorting = Array.tryFind (fun (v:bts.api.persistence.Record) -> v.key = "pnlSorting") pd.record
|
||||
|
||||
let newCols =
|
||||
match pnlCols with
|
||||
| None -> model.PnLColumns
|
||||
| Some record -> (decodeUint8<List<string * bool>> record.value)
|
||||
|
||||
let newFilterValues =
|
||||
match pnlFilterValues with
|
||||
| None -> model.PnLFilterValues
|
||||
| Some record -> (decodeUint8<Map<string, ColFilter>> record.value)
|
||||
|
||||
let newSorting =
|
||||
match pnlSorting with
|
||||
| None -> model.PnLSortDirection
|
||||
| Some record ->
|
||||
(decodeUint8<string * bool> record.value)
|
||||
|
||||
{ model with PnLColumns = newCols; PnLFilterValues = newFilterValues; PnLSortDirection = newSorting },
|
||||
Cmd.batch (
|
||||
(Cmd.ofMsg (SetPnLSort <| newSorting ))::[ for f in newFilterValues do yield Cmd.ofMsg (SetPnLFilter (f.Key, f.Value))])
|
||||
| _ ->
|
||||
model, Cmd.Empty
|
||||
| RpcResponse str ->
|
||||
printf "%s" str
|
||||
model, Cmd.Empty
|
||||
| SwitchTab tab ->
|
||||
match tab with
|
||||
| PnL ->
|
||||
{ model with currentTab = tab }, selectSub (Some PnL) model
|
||||
| ErrorOccured msg ->
|
||||
Global.alert Global.Error (string msg)
|
||||
model,Cmd.Empty
|
||||
| OpenConfirmModal (title, okMessage) ->
|
||||
{model with ConfirmModal = title, okMessage}, Cmd.Empty
|
||||
| SetUnsub u ->
|
||||
{model with unsub = Some u }, Cmd.Empty
|
||||
| SetPositionData data ->
|
||||
{ model with positionData = data }, Cmd.none
|
||||
| RequestExpirationNames ->
|
||||
model, Cmd.ofExistingPromise (requestExpNames model.mdRelay) id (box>>ErrorOccured)
|
||||
| UpdateExpirationNames (data:ExpirationNameList[]) ->
|
||||
let secs:(uint32 * PositionSecurity) list =
|
||||
data
|
||||
|> List.ofArray
|
||||
|> List.collect (fun i ->
|
||||
JSMap.toList i.names
|
||||
|> List.map (fun x ->
|
||||
(uint32 <| fst x),
|
||||
({ securityName = (snd x)
|
||||
venue = i.product.exchange
|
||||
symbol = i.product.symbol
|
||||
securityType = i.product.``type``
|
||||
}: PositionSecurity)
|
||||
)
|
||||
)
|
||||
|
||||
{ model with mdRelayExpNameData = data; securityList = secs |> Map.ofList },
|
||||
Cmd.batch (
|
||||
selectSub None model ::
|
||||
(Cmd.ofMsg (SetPnLSort <| model.PnLSortDirection ))::
|
||||
[ for f in model.PnLFilterValues do yield Cmd.ofMsg (SetPnLFilter (f.Key, f.Value))])
|
||||
| UpdateProducts data ->
|
||||
{ model with mdRelayProductData = data}, Cmd.ofExistingPromise (requestExpNames model.mdRelay) id (box>>ErrorOccured)
|
||||
| ChangePnLColumnVisibility (header, visibility) ->
|
||||
let newData = [ for colHead, colVis
|
||||
in model.PnLColumns do yield
|
||||
( match colHead = header with
|
||||
| false -> (colHead, colVis)
|
||||
| true -> (colHead, visibility)
|
||||
)]
|
||||
model, Cmd.ofMsg (SetPnLColumns <| newData)
|
||||
| SetPnLColumns data ->
|
||||
{ model with PnLColumns = data}, Cmd.ofMsg SaveSettings
|
||||
| ShowGridSettings data ->
|
||||
{model with GridSettingsOpen = data}, Cmd.Empty
|
||||
| SetPnLTempFilter (header, filter) ->
|
||||
let newFilter = match filter with
|
||||
| Empty ->
|
||||
match Map.tryFindKey (fun k _ -> (k = header)) model.PnLFilterValues with
|
||||
| None -> getPnLFilterType header
|
||||
| Some k -> model.PnLFilterValues.Item k
|
||||
| _ -> filter
|
||||
|
||||
{model with TempFilter = header, newFilter}, Cmd.Empty
|
||||
| ShowFilterBox (header, isOpen, loca) ->
|
||||
let newFilter = match Map.containsKey header model.PnLFilterValues with
|
||||
| false -> getPnLFilterType header
|
||||
| true -> model.PnLFilterValues.Item header
|
||||
|
||||
{model with FilterOpen = (isOpen, loca); TempFilter = (header, newFilter)}, Cmd.ofMsg (SetPnLTempFilter (header, newFilter))
|
||||
|
||||
| SetPnLFilter (header, filter) ->
|
||||
match filterIsDefault filter with
|
||||
| true ->
|
||||
let newFilterValues =
|
||||
match Map.containsKey header model.PnLFilterValues with
|
||||
| true -> (Map.remove header model.PnLFilterValues)
|
||||
| false -> model.PnLFilterValues
|
||||
|
||||
let newMap =
|
||||
match Map.containsKey header model.PnLFilter with
|
||||
| true -> Map.remove header model.PnLFilter
|
||||
| false -> model.PnLFilter
|
||||
|
||||
{ model with PnLFilter = newMap; PnLFilterValues = newFilterValues }, Cmd.batch [
|
||||
("", false, Wherever) |> ShowFilterBox |> Cmd.ofMsg
|
||||
Cmd.ofMsg SaveSettings
|
||||
]
|
||||
| false ->
|
||||
let newFilterValues =
|
||||
match Map.containsKey header model.PnLFilterValues with
|
||||
| true -> (Map.remove header model.PnLFilterValues).Add(header, filter)
|
||||
| false -> model.PnLFilterValues.Add(header, filter)
|
||||
|
||||
let newFilter =
|
||||
match filter with
|
||||
| StringSearch s ->
|
||||
function pnl ->
|
||||
let v = (getValFromPNLUnit header pnl (Some model.securityList)) |> fst
|
||||
if s.Trim() = "" || v.ToLower().Contains (s.Trim().ToLower()) then Some pnl else None
|
||||
| NumberRange (min, max) ->
|
||||
function pnl ->
|
||||
let curVal = (getValFromPNLUnit header pnl None) |> snd
|
||||
if (min <= curVal && curVal <= max) then Some pnl else None
|
||||
| Empty -> Some
|
||||
|
||||
let newMap = model.PnLFilter.Add (header, newFilter)
|
||||
|
||||
{ model with PnLFilter = newMap; PnLFilterValues = newFilterValues }, Cmd.batch [
|
||||
("", false, Wherever) |> ShowFilterBox |> Cmd.ofMsg
|
||||
Cmd.ofMsg SaveSettings
|
||||
]
|
||||
|
||||
| ClearPnLFilter header ->
|
||||
match Map.containsKey header model.PnLFilterValues with
|
||||
| true -> model, Cmd.ofMsg (SetPnLFilter (header, Empty))
|
||||
| false -> model, Cmd.Empty
|
||||
|
||||
| SwitchPnLSort header ->
|
||||
let direction = fst <| model.PnLSortDirection <> header || not <| snd model.PnLSortDirection
|
||||
model, Cmd.ofMsg <| SetPnLSort (header, direction)
|
||||
| SetPnLSort (header, direction) ->
|
||||
let gSec = getSec <| model.securityList << uint32
|
||||
|
||||
let compStr (str1, str2) =
|
||||
match direction with
|
||||
| true -> String.Compare (str1, str2)
|
||||
| false -> String.Compare (str2, str1)
|
||||
|
||||
let compNum (num1:double) num2 =
|
||||
match direction with
|
||||
| true -> num1 - num2
|
||||
| false -> num2 - num1
|
||||
|
||||
let sortFn = match header with
|
||||
| "Symbol" -> (fun (left:PNLUnit) (right:PNLUnit) -> compStr (
|
||||
(gSec left.pos.securityId).symbol,
|
||||
(gSec right.pos.securityId).symbol
|
||||
)
|
||||
)
|
||||
| "Account" -> (fun (left:PNLUnit) (right:PNLUnit) -> compStr(left.pos.account, right.pos.account )
|
||||
)
|
||||
| "Exchange" -> (fun (left:PNLUnit) (right:PNLUnit) -> compStr (
|
||||
(venueToString (gSec left.pos.securityId).venue),
|
||||
(venueToString (gSec right.pos.securityId).venue)
|
||||
)
|
||||
)
|
||||
| "Type" -> (fun (left:PNLUnit) (right:PNLUnit) -> compStr (
|
||||
(securityTypeToString <| (gSec left.pos.securityId).securityType),
|
||||
(securityTypeToString <| (gSec right.pos.securityId).securityType)
|
||||
)
|
||||
)
|
||||
| "Security" -> (fun (left:PNLUnit) (right:PNLUnit) -> compStr (
|
||||
(gSec left.pos.securityId).securityName,
|
||||
(gSec right.pos.securityId).securityName
|
||||
)
|
||||
)
|
||||
| "Security Key" -> (fun (left:PNLUnit) (right:PNLUnit) ->
|
||||
match direction with
|
||||
| true -> left.pos.securityId - right.pos.securityId
|
||||
| false -> right.pos.securityId - left.pos.securityId
|
||||
|> int)
|
||||
| "Total PnL" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) ->
|
||||
let liSod = ((calcPnL
|
||||
left.bidPrice
|
||||
left.askPrice
|
||||
left.pos.sod.longAvgPrice
|
||||
left.pos.sod.shortAvgPrice
|
||||
left.pos.sod.longPos
|
||||
left.pos.sod.shortPos
|
||||
left.cashValue))
|
||||
let liDay = (calcPnL
|
||||
left.bidPrice
|
||||
left.askPrice
|
||||
left.pos.day.longAvgPrice
|
||||
left.pos.day.shortAvgPrice
|
||||
left.pos.day.longPos
|
||||
left.pos.day.shortPos
|
||||
left.cashValue)
|
||||
|
||||
let riSod = (calcPnL
|
||||
right.bidPrice
|
||||
right.askPrice
|
||||
right.pos.sod.longAvgPrice
|
||||
right.pos.sod.shortAvgPrice
|
||||
right.pos.sod.longPos
|
||||
right.pos.sod.shortPos
|
||||
right.cashValue)
|
||||
let riDay = (calcPnL
|
||||
right.bidPrice
|
||||
right.askPrice
|
||||
right.pos.day.longAvgPrice
|
||||
right.pos.day.shortAvgPrice
|
||||
right.pos.day.longPos
|
||||
right.pos.day.shortPos
|
||||
right.cashValue)
|
||||
|
||||
(compNum (liSod + liDay) (riSod + riDay)) * 1000.0 |> int
|
||||
|
||||
)//Total PNL
|
||||
| "Open PnL" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) ->
|
||||
let li = calcPnL
|
||||
left.bidPrice
|
||||
left.askPrice
|
||||
left.pos.sod.longAvgPrice
|
||||
left.pos.sod.shortAvgPrice
|
||||
left.pos.sod.longPos
|
||||
left.pos.sod.shortPos
|
||||
left.cashValue
|
||||
let ri = calcPnL
|
||||
right.bidPrice
|
||||
right.askPrice
|
||||
right.pos.sod.longAvgPrice
|
||||
right.pos.sod.shortAvgPrice
|
||||
right.pos.sod.longPos
|
||||
right.pos.sod.shortPos
|
||||
right.cashValue
|
||||
(compNum li ri) * 1000.0 |> int
|
||||
)// SOD Pnl (Open PnL)
|
||||
| "Day PnL" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) ->
|
||||
let li = calcPnL
|
||||
left.bidPrice
|
||||
left.askPrice
|
||||
left.pos.day.longAvgPrice
|
||||
left.pos.day.shortAvgPrice
|
||||
left.pos.day.longPos
|
||||
left.pos.day.shortPos
|
||||
left.cashValue
|
||||
|
||||
let ri = calcPnL
|
||||
right.bidPrice
|
||||
right.askPrice
|
||||
right.pos.day.longAvgPrice
|
||||
right.pos.day.shortAvgPrice
|
||||
right.pos.day.longPos
|
||||
right.pos.day.shortPos
|
||||
right.cashValue
|
||||
(compNum li ri) * 1000.0 |> int
|
||||
)//Day PnL
|
||||
| "Total Pos" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) -> (compNum ((left.pos.day.longPos + left.pos.sod.longPos) - (left.pos.day.shortPos + left.pos.sod.shortPos))
|
||||
((right.pos.day.longPos + right.pos.sod.longPos) - (right.pos.day.shortPos + right.pos.sod.shortPos))) * 1000.0 |> int
|
||||
)
|
||||
| "Total Long" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) -> (compNum (left.pos.day.longPos + left.pos.sod.longPos)
|
||||
(right.pos.day.longPos + right.pos.sod.longPos)) * 1000.0 |> int
|
||||
)//Total Long
|
||||
| "Total Long Avg" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) -> (compNum(left.pos.day.longAvgPrice + left.pos.sod.longAvgPrice)
|
||||
(right.pos.day.longAvgPrice + right.pos.sod.longAvgPrice)
|
||||
) * 1000.0 |> int
|
||||
)//Total Long Avg
|
||||
| "Total Short" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) -> ( compNum
|
||||
(left.pos.day.shortPos + left.pos.sod.shortPos)
|
||||
(right.pos.day.shortPos + right.pos.sod.shortPos)
|
||||
) * 1000.0 |> int
|
||||
)//Total Short
|
||||
| "Total Short Avg" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) -> ( compNum
|
||||
(left.pos.day.shortAvgPrice + left.pos.sod.shortAvgPrice)
|
||||
(right.pos.day.shortAvgPrice + right.pos.sod.shortAvgPrice)
|
||||
) * 1000.0 |> int)//Total Short Avg
|
||||
| "Open Pos" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) -> ( compNum
|
||||
(left.pos.sod.longPos - left.pos.sod.shortPos)
|
||||
(right.pos.sod.longPos - right.pos.sod.shortPos)
|
||||
) * 1000.0 |> int
|
||||
) // SOD Net Pos
|
||||
| "Open Long" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) -> (compNum
|
||||
left.pos.sod.longPos
|
||||
right.pos.sod.longPos
|
||||
) * 1000.0 |> int
|
||||
) // SOD Long Pos
|
||||
| "Open Long Avg" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) -> (compNum left.pos.sod.longAvgPrice right.pos.sod.longAvgPrice) * 1000.0 |> int ) //SOD Long Avg Price
|
||||
| "Open Short" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) -> (compNum left.pos.sod.shortPos right.pos.sod.shortPos) * 1000.0 |> int ) //SOD Short Pos
|
||||
| "Open Short Avg" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) -> (compNum left.pos.sod.shortAvgPrice right.pos.sod.shortAvgPrice) * 1000.0 |> int) //SOD Short Avg Price
|
||||
| "Day Pos" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) -> ( compNum
|
||||
(left.pos.day.longPos - left.pos.day.shortPos)
|
||||
(right.pos.day.longPos - right.pos.day.shortPos)
|
||||
) * 1000.0 |> int )//Day Pos
|
||||
| "Day Long" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) -> (compNum left.pos.day.longPos right.pos.day.longPos) * 1000.0 |> int)
|
||||
| "Day Long Avg" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) -> (compNum left.pos.day.longAvgPrice right.pos.day.longAvgPrice) * 1000.0 |> int)
|
||||
| "Day Short" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) -> (compNum left.pos.day.shortPos right.pos.day.shortPos) * 1000.0 |> int )
|
||||
| "Day Short Avg" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) -> (compNum left.pos.day.shortAvgPrice right.pos.day.shortAvgPrice) * 1000.0 |> int)
|
||||
| "Raw Day Long" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) -> (compNum left.pos.rawDay.longPos right.pos.rawDay.longPos) * 1000.0 |> int )
|
||||
| "Raw Day Short" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) -> (compNum left.pos.rawDay.longPos right.pos.rawDay.longPos) * 1000.0 |> int )
|
||||
| "Raw Day Pos" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) -> ( compNum
|
||||
(left.pos.rawDay.longPos - left.pos.rawDay.shortPos)
|
||||
(right.pos.rawDay.longPos - right.pos.rawDay.shortPos)
|
||||
) * 1000.0 |> int)//Raw Day Pos
|
||||
| "Traded Today" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) -> (
|
||||
let lefti = if ((left.pos.day.longPos > 0.0) || (left.pos.day.shortPos > 0.0)) then 1 else 0
|
||||
let righti = if ((right.pos.day.longPos > 0.0) || (right.pos.day.shortPos > 0.0)) then 1 else 0
|
||||
match direction with
|
||||
| true -> lefti - righti
|
||||
| false -> righti - lefti
|
||||
)
|
||||
)//Traded Today
|
||||
| "Bid" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) -> (compNum left.bidPrice right.bidPrice) * 1000.0 |> int )//bid
|
||||
| "Ask" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) -> (compNum left.askPrice right.askPrice) * 1000.0 |> int )//ask
|
||||
| "Mid" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) -> ( compNum
|
||||
((left.askPrice + left.bidPrice) / 2.0)
|
||||
((right.askPrice + right.bidPrice) / 2.0)
|
||||
) * 1000.0 |> int )//mid
|
||||
| "Last" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) -> (compNum left.lastPrice right.lastPrice) * 1000.0 |> int)//last
|
||||
| "Settlement" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) -> (compNum left.settlementPrice right.settlementPrice) * 1000.0 |> int )
|
||||
| "CPP" ->
|
||||
(fun (left:PNLUnit) (right:PNLUnit) -> (compNum left.cashValue right.cashValue) * 1000.0 |> int )// CPP/cash value
|
||||
| _ -> (fun (left:PNLUnit) (right:PNLUnit) -> 0)
|
||||
|
||||
{ model with PnLSortFun = sortFn; PnLSortDirection = header, direction }, Cmd.ofMsg SaveSettings
|
||||
@@ -0,0 +1,80 @@
|
||||
module Reports.Types
|
||||
open System
|
||||
open bts.api.admin
|
||||
open bts.api.position
|
||||
open bts.api.persistence
|
||||
open bts.api
|
||||
open bts.co2
|
||||
open JSMap
|
||||
|
||||
type Tab =
|
||||
| PnL
|
||||
|
||||
type ColFilter =
|
||||
| StringSearch of string
|
||||
| NumberRange of min:double * max:double
|
||||
| Empty
|
||||
|
||||
type PositionSecurity =
|
||||
{
|
||||
securityName: string
|
||||
venue: bts.api.Venue
|
||||
symbol: string
|
||||
securityType: bts.api.SecurityType
|
||||
}
|
||||
|
||||
type Location =
|
||||
| Location of double*double
|
||||
| Wherever
|
||||
|
||||
type Msg =
|
||||
| RpcResponse of string
|
||||
| UpdateAdminData of AdminData
|
||||
| SaveSettings
|
||||
| UpdatePersistenceData of RetrieveResponse
|
||||
| PersistenceUpdateSuccess of StoreResponse
|
||||
| SwitchTab of Tab
|
||||
| ErrorOccured of obj
|
||||
| OpenConfirmModal of string * Msg
|
||||
| SetUnsub of System.IDisposable
|
||||
| SetPositionData of PNLUnit []
|
||||
| RequestExpirationNames
|
||||
| UpdateExpirationNames of ExpirationNameList[]
|
||||
| UpdateProducts of Product[]
|
||||
| ChangePnLColumnVisibility of string * bool
|
||||
| SetPnLColumns of (string * bool) List
|
||||
| SwitchPnLSort of string
|
||||
| SetPnLSort of string * bool
|
||||
| SetPnLTempFilter of string * ColFilter
|
||||
| SetPnLFilter of string * ColFilter
|
||||
| ClearPnLFilter of string
|
||||
| ShowGridSettings of bool
|
||||
| ShowFilterBox of string * bool * Location
|
||||
|
||||
type Model = {
|
||||
currentTab:Tab
|
||||
admin : IAdminService
|
||||
adminData : AdminData option
|
||||
positionData : PNLUnit []
|
||||
basePositionService: IBasePositionService
|
||||
positionService: IPositionService
|
||||
persistenceService: IPersistenceService
|
||||
persisenceBaseNamespace: string
|
||||
mdRelay : IMarketDataRelay
|
||||
mdRelayProductData: Product[]
|
||||
mdRelayExpNameData: ExpirationNameList[]
|
||||
unsub : System.IDisposable option
|
||||
securityList : Map<uint32, PositionSecurity>
|
||||
PnLColumns: (string * bool) list
|
||||
PnLSortFun: PNLUnit -> PNLUnit -> int
|
||||
PnLSortDirection: string * bool
|
||||
PnLFilter: Map<string, PNLUnit -> PNLUnit option>
|
||||
PnLFilterValues: Map<string, ColFilter>
|
||||
GridSettingsOpen: bool
|
||||
FilterOpen: bool * Location
|
||||
TempFilter: string * ColFilter
|
||||
|
||||
ConfirmModal: string * Msg
|
||||
token: string
|
||||
this_username: string
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
module Reports.View
|
||||
|
||||
open Fable.Core
|
||||
open Fable.Core.JsInterop
|
||||
open Fable.Helpers.React
|
||||
open Fable.Helpers.React.Props
|
||||
open Fable.Core.DynamicExtensions
|
||||
open Elmish
|
||||
open Elmish.React
|
||||
open Fable.Import
|
||||
open Global.Reflection
|
||||
open Types
|
||||
open Reports
|
||||
open bts.api.admin
|
||||
open bts.api
|
||||
open bts.api.position
|
||||
open EnumUtils
|
||||
open Microsoft.FSharp.Collections;
|
||||
open State
|
||||
|
||||
let isActiveTab tab currentTab =
|
||||
let ia = "is-active"
|
||||
match tab, currentTab with
|
||||
| Tab.PnL, Tab.PnL -> ia
|
||||
// | _ -> ""
|
||||
|
||||
let PnLTabView (model:Model) (dispatch:Msg->Unit) =
|
||||
// Browser.window.localStorage.setItem("PnLVisibleCols", "some")
|
||||
// let thing = Browser.window.localStorage.getItem("thing")
|
||||
let settingsModal =
|
||||
match model.GridSettingsOpen with
|
||||
| false -> span [][]
|
||||
| true ->
|
||||
Modal.show
|
||||
"PnL Grid Settings"
|
||||
(div[][
|
||||
h3 [Class "is-size-3"][str "Columns"]
|
||||
div [][
|
||||
for header, visible
|
||||
in model.PnLColumns do yield (
|
||||
Fields.checkbox header "" visible [] (fun _ -> (header, not visible) |> ChangePnLColumnVisibility |> dispatch)
|
||||
)
|
||||
]
|
||||
])
|
||||
(Button.button "Close" "" "times" Button.Enabled (fun _ -> false |> ShowGridSettings |> dispatch))
|
||||
(fun _ -> false |> ShowGridSettings |> dispatch)
|
||||
|
||||
let filterBox =
|
||||
let filterHeader, filterType = model.TempFilter
|
||||
let filterIsOpen, whereItIs = model.FilterOpen
|
||||
|
||||
let where =
|
||||
match whereItIs with
|
||||
| Wherever -> 0.0,0.0
|
||||
| Location (x, y) -> (x, y)
|
||||
|
||||
match filterType, filterIsOpen with
|
||||
| _, false | Empty, _ -> span [][]
|
||||
| StringSearch searchStr, true ->
|
||||
TableControl.showFilter
|
||||
(div[Id "filterbox"][
|
||||
Fields.text "Search:" (searchStr) [] (fun v -> (filterHeader, StringSearch v) |> SetPnLTempFilter |> dispatch)
|
||||
])
|
||||
(fun _ -> (filterHeader, filterType) |> SetPnLFilter |> dispatch)
|
||||
(fun _ -> filterHeader |> ClearPnLFilter |> dispatch)
|
||||
(fun _ -> ("", false, Wherever) |> ShowFilterBox |> dispatch)
|
||||
(sprintf "%fpx" (where |> snd)) (sprintf "%fpx" (where |> fst))
|
||||
| NumberRange (min, max), true ->
|
||||
TableControl.showFilter
|
||||
(div[Id "filterbox"][
|
||||
Fields.uncheckedDouble "Min" min [] (fun dv -> (filterHeader, NumberRange (dv, max)) |> SetPnLTempFilter |> dispatch)
|
||||
Fields.uncheckedDouble "Max" max [] (fun dv -> (filterHeader, NumberRange (min, dv)) |> SetPnLTempFilter |> dispatch)
|
||||
])
|
||||
(fun _ -> (filterHeader, filterType) |> SetPnLFilter |> dispatch)
|
||||
(fun _ -> filterHeader |> ClearPnLFilter |> dispatch)
|
||||
(fun _ -> ("", false, Wherever) |> ShowFilterBox |> dispatch)
|
||||
(sprintf "%fpx" (where |> snd)) (sprintf "%fpx" (where |> fst))
|
||||
|
||||
let sortIcon header visible =
|
||||
match fst model.PnLSortDirection = header, snd model.PnLSortDirection with
|
||||
| true, true -> i [ ClassName (sprintf "fa fa-sort-up")] [ ]
|
||||
| true, false -> i [ ClassName (sprintf "fa fa-sort-down")] [ ]
|
||||
| _, _ -> i [ ClassName (sprintf "fa fa-sort header-icon-hidden")] [ ]
|
||||
|
||||
let filterIcon header =
|
||||
let classStr = match Map.containsKey header model.PnLFilterValues || (fst model.TempFilter) = header with
|
||||
| true -> "fa fa-filter header-filter-icon yellow"
|
||||
| false -> "fa fa-filter header-filter-icon header-icon-hidden"
|
||||
let mutable origin = Location (0.0, 0.0)
|
||||
i [
|
||||
ClassName (classStr)
|
||||
Ref (fun me ->
|
||||
if me <> null then
|
||||
let r = me.getBoundingClientRect()
|
||||
origin <- Location (r.left, r.top + me.clientHeight) )
|
||||
OnClick (fun _ -> (header, true, origin) |> ShowFilterBox |> dispatch)
|
||||
] []
|
||||
|
||||
let headerRow:TableControl.row =
|
||||
{
|
||||
cells =
|
||||
[ for header, visible in model.PnLColumns do
|
||||
if visible then
|
||||
yield ( TableControl.Cell
|
||||
( div [ ClassName "table-header" ] [
|
||||
span [ OnClick (fun _ -> header |> SwitchPnLSort |> dispatch)] [str <| sprintf "%s " header]
|
||||
sortIcon header visible
|
||||
filterIcon header
|
||||
])
|
||||
) ]
|
||||
className = ""
|
||||
header = None
|
||||
onClick = None
|
||||
}
|
||||
|
||||
let fmtNum number = sprintf "%.2f" number
|
||||
|
||||
let rgSpan (s:string) =
|
||||
match s, s.Substring (0, 1) with
|
||||
| _, "-" -> span [Style [Color "#C41212"; FontWeight "bold"]] [str s]
|
||||
| "0.00", _ -> span [] [str s]
|
||||
| _ -> span [Style [Color "#008000"; FontWeight "bold"]] [str s]
|
||||
|
||||
let bidAskSpan (s:string) =
|
||||
match s, s.Substring (0, 1) with
|
||||
| _, "-" -> span [Style [Color "#A00000"]] [str s]
|
||||
| "0.00", _ -> span [] [str s]
|
||||
| _ -> span [Style [Color "#167ED3"]] [str s]
|
||||
|
||||
let boolToYesNo yes =
|
||||
match yes with
|
||||
| true -> "Yes"
|
||||
| false -> "No"
|
||||
|
||||
let bind a b i =
|
||||
match a i with
|
||||
| Some i -> b i
|
||||
| None -> None
|
||||
|
||||
let uberfilter = if model.PnLFilter.Count > 0 then model.PnLFilter |> Map.toArray |> Array.map snd |> Array.reduce bind else Some
|
||||
let filteredPositionData = (model.positionData |> Array.choose uberfilter)
|
||||
let sortedPositionData = filteredPositionData |> Array.sortWith model.PnLSortFun
|
||||
|
||||
let rws:TableControl.row list =
|
||||
[
|
||||
for data in sortedPositionData do yield {
|
||||
cells =
|
||||
[ for header, visible in
|
||||
model.PnLColumns do
|
||||
if visible then yield
|
||||
(
|
||||
let sec = getSec <| model.securityList <| uint32 data.pos.securityId
|
||||
match header with
|
||||
| "Symbol" -> TableControl.Cell (str (sprintf "%s" sec.symbol))
|
||||
| "Account" -> TableControl.Cell (str data.pos.account)
|
||||
| "Exchange" -> TableControl.Cell (str (sprintf "%s" (venueToString sec.venue)))
|
||||
| "Type" -> TableControl.Cell (str (sprintf "%s" (securityTypeToString <| sec.securityType)))
|
||||
| "Security" -> TableControl.Cell (str (sprintf "%s" sec.securityName))
|
||||
| "Security Key" -> TableControl.Cell (str (sprintf "%i" data.pos.securityId))
|
||||
| "Total PnL" -> TableControl.Cell (rgSpan (fmtNum (calcTotalPnL data)))
|
||||
| "Open PnL" -> TableControl.Cell (rgSpan (fmtNum (calcSODPnL data)))
|
||||
| "Day PnL" -> TableControl.Cell (rgSpan (fmtNum (calcDayPnL data)))
|
||||
| "Total Pos" ->
|
||||
TableControl.Cell (bidAskSpan (fmtNum ((data.pos.day.longPos + data.pos.sod.longPos) - (data.pos.day.shortPos + data.pos.sod.shortPos))))//Total Pos
|
||||
| "Total Long" ->
|
||||
TableControl.Cell (str (fmtNum (data.pos.day.longPos + data.pos.sod.longPos)))//Total Long
|
||||
| "Total Long Avg" ->
|
||||
TableControl.Cell (str (fmtNum ((data.pos.day.longAvgPrice + data.pos.sod.longAvgPrice))))//Total Long Avg
|
||||
| "Total Short" ->
|
||||
TableControl.Cell (str (fmtNum (data.pos.day.shortPos + data.pos.sod.shortPos)))//Total Short
|
||||
| "Total Short Avg" ->
|
||||
TableControl.Cell (str (fmtNum ((data.pos.day.shortAvgPrice + data.pos.sod.shortAvgPrice))))//Total Short Avg
|
||||
| "Open Pos" ->
|
||||
TableControl.Cell (bidAskSpan (fmtNum (data.pos.sod.longPos - data.pos.sod.shortPos))) // SOD Net Pos
|
||||
| "Open Long" ->
|
||||
TableControl.Cell (str (fmtNum data.pos.sod.longPos)) // SOD Long Pos
|
||||
| "Open Long Avg" ->
|
||||
TableControl.Cell (str (fmtNum data.pos.sod.longAvgPrice)) //SOD Long Avg Price
|
||||
| "Open Short" ->
|
||||
TableControl.Cell (str (fmtNum data.pos.sod.shortPos)) //SOD Short Pos
|
||||
| "Open Short Avg" ->
|
||||
TableControl.Cell (str (fmtNum data.pos.sod.shortAvgPrice)) //SOD Short Avg Price
|
||||
| "Day Pos" ->
|
||||
TableControl.Cell (str (fmtNum (data.pos.day.longPos - data.pos.day.shortPos)))//Day Pos
|
||||
| "Day Long" ->
|
||||
TableControl.Cell (str (fmtNum data.pos.day.longPos))
|
||||
| "Day Long Avg" ->
|
||||
TableControl.Cell (str (fmtNum data.pos.day.longAvgPrice))
|
||||
| "Day Short" ->
|
||||
TableControl.Cell (str (fmtNum data.pos.day.shortPos))
|
||||
| "Day Short Avg" ->
|
||||
TableControl.Cell (str (fmtNum data.pos.day.shortAvgPrice))
|
||||
| "Raw Day Long" ->
|
||||
TableControl.Cell (str (fmtNum data.pos.rawDay.longPos))
|
||||
| "Raw Day Short" ->
|
||||
TableControl.Cell (str (fmtNum data.pos.rawDay.longPos))
|
||||
| "Raw Day Pos" ->
|
||||
TableControl.Cell (str (fmtNum (data.pos.rawDay.longPos - data.pos.rawDay.shortPos)))//Raw Day Pos
|
||||
| "Traded Today" ->
|
||||
TableControl.Cell (str (boolToYesNo ((data.pos.day.longPos > 0.0) || (data.pos.day.shortPos > 0.0)) )) //Traded Today
|
||||
| "Bid" ->
|
||||
TableControl.Cell (span [Style [Color "#167ED3"]] [str (fmtNum data.bidPrice)])//bid
|
||||
| "Ask" ->
|
||||
TableControl.Cell (span [Style [Color "#A00000"]] [str (fmtNum data.askPrice)])//ask
|
||||
| "Mid" ->
|
||||
TableControl.Cell (str (fmtNum ((data.askPrice + data.bidPrice) / 2.0)))//mid
|
||||
| "Last" ->
|
||||
TableControl.Cell (str (fmtNum data.lastPrice))//last
|
||||
| "Settlement" ->
|
||||
TableControl.Cell (str (fmtNum data.settlementPrice))
|
||||
| "CPP" ->
|
||||
TableControl.Cell (str (fmtNum data.cashValue))//CPP/cash value
|
||||
| _ -> TableControl.Cell (str "")
|
||||
)]
|
||||
className = ""
|
||||
header = None
|
||||
onClick = None
|
||||
}
|
||||
]
|
||||
|
||||
div [] [
|
||||
TableControl.createDataTable (Some headerRow) rws (Button.fabutton "Settings" "cog" "" Button.Enabled ((fun _ -> true |> ShowGridSettings |> dispatch)))
|
||||
settingsModal
|
||||
filterBox
|
||||
]
|
||||
|
||||
let PnLAreEqual (one:PNLUnit []) (two:PNLUnit []) =
|
||||
match one.Length = two.Length with
|
||||
| false -> false
|
||||
| true ->
|
||||
let mutable acc = true
|
||||
for i = 0 to (one.Length - 1) do
|
||||
acc <- acc
|
||||
&& (int <| one.[i].pos.dayLongPos) = (int <| two.[i].pos.dayLongPos)
|
||||
&& (int <| one.[i].pos.dayShortPos) = (int <| two.[i].pos.dayShortPos)
|
||||
&& (int <| one.[i].pos.sodLongPos) = (int <| two.[i].pos.sodLongPos)
|
||||
&& (int <| one.[i].pos.sodShortPos) = (int <| two.[i].pos.sodShortPos)
|
||||
&& (int <| one.[i].askPrice * 100.0) = (int <| two.[i].askPrice * 100.0)
|
||||
&& (int <| one.[i].bidPrice * 100.0) = (int <| two.[i].bidPrice * 100.0)
|
||||
acc
|
||||
|
||||
let lazyPnLView model dispatch =
|
||||
lazyView2With (fun a b ->
|
||||
//LanguagePrimitives.PhysicalEquality a.positionData b.positionData
|
||||
(PnLAreEqual a.positionData b.positionData)
|
||||
&& a.GridSettingsOpen = b.GridSettingsOpen
|
||||
&& a.PnLColumns = b.PnLColumns
|
||||
&& a.PnLSortDirection = b.PnLSortDirection
|
||||
&& a.FilterOpen = b.FilterOpen
|
||||
&& a.TempFilter = b.TempFilter
|
||||
)
|
||||
PnLTabView
|
||||
model dispatch
|
||||
|
||||
let tabContent (model:Model) (dispatch:Msg->Unit) =
|
||||
div [ ClassName "is-tab-content"] [
|
||||
// match model.currentTab with
|
||||
// | Tab.PnL -> PnLTabView model dispatch
|
||||
lazyPnLView model dispatch
|
||||
]
|
||||
|
||||
let root (model:Model) (dispatch:Msg->Unit) =
|
||||
(match model.adminData with
|
||||
| None -> div[][str "Loading data..."]
|
||||
| Some data ->
|
||||
div [ ] [
|
||||
match model.unsub with | Some d -> yield Unsub.unsub d | None -> ()
|
||||
yield div [ Class "column is-main-content" ] [
|
||||
div [ ClassName "tabs is-boxed" ] [
|
||||
ul [ ] [
|
||||
li [ ClassName (isActiveTab Tab.PnL model.currentTab) ] [
|
||||
a [ OnClick (fun _ -> dispatch (SwitchTab Tab.PnL)) ] [ str "PnL" ]
|
||||
]
|
||||
]
|
||||
]
|
||||
tabContent model dispatch
|
||||
]
|
||||
]
|
||||
)
|
||||
@@ -19,21 +19,24 @@ let updateLoggedIn msg model =
|
||||
| AccountsMsg msg, AccountsModel m ->
|
||||
let (acc, msg) = Accounts.State.update msg m
|
||||
{ model with page = AccountsModel acc }, Cmd.map AccountsMsg msg
|
||||
| ReportsMsg msg, ReportsModel m ->
|
||||
let (reps, reportsCmd) = Reports.State.update msg m
|
||||
{ model with page = ReportsModel reps }, Cmd.map ReportsMsg reportsCmd
|
||||
| Navigate (page,pcmd), _ ->
|
||||
{ model with page = page }, pcmd
|
||||
| _ -> model, Cmd.Empty
|
||||
|
||||
let update msg model =
|
||||
match model with
|
||||
| PreLogin p ->
|
||||
| PreLogin p ->
|
||||
match msg with
|
||||
| LoginMsg (Login.LoginSuccess ctx) ->
|
||||
let (lmodel, lmsg) = Users.State.init ctx
|
||||
LoggedIn { context = ctx; page = UsersModel lmodel }, Cmd.map UsersMsg lmsg
|
||||
| LoginMsg m' ->
|
||||
| LoginMsg m' ->
|
||||
let (lmodel, lmsg) = Login.update m' p
|
||||
PreLogin lmodel, Cmd.map LoginMsg lmsg
|
||||
| _ -> model, Cmd.Empty
|
||||
| LoggedIn m ->
|
||||
| LoggedIn m ->
|
||||
let lmodel, lmsg = updateLoggedIn msg m
|
||||
LoggedIn lmodel, lmsg
|
||||
@@ -7,12 +7,14 @@ open Imports
|
||||
type Page =
|
||||
| UsersModel of Users.Types.Model
|
||||
| AccountsModel of Accounts.Types.Model
|
||||
| ReportsModel of Reports.Types.Model
|
||||
|
||||
type Msg =
|
||||
| UsersMsg of Users.Types.Msg
|
||||
| LoginMsg of Login.Message
|
||||
| Navigate of Page*Elmish.Cmd<Msg>
|
||||
| AccountsMsg of Accounts.Types.Msg
|
||||
| ReportsMsg of Reports.Types.Msg
|
||||
|
||||
|
||||
type LoggedInModel = {
|
||||
|
||||
@@ -204,6 +204,9 @@ let applyUpdate (field:UserField) (user:BTSUser) =
|
||||
| CanCreate cr -> { user with canCreate = cr }
|
||||
| CMEOperatorID oid -> updateCMEExchangeUserData user { user.userData.exchangeUserData.cmeData with operatorId = oid }
|
||||
|
||||
| CMEATSOperatorID _ -> user
|
||||
| MaxOrderRatePerConnection _ -> user
|
||||
|
||||
| ICECompanyID oid -> updateICEExchangeUserData user { user.userData.exchangeUserData.iceData with companyId = oid }
|
||||
| ICEOperatorID oid -> updateICEExchangeUserData user { user.userData.exchangeUserData.iceData with operatorId = oid }
|
||||
| ICEAuthorizedTrader oid -> updateICEExchangeUserData user { user.userData.exchangeUserData.iceData with authorizedTrader = oid }
|
||||
|
||||
@@ -280,7 +280,7 @@ let orderRoutingTabView (model:Model) (dispatch:Msg->Unit) (readonly:bool) =
|
||||
br []
|
||||
h2 [Class "is-size-3"] [ str "ICE" ]
|
||||
div [] [
|
||||
Fields.number_novalidate "Company ID" iceData.companyId [ ] (ICECompanyID >> UpdateField >> dispatch)
|
||||
Fields.uncheckedInt "Company ID" iceData.companyId [ ] (ICECompanyID >> UpdateField >> dispatch)
|
||||
Fields.text "Operator ID" iceData.operatorId [ ] (ICEOperatorID >> UpdateField >> dispatch)
|
||||
Fields.text "Authorized Trader" iceData.authorizedTrader [ ] (ICEAuthorizedTrader >> UpdateField >> dispatch)
|
||||
Fields.text "Authorized Group" iceData.authorizedGroup [ ] (ICEAuthorizedGroup >> UpdateField >> dispatch)
|
||||
@@ -289,14 +289,14 @@ let orderRoutingTabView (model:Model) (dispatch:Msg->Unit) (readonly:bool) =
|
||||
|
||||
h2 [Class "is-size-4"] [ str "MIFID" ]
|
||||
div [Style [MarginLeft "2em"]] [
|
||||
Fields.number_novalidate "Client Id Code" iceData.mifidClientIdCode [ ] (MifidClientIdCode >> UpdateField >> dispatch)
|
||||
Fields.number_novalidate "Commodity Deriv Indicator" iceData.mifidCommodityDerivIndicator [ ] (MifidCommodityDerivIndicator >> UpdateField >> dispatch)
|
||||
Fields.number_novalidate "Direct Electronic Access" iceData.mifidDirectElectronicAccess [ ] (MifidDirectElectronicAccess >> UpdateField >> dispatch)
|
||||
Fields.number_novalidate "Execution Decision" iceData.mifidExecutionDecision [ ] (MifidExecutionDecision >> UpdateField >> dispatch)
|
||||
Fields.number_novalidate "Id" iceData.mifidId [ ] (MifidId >> UpdateField >> dispatch)
|
||||
Fields.number_novalidate "Investment Decision" iceData.mifidInvestmentDecision [ ] (MifidInvestmentDecision >> UpdateField >> dispatch)
|
||||
Fields.number_novalidate "Liquidity Provision" iceData.mifidLiquidityProvision [ ] (MifidLiquidityProvision >> UpdateField >> dispatch)
|
||||
Fields.number_novalidate "Trading Capacity" iceData.mifidTradingCapacity [ ] (MifidTradingCapacity >> UpdateField >> dispatch)
|
||||
Fields.uncheckedInt64 "Client Id Code" iceData.mifidClientIdCode [ ] (MifidClientIdCode >> UpdateField >> dispatch)
|
||||
Fields.uncheckedInt "Commodity Deriv Indicator" iceData.mifidCommodityDerivIndicator [ ] (MifidCommodityDerivIndicator >> UpdateField >> dispatch)
|
||||
Fields.uncheckedInt "Direct Electronic Access" iceData.mifidDirectElectronicAccess [ ] (MifidDirectElectronicAccess >> UpdateField >> dispatch)
|
||||
Fields.uncheckedInt64 "Execution Decision" iceData.mifidExecutionDecision [ ] (MifidExecutionDecision >> UpdateField >> dispatch)
|
||||
Fields.uncheckedInt64 "Id" iceData.mifidId [ ] (MifidId >> UpdateField >> dispatch)
|
||||
Fields.uncheckedInt64 "Investment Decision" iceData.mifidInvestmentDecision [ ] (MifidInvestmentDecision >> UpdateField >> dispatch)
|
||||
Fields.uncheckedInt "Liquidity Provision" iceData.mifidLiquidityProvision [ ] (MifidLiquidityProvision >> UpdateField >> dispatch)
|
||||
Fields.uncheckedInt "Trading Capacity" iceData.mifidTradingCapacity [ ] (MifidTradingCapacity >> UpdateField >> dispatch)
|
||||
]
|
||||
]
|
||||
br []
|
||||
|
||||
@@ -34,10 +34,12 @@
|
||||
<Compile Include="$(API)/fable/MarginService.fable.fs" />
|
||||
<Compile Include="$(API)/fable/co2.fable.fs" />
|
||||
<Compile Include="$(API)/fable/MarketDataRelay.fable.fs" />
|
||||
<Compile Include="$(API)/fable/PersistenceService.fable.fs" />
|
||||
<!-- Global to the app -->
|
||||
<Compile Include="Lens.fs" />
|
||||
<Compile Include="Global.fs" />
|
||||
<Compile Include="Cmd.fs" />
|
||||
<Compile Include="Controls/Unsub.fs" />
|
||||
<Compile Include="Controls/nbsp.fs" />
|
||||
<Compile Include="Controls/Button.fs" />
|
||||
<Compile Include="Controls/ReactGrid.fs" />
|
||||
@@ -59,6 +61,10 @@
|
||||
<Compile Include="Users/Types.fs" />
|
||||
<Compile Include="Users/State.fs" />
|
||||
<Compile Include="Users/View.fs" />
|
||||
<!-- Reports -->
|
||||
<Compile Include="Reports/Types.fs" />
|
||||
<Compile Include="Reports/State.fs" />
|
||||
<Compile Include="Reports/View.fs" />
|
||||
<!-- App -->
|
||||
<Compile Include="Types.fs" />
|
||||
<Compile Include="State.fs" />
|
||||
|
||||
Reference in new issue
Block a user