// Common Order FIX client routines serving requests from order router // if a market connector has special case it will need to implement its own start routine like below func (app FIXClient) Start() error { err := app.Initiator.Start() // Subscribe to order flow topics // NEW app.MessageBus.Subscribe("order.NewOrderRequest.MC."+app.marketConnectorName, func(m *messagebus.Msg) { request := new(proto.NewOrderRequest) if err := request.Unmarshal(m.Data); err == nil { order := request.Order //TODO: this is only prototype, migrate common tasks: instruments / limits processing fixmsg := fix44nos.Message{ ClOrdID: strconv.Itoa(int(order.OrderKey)) + "." + strconv.Itoa(int(order.Version)), Side: util.ProtoEnumToFIXEnum(int(order.Side)), TransactTime: time.Now(), OrdType: util.ProtoEnumToFIXEnum(int(order.OrderType)), } // Instrument specific fixmsg.Instrument.Symbol = &order.Symbol fixmsg.OrderQty = &order.Quantity if order.OrderType == proto.OrderType_LIMIT || order.OrderType == proto.OrderType_LIMIT_ON_CLOSE { fixmsg.Price = &order.LimitPrice } // Broker specific fixmsg.Account = &order.BrokerAccount fixmsg.HandlInst = stringPtr(util.ProtoEnumToFIXEnum(int(order.HandleInst))) // 142 SenderLocationID // Mandatory for CME exchanges. It contains a 2-character country. For the US and Canada, the state/province is included. fixmsg.SenderLocationID = stringPtr("UK") log.Info("MC->FIX FIX44NewOrderSingle") if err := quickfix.SendToTarget(fixmsg, app.Session); err != nil { log.WithError(err).Fatal("FIX quickfix.SendToTarget Error") } } }) // TODO: CANCEL // TODO: REPLACE return err }
func TestOrderRouterNewOrderRequest(t *testing.T) { svc := mockOrderRouter() svc.Start() defer svc.Close() // mock new order request request := &proto.NewOrderRequest{ Order: testOrder.MockOrder(), } data, merr := request.Marshal() if merr != nil { t.Fatal(merr) } // send mock order, expecting reject due to market connector not up req := new(proto.NewOrderRequest) if err := req.Unmarshal(data); err != nil { t.Fatal(err) } if reply, err := svc.msgbus.Request("order.NewOrderRequest", data, 200*time.Millisecond); err != nil { t.Fatalf("an error '%s' was not expected when sending NewOrderRequest", err) } else { resp := new(proto.NewOrderResponse) if err := resp.Unmarshal(reply.Data); err != nil { t.Fatalf("an error '%s' was not expected when decoding NewOrderResponse", err) } if *resp.ErrorMessage != "LINK TO BROKER DOWN" { t.Fatalf("unexpected NewOrderResponse %s, expecting LINK TO BROKER DOWN", *resp.ErrorMessage) } } // mock a test market connector mcname := "MC.TestCase" hbMsg := pService.Heartbeat{ Name: mcname, Status: pService.RUNNING, } if hbMsgData, err := hbMsg.Marshal(); err != nil { t.Fatal(err) } else { svc.msgbusService.Publish("service.Heartbeat.MC.TestCase", hbMsgData) time.Sleep(100 * time.Millisecond) if reply, err := svc.msgbus.Request("order.NewOrderRequest", data, 200*time.Millisecond); err != nil { t.Fatalf("an error '%s' was not expected when sending NewOrderRequest", err) } else { resp := new(proto.NewOrderResponse) if err := resp.Unmarshal(reply.Data); err != nil { t.Fatalf("an error '%s' was not expected when decoding NewOrderResponse", err) } if resp.ErrorMessage != nil && len(*resp.ErrorMessage) > 0 { t.Fatalf("unexpected NewOrderResponse error message: %s", *resp.ErrorMessage) } if resp.Order == nil || resp.Order.OrderId != 2 { t.Fatalf("unexpected NewOrderResponse id not matching mock OrderId(%v), expecting 2") } if resp.Order == nil || resp.Order.OrderKey != 2 { t.Fatalf("unexpected NewOrderResponse OrderKey %v, expecting 2", resp.Order.OrderKey) } } } }
// Initialise OrderRouter instance and set up topic subscribers func NewOrderRouter(c Config) *OrderRouter { or := &OrderRouter{ Config: c, // internal variables stopChan: make(chan bool), mclist: map[string]time.Time{}, mcChan: make(chan *service.Heartbeat), reqChan: make(chan OrderRequest), } // Connect to database storage driver if storage, err := database.NewOrderStore(c.DatabaseDriver, c.DatabaseUrl, nil); err != nil { log.Fatalf("error: Cannot connect to database driver %v @ %v", c.DatabaseDriver, c.DatabaseUrl) } else { or.orderStore = storage } // Keep a list of active MarketConnectors if ncSvc, err := messagebus.Connect(c.ServiceMessageBusURL); err != nil { log.Fatalf("error: Cannot connect to service message bus @ %v", c.ServiceMessageBusURL) } else { or.msgbusService = ncSvc ncSvc.Subscribe("service.Heartbeat.MC.>", func(m *messagebus.Msg) { hbMsg := &service.Heartbeat{} if err := hbMsg.Unmarshal(m.Data); err == nil { or.mcChan <- hbMsg } }) } // Order requests processors subscribing if msgbus, err := messagebus.Connect(c.MessageBusURL); err != nil { log.Fatalf("error: Cannot connect to order message bus @ %v", c.MessageBusURL) } else { or.msgbus = msgbus //CL->OR order NEW request msgbus.Subscribe("order.NewOrderRequest", func(m *messagebus.Msg) { request := new(proto.NewOrderRequest) if err := request.Unmarshal(m.Data); err == nil && len(m.Reply) > 0 { // validate basic fields if request.Order == nil { // empty request return } if request.Order.MarketConnector == "" { // empty market connect return } orReq := OrderRequest{ ReplyAddr: m.Reply, RequestType: REQ_NEW, Request: request, } oO := order.Order{ Order: request.Order, } log.Infof("CL->OR NEW %v", oO.String()) or.reqChan <- orReq } }) //CL->OR order CANCEL request msgbus.Subscribe("order.CancelOrderRequest", func(m *messagebus.Msg) { request := new(proto.CancelOrderRequest) if err := request.Unmarshal(m.Data); err == nil && len(m.Reply) > 0 { // validate basic fields if request.OrderId == 0 { // request without order id return } orReq := OrderRequest{ ReplyAddr: m.Reply, RequestType: REQ_CANCEL, Request: request, OrderId: request.OrderId, } log.Infof("CL->OR CXL OrderKey := %v OrderId := %v", request.OrderKey, request.OrderId) or.reqChan <- orReq } }) //CL->OR order REPLACE request msgbus.Subscribe("order.ReplaceOrderRequest", func(m *messagebus.Msg) { request := new(proto.ReplaceOrderRequest) if err := request.Unmarshal(m.Data); err == nil && len(m.Reply) > 0 { // validate basic fields if request.Order == nil { // empty request return } if request.Order.OrderId == 0 { // request without order id return } if request.Order.MarketConnector == "" { // empty market connect return } orReq := OrderRequest{ ReplyAddr: m.Reply, RequestType: REQ_REPLACE, Request: request, OrderId: request.Order.OrderId, } log.Infof("CL->OR RPL OrderKey := %v OrderId := %v", request.Order.OrderKey, request.Order.OrderId) or.reqChan <- orReq } }) } return or }