diff options
Diffstat (limited to 'src/FundLab.Api/App.fs')
| -rw-r--r-- | src/FundLab.Api/App.fs | 95 |
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 |
