Add QuoteStatusReport and QuoteAck handlers
This commit is contained in:
@ -6,6 +6,7 @@ import (
|
||||
|
||||
"quantex.com/qfixdpl/quickfix"
|
||||
"quantex.com/qfixdpl/quickfix/gen/fix50sp2/quote"
|
||||
"quantex.com/qfixdpl/quickfix/gen/fix50sp2/quoteack"
|
||||
"quantex.com/qfixdpl/quickfix/gen/fix50sp2/quoterequest"
|
||||
"quantex.com/qfixdpl/quickfix/gen/tag"
|
||||
"quantex.com/qfixdpl/src/domain"
|
||||
@ -18,6 +19,7 @@ type application struct {
|
||||
onLogout func(quickfix.SessionID)
|
||||
onQuote func(quote.Quote, quickfix.SessionID)
|
||||
onQuoteRequest func(quoterequest.QuoteRequest, quickfix.SessionID)
|
||||
onQuoteAck func(quoteack.QuoteAck, quickfix.SessionID)
|
||||
}
|
||||
|
||||
func newApplication(n domain.Notifier) *application {
|
||||
@ -27,6 +29,7 @@ func newApplication(n domain.Notifier) *application {
|
||||
}
|
||||
|
||||
app.router.AddRoute(quote.Route(app.handleQuote))
|
||||
app.router.AddRoute(quoteack.Route(app.handleQuoteAck))
|
||||
app.router.AddRoute(quoterequest.Route(app.handleQuoteRequest))
|
||||
|
||||
return app
|
||||
@ -107,6 +110,27 @@ func (a *application) handleQuoteRequest(msg quoterequest.QuoteRequest, sessionI
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *application) handleQuoteAck(msg quoteack.QuoteAck, sessionID quickfix.SessionID) quickfix.MessageRejectError {
|
||||
quoteReqID, _ := msg.GetQuoteReqID()
|
||||
quoteID, _ := msg.GetQuoteID()
|
||||
status, _ := msg.GetQuoteAckStatus()
|
||||
text, _ := msg.GetText()
|
||||
|
||||
slog.Info("QuoteAck received",
|
||||
"quoteReqID", quoteReqID,
|
||||
"quoteID", quoteID,
|
||||
"quoteAckStatus", status,
|
||||
"text", text,
|
||||
"session", sessionID.String(),
|
||||
)
|
||||
|
||||
if a.onQuoteAck != nil {
|
||||
a.onQuoteAck(msg, sessionID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *application) handleQuote(msg quote.Quote, sessionID quickfix.SessionID) quickfix.MessageRejectError {
|
||||
quoteID, err := msg.GetQuoteID()
|
||||
if err != nil {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package fix
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"sync"
|
||||
@ -14,6 +15,7 @@ import (
|
||||
"quantex.com/qfixdpl/quickfix/gen/field"
|
||||
"quantex.com/qfixdpl/quickfix/gen/fix50sp2/quote"
|
||||
"quantex.com/qfixdpl/quickfix/gen/fix50sp2/quoterequest"
|
||||
"quantex.com/qfixdpl/quickfix/gen/fix50sp2/quotestatusreport"
|
||||
"quantex.com/qfixdpl/quickfix/store/file"
|
||||
"quantex.com/qfixdpl/src/app"
|
||||
"quantex.com/qfixdpl/src/common/tracerr"
|
||||
@ -139,7 +141,7 @@ func (m *Manager) SendQuote(
|
||||
}
|
||||
|
||||
q := quote.New(
|
||||
field.NewQuoteID("NONREF"),
|
||||
field.NewQuoteID(quoteID),
|
||||
field.NewQuoteType(enum.QuoteType_INDICATIVE),
|
||||
field.NewTransactTime(time.Now()),
|
||||
)
|
||||
@ -152,7 +154,7 @@ func (m *Manager) SendQuote(
|
||||
q.SetSymbol("[N/A]")
|
||||
q.SetSecurityID(symbol)
|
||||
q.SetSecurityIDSource(sIDSource)
|
||||
q.SetQuoteID(quoteID)
|
||||
q.SetQuoteReqID(clOrdID)
|
||||
|
||||
if currency != "" {
|
||||
q.SetCurrency(currency)
|
||||
@ -180,7 +182,23 @@ func (m *Manager) SendQuote(
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleQuoteRequest auto-responds to an incoming QuoteRequest with a quote at price 99.6.
|
||||
// sendQuoteStatusReport sends a QuoteStatusReport (35=AI) to acknowledge the incoming QuoteRequest.
|
||||
func (m *Manager) sendQuoteStatusReport(quoteReqID, ownerTraderID string, sessionID quickfix.SessionID) error {
|
||||
qsr := quotestatusreport.New(
|
||||
field.NewTransactTime(time.Now()),
|
||||
field.NewQuoteStatus(enum.QuoteStatus_ACCEPTED),
|
||||
)
|
||||
qsr.SetQuoteReqID(quoteReqID)
|
||||
qsr.SetQuoteID(quoteReqID)
|
||||
qsr.SetSymbol("[N/A]")
|
||||
if ownerTraderID != "" {
|
||||
qsr.SetOwnerTraderID(ownerTraderID)
|
||||
}
|
||||
|
||||
return quickfix.SendToTarget(qsr, sessionID)
|
||||
}
|
||||
|
||||
// handleQuoteRequest auto-responds to an incoming QuoteRequest with a QuoteStatusReport (acknowledge) followed by a Quote at price 99.6.
|
||||
func (m *Manager) handleQuoteRequest(msg quoterequest.QuoteRequest, sessionID quickfix.SessionID) {
|
||||
quoteReqID, err := msg.GetQuoteReqID()
|
||||
if err != nil {
|
||||
@ -189,9 +207,10 @@ func (m *Manager) handleQuoteRequest(msg quoterequest.QuoteRequest, sessionID qu
|
||||
}
|
||||
|
||||
var (
|
||||
symbol, currency string
|
||||
side enum.Side
|
||||
secIDSource enum.SecurityIDSource
|
||||
symbol, currency, ownerTraderID, settlDate string
|
||||
side enum.Side
|
||||
secIDSource enum.SecurityIDSource
|
||||
orderQty decimal.Decimal
|
||||
)
|
||||
|
||||
relatedSyms, relErr := msg.GetNoRelatedSym()
|
||||
@ -201,33 +220,68 @@ func (m *Manager) handleQuoteRequest(msg quoterequest.QuoteRequest, sessionID qu
|
||||
secIDSource, _ = sym.GetSecurityIDSource()
|
||||
currency, _ = sym.GetCurrency()
|
||||
side, _ = sym.GetSide()
|
||||
ownerTraderID, _ = sym.GetOwnerTraderID()
|
||||
orderQty, _ = sym.GetOrderQty()
|
||||
settlDate, _ = sym.GetSettlDate()
|
||||
}
|
||||
|
||||
// Step 1: Send QuoteStatusReport (35=AI) to acknowledge the inquiry.
|
||||
if ackErr := m.sendQuoteStatusReport(quoteReqID, ownerTraderID, sessionID); ackErr != nil {
|
||||
slog.Error("handleQuoteRequest: failed to send QuoteStatusReport", "quoteReqID", quoteReqID, "error", ackErr.Error())
|
||||
return
|
||||
}
|
||||
slog.Info("QuoteStatusReport sent", "quoteReqID", quoteReqID)
|
||||
|
||||
// Step 2: Build and send Quote (35=S) with price.
|
||||
price := decimal.NewFromFloat(99.6)
|
||||
bidPx, offerPx := decimal.Zero, decimal.Zero
|
||||
|
||||
sIDSource := enum.SecurityIDSource_ISIN_NUMBER
|
||||
if secIDSource == enum.SecurityIDSource_CUSIP {
|
||||
sIDSource = enum.SecurityIDSource_CUSIP
|
||||
}
|
||||
|
||||
quoteID := fmt.Sprintf("Q-%d", time.Now().UnixMilli())
|
||||
q := quote.New(
|
||||
field.NewQuoteID(quoteID),
|
||||
field.NewQuoteType(enum.QuoteType_SEND_QUOTE),
|
||||
field.NewTransactTime(time.Now()),
|
||||
)
|
||||
|
||||
q.SetSymbol("[N/A]")
|
||||
q.SetSecurityID(symbol)
|
||||
q.SetSecurityIDSource(sIDSource)
|
||||
q.SetQuoteReqID(quoteReqID)
|
||||
|
||||
if currency != "" {
|
||||
q.SetCurrency(currency)
|
||||
}
|
||||
|
||||
if !orderQty.IsZero() {
|
||||
q.SetOrderQty(orderQty, 0)
|
||||
}
|
||||
|
||||
if settlDate != "" {
|
||||
q.SetSettlDate(settlDate)
|
||||
}
|
||||
|
||||
if side == enum.Side_BUY {
|
||||
offerPx = price
|
||||
q.SetOfferPx(price, 8)
|
||||
q.SetSide(enum.Side_BUY)
|
||||
} else {
|
||||
bidPx = price
|
||||
q.SetBidPx(price, 8)
|
||||
q.SetSide(enum.Side_SELL)
|
||||
}
|
||||
|
||||
var sIDSource string
|
||||
if secIDSource == enum.SecurityIDSource_ISIN_NUMBER {
|
||||
sIDSource = "4"
|
||||
} else {
|
||||
sIDSource = "1"
|
||||
q.SetPriceType(enum.PriceType_PERCENTAGE)
|
||||
|
||||
if ownerTraderID != "" {
|
||||
q.SetOwnerTraderID(ownerTraderID)
|
||||
}
|
||||
|
||||
if sendErr := m.SendQuote(
|
||||
quoteReqID,
|
||||
quoteReqID,
|
||||
symbol,
|
||||
sIDSource,
|
||||
currency,
|
||||
bidPx,
|
||||
offerPx,
|
||||
); sendErr != nil {
|
||||
if sendErr := quickfix.SendToTarget(q, sessionID); sendErr != nil {
|
||||
slog.Error("handleQuoteRequest: failed to send quote", "quoteReqID", quoteReqID, "error", sendErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
slog.Info("Quote sent", "quoteReqID", quoteReqID, "quoteID", quoteID, "symbol", symbol)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user