summaryrefslogtreecommitdiff
path: root/src/FundLab.Api/App.fs
diff options
context:
space:
mode:
Diffstat (limited to 'src/FundLab.Api/App.fs')
-rw-r--r--src/FundLab.Api/App.fs95
1 files changed, 93 insertions, 2 deletions
diff --git a/src/FundLab.Api/App.fs b/src/FundLab.Api/App.fs
index 9badcfa..3de514e 100644
--- a/src/FundLab.Api/App.fs
+++ b/src/FundLab.Api/App.fs
@@ -148,6 +148,22 @@ type SipPlanResponse =
createdAt: string
}
+type RebalancePlanResponse =
+ {
+ id: Guid
+ fundId: Guid
+ targets: {| instrumentCode: string; targetPercent: string |} list
+ status: string
+ createdAt: string
+ }
+
+type RebalanceExecutionResponse =
+ {
+ planId: Guid
+ runDate: string
+ outcomes: {| instrumentCode: string; action: string; amount: string; status: string; orderId: string option; pendingReason: string option |} list
+ }
+
type CapitalDepositResponse =
{
id: Guid
@@ -314,6 +330,38 @@ module App =
createdAt = timestampText plan.CreatedAt
}
+ let private rebalancePlanResponse (plan: RebalancePlanRecord) : RebalancePlanResponse =
+ {
+ id = plan.Id
+ fundId = plan.FundId
+ targets =
+ plan.Targets
+ |> List.map (fun target ->
+ {|
+ instrumentCode = target.InstrumentCode
+ targetPercent = cashText target.TargetPercent
+ |})
+ status = plan.Status
+ createdAt = timestampText plan.CreatedAt
+ }
+
+ let private rebalanceExecutionResponse (result: RebalanceExecutionResult) : RebalanceExecutionResponse =
+ {
+ planId = result.PlanId
+ runDate = dateText result.RunDate
+ outcomes =
+ result.Outcomes
+ |> List.map (fun outcome ->
+ {|
+ instrumentCode = outcome.InstrumentCode
+ action = outcome.Action
+ amount = outcome.Amount
+ status = outcome.Status
+ orderId = outcome.OrderId |> Option.map (fun id -> id.ToString("D"))
+ pendingReason = outcome.PendingReason
+ |})
+ }
+
let private errorResponse status error message : HttpHandler =
setStatusCode status
>=> json ({
@@ -469,9 +517,52 @@ module App =
with
| :? JsonException -> Error "request body must be valid JSON"
+ let private parseRebalancePlanCommand (body: string) =
+ try
+ use document = JsonDocument.Parse(body)
+ let root = document.RootElement
+
+ if root.ValueKind <> JsonValueKind.Object then
+ Error "request body must be a JSON object"
+ else
+ let mutable parsed = None
+ let mutable failure = None
+ use targetsElement = document.RootElement.Clone()
+
+ if not (root.TryGetProperty("targets", &targetsElement)) then
+ failure <- Some "targets is required"
+ elif targetsElement.ValueKind <> JsonValueKind.Array then
+ failure <- Some "targets must be an array"
+ else
+ let rows = ResizeArray<RebalanceTarget>()
+ let mutable rowError = None
+
+ for item in targetsElement.EnumerateArray() do
+ if rowError.IsNone then
+ match tryStringProperty item "instrumentCode", tryStringProperty item "targetPercent" with
+ | Some code, Some percentText ->
+ match tryDecimal "targetPercent" percentText with
+ | Ok percent -> rows.Add({ InstrumentCode = code; TargetPercent = percent })
+ | Error message -> rowError <- Some message
+ | _ -> rowError <- Some "each target needs instrumentCode and targetPercent"
+
+ failure <- rowError
+
+ if failure.IsNone then parsed <- Some { Targets = rows |> Seq.toList }
+
+ match failure with
+ | Some message -> Error message
+ | None ->
+ match parsed with
+ | Some command -> Ok command
+ | None -> Error "targets is required"
+ with
+ | :? JsonException -> Error "request body must be valid JSON"
+
let private invokeHandler handler next ctx = handler next ctx
+
let private unauthorized : HttpHandler =
setStatusCode 401
>=> setHttpHeader "WWW-Authenticate" "Bearer"
@@ -621,9 +712,9 @@ module App =
match repository.GetFund fundId with
| None -> errorResponse 404 "FUND_NOT_FOUND" "fund was not found" next ctx
| Some fund ->
- let positions =
+ let positions: FundPositionResponse list =
repository.GetFundPositions fundId
- |> List.map (fun position ->
+ |> List.map (fun (position: FundPositionRecord) ->
{
instrumentCode = position.InstrumentCode
units = decimalText position.Units