실거래 주문 생성/조회/취소 흐름이 contracts, worker, API, CLI, Flutter parser까지 같은 메시지 경계로 이어져야 한다.\n\n브로커 호출 전 operator confirmation과 malformed order validation을 worker-owned runtime에서 막고, 리뷰 완료 산출물을 archive에 보존한다.
117 lines
3.8 KiB
Go
117 lines
3.8 KiB
Go
package socket
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
altv1 "git.toki-labs.com/toki/alt/packages/contracts/gen/go/alt/v1"
|
|
"git.toki-labs.com/toki/alt/packages/domain/market"
|
|
"git.toki-labs.com/toki/alt/packages/domain/trading"
|
|
"git.toki-labs.com/toki/alt/services/worker/internal/livetrading"
|
|
)
|
|
|
|
func liveCapabilityToProto(c trading.BrokerCapability) *altv1.LiveBrokerCapability {
|
|
cap := &altv1.LiveBrokerCapability{
|
|
Broker: string(c.Broker),
|
|
SupportsAmend: c.SupportsAmend,
|
|
SupportsCancel: c.SupportsCancel,
|
|
SupportsAccount: c.SupportsAccount,
|
|
SupportsAuditTags: c.SupportsAuditTags,
|
|
CheckedAtUnixMs: c.CheckedAt.UnixMilli(),
|
|
}
|
|
for m := range c.Markets {
|
|
cap.Markets = append(cap.Markets, marketToProto(m))
|
|
}
|
|
for v := range c.Venues {
|
|
cap.Venues = append(cap.Venues, venueToProto(v))
|
|
}
|
|
for _, t := range c.OrderTypes {
|
|
cap.OrderTypes = append(cap.OrderTypes, string(t))
|
|
}
|
|
return cap
|
|
}
|
|
|
|
// protoToSubmitRequest maps a SubmitLiveOrderRequest proto onto the domain
|
|
// SubmitRequest type. The intent.type field is preserved as-is so custom
|
|
// broker order type strings are not narrowed to market/limit.
|
|
// Returns an error for unsupported limit_price currency values.
|
|
func protoToSubmitRequest(req *altv1.SubmitLiveOrderRequest) (livetrading.SubmitRequest, error) {
|
|
intent := req.GetIntent()
|
|
domainIntent := trading.OrderIntent{}
|
|
if intent != nil {
|
|
domainIntent = trading.OrderIntent{
|
|
InstrumentID: market.InstrumentID(intent.GetInstrumentId()),
|
|
Side: trading.OrderSide(intent.GetSide()),
|
|
Type: trading.OrderType(intent.GetType()),
|
|
TimeInForce: trading.OrderTimeInForce(intent.GetTimeInForce()),
|
|
}
|
|
if q := intent.GetQuantity(); q != nil {
|
|
if amt := q.GetAmount(); amt != nil {
|
|
domainIntent.Quantity = market.Quantity{Amount: market.Decimal{Value: amt.GetValue()}}
|
|
}
|
|
}
|
|
if lp := intent.GetLimitPrice(); lp != nil {
|
|
if amt := lp.GetAmount(); amt != nil {
|
|
currency, err := currencyFromProto(lp.GetCurrency())
|
|
if err != nil {
|
|
return livetrading.SubmitRequest{}, fmt.Errorf("limit_price: %w", err)
|
|
}
|
|
domainIntent.LimitPrice = market.Price{
|
|
Currency: currency,
|
|
Amount: market.Decimal{Value: amt.GetValue()},
|
|
}
|
|
}
|
|
}
|
|
if tags := intent.GetCustomTags(); len(tags) > 0 {
|
|
domainIntent.CustomTags = tags
|
|
}
|
|
}
|
|
|
|
conf := req.GetOperatorConfirmation()
|
|
domainConf := livetrading.OperatorConfirmation{}
|
|
if conf != nil {
|
|
domainConf = livetrading.OperatorConfirmation{
|
|
Confirmed: conf.GetConfirmed(),
|
|
OperatorID: conf.GetOperatorId(),
|
|
Reason: conf.GetReason(),
|
|
ConfirmedAtMs: conf.GetConfirmedAtUnixMs(),
|
|
}
|
|
}
|
|
|
|
return livetrading.SubmitRequest{
|
|
AccountID: req.GetAccountId(),
|
|
Intent: domainIntent,
|
|
Confirmation: domainConf,
|
|
IdempotencyKey: req.GetIdempotencyKey(),
|
|
}, nil
|
|
}
|
|
|
|
// liveOrderToProto converts a domain LiveOrder to the proto wire type.
|
|
func liveOrderToProto(o livetrading.LiveOrder) *altv1.LiveOrder {
|
|
proto := &altv1.LiveOrder{
|
|
Id: o.ID,
|
|
BrokerId: o.BrokerOrderID,
|
|
AccountId: o.AccountID,
|
|
InstrumentId: string(o.Intent.InstrumentID),
|
|
Side: string(o.Intent.Side),
|
|
Type: string(o.Intent.Type),
|
|
TimeInForce: string(o.Intent.TimeInForce),
|
|
Status: string(o.Status),
|
|
BrokerStatus: string(o.BrokerStatus),
|
|
RejectionReason: o.RejectionReason,
|
|
CreatedAtUnixMs: o.CreatedAt.UnixMilli(),
|
|
UpdatedAtUnixMs: o.UpdatedAt.UnixMilli(),
|
|
}
|
|
if o.Intent.Quantity.Amount.Value != "" {
|
|
proto.Quantity = &altv1.Quantity{Amount: &altv1.Decimal{Value: o.Intent.Quantity.Amount.Value}}
|
|
}
|
|
if o.Intent.LimitPrice.Amount.Value != "" {
|
|
proto.LimitPrice = priceToProto(o.Intent.LimitPrice)
|
|
}
|
|
return proto
|
|
}
|
|
|
|
// isLiveErr unwraps err and checks whether it matches target using errors.Is.
|
|
func isLiveErr(err, target error) bool {
|
|
return errors.Is(err, target)
|
|
}
|