Example #1
0
// 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
}
Example #2
0
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)
			}
		}
	}
}
Example #3
0
// 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
}