Paper trading readiness에서 API/worker/CLI가 같은 protobuf 계약으로 paper state를 시작하고 조회할 수 있어야 한다. Headless 운영 경로를 먼저 닫기 위해 contract, worker runtime, API forwarding, CLI scenario, client parser map과 검증 artifact를 함께 반영한다.
84 lines
3.4 KiB
Go
84 lines
3.4 KiB
Go
package socket
|
|
|
|
import (
|
|
"context"
|
|
|
|
protoSocket "git.toki-labs.com/toki/proto-socket/go"
|
|
|
|
altv1 "git.toki-labs.com/toki/alt/packages/contracts/gen/go/alt/v1"
|
|
"git.toki-labs.com/toki/alt/services/api/internal/workerclient"
|
|
)
|
|
|
|
// paperHandlers returns the API session handlers for the paper trading surface.
|
|
// Each handler validates the request shape and forwards it to the worker through
|
|
// the injected Worker client; paper execution and state stay worker-owned.
|
|
func paperHandlers(worker workerclient.WorkerClient) []sessionHandler {
|
|
return []sessionHandler{
|
|
{
|
|
requestType: protoSocket.TypeNameOf(&altv1.StartPaperTradingRequest{}),
|
|
register: func(client *protoSocket.WsClient) {
|
|
protoSocket.AddRequestListenerTyped[*altv1.StartPaperTradingRequest, *altv1.StartPaperTradingResponse](&client.Communicator, func(req *altv1.StartPaperTradingRequest) (*altv1.StartPaperTradingResponse, error) {
|
|
return handleStartPaperTrading(worker, req)
|
|
})
|
|
},
|
|
},
|
|
{
|
|
requestType: protoSocket.TypeNameOf(&altv1.GetPaperTradingStateRequest{}),
|
|
register: func(client *protoSocket.WsClient) {
|
|
protoSocket.AddRequestListenerTyped[*altv1.GetPaperTradingStateRequest, *altv1.GetPaperTradingStateResponse](&client.Communicator, func(req *altv1.GetPaperTradingStateRequest) (*altv1.GetPaperTradingStateResponse, error) {
|
|
return handleGetPaperTradingState(worker, req)
|
|
})
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func handleStartPaperTrading(worker workerclient.WorkerClient, req *altv1.StartPaperTradingRequest) (*altv1.StartPaperTradingResponse, error) {
|
|
if req.GetAccountId() == "" {
|
|
return &altv1.StartPaperTradingResponse{Error: invalidRequest("account_id is required")}, nil
|
|
}
|
|
if req.GetSpec() == nil {
|
|
return &altv1.StartPaperTradingResponse{Error: invalidRequest("spec is required")}, nil
|
|
}
|
|
if req.GetStartingCash() == nil {
|
|
return &altv1.StartPaperTradingResponse{Error: invalidRequest("starting_cash is required")}, nil
|
|
}
|
|
if worker == nil {
|
|
return &altv1.StartPaperTradingResponse{Error: workerUnavailable()}, nil
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), workerRequestTimeout)
|
|
defer cancel()
|
|
if err := worker.Connect(ctx); err != nil {
|
|
return &altv1.StartPaperTradingResponse{Error: workerErrorInfo(err)}, nil
|
|
}
|
|
res, err := worker.StartPaperTrading(ctx, req)
|
|
if err != nil {
|
|
return &altv1.StartPaperTradingResponse{Error: workerErrorInfo(err)}, nil
|
|
}
|
|
if res == nil {
|
|
return &altv1.StartPaperTradingResponse{Error: internalError("worker returned no paper start response")}, nil
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func handleGetPaperTradingState(worker workerclient.WorkerClient, req *altv1.GetPaperTradingStateRequest) (*altv1.GetPaperTradingStateResponse, error) {
|
|
if req.GetAccountId() == "" {
|
|
return &altv1.GetPaperTradingStateResponse{Error: invalidRequest("account_id is required")}, nil
|
|
}
|
|
if worker == nil {
|
|
return &altv1.GetPaperTradingStateResponse{Error: workerUnavailable()}, nil
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), workerRequestTimeout)
|
|
defer cancel()
|
|
if err := worker.Connect(ctx); err != nil {
|
|
return &altv1.GetPaperTradingStateResponse{Error: workerErrorInfo(err)}, nil
|
|
}
|
|
res, err := worker.GetPaperTradingState(ctx, req)
|
|
if err != nil {
|
|
return &altv1.GetPaperTradingStateResponse{Error: workerErrorInfo(err)}, nil
|
|
}
|
|
if res == nil {
|
|
return &altv1.GetPaperTradingStateResponse{Error: internalError("worker returned no paper state response")}, nil
|
|
}
|
|
return res, nil
|
|
}
|