diff --git a/example/basket/src/main/scala/org/finos/vuu/core/module/basket/BasketConstants.scala b/example/basket/src/main/scala/org/finos/vuu/core/module/basket/BasketConstants.scala index 6de498283..8e8baf046 100644 --- a/example/basket/src/main/scala/org/finos/vuu/core/module/basket/BasketConstants.scala +++ b/example/basket/src/main/scala/org/finos/vuu/core/module/basket/BasketConstants.scala @@ -2,7 +2,7 @@ package org.finos.vuu.core.module.basket object BasketConstants { - object Side{ + object Side { final val Buy = "BUY" final val Sell = "SELL" } diff --git a/example/basket/src/main/scala/org/finos/vuu/core/module/basket/BasketModule.scala b/example/basket/src/main/scala/org/finos/vuu/core/module/basket/BasketModule.scala index 70a5683f9..8630bcb8e 100644 --- a/example/basket/src/main/scala/org/finos/vuu/core/module/basket/BasketModule.scala +++ b/example/basket/src/main/scala/org/finos/vuu/core/module/basket/BasketModule.scala @@ -202,9 +202,4 @@ object BasketModule extends DefaultModule { final val OrderStatus = "orderStatus" final val FilledQty = "filledQty" } - - object Sides{ - final val Buy = "Buy" - final val Sell = "Sell" - } } diff --git a/example/basket/src/main/scala/org/finos/vuu/core/module/basket/provider/BasketConstituentProvider.scala b/example/basket/src/main/scala/org/finos/vuu/core/module/basket/provider/BasketConstituentProvider.scala index 4b1ec3d06..a87c400ed 100644 --- a/example/basket/src/main/scala/org/finos/vuu/core/module/basket/provider/BasketConstituentProvider.scala +++ b/example/basket/src/main/scala/org/finos/vuu/core/module/basket/provider/BasketConstituentProvider.scala @@ -34,7 +34,7 @@ class BasketConstituentProvider(val table: DataTable)(implicit lifecycle: Lifecy val weighting = row("Weighting") val side = BasketConstants.Side.Buy val ricBasketId = symbol + "." + basketId - table.processUpdate(ricBasketId, RowWithData(symbol, Map( + table.processUpdate(ricBasketId, RowWithData(ricBasketId, Map( Ric -> symbol, BasketId -> basketId, RicBasketId -> ricBasketId, diff --git a/example/basket/src/main/scala/org/finos/vuu/core/module/basket/provider/BasketTradingConstituentProvider.scala b/example/basket/src/main/scala/org/finos/vuu/core/module/basket/provider/BasketTradingConstituentProvider.scala index 2cc9d691d..74fb2dd71 100644 --- a/example/basket/src/main/scala/org/finos/vuu/core/module/basket/provider/BasketTradingConstituentProvider.scala +++ b/example/basket/src/main/scala/org/finos/vuu/core/module/basket/provider/BasketTradingConstituentProvider.scala @@ -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()) } }) diff --git a/example/basket/src/main/scala/org/finos/vuu/core/module/basket/result/ErrorReason.scala b/example/basket/src/main/scala/org/finos/vuu/core/module/basket/result/ErrorReason.scala new file mode 100644 index 000000000..e4a453405 --- /dev/null +++ b/example/basket/src/main/scala/org/finos/vuu/core/module/basket/result/ErrorReason.scala @@ -0,0 +1,3 @@ +package org.finos.vuu.core.module.basket.result + +case class ErrorReason(reason: String) diff --git a/example/basket/src/main/scala/org/finos/vuu/core/module/basket/service/BasketService.scala b/example/basket/src/main/scala/org/finos/vuu/core/module/basket/service/BasketService.scala index a83ad5de8..991aa248d 100644 --- a/example/basket/src/main/scala/org/finos/vuu/core/module/basket/service/BasketService.scala +++ b/example/basket/src/main/scala/org/finos/vuu/core/module/basket/service/BasketService.scala @@ -2,86 +2,90 @@ package org.finos.vuu.core.module.basket.service import com.typesafe.scalalogging.StrictLogging import org.finos.toolbox.time.Clock +import org.finos.vuu.core.module.basket.BasketConstants.Side import org.finos.vuu.core.module.basket.BasketModule -import org.finos.vuu.core.module.basket.BasketModule.{BasketConstituentTable, PriceStrategy, Sides} -import org.finos.vuu.core.module.basket.service.BasketService.counter +import org.finos.vuu.core.module.basket.BasketModule.{BasketConstituentTable} import org.finos.vuu.core.table.{DataTable, RowData, RowWithData, TableContainer} -import org.finos.vuu.net.rpc.{EditRpcHandler, RpcHandler} +import org.finos.vuu.net.rpc.RpcHandler import org.finos.vuu.net.{ClientSessionId, RequestContext} -import org.finos.vuu.order.oms.{NewOrder, OmsApi} +import org.finos.vuu.order.oms.OmsApi import org.finos.vuu.viewport._ - import java.util.concurrent.atomic.AtomicInteger -object BasketService{ - val counter = new AtomicInteger(0) +object BasketTradeId { + + private val counter: AtomicInteger = new AtomicInteger(0) + var current:String = "NoneInitalised" //this is for testing but only works if tests that use this doesnt run in parallel + def oneNew(user:String): String = { + val counterValue = counter.incrementAndGet() + current = user + "-" + "".padTo(5 - counterValue.toString.length, "0").mkString + counterValue + current + } } trait BasketServiceIF{ - def createBasket(basketKey: String, name: String)(ctx: RequestContext): ViewPortAction + def createBasket(basketId: String, name: String)(ctx: RequestContext): ViewPortAction } class BasketService(val table: DataTable, val tableContainer: TableContainer, val omsApi: OmsApi)(implicit clock: Clock) extends RpcHandler with BasketServiceIF with StrictLogging { import org.finos.vuu.core.module.basket.BasketModule.{BasketConstituentColumnNames => BC, BasketTradingColumnNames => BT, BasketTradingConstituentColumnNames => BTC} - private def getAndPadCounter(session: ClientSessionId): String = { - val counterValue = counter.incrementAndGet() - session.user + "-" + "".padTo(5 - counterValue.toString.length, "0").mkString + counterValue - } - - private def getConstituentsForBasketKey(key: String): List[RowData] = { + private def getConstituentsForSourceBasket(basketId: String): List[RowData] = { val table = tableContainer.getTable(BasketConstituentTable) val keys = table.primaryKeys.toList - keys.map( key => table.pullRow(key) ).filter(_.get(BC.BasketId).toString == key) + keys.map( key => table.pullRow(key) ).filter(_.get(BC.BasketId).toString == basketId) } - private def mkTradingConstituentRow(side: String, basketKey: String, instanceKey: String, - constituentKey: String, quantity: Long, weighting: Double, basketConsRow: RowData): RowWithData = { - RowWithData(constituentKey, Map(BTC.BasketId -> basketKey, - BTC.Ric -> basketConsRow.get(BC.Ric), - BTC.InstanceId -> instanceKey, - BTC.Quantity -> quantity, - BTC.InstanceIdRic -> constituentKey, - BTC.Description -> basketConsRow.get(BC.Description), - BTC.Side -> side, - BTC.Weighting -> weighting, - BTC.PriceStrategyId -> 2, - BTC.Algo -> -1, - BTC.OrderStatus -> OrderStates.PENDING, - BTC.FilledQty -> 0 - )) + private def mkTradingConstituentRow(side: String, sourceBasketId: String, basketTradeInstanceId: String, constituentKey: String, + quantity: Long, weighting: Double, basketConsRow: RowData): RowWithData = { + RowWithData( + constituentKey, + Map( + BTC.Ric -> basketConsRow.get(BC.Ric), + BTC.BasketId -> sourceBasketId, + BTC.InstanceId -> basketTradeInstanceId, + BTC.InstanceIdRic -> constituentKey, + BTC.Quantity -> quantity, + BTC.Description -> basketConsRow.get(BC.Description), + BTC.Side -> side, + BTC.Weighting -> weighting, + BTC.PriceStrategyId -> 2, + BTC.Algo -> -1, + BTC.OrderStatus -> OrderStates.PENDING, + BTC.FilledQty -> 0 + )) } - private def mkTradingBasketRow(instanceKey: String, basketKey: String): RowWithData = { - RowWithData(instanceKey, Map(BT.InstanceId -> instanceKey, BT.Status -> "OFF-MARKET", BT.BasketId -> basketKey, BT.BasketName -> instanceKey, BT.Side -> Sides.Buy, BT.Units -> 1)) + private def mkTradingBasketRow(sourceBasketId: String, basketTradeName: String, basketTradeInstanceId: String) = { + RowWithData(basketTradeInstanceId, Map(BT.InstanceId -> basketTradeInstanceId, BT.Status -> "OFF-MARKET", BT.BasketId -> sourceBasketId, BT.BasketName -> basketTradeName, BT.Side -> Side.Buy, BT.Units -> 1)) } - def createBasketFromRpc(basketKey: String, name: String)(ctx: RequestContext): ViewPortAction = { - createBasket(basketKey, name)(ctx) + def createBasketFromRpc(basketId: String, name: String)(ctx: RequestContext): ViewPortAction = { + createBasket(basketId, name)(ctx) } def createBasket(selection: ViewPortSelection, session: ClientSessionId): ViewPortAction = { - val basketKey = selection.rowKeyIndex.map({ case (key, _) => key }).toList.head + val basketId = selection.rowKeyIndex.map({ case (key, _) => key }).toList.head - val instanceKey = getAndPadCounter(session) + val instanceKey = BasketTradeId.oneNew(session.user) - createBasketInternal(basketKey, instanceKey, session) + createBasketInternal(basketId, instanceKey, instanceKey, session) } - def createBasket(basketKey: String, name: String)(ctx: RequestContext): ViewPortAction = { - createBasketInternal(basketKey, name, ctx.session) + def createBasket(basketId: String, name: String)(ctx: RequestContext): ViewPortAction = { + val basketTradeId = BasketTradeId.oneNew(ctx.session.user) + createBasketInternal(basketId, name, basketTradeId, ctx.session) } + private def createBasketInternal(sourceBasketId: String, basketTradeName: String, basketTradeId: String, sessionId: ClientSessionId) = { - private def createBasketInternal(basketKey: String, name: String, sessionId: ClientSessionId): ViewPortAction = { - - val constituents = getConstituentsForBasketKey(basketKey) + val constituents = getConstituentsForSourceBasket(sourceBasketId) tableContainer.getTable(BasketModule.BasketTradingTable) match { case table: DataTable => - table.processUpdate(name, mkTradingBasketRow(name, basketKey), clock.now()) + table.processUpdate(basketTradeId, mkTradingBasketRow(sourceBasketId, basketTradeName, basketTradeId), clock.now()) case null => logger.error("Cannot find the Basket Trading table.") } @@ -89,11 +93,11 @@ class BasketService(val table: DataTable, val tableContainer: TableContainer, va tableContainer.getTable(BasketModule.BasketTradingConstituentTable) match { case table: DataTable => constituents.foreach( rowData => { - val constituentKey = name + "." + rowData.get(BTC.Ric) + val constituentKey = basketTradeId + "." + rowData.get(BTC.Ric) val weighting = rowData.get(BTC.Weighting).asInstanceOf[Double] val quantity = (weighting * 100).asInstanceOf[Long] val side = rowData.get(BTC.Side).toString - table.processUpdate(constituentKey, mkTradingConstituentRow(side, basketKey, name, constituentKey, quantity, weighting, rowData), clock.now()) + table.processUpdate(constituentKey, mkTradingConstituentRow(side, sourceBasketId, basketTradeId, constituentKey, quantity, weighting, rowData), clock.now()) }) case null => logger.error("Cannot find the Basket Trading Constituent.") diff --git a/example/basket/src/main/scala/org/finos/vuu/core/module/basket/service/BasketTradingConstituentJoinService.scala b/example/basket/src/main/scala/org/finos/vuu/core/module/basket/service/BasketTradingConstituentJoinService.scala index 6a6685641..41d113792 100644 --- a/example/basket/src/main/scala/org/finos/vuu/core/module/basket/service/BasketTradingConstituentJoinService.scala +++ b/example/basket/src/main/scala/org/finos/vuu/core/module/basket/service/BasketTradingConstituentJoinService.scala @@ -3,35 +3,121 @@ package org.finos.vuu.core.module.basket.service import com.typesafe.scalalogging.StrictLogging import org.finos.toolbox.time.Clock import org.finos.vuu.api.JoinTableDef -import org.finos.vuu.core.module.basket.BasketModule.BasketTradingConstituentColumnNames.InstanceIdRic -import org.finos.vuu.core.table.{DataTable, JoinTable, RowWithData, TableContainer} -import org.finos.vuu.net.ClientSessionId +import org.finos.vuu.core.module.basket.BasketConstants.Side +import org.finos.vuu.core.module.basket.BasketModule +import org.finos.vuu.core.module.basket.BasketModule.{BasketConstituentColumnNames => BCColumnName, BasketTradingColumnNames => BTColumnName, BasketTradingConstituentColumnNames => ColumnName} +import org.finos.vuu.core.module.basket.result.ErrorReason +import org.finos.vuu.core.table.{DataTable, JoinTable, RowData, RowWithData, TableContainer} import org.finos.vuu.net.rpc.{EditRpcHandler, RpcHandler} +import org.finos.vuu.net.{ClientSessionId, RequestContext} import org.finos.vuu.viewport._ -class BasketTradingConstituentJoinService(val table: DataTable, val tableContainer: TableContainer)(implicit clock: Clock) extends RpcHandler with EditRpcHandler with StrictLogging { +import scala.util.control.NonFatal - def onDeleteRow(key: String, vp: ViewPort, session: ClientSessionId): ViewPortEditAction = { - ViewPortEditSuccess() + +trait BasketTradingConstituentJoinServiceIF extends EditRpcHandler { + def setSell(selection: ViewPortSelection, session: ClientSessionId): ViewPortAction + + def setBuy(selection: ViewPortSelection, session: ClientSessionId): ViewPortAction + + def addConstituent(ric: String)(ctx: RequestContext): ViewPortAction +} + +class BasketTradingConstituentJoinService(val table: DataTable, val tableContainer: TableContainer)(implicit clock: Clock) extends BasketTradingConstituentJoinServiceIF with RpcHandler with StrictLogging { + + override def menuItems(): ViewPortMenu = ViewPortMenu("Direction", + new SelectionViewPortMenuItem("Set Sell", "", this.setSell, "SET_SELECTION_SELL"), + new SelectionViewPortMenuItem("Set Buy", "", this.setBuy, "SET_SELECTION_Buy"), + ) + + override def deleteRowAction(): ViewPortDeleteRowAction = ViewPortDeleteRowAction("", this.onDeleteRow) + + override def deleteCellAction(): ViewPortDeleteCellAction = ViewPortDeleteCellAction("", this.onDeleteCell) + + override def addRowAction(): ViewPortAddRowAction = ViewPortAddRowAction("", this.onAddRow) + + override def editCellAction(): ViewPortEditCellAction = ViewPortEditCellAction("", this.onEditCell) + + override def editRowAction(): ViewPortEditRowAction = ViewPortEditRowAction("", this.onEditRow) + + override def onFormSubmit(): ViewPortFormSubmitAction = ViewPortFormSubmitAction("", this.onFormSubmit) + + override def onFormClose(): ViewPortFormCloseAction = ViewPortFormCloseAction("", this.onFormClose) + + def setSell(selection: ViewPortSelection, session: ClientSessionId): ViewPortAction = { + updateSelected(selection, Map(ColumnName.Side -> Side.Sell)) } - def onDeleteCell(key: String, column: String, vp: ViewPort, session: ClientSessionId): ViewPortEditAction = { - ViewPortEditSuccess() + def setBuy(selection: ViewPortSelection, session: ClientSessionId): ViewPortAction = { + updateSelected(selection, Map(ColumnName.Side -> Side.Buy)) } - def onAddRow(key: String, data: Map[String, Any], vp: ViewPort, session: ClientSessionId): ViewPortEditAction = { - ViewPortEditSuccess() + //this is RCP call and method name is part of contract with UI + def addConstituent(ric: String)(ctx: RequestContext): ViewPortAction = { + if (table.size() == 0) + ViewPortEditFailure(s"Failed to add constituents to ${table.name} as adding row to empty table is currently not supported") + else { + val existingConstituentRow = table.pullRow(table.primaryKeys.head) + val tradeId = existingConstituentRow.get(ColumnName.InstanceId).asInstanceOf[String] + + val tradeRow = tableContainer.getTable(BasketModule.BasketTradingTable).pullRow(tradeId) + val basketId = tradeRow.get(BTColumnName.BasketId).asInstanceOf[String] + val tradeUnit = tradeRow.get(BTColumnName.Units).asInstanceOf[Int] + + val basketConstituentRows = getConstituentsWith(ric) //todo what to do when multiple result? + val description = + if(basketConstituentRows.nonEmpty) + basketConstituentRows.head.get(BCColumnName.Description).asInstanceOf[String] + else "" + + val newRow = mkTradingConstituentRow( + basketTradeInstanceId = tradeId, + sourceBasketId = basketId, + tradeUnit = tradeUnit, + ric = ric, + description = description) + + //todo should we guard against adding row for ric that already exist? + updateJoinTable(Array(newRow)) match { + case Right(_) => ViewPortRpcSuccess() + case Left(errorReason) => + ViewPortRpcFailure(errorReason.reason) + } + } + } + + private def getConstituentsWith(ric: String): List[RowData] = { + val table = tableContainer.getTable(BasketModule.BasketConstituentTable) + val keys = table.primaryKeys.toList + keys.map(key => table.pullRow(key)).filter(_.get(BCColumnName.Ric).toString == ric) } private def onEditCell(key: String, columnName: String, data: Any, vp: ViewPort, session: ClientSessionId): ViewPortEditAction = { - val joinTable = vp.table.asTable.asInstanceOf[JoinTable] - val baseTableDef = joinTable.getTableDef.asInstanceOf[JoinTableDef].baseTable - joinTable.sourceTables.get(baseTableDef.name) match { - case Some(table: DataTable) => - table.processUpdate(key, RowWithData(key, Map(InstanceIdRic -> key, columnName -> data)), clock.now()) - ViewPortEditSuccess() - case None => - ViewPortEditFailure("Could not find base table") + try { + getBaseTable() match { + case Some(baseTable: DataTable) => + columnName match { + case ColumnName.Weighting | ColumnName.LimitPrice => + val doubleValue = convertToDouble(data) + baseTable.processUpdate(key, RowWithData(key, Map(ColumnName.InstanceIdRic -> key, columnName -> doubleValue)), clock.now()) + case _ => baseTable.processUpdate(key, RowWithData(key, Map(ColumnName.InstanceIdRic -> key, columnName -> data)), clock.now()) + } + ViewPortEditSuccess() + case None => + ViewPortEditFailure("Could not find base table for basket trading constituent join ") + } + } catch { + case NonFatal(t) => ViewPortEditFailure(s"Could not update $columnName. $t") + } + } + + private def convertToDouble(data: Any): Double = { + data match { + case decimalValue: java.math.BigDecimal => + decimalValue.doubleValue + case integer: java.lang.Integer => integer.toDouble + case int: Int => int.toDouble + case _ => data.asInstanceOf[Double] } } @@ -41,19 +127,31 @@ class BasketTradingConstituentJoinService(val table: DataTable, val tableContain ViewPortEditSuccess() } + private def onDeleteRow(key: String, vp: ViewPort, session: ClientSessionId): ViewPortEditAction = { + ViewPortEditSuccess() + } + + private def onDeleteCell(key: String, column: String, vp: ViewPort, session: ClientSessionId): ViewPortEditAction = { + ViewPortEditSuccess() + } + + private def onAddRow(key: String, data: Map[String, Any], vp: ViewPort, session: ClientSessionId): ViewPortEditAction = { + ViewPortEditSuccess() + } + private def onFormSubmit(vp: ViewPort, session: ClientSessionId): ViewPortAction = { -// val table = vp.table.asTable -// val primaryKeys = table.primaryKeys -// val headKey = primaryKeys.head -// val sequencerNumber = table.pullRow(headKey).get("sequenceNumber").asInstanceOf[Int].toLong -// -// if (sequencerNumber > 0) { -// logger.info("I would now send this fix seq to a fix engine to reset, we're all good:" + sequencerNumber) -// CloseDialogViewPortAction(vp.id) -// } else { -// logger.error("Seq number not set, returning error") -// ViewPortEditFailure("Sequencer number has not been set.") -// } + // val table = vp.table.asTable + // val primaryKeys = table.primaryKeys + // val headKey = primaryKeys.head + // val sequencerNumber = table.pullRow(headKey).get("sequenceNumber").asInstanceOf[Int].toLong + // + // if (sequencerNumber > 0) { + // logger.info("I would now send this fix seq to a fix engine to reset, we're all good:" + sequencerNumber) + // CloseDialogViewPortAction(vp.id) + // } else { + // logger.error("Seq number not set, returning error") + // ViewPortEditFailure("Sequencer number has not been set.") + // } CloseDialogViewPortAction(vp.id) } @@ -61,32 +159,57 @@ class BasketTradingConstituentJoinService(val table: DataTable, val tableContain CloseDialogViewPortAction(vp.id) } + private def getBaseTable(): Option[DataTable] = { + val joinTable = table.asTable.asInstanceOf[JoinTable] + val baseTableDef = joinTable.getTableDef.asInstanceOf[JoinTableDef].baseTable + joinTable.sourceTables.get(baseTableDef.name) + } - override def deleteRowAction(): ViewPortDeleteRowAction = ViewPortDeleteRowAction("", this.onDeleteRow) - - override def deleteCellAction(): ViewPortDeleteCellAction = ViewPortDeleteCellAction("", this.onDeleteCell) - - override def addRowAction(): ViewPortAddRowAction = ViewPortAddRowAction("", this.onAddRow) - - override def editCellAction(): ViewPortEditCellAction = ViewPortEditCellAction("", this.onEditCell) - - override def editRowAction(): ViewPortEditRowAction = ViewPortEditRowAction("", this.onEditRow) - - override def onFormSubmit(): ViewPortFormSubmitAction = ViewPortFormSubmitAction("", this.onFormSubmit) - - override def onFormClose(): ViewPortFormCloseAction = ViewPortFormCloseAction("", this.onFormClose) - - def setSell(selection: ViewPortSelection, session: ClientSessionId): ViewPortAction = { - ViewPortEditSuccess() + private def updateJoinTable(rows: Array[RowWithData]): Either[ErrorReason, Unit] = { + getBaseTable() match { + case Some(baseTable: DataTable) => + rows.foreach(row => + baseTable.processUpdate(row.key, row, clock.now()) + ) + Right() + case None => + Left(ErrorReason(s"Could not find base table for ${table.name}")) + } } - def setBuy(selection: ViewPortSelection, session: ClientSessionId): ViewPortAction = { - ViewPortEditSuccess() + private def updateSelected(selection: ViewPortSelection, updates: Map[String, Any]): ViewPortAction = { + val selectedKeys = selection.rowKeyIndex.map({ case (key, _) => key }).toList + val updateRows = selectedKeys.map(key => { + //require source table primary key for join table updates + val sourceTableKey = Map(ColumnName.InstanceIdRic -> key) + RowWithData(key, sourceTableKey ++ updates) + }) + + updateJoinTable(updateRows.toArray) match { + case Right(_) => NoAction() + case Left(errorReason) => + logger.info(s"Could not update selection values${errorReason.reason}") + NoAction() + } } - override def menuItems(): ViewPortMenu = ViewPortMenu("Direction", - new SelectionViewPortMenuItem("Set Sell", "", this.setSell, "SET_SELECTION_SELL"), - new SelectionViewPortMenuItem("Set Buy", "", this.setBuy, "SET_SELECTION_Buy") - ) + private def mkTradingConstituentRow(basketTradeInstanceId: String, sourceBasketId: String, tradeUnit: Int, ric: String, description: String): RowWithData = { + val constituentKey = s"$basketTradeInstanceId.$ric" + val weighting: Double = 0.1 + RowWithData( + constituentKey, + Map( + ColumnName.Ric -> ric, + ColumnName.BasketId -> sourceBasketId, + ColumnName.InstanceId -> basketTradeInstanceId, + ColumnName.InstanceIdRic -> constituentKey, + ColumnName.Quantity -> (weighting * 100).asInstanceOf[Long], + ColumnName.Description -> description, + ColumnName.Side -> Side.Buy, + ColumnName.Weighting -> weighting, + ColumnName.PriceStrategyId -> 2, + ColumnName.Algo -> -1, + )) + } } diff --git a/example/basket/src/main/scala/org/finos/vuu/core/module/basket/service/BasketTradingService.scala b/example/basket/src/main/scala/org/finos/vuu/core/module/basket/service/BasketTradingService.scala index 488935d19..8a805e9bc 100644 --- a/example/basket/src/main/scala/org/finos/vuu/core/module/basket/service/BasketTradingService.scala +++ b/example/basket/src/main/scala/org/finos/vuu/core/module/basket/service/BasketTradingService.scala @@ -3,34 +3,35 @@ package org.finos.vuu.core.module.basket.service 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} +import org.finos.vuu.core.module.basket.BasketConstants.Side +import org.finos.vuu.core.module.basket.BasketModule.{BasketTradingConstituentTable} +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 { def sendToMarket(basketInstanceId: String)(ctx: RequestContext): ViewPortAction + + def takeOffMarket(basketInstanceId: String)(ctx: RequestContext): ViewPortAction } + class BasketTradingService(val table: DataTable, val tableContainer: TableContainer, val omsApi: OmsApi)(implicit clock: Clock) extends RpcHandler with BasketTradingServiceIF with StrictLogging { import org.finos.vuu.core.module.basket.BasketModule.{BasketTradingColumnNames => BT, BasketTradingConstituentColumnNames => BTC} + /** - * 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 => { @@ -47,44 +48,85 @@ 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("Changing cell value for key:" + key + "(" + columnName + ":" + data + ")") - table.processUpdate(key, RowWithData(key, Map(BT.InstanceId -> key, columnName -> data)), clock.now()) - - columnName match { - case BT.Units => - val constituentTable = tableContainer.getTable(BasketTradingConstituentTable) - val constituents = constituentTable.primaryKeys.map(key => constituentTable.pullRow(key)).filter(_.get(BTC.InstanceId) == key) - constituents.foreach(row => { - val unitsAsInt = data.asInstanceOf[Int] - val weighting = row.get(BTC.Weighting) - val quantity = (weighting.asInstanceOf[Double] * unitsAsInt).toLong - constituentTable.processUpdate(row.key(), RowWithData(row.key(), Map(BTC.InstanceIdRic -> row.key(), BTC.Quantity -> quantity)), clock.now()) - }) - case BT.Side => - val constituentTable = tableContainer.getTable(BasketTradingConstituentTable) - val constituents = constituentTable.primaryKeys.map(key => constituentTable.pullRow(key)).filter(_.get(BTC.InstanceId) == key) - val side = data.asInstanceOf[String] - constituents.foreach(row => { - val newSide = row.get(BTC.Side) match { - case Sides.Buy => Sides.Sell - case _ => Sides.Buy - } - constituentTable.processUpdate(row.key(), RowWithData(row.key(), Map(BTC.InstanceIdRic -> row.key(), BTC.Side -> newSide)), clock.now()) - }) - - case _ => - } + logger.info("Change requested for cell value for key:" + key + "(" + columnName + ":" + data + ")") + val currentData = getRowData(key, columnName) + if (currentData == data) { + logger.info("Current cell value is same and therefore skipping update for key:" + key + "(" + columnName + ":" + data + ")") + } + else { + logger.info("Changing cell value for key:" + key + "(" + columnName + ":" + data + ")") + table.processUpdate(key, RowWithData(key, Map(BT.InstanceId -> key, columnName -> data)), clock.now()) + + columnName match { + case BT.Units => + val constituentTable = tableContainer.getTable(BasketTradingConstituentTable) + val constituents = constituentTable.primaryKeys.map(key => constituentTable.pullRow(key)).filter(_.get(BTC.InstanceId) == key) + constituents.foreach(row => { + val unitsAsInt = data.asInstanceOf[Int] + val weighting = row.get(BTC.Weighting) + val quantity = (weighting.asInstanceOf[Double] * unitsAsInt).toLong + constituentTable.processUpdate(row.key(), RowWithData(row.key(), Map(BTC.InstanceIdRic -> row.key(), BTC.Quantity -> quantity)), clock.now()) + }) + case BT.Side => + val constituentTable = tableContainer.getTable(BasketTradingConstituentTable) + val constituents = constituentTable.primaryKeys.map(key => constituentTable.pullRow(key)).filter(_.get(BTC.InstanceId) == key) + constituents.foreach(row => { + val newSide = row.get(BTC.Side) match { + case Side.Buy => Side.Sell + case _ => Side.Buy + } + constituentTable.processUpdate(row.key(), RowWithData(row.key(), Map(BTC.InstanceIdRic -> row.key(), BTC.Side -> newSide)), clock.now()) + }) + case _ => + } + } ViewPortEditSuccess() } + private def getRowData(rowKey: String, columnName: String): Any = { + val row = table.pullRow(rowKey, ViewPortColumnCreator.create(table, List(columnName))) + row.get(columnName) + } + override def deleteRowAction(): ViewPortDeleteRowAction = ??? override def deleteCellAction(): ViewPortDeleteCellAction = ??? @@ -98,6 +140,4 @@ class BasketTradingService(val table: DataTable, val tableContainer: TableContai override def onFormSubmit(): ViewPortFormSubmitAction = ??? override def onFormClose(): ViewPortFormCloseAction = ??? - - } diff --git a/example/basket/src/test/scala/org/finos/vuu/core/module/basket/BasketConstituentMutateTest.scala b/example/basket/src/test/scala/org/finos/vuu/core/module/basket/BasketConstituentMutateTest.scala new file mode 100644 index 000000000..e3493813b --- /dev/null +++ b/example/basket/src/test/scala/org/finos/vuu/core/module/basket/BasketConstituentMutateTest.scala @@ -0,0 +1,113 @@ +package org.finos.vuu.core.module.basket + +import org.finos.toolbox.jmx.{MetricsProvider, MetricsProviderImpl} +import org.finos.toolbox.lifecycle.LifecycleContainer +import org.finos.toolbox.time.{Clock, TestFriendlyClock} +import org.finos.vuu.api.JoinTableDef +import org.finos.vuu.core.module.TableDefContainer +import org.finos.vuu.core.module.basket.TestHelper.TestDataFactory +import org.finos.vuu.core.module.basket.service.BasketTradingConstituentJoinServiceIF +import org.finos.vuu.core.module.price.PriceModule +import org.finos.vuu.core.table.{DataTable, JoinTable, RowWithData, TableContainer} +import org.finos.vuu.order.oms.OmsApi +import org.finos.vuu.test.VuuServerTestCase +import org.finos.vuu.util.table.TableAsserts.assertVpEq +import org.finos.vuu.viewport.ViewPortSelection +import org.scalatest.Ignore +import org.scalatest.prop.Tables.Table + +@Ignore //working progress +class BasketConstituentMutateTest extends VuuServerTestCase { + + import BasketModule._ + implicit val clock: Clock = new TestFriendlyClock(10001L) + implicit val lifecycle: LifecycleContainer = new LifecycleContainer() + implicit val tableDefContainer: TableDefContainer = new TableDefContainer(Map()) + implicit val metricsProvider: MetricsProvider = new MetricsProviderImpl + val omsApi = OmsApi() + Feature("Basket Trading Constituent Join Service Test Case") { + Scenario("Update selected trade constituent sides when menu action is triggered") { + withVuuServer(PriceModule(), BasketModule(omsApi)) { + vuuServer => + + vuuServer.login("testUser", "testToken") + + val basketName = "test_basket1" + val tradeId = GivenBasketTrade(vuuServer.tableContainer, basketName, "BUY") + GivenPrices(vuuServer.tableContainer, List(("VOD.L", 1.1, 1.4), ("BP.L", 2.1, 2.4))) + GivenBasketTradeConstituentsJoin(vuuServer.tableContainer, tradeId, Map(("VOD.L" -> "BUY"), ("BP.L" -> "SELL"))) + + val vpBasketTrading = vuuServer.createViewPort(BasketModule.NAME, BasketTradingTable) + val vpBasketTradingConsJoin = vuuServer.createViewPort(BasketModule.NAME, BasketTradingConstituentJoin) + vuuServer.runOnce() + + When("we select multiple constituent rows ") + vpBasketTradingConsJoin.setSelection(Array(1, 2)) + + And("select set sell context menu") + val basketTradingConstituentJoinService = vuuServer.getViewPortRpcServiceProxy[BasketTradingConstituentJoinServiceIF](vpBasketTradingConsJoin) + val selection = vpBasketTradingConsJoin.getSelection + val vpSelection = ViewPortSelection(selection, vpBasketTradingConsJoin) + basketTradingConstituentJoinService.setSell(vpSelection, vuuServer.session) + vuuServer.runOnce() + + Then("get all the updates that have occurred for all view ports from the outbound queue") + val updates = combineQs(vpBasketTrading) + + And("assert only selected constituent has changed set side to sell") + + + assertVpEq(filterByVp(vpBasketTradingConsJoin, updates)) { + Table( + ("quantity", "side", "instanceId", "instanceIdRic", "basketId", "ric", "description", "notionalUsd", "notionalLocal", "venue", "algo", "algoParams", "pctFilled", "weighting", "priceSpread", "limitPrice", "priceStrategyId", "bid", "ask", "filledQty", "orderStatus"), + (10L, "BUY", "testUser-00001", "testUser-00001.BP.L", ".FTSE", "BP.L", "Beyond Petroleum", null, null, null, -1, null, null, 0.1, null, null, 2, 2.1, 2.4, 0, "PENDING"), + (10L, "SELL", "testUser-00001", "testUser-00001.BT.L", ".FTSE", "BT.L", "British Telecom", null, null, null, -1, null, null, 0.1, null, null, 2, null, null, 0, "PENDING"), + (10L, "SELL", "testUser-00001", "testUser-00001.VOD.L", ".FTSE", "VOD.L", "Vodafone", null, null, null, -1, null, null, 0.1, null, null, 2, 1.1, 1.4, 0, "PENDING") + ) + } + } + } + } + + def uuid = java.util.UUID.randomUUID.toString + + def GivenBasketTrade(tableContainer: TableContainer, basketName: String, side: String): String = { + val table = tableContainer.getTable(BasketModule.BasketTradingTable) + val rowKey = s"$uuid.$basketName" + table.processUpdate(rowKey, TestDataFactory.createBasketTradingRow(rowKey, basketName, side), clock.now()) + rowKey + } + + def GivenBasketTradeConstituentsJoin(tableContainer: TableContainer, tradeId: String, ricToSide: Map[String, String]): Unit = { + val table = tableContainer.getTable(BasketModule.BasketTradingConstituentJoin) + ricToSide.foreach(item => { + val (ric, side) = item + val row = TestDataFactory.createBasketTradingConstituentJoinRow(tradeId, ric, side) + updateJoinTable(table, row) + }) + } + + def updateJoinTable(table: DataTable, row: RowWithData): Unit = { + val joinTable = table.asTable.asInstanceOf[JoinTable] + val baseTableDef = joinTable.getTableDef.asInstanceOf[JoinTableDef].baseTable + joinTable.sourceTables.get(baseTableDef.name) match { + case Some(table: DataTable) => + table.processUpdate(row.key, row, clock.now()) + case None => + //log and throw? + } + } + + def GivenPrices(tableContainer: TableContainer, prices: List[(String, Double, Double)]) = { + val table = tableContainer.getTable("prices") + for ((ric, bid, ask) <- prices) { + val rowKey = s"$uuid" + table.processUpdate(rowKey, TestDataFactory.createPricesRow(rowKey, ric, bid, ask), clock.now()) + } + } + + //todo having issue importing side constant + //todo introduce case class BasketTrading? + + +} diff --git a/example/basket/src/test/scala/org/finos/vuu/core/module/basket/BasketCreateTest.scala b/example/basket/src/test/scala/org/finos/vuu/core/module/basket/BasketCreateTest.scala index c0a31d6d2..9451fa78f 100644 --- a/example/basket/src/test/scala/org/finos/vuu/core/module/basket/BasketCreateTest.scala +++ b/example/basket/src/test/scala/org/finos/vuu/core/module/basket/BasketCreateTest.scala @@ -5,7 +5,7 @@ import org.finos.toolbox.lifecycle.LifecycleContainer import org.finos.toolbox.time.{Clock, TestFriendlyClock} import org.finos.vuu.api.ViewPortDef import org.finos.vuu.core.module.TableDefContainer -import org.finos.vuu.core.module.basket.service.{BasketServiceIF, BasketTradingServiceIF} +import org.finos.vuu.core.module.basket.service.{BasketServiceIF, BasketTradeId, BasketTradingServiceIF} import org.finos.vuu.core.module.price.PriceModule import org.finos.vuu.core.table.TableTestHelper.combineQs import org.finos.vuu.order.oms.OmsApi @@ -54,21 +54,21 @@ class BasketCreateTest extends VuuServerTestCase { val basketService = vuuServer.getViewPortRpcServiceProxy[BasketServiceIF](viewportBasket) - val action = basketService.createBasket(".FTSE", "chris-001")(vuuServer.requestContext) + val action = basketService.createBasket(".FTSE", "TestBasket")(vuuServer.requestContext) val viewportBasketTrading = vuuServer.createViewPort(BasketModule.NAME, BasketTradingTable) val basketTradingService = vuuServer.getViewPortRpcServiceProxy[BasketTradingServiceIF](viewportBasketTrading) //CJS: I don't like this forced cast, need to look at that a bit - basketTradingService.editCellAction().func("chris-001", BT.Units, 100.asInstanceOf[Object], viewportBasketTrading, vuuServer.session) + basketTradingService.editCellAction().func(BasketTradeId.current, BT.Units, 100.asInstanceOf[Object], viewportBasketTrading, vuuServer.session) vuuServer.runOnce() assertVpEq(combineQsForVp(viewportBasketTrading)) { Table( ("basketId", "instanceId", "basketName", "units", "status", "filledPct", "totalNotionalUsd", "totalNotional", "fxRateToUsd", "side"), - (".FTSE", "chris-001", "chris-001", 100, "OFF-MARKET", null, null, null, null, "Buy") + (".FTSE", BasketTradeId.current, "TestBasket", 100, "OFF-MARKET", null, null, null, null, "BUY") ) } } diff --git a/example/basket/src/test/scala/org/finos/vuu/core/module/basket/BasketMutateOffMarketTest.scala b/example/basket/src/test/scala/org/finos/vuu/core/module/basket/BasketMutateOffMarketTest.scala index ead49b52c..c60cb88d1 100644 --- a/example/basket/src/test/scala/org/finos/vuu/core/module/basket/BasketMutateOffMarketTest.scala +++ b/example/basket/src/test/scala/org/finos/vuu/core/module/basket/BasketMutateOffMarketTest.scala @@ -5,30 +5,25 @@ import org.finos.toolbox.lifecycle.LifecycleContainer import org.finos.toolbox.time.{Clock, TestFriendlyClock} import org.finos.vuu.api.ViewPortDef import org.finos.vuu.core.module.TableDefContainer -import org.finos.vuu.core.module.basket.service.{BasketServiceIF, BasketTradingServiceIF} +import org.finos.vuu.core.module.basket.BasketModule.{BasketColumnNames => B, BasketConstituentColumnNames => BC} +import org.finos.vuu.core.module.basket.service.{BasketServiceIF, BasketTradeId, BasketTradingConstituentJoinServiceIF, BasketTradingServiceIF} import org.finos.vuu.core.module.price.PriceModule import org.finos.vuu.order.oms.OmsApi -import org.finos.vuu.test.VuuServerTestCase +import org.finos.vuu.test.{TestVuuServer, VuuServerTestCase} import org.finos.vuu.util.table.TableAsserts.assertVpEq import org.scalatest.prop.Tables.Table class BasketMutateOffMarketTest extends VuuServerTestCase { - import BasketTestCaseHelper._ - Feature("Basket Service Test Case") { - Scenario("Check the creation of the baskets and constituents") { - + import BasketModule._ + import BasketTestCaseHelper._ implicit val clock: Clock = new TestFriendlyClock(10001L) implicit val lifecycle: LifecycleContainer = new LifecycleContainer() implicit val tableDefContainer: TableDefContainer = new TableDefContainer(Map()) implicit val metricsProvider: MetricsProvider = new MetricsProviderImpl - val omsApi = OmsApi() - - import BasketModule._ - withVuuServer(PriceModule(), BasketModule(omsApi)) { vuuServer => @@ -65,15 +60,16 @@ class BasketMutateOffMarketTest extends VuuServerTestCase { assertVpEq(combineQsForVp(vpConstituent)) { Table( ("ricBasketId", "ric", "basketId", "weighting", "lastTrade", "change", "volume", "description", "side"), - ("BP.L.FTSE", "BP.L", ".FTSE", 0.1, null, null, null, "Beyond Petroleum", "Buy"), - ("BT.L.FTSE", "BT.L", ".FTSE", 0.1, null, null, null, "British Telecom", "Sell"), - ("VOD.L.FTSE", "VOD.L", ".FTSE", 0.1, null, null, null, "Vodafone", "Buy") + ("BP.L.FTSE", "BP.L", ".FTSE", 0.1, null, null, null, "Beyond Petroleum", "BUY"), + ("BT.L.FTSE", "BT.L", ".FTSE", 0.1, null, null, null, "British Telecom", "SELL"), + ("VOD.L.FTSE", "VOD.L", ".FTSE", 0.1, null, null, null, "Vodafone", "BUY") ) } Then("Get the Basket RPC Service and call create basket") val basketService = vuuServer.getViewPortRpcServiceProxy[BasketServiceIF](vpBasket) - basketService.createBasket(".FTSE", "chris-001")(vuuServer.requestContext) + basketService.createBasket(".FTSE", "MyCustomBasket")(vuuServer.requestContext) + val basketTradeInstanceId = BasketTradeId.current val vpBasketTrading = vuuServer.createViewPort(BasketModule.NAME, BasketTradingTable) @@ -84,7 +80,7 @@ class BasketMutateOffMarketTest extends VuuServerTestCase { assertVpEq(combineQsForVp(vpBasketTrading)) { Table( ("instanceId", "basketId", "basketName", "status", "units", "filledPct", "fxRateToUsd", "totalNotional", "totalNotionalUsd", "side"), - ("chris-001", ".FTSE", "chris-001", "OFF-MARKET", 1, null, null, null, null, "Buy") + (basketTradeInstanceId, ".FTSE", "MyCustomBasket", "OFF-MARKET", 1, null, null, null, null, "BUY") ) } @@ -95,16 +91,16 @@ class BasketMutateOffMarketTest extends VuuServerTestCase { assertVpEq(combineQsForVp(vpBasketTradingCons)) { Table( ("quantity", "side", "instanceId", "instanceIdRic", "basketId", "ric", "description", "notionalUsd", "notionalLocal", "venue", "algo", "algoParams", "pctFilled", "weighting", "priceSpread", "limitPrice", "priceStrategyId", "filledQty", "orderStatus"), - (10L, "Buy", "chris-001", "chris-001.BP.L", ".FTSE", "BP.L", "Beyond Petroleum", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING"), - (10L, "Sell", "chris-001", "chris-001.BT.L", ".FTSE", "BT.L", "British Telecom", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING"), - (10L, "Buy", "chris-001", "chris-001.VOD.L", ".FTSE", "VOD.L", "Vodafone", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING") + (10L, "BUY", basketTradeInstanceId, s"$basketTradeInstanceId.BP.L", ".FTSE", "BP.L", "Beyond Petroleum", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING"), + (10L, "SELL", basketTradeInstanceId, s"$basketTradeInstanceId.BT.L", ".FTSE", "BT.L", "British Telecom", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING"), + (10L, "BUY", basketTradeInstanceId, s"$basketTradeInstanceId.VOD.L", ".FTSE", "VOD.L", "Vodafone", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING") ) } val basketTradingService = vuuServer.getViewPortRpcServiceProxy[BasketTradingServiceIF](vpBasketTrading) When("we edit the side of the parent basket") - basketTradingService.editCellAction().func("chris-001", "side", "Sell", vpBasketTrading, vuuServer.session) + basketTradingService.editCellAction().func(basketTradeInstanceId, "side", "SELL", vpBasketTrading, vuuServer.session) Then("get all the updates that have occurred for all view ports from the outbound queue") val updates = combineQs(vpBasketTrading) @@ -113,34 +109,141 @@ class BasketMutateOffMarketTest extends VuuServerTestCase { assertVpEq(filterByVp(vpBasketTrading, updates)) { Table( ("instanceId", "basketId", "basketName", "status", "units", "filledPct", "fxRateToUsd", "totalNotional", "totalNotionalUsd", "side"), - ("chris-001", ".FTSE", "chris-001", "OFF-MARKET", 1, null, null, null, null, "Sell") + (basketTradeInstanceId, ".FTSE", "MyCustomBasket", "OFF-MARKET", 1, null, null, null, null, "SELL") ) } //vuuServer.runOnce() And("assert the basket trading constituent table has flipped sides also") - assertVpEq(filterByVp(vpBasketTradingCons,updates)) { + assertVpEq(filterByVp(vpBasketTradingCons, updates)) { Table( ("quantity", "side", "instanceId", "instanceIdRic", "basketId", "ric", "description", "notionalUsd", "notionalLocal", "venue", "algo", "algoParams", "pctFilled", "weighting", "priceSpread", "limitPrice", "priceStrategyId", "filledQty", "orderStatus"), - (10L, "Sell", "chris-001", "chris-001.BP.L", ".FTSE", "BP.L", "Beyond Petroleum", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING"), - (10L, "Buy", "chris-001", "chris-001.BT.L", ".FTSE", "BT.L", "British Telecom", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING"), - (10L, "Sell", "chris-001", "chris-001.VOD.L", ".FTSE", "VOD.L", "Vodafone", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING") + (10L, "SELL", basketTradeInstanceId, s"$basketTradeInstanceId.BP.L", ".FTSE", "BP.L", "Beyond Petroleum", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING"), + (10L, "BUY", basketTradeInstanceId, s"$basketTradeInstanceId.BT.L", ".FTSE", "BT.L", "British Telecom", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING"), + (10L, "SELL", basketTradeInstanceId, s"$basketTradeInstanceId.VOD.L", ".FTSE", "VOD.L", "Vodafone", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING") ) } When("we edit the units of the parent basket") - basketTradingService.editCellAction().func("chris-001", "units", 1000L.asInstanceOf[Object], vpBasketTrading, vuuServer.session) + basketTradingService.editCellAction().func(basketTradeInstanceId, "units", 1000L.asInstanceOf[Object], vpBasketTrading, vuuServer.session) And("assert the basket trading constituent table has increased the units") assertVpEq(filterByVp(vpBasketTradingCons, combineQs(vpBasketTrading))) { Table( ("quantity", "side", "instanceId", "instanceIdRic", "basketId", "ric", "description", "notionalUsd", "notionalLocal", "venue", "algo", "algoParams", "pctFilled", "weighting", "priceSpread", "limitPrice", "priceStrategyId", "filledQty", "orderStatus"), - (100L, "Sell", "chris-001", "chris-001.BP.L", ".FTSE", "BP.L", "Beyond Petroleum", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING"), - (100L, "Buy", "chris-001", "chris-001.BT.L", ".FTSE", "BT.L", "British Telecom", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING"), - (100L, "Sell", "chris-001", "chris-001.VOD.L", ".FTSE", "VOD.L", "Vodafone", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING") + (100L, "SELL", basketTradeInstanceId, s"$basketTradeInstanceId.BP.L", ".FTSE", "BP.L", "Beyond Petroleum", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING"), + (100L, "BUY", basketTradeInstanceId, s"$basketTradeInstanceId.BT.L", ".FTSE", "BT.L", "British Telecom", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING"), + (100L, "SELL", basketTradeInstanceId, s"$basketTradeInstanceId.VOD.L", ".FTSE", "VOD.L", "Vodafone", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING") ) } } } + + Scenario("Check updating trade basket side with no change does not update constituents side") { + import BasketModule._ + implicit val clock: Clock = new TestFriendlyClock(10001L) + implicit val lifecycle: LifecycleContainer = new LifecycleContainer() + implicit val tableDefContainer: TableDefContainer = new TableDefContainer(Map()) + implicit val metricsProvider: MetricsProvider = new MetricsProviderImpl + + val omsApi = OmsApi() + withVuuServer(PriceModule(), BasketModule(omsApi)) { + vuuServer => + + vuuServer.login("testUser", "testToken2") + + GivenBasketTradeExist(vuuServer, ".FTSE", "MyCustomBasket") + val basketTradeInstanceId = BasketTradeId.current + + val vpBasketTrading = vuuServer.createViewPort(BasketModule.NAME, BasketTradingTable) + val vpBasketTradingCons = vuuServer.createViewPort(BasketModule.NAME, BasketTradingConstituentTable) + val basketTradingService = vuuServer.getViewPortRpcServiceProxy[BasketTradingServiceIF](vpBasketTrading) + + When("we edit the side of the parent basket to same side as current value") + basketTradingService.editCellAction().func(basketTradeInstanceId, "side", "BUY", vpBasketTrading, vuuServer.session) + vuuServer.runOnce() + + Then("get all the updates that have occurred for all view ports from the outbound queue") + val updates2 = combineQs(vpBasketTrading) + + And("assert the basket trading table has not changed side....") + assertVpEq(filterByVp(vpBasketTrading, updates2)) { + Table( + ("instanceId", "basketId", "basketName", "status", "units", "filledPct", "fxRateToUsd", "totalNotional", "totalNotionalUsd", "side"), + (basketTradeInstanceId, ".FTSE", "MyCustomBasket", "OFF-MARKET", 1, null, null, null, null, "BUY") + ) + } + + And("assert the basket trading constituent table has not changed sides") + assertVpEq(filterByVp(vpBasketTradingCons, updates2)) { + Table( + ("quantity", "side", "instanceId", "instanceIdRic", "basketId", "ric", "description", "notionalUsd", "notionalLocal", "venue", "algo", "algoParams", "pctFilled", "weighting", "priceSpread", "limitPrice", "priceStrategyId", "filledQty", "orderStatus"), + (10L, "BUY", basketTradeInstanceId, s"$basketTradeInstanceId.BP.L", ".FTSE", "BP.L", "Beyond Petroleum", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING"), + (10L, "SELL", basketTradeInstanceId, s"$basketTradeInstanceId.BT.L", ".FTSE", "BT.L", "British Telecom", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING"), + (10L, "BUY", basketTradeInstanceId, s"$basketTradeInstanceId.VOD.L", ".FTSE", "VOD.L", "Vodafone", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING") + ) + } + } + } + + //TODO join table cannot be tested currently as it doesnt get updated when underlying table gets updated +// Scenario("Adding new constituents by ric should add it to basket trading") { +// import BasketModule._ +// implicit val clock: Clock = new TestFriendlyClock(10001L) +// implicit val lifecycle: LifecycleContainer = new LifecycleContainer() +// implicit val tableDefContainer: TableDefContainer = new TableDefContainer(Map()) +// implicit val metricsProvider: MetricsProvider = new MetricsProviderImpl +// +// val omsApi = OmsApi() +// withVuuServer(PriceModule(), BasketModule(omsApi)) { +// vuuServer => +// +// vuuServer.login("testUser", "testToken2") +// +// GivenBasketTradeExist(vuuServer, ".FTSE", "MyCustomBasket") +// val basketTradeInstanceId = BasketTradeId.current +// +// val vpBasketTrading = vuuServer.createViewPort(BasketModule.NAME, BasketTradingTable) +// val vpBasketTradingCons = vuuServer.createViewPort(BasketModule.NAME, BasketTradingConstituentTable) +// val vpBasketTradingConsJoin = vuuServer.createViewPort(BasketModule.NAME, BasketTradingConstituentJoin) +// val basketTradingConstituentJoinService = vuuServer.getViewPortRpcServiceProxy[BasketTradingConstituentJoinServiceIF](vpBasketTradingConsJoin) +// +// vuuServer.runOnce() +// +// When("we edit the side of the parent basket to same side as current value") +// basketTradingConstituentJoinService.addConstituent("0001.HK")(vuuServer.requestContext) +// vuuServer.runOnce() +// +// Then("get all the updates that have occurred for all view ports from the outbound queue") +// val updates = combineQs(vpBasketTradingConsJoin) +// +// //todo should basketid be where the stock was sourced from? in this case .HSI? +// +// And("assert the basket trading constituent table has added row") +// assertVpEq(filterByVp(vpBasketTradingCons, updates)) { +// Table( +// ("quantity", "side", "instanceId", "instanceIdRic", "basketId", "ric", "description", "notionalUsd", "notionalLocal", "venue", "algo", "algoParams", "pctFilled", "weighting", "priceSpread", "limitPrice", "priceStrategyId", "filledQty", "orderStatus"), +// (10L, "BUY", basketTradeInstanceId, s"$basketTradeInstanceId.BP.L", ".FTSE", "BP.L", "Beyond Petroleum", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING"), +// (10L, "SELL", basketTradeInstanceId, s"$basketTradeInstanceId.BT.L", ".FTSE", "BT.L", "British Telecom", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING"), +// (10L, "BUY", basketTradeInstanceId, s"$basketTradeInstanceId.VOD.L", ".FTSE", "VOD.L", "Vodafone", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING"), +// (10L, "BUY", basketTradeInstanceId, s"$basketTradeInstanceId.0001.HK", ".FTSE", "0001.HK", "", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING") +// ) +// } +// } +// } + } + + def GivenBasketTradeExist(vuuServer: TestVuuServer, basketId: String, basketTradeName: String): Unit = { + val basketProvider = vuuServer.getProvider(BasketModule.NAME, BasketModule.BasketTable) + basketProvider.tick(".FTSE", Map(B.Id -> ".FTSE", B.Name -> ".FTSE 100", B.NotionalValue -> 1000001, B.NotionalValueUsd -> 1500001)) + + val constituentProvider = vuuServer.getProvider(BasketModule.NAME, BasketModule.BasketConstituentTable) + constituentProvider.tick("VOD.L.FTSE", Map(BC.RicBasketId -> "VOD.L.FTSE", BC.Ric -> "VOD.L", BC.BasketId -> basketId, BC.Weighting -> 0.1, BC.Side -> "BUY", BC.Description -> "Vodafone")) + constituentProvider.tick("BT.L.FTSE", Map(BC.RicBasketId -> "BT.L.FTSE", BC.Ric -> "BT.L", BC.BasketId -> basketId, BC.Weighting -> 0.1, BC.Side -> "SELL", BC.Description -> "British Telecom")) + constituentProvider.tick("BP.L.FTSE", Map(BC.RicBasketId -> "BP.L.FTSE", BC.Ric -> "BP.L", BC.BasketId -> basketId, BC.Weighting -> 0.1, BC.Side -> "BUY", BC.Description -> "Beyond Petroleum")) + + val vpBasket = vuuServer.createViewPort(BasketModule.NAME, BasketModule.BasketTable) + val basketService = vuuServer.getViewPortRpcServiceProxy[BasketServiceIF](vpBasket) + basketService.createBasket(basketId, basketTradeName)(vuuServer.requestContext) } } \ No newline at end of file diff --git a/example/basket/src/test/scala/org/finos/vuu/core/module/basket/BasketSendToMarketTest.scala b/example/basket/src/test/scala/org/finos/vuu/core/module/basket/BasketSendToMarketTest.scala index 315e5cb88..7bc7511ea 100644 --- a/example/basket/src/test/scala/org/finos/vuu/core/module/basket/BasketSendToMarketTest.scala +++ b/example/basket/src/test/scala/org/finos/vuu/core/module/basket/BasketSendToMarketTest.scala @@ -6,7 +6,7 @@ import org.finos.toolbox.time.{Clock, TestFriendlyClock} import org.finos.vuu.api.ViewPortDef import org.finos.vuu.core.module.TableDefContainer import org.finos.vuu.core.module.basket.BasketTestCaseHelper.{tickBasketDef, tickConstituentsDef, tickPrices} -import org.finos.vuu.core.module.basket.service.{BasketServiceIF, BasketTradingServiceIF} +import org.finos.vuu.core.module.basket.service.{BasketServiceIF, BasketTradeId, BasketTradingServiceIF} import org.finos.vuu.core.module.price.PriceModule import org.finos.vuu.order.oms.OmsApi import org.finos.vuu.test.VuuServerTestCase @@ -50,16 +50,28 @@ class BasketSendToMarketTest extends VuuServerTestCase { Then("Get the Basket RPC Service and call create basket") val basketService = vuuServer.getViewPortRpcServiceProxy[BasketServiceIF](vpBasket) - basketService.createBasket(".FTSE", "chris-001")(vuuServer.requestContext) + basketService.createBasket(".FTSE", "TestBasket")(vuuServer.requestContext) + val basketTradeInstanceId = BasketTradeId.current val vpBasketTrading = vuuServer.createViewPort(BasketModule.NAME, BasketTradingTable) + val vpBasketTradingCons = vuuServer.createViewPort(BasketModule.NAME, BasketTradingConstituentTable) vuuServer.runOnce() + And("Check the trading constituents are created") + assertVpEq(combineQsForVp(vpBasketTradingCons)) { + Table( + ("quantity", "side", "instanceId", "instanceIdRic", "basketId", "ric", "description", "notionalUsd", "notionalLocal", "venue", "algo", "algoParams", "pctFilled", "weighting", "priceSpread", "limitPrice", "priceStrategyId", "filledQty", "orderStatus"), + (10L, "BUY", basketTradeInstanceId, s"$basketTradeInstanceId.BP.L", ".FTSE", "BP.L", "Beyond Petroleum", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING"), + (10L, "SELL", basketTradeInstanceId, s"$basketTradeInstanceId.BT.L", ".FTSE", "BT.L", "British Telecom", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING"), + (10L, "BUY", basketTradeInstanceId, s"$basketTradeInstanceId.VOD.L", ".FTSE", "VOD.L", "Vodafone", null, null, null, -1, null, null, 0.1, null, null, 2, 0, "PENDING") + ) + } + val tradingService = vuuServer.getViewPortRpcServiceProxy[BasketTradingServiceIF](vpBasketTrading) And("send the basket to market") - tradingService.sendToMarket("chris-001")(vuuServer.requestContext) + tradingService.sendToMarket(basketTradeInstanceId)(vuuServer.requestContext) vuuServer.runOnce() @@ -67,7 +79,20 @@ class BasketSendToMarketTest extends VuuServerTestCase { assertVpEq(combineQsForVp(vpBasketTrading)) { Table( ("instanceId", "basketId", "basketName", "status", "units", "filledPct", "fxRateToUsd", "totalNotional", "totalNotionalUsd", "side"), - ("chris-001", ".FTSE", "chris-001", "ON_MARKET", 1, null, null, null, null, "Buy") + (basketTradeInstanceId, ".FTSE", "TestBasket", "ON_MARKET", 1, null, null, null, null, "BUY") + ) + } + + Then("Take the basket off the market") + tradingService.takeOffMarket(basketTradeInstanceId)(vuuServer.requestContext) + + vuuServer.runOnce() + + And("verify basket is on market") + assertVpEq(combineQsForVp(vpBasketTrading)) { + Table( + ("instanceId", "basketId", "basketName", "status", "units", "filledPct", "fxRateToUsd", "totalNotional", "totalNotionalUsd", "side"), + (basketTradeInstanceId, ".FTSE", "TestBasket", "OFF_MARKET", 1, null, null, null, null, "BUY") ) } } diff --git a/example/basket/src/test/scala/org/finos/vuu/core/module/basket/BasketTestCaseHelper.scala b/example/basket/src/test/scala/org/finos/vuu/core/module/basket/BasketTestCaseHelper.scala index 540cb1d2d..657af5767 100644 --- a/example/basket/src/test/scala/org/finos/vuu/core/module/basket/BasketTestCaseHelper.scala +++ b/example/basket/src/test/scala/org/finos/vuu/core/module/basket/BasketTestCaseHelper.scala @@ -20,8 +20,8 @@ object BasketTestCaseHelper { //symbol + "." + basketId //Columns.fromNames(BC.RicBasketId.string(), BC.Ric.string(), BC.BasketId.string(), BC.Weighting.double(), BC.LastTrade.string(), BC.Change.string(), // BC.Volume.string(), BC.Side.string()) - provider.tick("VOD.L.FTSE", Map(BC.RicBasketId -> "VOD.L.FTSE", BC.Ric -> "VOD.L", BC.BasketId -> ".FTSE", BC.Weighting -> 0.1, BC.Side -> "Buy", BC.Description -> "Vodafone")) - provider.tick("BT.L.FTSE", Map(BC.RicBasketId -> "BT.L.FTSE", BC.Ric -> "BT.L", BC.BasketId -> ".FTSE", BC.Weighting -> 0.1, BC.Side -> "Sell", BC.Description -> "British Telecom")) - provider.tick("BP.L.FTSE", Map(BC.RicBasketId -> "BP.L.FTSE", BC.Ric -> "BP.L", BC.BasketId -> ".FTSE", BC.Weighting -> 0.1, BC.Side -> "Buy", BC.Description -> "Beyond Petroleum")) + provider.tick("VOD.L.FTSE", Map(BC.RicBasketId -> "VOD.L.FTSE", BC.Ric -> "VOD.L", BC.BasketId -> ".FTSE", BC.Weighting -> 0.1, BC.Side -> "BUY", BC.Description -> "Vodafone")) + provider.tick("BT.L.FTSE", Map(BC.RicBasketId -> "BT.L.FTSE", BC.Ric -> "BT.L", BC.BasketId -> ".FTSE", BC.Weighting -> 0.1, BC.Side -> "SELL", BC.Description -> "British Telecom")) + provider.tick("BP.L.FTSE", Map(BC.RicBasketId -> "BP.L.FTSE", BC.Ric -> "BP.L", BC.BasketId -> ".FTSE", BC.Weighting -> 0.1, BC.Side -> "BUY", BC.Description -> "Beyond Petroleum")) } } diff --git a/example/basket/src/test/scala/org/finos/vuu/core/module/basket/TestHelper/TestDataFactory.scala b/example/basket/src/test/scala/org/finos/vuu/core/module/basket/TestHelper/TestDataFactory.scala new file mode 100644 index 000000000..a7ce889b1 --- /dev/null +++ b/example/basket/src/test/scala/org/finos/vuu/core/module/basket/TestHelper/TestDataFactory.scala @@ -0,0 +1,65 @@ +package org.finos.vuu.core.module.basket.TestHelper + +import org.finos.vuu.core.table.RowWithData +import org.finos.vuu.core.module.basket.BasketModule.{Sides, BasketColumnNames => B, BasketConstituentColumnNames => BC, BasketTradingColumnNames => BT, BasketTradingConstituentColumnNames => BTC} + +object TestDataFactory { + def uuid = java.util.UUID.randomUUID.toString + def createBasketTradingRow(rowKey: String, basketName: String, side: String): RowWithData = + RowWithData(rowKey, Map(BT.InstanceId -> rowKey, BT.Status -> "OFF-MARKET", BT.BasketId -> ".FTSE", BT.BasketName -> basketName, BT.Side -> side, BT.Units -> 1)) + + def createBasketTradingConstituentRow(basketTradeInstanceId: String, constituentRic: String, side: String): RowWithData = { + RowWithData( + key = uuid, + data = Map( + BTC.BasketId -> "someBasketId", + BTC.Ric -> constituentRic, + BTC.InstanceId -> basketTradeInstanceId, + BTC.Quantity -> 40 , + BTC.InstanceIdRic -> s"$basketTradeInstanceId.$constituentRic", + BTC.Description -> "some instrument description", + BTC.Side -> side, + BTC.Weighting -> 0.4, + BTC.PriceStrategyId -> 2, + BTC.Algo -> -1, + ) + ) + } + + + //todo does row key need to match instance id (primary key) + def createBasketTradingConstituentJoinRow(basketTradeInstanceId: String, constituentRic: String, side: String): RowWithData = { + RowWithData( + key = uuid, + data = Map( + BTC.BasketId -> "someBasketId", + BTC.Ric -> constituentRic, + BTC.InstanceId -> basketTradeInstanceId, + BTC.Quantity -> 40, + BTC.InstanceIdRic -> s"$basketTradeInstanceId.$constituentRic", + BTC.Description -> "some instrument description", + BTC.Side -> side, + BTC.Weighting -> 0.4, + BTC.PriceStrategyId -> 2, + BTC.Algo -> -1, + "bid" -> 1.1, + "bidSize" -> 1, + "ask" -> 1.5, + "askSize" -> 1, + ) + ) + } + + def createPricesRow(rowKey: String, ric: String, bid: Double, ask: Double): RowWithData = { + RowWithData( + key = rowKey, + data = Map( + "ric" -> ric, + "bid" -> bid, + "bidSize" -> 1, + "ask" -> ask, + "askSize" -> 1, + ) + ) + } +} diff --git a/example/order/src/main/scala/org/finos/vuu/core/module/simul/provider/ParentChildOrdersModel.scala b/example/order/src/main/scala/org/finos/vuu/core/module/simul/provider/ParentChildOrdersModel.scala index 5d08aee37..9b0be7bbf 100644 --- a/example/order/src/main/scala/org/finos/vuu/core/module/simul/provider/ParentChildOrdersModel.scala +++ b/example/order/src/main/scala/org/finos/vuu/core/module/simul/provider/ParentChildOrdersModel.scala @@ -291,7 +291,7 @@ class ParentChildOrdersModel(implicit clock: Clock, lifecycleContainer: Lifecycl val instIdx = randomNumbers.seededRand(0, instruments.length - 1) val instrument = instruments(instIdx) val quantity = randomNumbers.seededRand(0, 30) * 100 + randomNumbers.seededRand(0, 100) - val side = if (randomNumbers.seededRand(0, 10) > 8) "Buy" else "Sell" + val side = if (randomNumbers.seededRand(0, 10) > 8) "BUY" else "SELL" val account = accounts(randomNumbers.seededRand(0, accounts.length - 1)) val algo = algos(randomNumbers.seededRand(0, algos.length - 1)) val volLimit = randomNumbers.seededRand(0, 10) * 10 diff --git a/example/order/src/main/scala/org/finos/vuu/order/oms/OmsApi.scala b/example/order/src/main/scala/org/finos/vuu/order/oms/OmsApi.scala index 12a15fdac..a780c854a 100644 --- a/example/order/src/main/scala/org/finos/vuu/order/oms/OmsApi.scala +++ b/example/order/src/main/scala/org/finos/vuu/order/oms/OmsApi.scala @@ -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{ diff --git a/example/order/src/main/scala/org/finos/vuu/order/oms/impl/InMemOmsApi.scala b/example/order/src/main/scala/org/finos/vuu/order/oms/impl/InMemOmsApi.scala index 1805f5a9b..09b4ac4c2 100644 --- a/example/order/src/main/scala/org/finos/vuu/order/oms/impl/InMemOmsApi.scala +++ b/example/order/src/main/scala/org/finos/vuu/order/oms/impl/InMemOmsApi.scala @@ -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) @@ -58,9 +64,14 @@ class InMemOmsApi(implicit val clock: Clock) extends OmsApi { orderstate.copy(state = States.ACKED, nextEventTime = clock.now() + random.between(1000, MaxTimes.MAX_FILL_TIME_MS), orderId = orderId) case 'A' => val remainingQty = orderstate.qty - orderstate.filledQty - val fillQty = if(remainingQty > 1) random.between(1, orderstate.qty - orderstate.filledQty) else 1 - listeners.foreach(_.onFill(Fill(orderstate.orderId, fillQty, orderstate.price, orderstate.clientOrderId, orderstate.filledQty + fillQty, orderstate.qty))) - orderstate.copy(filledQty = orderstate.filledQty + fillQty, nextEventTime = clock.now() + random.between(1000, 5000)) + val fillQty = if(remainingQty > 1) random.between(1, remainingQty) else 1 + val totalFilledQty = orderstate.filledQty + fillQty + 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 = nextState, filledQty = totalFilledQty, nextEventTime = clock.now() + random.between(1000, MaxTimes.MAX_FILL_TIME_MS)) + case 'X' => + listeners.foreach(_.onCancelAck(CancelAck(orderstate.orderId, orderstate.clientOrderId))) + orderstate case _ => orderstate } @@ -70,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(os => os.clientOrderId == clientOrderId) match { + case Some(os) => Some(os.orderId) + case None => None + } + } } diff --git a/example/order/src/test/scala/org/finos/vuu/order/oms/OmsApiTest.scala b/example/order/src/test/scala/org/finos/vuu/order/oms/OmsApiTest.scala index e3e2b54f5..eb2ed2454 100644 --- a/example/order/src/test/scala/org/finos/vuu/order/oms/OmsApiTest.scala +++ b/example/order/src/test/scala/org/finos/vuu/order/oms/OmsApiTest.scala @@ -62,7 +62,7 @@ class OmsApiTest extends AnyFeatureSpec with GivenWhenThen with Matchers { omsApi.addListener(listener) - omsApi.createOrder(NewOrder("Buy","VOD.L", 1000L, 100.01, "clOrdId1")) + omsApi.createOrder(NewOrder("BUY","VOD.L", 1000L, 100.01, "clOrdId1")) clock.sleep(MAX_ACK_TIME_MS) omsApi.runOnce() @@ -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) @@ -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) + + } } } diff --git a/vuu/src/main/scala/org/finos/vuu/viewport/ViewPortAction.scala b/vuu/src/main/scala/org/finos/vuu/viewport/ViewPortAction.scala index 4d557c7b2..3575beab6 100644 --- a/vuu/src/main/scala/org/finos/vuu/viewport/ViewPortAction.scala +++ b/vuu/src/main/scala/org/finos/vuu/viewport/ViewPortAction.scala @@ -24,7 +24,8 @@ case class CloseDialogViewPortAction(vpId: String) extends ViewPortAction new Type(value = classOf[CloseDialogViewPortAction], name = "CLOSE_DIALOG_ACTION"), new Type(value = classOf[NoAction], name = "NO_ACTION"), new Type(value = classOf[ViewPortEditSuccess], name = "VP_EDIT_SUCCESS"), - new Type(value = classOf[ViewPortEditFailure], name = "VP_EDIT_FAIL"), - new Type(value = classOf[ViewPortRpcFailure], name = "VP_RPC_FAILURE") + new Type(value = classOf[ViewPortEditFailure], name = "VP_EDIT_FAILURE"), + new Type(value = classOf[ViewPortRpcSuccess], name = "VP_RPC_SUCCESS"), + new Type(value = classOf[ViewPortRpcFailure], name = "VP_RPC_FAILURE"), )) trait ViewPortActionMixin {} diff --git a/vuu/src/main/scala/org/finos/vuu/viewport/ViewPortColumns.scala b/vuu/src/main/scala/org/finos/vuu/viewport/ViewPortColumns.scala index 02775ed7c..eca0bd55b 100644 --- a/vuu/src/main/scala/org/finos/vuu/viewport/ViewPortColumns.scala +++ b/vuu/src/main/scala/org/finos/vuu/viewport/ViewPortColumns.scala @@ -2,40 +2,55 @@ package org.finos.vuu.viewport import org.finos.vuu.core.table.{CalculatedColumn, Column, RowData, RowWithData} -class ViewPortColumns(sourceColumns: List[Column]){ +class ViewPortColumns(sourceColumns: List[Column]) { - @volatile private var columns: List[Column] = sourceColumns + @volatile private var columns: List[Column] = sourceColumns - def addColumn(column: Column): Unit = { - columns = columns ++ List(column) - } + def canEqual(a: Any): Boolean = a.isInstanceOf[ViewPortColumns] - def columnExists(name: String): Boolean = { - columns.exists(_.name == name) + override def equals(that: Any): Boolean = + that match { + case that: ViewPortColumns => + that.canEqual(this) && + this.columns.size == that.columns.size && + 0 == this.columns.sortBy(c => c.name) + .zip(that.columns.sortBy(c => c.name)) + .count(columnPair => columnPair._1.name != columnPair._2.name || columnPair._1.dataType != columnPair._2.dataType) + case _ => false } - def getColumns(): List[Column] = columns + override def hashCode(): Int = columns.sortBy(c => c.name).map(_.hashCode()).hashCode() - def getColumnForName(name: String): Option[Column] = { - columns.find(_.name == name) - } + def addColumn(column: Column): Unit = { + columns = columns ++ List(column) + } - def count(): Int = columns.size + def columnExists(name: String): Boolean = { + columns.exists(_.name == name) + } - private lazy val hasCalculatedColumn = columns.exists(c => c.isInstanceOf[CalculatedColumn]) + def getColumns(): List[Column] = columns - def pullRow(key: String, row: RowData): RowData = { + def getColumnForName(name: String): Option[Column] = { + columns.find(_.name == name) + } - if(!hasCalculatedColumn){ - row - }else{ - val rowData = this.getColumns().map(c => c.name -> row.get(c)).toMap - RowWithData(key, rowData) - } - } + def count(): Int = columns.size - def pullRowAlwaysFilter(key: String, row: RowData): RowData = { + private lazy val hasCalculatedColumn = columns.exists(c => c.isInstanceOf[CalculatedColumn]) + + def pullRow(key: String, row: RowData): RowData = { + + if (!hasCalculatedColumn) { + row + } else { val rowData = this.getColumns().map(c => c.name -> row.get(c)).toMap RowWithData(key, rowData) + } + } + + def pullRowAlwaysFilter(key: String, row: RowData): RowData = { + val rowData = this.getColumns().map(c => c.name -> row.get(c)).toMap + RowWithData(key, rowData) } } diff --git a/vuu/src/test/scala/org/finos/vuu/test/TestVuuServer.scala b/vuu/src/test/scala/org/finos/vuu/test/TestVuuServer.scala index 012ad3216..23bb1779a 100644 --- a/vuu/src/test/scala/org/finos/vuu/test/TestVuuServer.scala +++ b/vuu/src/test/scala/org/finos/vuu/test/TestVuuServer.scala @@ -103,4 +103,5 @@ trait TestVuuServer { def overrideViewPortDef(table: String, vpDefFunc: (DataTable, Provider, ProviderContainer, TableContainer) => ViewPortDef): Unit def getViewPortRpcServiceProxy[TYPE: _root_.scala.reflect.ClassTag](viewport: ViewPort): TYPE def requestContext: RequestContext + def tableContainer: TableContainer } diff --git a/vuu/src/test/scala/org/finos/vuu/test/impl/TestVuuServerImpl.scala b/vuu/src/test/scala/org/finos/vuu/test/impl/TestVuuServerImpl.scala index e65baca45..bd1863165 100644 --- a/vuu/src/test/scala/org/finos/vuu/test/impl/TestVuuServerImpl.scala +++ b/vuu/src/test/scala/org/finos/vuu/test/impl/TestVuuServerImpl.scala @@ -165,7 +165,7 @@ class TestVuuServerImpl(val modules: List[ViewServerModule])(implicit clock: Clo override def login(user: String, token: String): Unit = { handler = factory.create() - val packet = serializer.serialize(JsonViewServerMessage(RequestId.oneNew(), "", "", "", LoginRequest("TOKEN", "AAAA"))) + val packet = serializer.serialize(JsonViewServerMessage(RequestId.oneNew(), "", "", "", LoginRequest("TOKEN", user))) handler.handle(packet, channel) channel.popMsg match { @@ -173,7 +173,6 @@ class TestVuuServerImpl(val modules: List[ViewServerModule])(implicit clock: Clo val msg = serializer.deserialize(msgPacket) clientSessionId = ClientSessionId(msg.sessionId, user) } - } override def overrideViewPortDef(table: String, vpDefFunc: (DataTable, Provider, ProviderContainer, TableContainer) => ViewPortDef): Unit = { diff --git a/vuu/src/test/scala/org/finos/vuu/viewport/ViewPortColumnsTests.scala b/vuu/src/test/scala/org/finos/vuu/viewport/ViewPortColumnsTests.scala new file mode 100644 index 000000000..ff678c1bf --- /dev/null +++ b/vuu/src/test/scala/org/finos/vuu/viewport/ViewPortColumnsTests.scala @@ -0,0 +1,55 @@ +package org.finos.vuu.viewport + +import org.finos.vuu.core.table.Columns +import org.scalatest.featurespec.AnyFeatureSpec + +class ViewPortColumnsTests extends AnyFeatureSpec { + + Feature("compare two view port columns") { + + Scenario("when all the columns names are same equality check should return true") { + val sourceColumn = Columns.fromNames("firstColumn:String", "secondColumn:String") + val oldVPColumns = new ViewPortColumns(sourceColumn.toList) + val sourceColumn2 = Columns.fromNames("firstColumn:String", "secondColumn:String") + val newVPColumns = new ViewPortColumns(sourceColumn2.toList) + + assert(oldVPColumns == newVPColumns) + } + + Scenario("when all the columns names are same in different order equality check should return true") { + val sourceColumn = Columns.fromNames("firstColumn:String", "secondColumn:String") + val oldVPColumns = new ViewPortColumns(sourceColumn.toList) + val sourceColumn2 = Columns.fromNames("secondColumn:String", "firstColumn:String") + val newVPColumns = new ViewPortColumns(sourceColumn2.toList) + + assert(oldVPColumns == newVPColumns) + } + + Scenario("when one if the column has different type equality check should return false") { + val sourceColumn = Columns.fromNames("firstColumn:String", "secondColumn:String") + val oldVPColumns = new ViewPortColumns(sourceColumn.toList) + val sourceColumn2 = Columns.fromNames("firstColumn:String", "secondColumn:Int") + val newVPColumns = new ViewPortColumns(sourceColumn2.toList) + + assert(oldVPColumns != newVPColumns) + } + + Scenario("when one if the column has different name equality check should return false") { + val sourceColumn = Columns.fromNames("firstColumn:String", "secondColumn:String") + val oldVPColumns = new ViewPortColumns(sourceColumn.toList) + val sourceColumn2 = Columns.fromNames("firstColumn:String", "someOtherColumn:String") + val newVPColumns = new ViewPortColumns(sourceColumn2.toList) + + assert(oldVPColumns != newVPColumns) + } + + Scenario("when all the columns not same equality check should return false") { + val sourceColumn = Columns.fromNames("firstColumn:String", "secondColumn:String") + val oldVPColumns = new ViewPortColumns(sourceColumn.toList) + val sourceColumn2 = Columns.fromNames("firstColumn:String") + val newVPColumns = new ViewPortColumns(sourceColumn2.toList) + + assert(oldVPColumns != newVPColumns) + } + } +}