Compare commits

...
Author SHA1 Message Date
Michael Harding ffce497e3f SP-54: Fix sort direction 2020-01-24 09:34:24 -05:00
Michael Harding f3546d79b1 SP-54: Apply filters after md change 2020-01-24 09:34:12 -05:00
Michael Harding 552d79f964 SP-54: Save sorting 2020-01-24 09:34:01 -05:00
Michael Harding 109858572f SP-54: colorful text 2020-01-24 09:33:47 -05:00
Michael Harding 34f288be5d SP-54: Save Filters 2020-01-24 09:33:34 -05:00
Michael Harding a8bb95aa51 Safer number fields in Spark admin 2020-01-24 09:33:24 -05:00
Michael Harding 117e21a8b2 SP-54: Save Column settings to PersistenceService 2020-01-24 09:33:07 -05:00
Michael Harding 51e0b8ce4f Add eric's unsub hack 2020-01-24 09:32:55 -05:00
Michael Harding bce4dcbc5d SP-54: Clear filters and ui refinement 2020-01-24 09:32:44 -05:00
Michael Harding 4268604cb6 SP-54: filterbox and header style 2020-01-24 09:32:33 -05:00
Michael Harding 15bc2c7ca1 SP-54: PnL filters 2020-01-24 09:32:21 -05:00
Michael Harding 1389237f97 fix local host port issue 2020-01-24 09:32:10 -05:00
Michael Harding 95399c64cd SP-54: Sort fixes 2020-01-24 09:31:28 -05:00
Michael Harding 02ca455e92 SP-54: Column sorting 2020-01-24 09:30:54 -05:00
Michael Harding e1aff10478 SP-54: Show/Hide PnL columns 2020-01-24 09:30:41 -05:00
Michael Harding e3636825fb Hide PnL columns 2020-01-24 09:30:26 -05:00
Michael Harding 9beeb038ff Add security to the PnL form and make table lazy 2020-01-24 09:30:11 -05:00
Michael Harding 0019cfb7c0 Subscribe to and display PnL data 2020-01-24 09:29:59 -05:00
Michael Harding c057f636e3 Clean up warnings in Spark Admin 2020-01-24 09:29:43 -05:00
Michael Harding 1868d5914a SP-54: Add Reports Module 2020-01-24 09:29:28 -05:00
Michael Harding 8bfb1d1e95 Update Copyright in About 2020-01-24 09:29:10 -05:00
22 changed files with 1256 additions and 82 deletions

No files matched your search

+1
View File
@@ -33,4 +33,5 @@ trace_data/
/src/lib/orderman/perf_analysis/log.csv
/doc/user/public/*
.ionide/*
*.ionide
/.ignore
+4 -4
View File
@@ -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">&#169; Blue Trading Systems 2013 - 2018</TextBlock>
<TextBlock Grid.ColumnSpan="2" VerticalAlignment="Bottom" HorizontalAlignment="Center" Margin="0,0,0,10">&#169; 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>
+7 -7
View File
@@ -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
+1 -1
View File
@@ -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 })
+9 -9
View File
@@ -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);
}
}
}
+39
View File
@@ -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;
}
+14 -16
View File
@@ -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 ->
+16 -15
View File
@@ -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
+3 -2
View File
@@ -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 -8
View File
@@ -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
+23 -1
View File
@@ -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" ] ] ] ] ]
]
+18
View File
@@ -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
View File
@@ -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
+691
View File
@@ -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
+80
View File
@@ -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
}
+279
View File
@@ -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
]
]
)
+6 -3
View File
@@ -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
+2
View File
@@ -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 = {
+3
View File
@@ -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 }
+9 -9
View File
@@ -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 []
+6
View File
@@ -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" />