Add QuoteStatusReport and QuoteAck handlers
This commit is contained in:
@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"quantex.com/qfixdpl/quickfix"
|
"quantex.com/qfixdpl/quickfix"
|
||||||
"quantex.com/qfixdpl/quickfix/gen/fix50sp2/quote"
|
"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/fix50sp2/quoterequest"
|
||||||
"quantex.com/qfixdpl/quickfix/gen/tag"
|
"quantex.com/qfixdpl/quickfix/gen/tag"
|
||||||
"quantex.com/qfixdpl/src/domain"
|
"quantex.com/qfixdpl/src/domain"
|
||||||
@ -18,6 +19,7 @@ type application struct {
|
|||||||
onLogout func(quickfix.SessionID)
|
onLogout func(quickfix.SessionID)
|
||||||
onQuote func(quote.Quote, quickfix.SessionID)
|
onQuote func(quote.Quote, quickfix.SessionID)
|
||||||
onQuoteRequest func(quoterequest.QuoteRequest, quickfix.SessionID)
|
onQuoteRequest func(quoterequest.QuoteRequest, quickfix.SessionID)
|
||||||
|
onQuoteAck func(quoteack.QuoteAck, quickfix.SessionID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newApplication(n domain.Notifier) *application {
|
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(quote.Route(app.handleQuote))
|
||||||
|
app.router.AddRoute(quoteack.Route(app.handleQuoteAck))
|
||||||
app.router.AddRoute(quoterequest.Route(app.handleQuoteRequest))
|
app.router.AddRoute(quoterequest.Route(app.handleQuoteRequest))
|
||||||
|
|
||||||
return app
|
return app
|
||||||
@ -107,6 +110,27 @@ func (a *application) handleQuoteRequest(msg quoterequest.QuoteRequest, sessionI
|
|||||||
return nil
|
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 {
|
func (a *application) handleQuote(msg quote.Quote, sessionID quickfix.SessionID) quickfix.MessageRejectError {
|
||||||
quoteID, err := msg.GetQuoteID()
|
quoteID, err := msg.GetQuoteID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package fix
|
package fix
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
@ -14,6 +15,7 @@ import (
|
|||||||
"quantex.com/qfixdpl/quickfix/gen/field"
|
"quantex.com/qfixdpl/quickfix/gen/field"
|
||||||
"quantex.com/qfixdpl/quickfix/gen/fix50sp2/quote"
|
"quantex.com/qfixdpl/quickfix/gen/fix50sp2/quote"
|
||||||
"quantex.com/qfixdpl/quickfix/gen/fix50sp2/quoterequest"
|
"quantex.com/qfixdpl/quickfix/gen/fix50sp2/quoterequest"
|
||||||
|
"quantex.com/qfixdpl/quickfix/gen/fix50sp2/quotestatusreport"
|
||||||
"quantex.com/qfixdpl/quickfix/store/file"
|
"quantex.com/qfixdpl/quickfix/store/file"
|
||||||
"quantex.com/qfixdpl/src/app"
|
"quantex.com/qfixdpl/src/app"
|
||||||
"quantex.com/qfixdpl/src/common/tracerr"
|
"quantex.com/qfixdpl/src/common/tracerr"
|
||||||
@ -139,7 +141,7 @@ func (m *Manager) SendQuote(
|
|||||||
}
|
}
|
||||||
|
|
||||||
q := quote.New(
|
q := quote.New(
|
||||||
field.NewQuoteID("NONREF"),
|
field.NewQuoteID(quoteID),
|
||||||
field.NewQuoteType(enum.QuoteType_INDICATIVE),
|
field.NewQuoteType(enum.QuoteType_INDICATIVE),
|
||||||
field.NewTransactTime(time.Now()),
|
field.NewTransactTime(time.Now()),
|
||||||
)
|
)
|
||||||
@ -152,7 +154,7 @@ func (m *Manager) SendQuote(
|
|||||||
q.SetSymbol("[N/A]")
|
q.SetSymbol("[N/A]")
|
||||||
q.SetSecurityID(symbol)
|
q.SetSecurityID(symbol)
|
||||||
q.SetSecurityIDSource(sIDSource)
|
q.SetSecurityIDSource(sIDSource)
|
||||||
q.SetQuoteID(quoteID)
|
q.SetQuoteReqID(clOrdID)
|
||||||
|
|
||||||
if currency != "" {
|
if currency != "" {
|
||||||
q.SetCurrency(currency)
|
q.SetCurrency(currency)
|
||||||
@ -180,7 +182,23 @@ func (m *Manager) SendQuote(
|
|||||||
return nil
|
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) {
|
func (m *Manager) handleQuoteRequest(msg quoterequest.QuoteRequest, sessionID quickfix.SessionID) {
|
||||||
quoteReqID, err := msg.GetQuoteReqID()
|
quoteReqID, err := msg.GetQuoteReqID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -189,9 +207,10 @@ func (m *Manager) handleQuoteRequest(msg quoterequest.QuoteRequest, sessionID qu
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
symbol, currency string
|
symbol, currency, ownerTraderID, settlDate string
|
||||||
side enum.Side
|
side enum.Side
|
||||||
secIDSource enum.SecurityIDSource
|
secIDSource enum.SecurityIDSource
|
||||||
|
orderQty decimal.Decimal
|
||||||
)
|
)
|
||||||
|
|
||||||
relatedSyms, relErr := msg.GetNoRelatedSym()
|
relatedSyms, relErr := msg.GetNoRelatedSym()
|
||||||
@ -201,33 +220,68 @@ func (m *Manager) handleQuoteRequest(msg quoterequest.QuoteRequest, sessionID qu
|
|||||||
secIDSource, _ = sym.GetSecurityIDSource()
|
secIDSource, _ = sym.GetSecurityIDSource()
|
||||||
currency, _ = sym.GetCurrency()
|
currency, _ = sym.GetCurrency()
|
||||||
side, _ = sym.GetSide()
|
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)
|
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 {
|
if side == enum.Side_BUY {
|
||||||
offerPx = price
|
q.SetOfferPx(price, 8)
|
||||||
|
q.SetSide(enum.Side_BUY)
|
||||||
} else {
|
} else {
|
||||||
bidPx = price
|
q.SetBidPx(price, 8)
|
||||||
|
q.SetSide(enum.Side_SELL)
|
||||||
}
|
}
|
||||||
|
|
||||||
var sIDSource string
|
q.SetPriceType(enum.PriceType_PERCENTAGE)
|
||||||
if secIDSource == enum.SecurityIDSource_ISIN_NUMBER {
|
|
||||||
sIDSource = "4"
|
if ownerTraderID != "" {
|
||||||
} else {
|
q.SetOwnerTraderID(ownerTraderID)
|
||||||
sIDSource = "1"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if sendErr := m.SendQuote(
|
if sendErr := quickfix.SendToTarget(q, sessionID); sendErr != nil {
|
||||||
quoteReqID,
|
|
||||||
quoteReqID,
|
|
||||||
symbol,
|
|
||||||
sIDSource,
|
|
||||||
currency,
|
|
||||||
bidPx,
|
|
||||||
offerPx,
|
|
||||||
); sendErr != nil {
|
|
||||||
slog.Error("handleQuoteRequest: failed to send quote", "quoteReqID", quoteReqID, "error", sendErr.Error())
|
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