Skip to content

Commit

Permalink
finos#1025 implementing taking basket trade off the market
Browse files Browse the repository at this point in the history
  • Loading branch information
naleeha committed Dec 5, 2023
1 parent 6390950 commit 5bf65d0
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ class BasketTradingConstituentProvider(val table: DataTable, val omsApi: OmsApi)
table.processUpdate(ack.clientOrderId, RowWithData(ack.clientOrderId, Map[String, Any](BTC.InstanceIdRic -> ack.clientOrderId,
BTC.OrderStatus -> OrderStates.ACKED)),clock.now())
}
override def onCancelAck(ack: CancelAck): Unit = ???
override def onCancelAck(ack: CancelAck): Unit = {
table.processUpdate(ack.clientOrderId, RowWithData(ack.clientOrderId, Map[String, Any](BTC.InstanceIdRic -> ack.clientOrderId,
BTC.OrderStatus -> OrderStates.CANCELLED)), clock.now())
}
override def onReplaceAck(ack: ReplaceAck): Unit = ???
override def onFill(fill: Fill): Unit = {
val state = if(fill.orderQty == fill.totalFilledQty) OrderStates.FILLED else OrderStates.ACKED
table.processUpdate(fill.clientOrderId,
RowWithData(fill.clientOrderId, Map[String, Any](BTC.InstanceIdRic -> fill.clientOrderId,
BTC.FilledQty -> fill.totalFilledQty, BTC.OrderStatus -> state))
,clock.now())
BTC.FilledQty -> fill.totalFilledQty, BTC.OrderStatus -> state)),clock.now())
}
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ import com.typesafe.scalalogging.StrictLogging
import org.finos.toolbox.time.Clock
import org.finos.vuu.core.module.basket.BasketModule
import org.finos.vuu.core.module.basket.BasketModule.{BasketTradingConstituentTable, Sides}
import org.finos.vuu.core.table.{DataTable, RowWithData, TableContainer, ViewPortColumnCreator}
import org.finos.vuu.core.table.{DataTable, RowData, RowWithData, TableContainer, ViewPortColumnCreator}
import org.finos.vuu.net.rpc.{EditRpcHandler, RpcHandler}
import org.finos.vuu.net.{ClientSessionId, RequestContext}
import org.finos.vuu.order.oms.{NewOrder, OmsApi}
import org.finos.vuu.order.oms.{CancelOrder, NewOrder, OmsApi}
import org.finos.vuu.viewport._
trait BasketTradingServiceIF extends EditRpcHandler{

trait BasketTradingServiceIF extends EditRpcHandler {
def sendToMarket(basketInstanceId: String)(ctx: RequestContext): ViewPortAction

def takeOffMarket(basketInstanceId: String)(ctx: RequestContext): ViewPortAction
}


Expand All @@ -20,18 +23,14 @@ class BasketTradingService(val table: DataTable, val tableContainer: TableContai


/**
* Send basket to market
* Send basket to market rpc call
*/
override def sendToMarket(name: String)(ctx: RequestContext): ViewPortAction = {
val tableRow = table.asTable.pullRow(name)

logger.info("Sending basket to market:" + name + " (row:" + tableRow + ")")
override def sendToMarket(basketInstanceId: String)(ctx: RequestContext): ViewPortAction = {
val tableRow = table.asTable.pullRow(basketInstanceId)

val tradingConsTable = tableContainer.getTable(BasketModule.BasketTradingConstituentTable)
logger.info("Sending basket to market:" + basketInstanceId + " (row:" + tableRow + ")")

val constituents = tradingConsTable.primaryKeys.toList
.map(tradingConsTable.pullRow)
.filter(_.get(BTC.InstanceId) == name)
val constituents = getConstituents(basketInstanceId)

constituents.foreach(constituentRow => {

Expand All @@ -48,12 +47,43 @@ class BasketTradingService(val table: DataTable, val tableContainer: TableContai
omsApi.createOrder(nos)
})

table.processUpdate(name, RowWithData(name,
Map(BT.InstanceId -> name, BT.Status -> BasketStates.ON_MARKET)), clock.now())
updateBasketTradeStatus(basketInstanceId, state = BasketStates.ON_MARKET)

ViewPortEditSuccess()
}

/**
* Take basket off market rpc call
*/
override def takeOffMarket(basketInstanceId: String)(ctx: RequestContext): ViewPortAction = {
val tableRow = table.asTable.pullRow(basketInstanceId)

logger.info("Tasking basket off market:" + basketInstanceId + " (row:" + tableRow + ")")

updateBasketTradeStatus(basketInstanceId, BasketStates.OFF_MARKET)

getConstituents(basketInstanceId)
.flatMap(c => omsApi.getOrderId(clientOrderId = c.get(BTC.InstanceIdRic).toString))
.foreach(orderId => omsApi.cancelOrder(CancelOrder(orderId)))

ViewPortEditSuccess()
}

private def getConstituents(basketInstanceId: String): List[RowData] = {
val tradingConsTable = tableContainer.getTable(BasketModule.BasketTradingConstituentTable)

tradingConsTable.primaryKeys.toList
.map(tradingConsTable.pullRow)
.filter(_.get(BTC.InstanceId) == basketInstanceId)
}

private def updateBasketTradeStatus(basketInstanceId: String, state: String): Unit = {
table.processUpdate(
basketInstanceId,
RowWithData(basketInstanceId, Map(BT.InstanceId -> basketInstanceId, BT.Status -> state)),
clock.now())
}

private def onEditCell(key: String, columnName: String, data: Any, vp: ViewPort, session: ClientSessionId): ViewPortEditAction = {
logger.info("Change requested for cell value for key:" + key + "(" + columnName + ":" + data + ")")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ trait OmsApi {
def addListener(omsListener: OmsListener): Unit
def runOnce(): Unit
def containsOrder(clientOrderId: String): Boolean
def getOrderId(clientOrderId: String): Option[Int]
}

trait OmsListener{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,13 @@ class InMemOmsApi(implicit val clock: Clock) extends OmsApi {

override def replaceOrder(replaceOrder: ReplaceOrder): Unit = ???

override def cancelOrder(cancelOrder: CancelOrder): Unit = ???
override def cancelOrder(cancelOrder: CancelOrder): Unit = {
orders = orders.map(orderState =>
if (orderState.orderId == cancelOrder.orderId)
orderState.copy(state = States.CANCELLED, nextEventTime = clock.now())
else orderState
)
}

override def addListener(omsListener: OmsListener): Unit = listeners = listeners ++ List(omsListener)

Expand All @@ -60,9 +66,12 @@ class InMemOmsApi(implicit val clock: Clock) extends OmsApi {
val remainingQty = orderstate.qty - orderstate.filledQty
val fillQty = if(remainingQty > 1) random.between(1, remainingQty) else 1
val totalFilledQty = orderstate.filledQty + fillQty
val state = if( orderstate.qty == totalFilledQty) States.FILLED else States.ACKED
val nextState = if( orderstate.qty == totalFilledQty) States.FILLED else States.ACKED
listeners.foreach(_.onFill(Fill(orderstate.orderId, fillQty, orderstate.price, orderstate.clientOrderId, totalFilledQty, orderstate.qty)))
orderstate.copy(state = state, filledQty = totalFilledQty, nextEventTime = clock.now() + random.between(1000, 5000))
orderstate.copy(state = nextState, filledQty = totalFilledQty, nextEventTime = clock.now() + random.between(1000, 5000))
case 'X' =>
listeners.foreach(_.onCancelAck(CancelAck(orderstate.orderId, orderstate.clientOrderId)))
orderstate
case _ =>
orderstate
}
Expand All @@ -72,8 +81,14 @@ class InMemOmsApi(implicit val clock: Clock) extends OmsApi {
}
})

orders = orders.filter(os => os.filledQty != os.qty)
orders = orders.filter(os => os.state != States.FILLED && os.state != States.CANCELLED)

}

override def getOrderId(clientOrderId: String): Option[Int] = {
orders.find(order => order.clientOrderId == clientOrderId) match {
case Some(o) => Some(o.orderId)
case None => None
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class OmsApiTest extends AnyFeatureSpec with GivenWhenThen with Matchers {

omsApi.containsOrder("clOrdId1") should equal(true)

listener.getOrderState("clOrdId1").get.filledQty should be > (0L)
listener.getOrderState("clOrdId1").get.filledQty should be > 0L

for (n <- 1 to 1000) {
clock.sleep(MAX_FILL_TIME_MS)
Expand All @@ -84,5 +84,34 @@ class OmsApiTest extends AnyFeatureSpec with GivenWhenThen with Matchers {
omsApi.containsOrder("clOrdId1") should equal(false)
}


Scenario("Check we can submit order and cancel") {

implicit val clock: Clock = new TestFriendlyClock(1000L)

val omsApi = OmsApi()

val listener = new TestListener()

omsApi.addListener(listener)

omsApi.createOrder(NewOrder("Buy", "VOD.L", 1000L, 100.01, "clOrdId1"))
omsApi.createOrder(NewOrder("Buy", "BP.L", 1000L, 150.01, "clOrdId2"))

clock.sleep(MAX_ACK_TIME_MS)
omsApi.runOnce()

listener.getOrderState("clOrdId1").get.state should equal("ACKED")

val orderId = omsApi.getOrderId("clOrdId1").get
omsApi.cancelOrder(CancelOrder(orderId))
omsApi.runOnce()

omsApi.containsOrder("clOrdId1") should equal(false)
listener.getOrderState("clOrdId1").get.state should equal("CANCELLED")

omsApi.containsOrder("clOrdId2") should equal(true)

}
}
}

0 comments on commit 5bf65d0

Please sign in to comment.