Skip to content

Commit

Permalink
#982 initial implementation of editing basket trade constituent side
Browse files Browse the repository at this point in the history
  • Loading branch information
naleeha committed Dec 5, 2023
1 parent 436c889 commit c4c6afb
Show file tree
Hide file tree
Showing 4 changed files with 273 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,22 @@ 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.module.basket.BasketConstants.Side
import org.finos.vuu.core.module.basket.BasketModule.{BasketTradingConstituentColumnNames => ColumnName}
import org.finos.vuu.core.table.{DataTable, JoinTable, RowWithData, TableContainer}
import org.finos.vuu.net.ClientSessionId
import org.finos.vuu.net.rpc.{EditRpcHandler, RpcHandler}
import org.finos.vuu.viewport._

class BasketTradingConstituentJoinService(val table: DataTable, val tableContainer: TableContainer)(implicit clock: Clock) extends RpcHandler with EditRpcHandler with StrictLogging {
trait BasketTradingConstituentJoinServiceIF extends EditRpcHandler {
def setSell(selection: ViewPortSelection, session: ClientSessionId): ViewPortAction

def setBuy(selection: ViewPortSelection, session: ClientSessionId): ViewPortAction
}

object BasketTradingConstituentJoinService {}

class BasketTradingConstituentJoinService(val table: DataTable, val tableContainer: TableContainer)(implicit clock: Clock) extends BasketTradingConstituentJoinServiceIF with RpcHandler with StrictLogging {

def onDeleteRow(key: String, vp: ViewPort, session: ClientSessionId): ViewPortEditAction = {
ViewPortEditSuccess()
Expand All @@ -28,7 +37,7 @@ class BasketTradingConstituentJoinService(val table: DataTable, val tableContain
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())
table.processUpdate(key, RowWithData(key, Map(ColumnName.InstanceIdRic -> key, columnName -> data)), clock.now())
ViewPortEditSuccess()
case None =>
ViewPortEditFailure("Could not find base table")
Expand All @@ -42,26 +51,25 @@ class BasketTradingConstituentJoinService(val table: DataTable, val tableContain
}

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)
}

private def onFormClose(vp: ViewPort, session: ClientSessionId): ViewPortAction = {
CloseDialogViewPortAction(vp.id)
}


override def deleteRowAction(): ViewPortDeleteRowAction = ViewPortDeleteRowAction("", this.onDeleteRow)

override def deleteCellAction(): ViewPortDeleteCellAction = ViewPortDeleteCellAction("", this.onDeleteCell)
Expand All @@ -77,16 +85,39 @@ class BasketTradingConstituentJoinService(val table: DataTable, val tableContain
override def onFormClose(): ViewPortFormCloseAction = ViewPortFormCloseAction("", this.onFormClose)

def setSell(selection: ViewPortSelection, session: ClientSessionId): ViewPortAction = {
ViewPortEditSuccess()
updateSelected(selection, Map(ColumnName.Side -> Side.Sell))
}

def setBuy(selection: ViewPortSelection, session: ClientSessionId): ViewPortAction = {
updateSelected(selection, Map(ColumnName.Side -> Side.Buy))
}

private def updateSelected(selection: ViewPortSelection, updates: Map[String, Any]): ViewPortAction = {
val selectedKeys = selection.rowKeyIndex.map({ case (key, _) => key }).toList
selectedKeys.foreach(key => {
//require source table primary key for join table updates
val sourceTableKey = Map(ColumnName.InstanceIdRic -> key)
update(key, sourceTableKey ++ updates)
})
//todo handle error
//todo care about checking current value? simpler not to
ViewPortEditSuccess()
}

//todo can update multiple rows in one go?
private def update(key: String, keyValuesToUpdate: Map[String, Any]): 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(key, RowWithData(key, keyValuesToUpdate), clock.now())
ViewPortEditSuccess()
case None =>
ViewPortEditFailure("Could not find base table")
}
}
override def menuItems(): ViewPortMenu = ViewPortMenu("Direction",
new SelectionViewPortMenuItem("Set Sell", "", this.setSell, "SET_SELECTION_SELL"),
new SelectionViewPortMenuItem("Set Buy", "", this.setBuy, "SET_SELECTION_Buy")
new SelectionViewPortMenuItem("Set Sell", "", this.setSell, "SET_SELECTION_SELL"),
new SelectionViewPortMenuItem("Set Buy", "", this.setBuy, "SET_SELECTION_Buy")
)

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,30 @@ 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.ViewPortDef
import org.finos.vuu.api.{JoinTableDef, ViewPortDef}
import org.finos.vuu.core.module.TableDefContainer
import org.finos.vuu.core.module.basket.BasketModule.{BasketColumnNames => B, BasketConstituentColumnNames => BC}
import org.finos.vuu.core.module.basket.service.{BasketServiceIF, BasketTradingServiceIF}
import org.finos.vuu.core.module.basket.TestHelper.TestDataFactory
import org.finos.vuu.core.module.basket.service.{BasketServiceIF, BasketTradingConstituentJoinServiceIF, BasketTradingServiceIF}
import org.finos.vuu.core.module.price.PriceModule
import org.finos.vuu.order.oms.OmsApi
import org.finos.vuu.core.table.{DataTable, JoinTable, RowWithData, TableContainer}
import org.finos.vuu.test.{TestVuuServer, VuuServerTestCase}
import org.finos.vuu.util.table.TableAsserts.assertVpEq
import org.finos.vuu.viewport.ViewPortSelection
import org.scalatest.prop.Tables.Table

class BasketMutateOffMarketTest extends VuuServerTestCase {

import BasketTestCaseHelper._
import BasketModule._

Feature("Basket Service Test Case") {
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
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

Feature("Basket Service Test Case") {
val omsApi = OmsApi()

Scenario("Check updating trade basket side with no change does not update constituents side") {
Expand Down Expand Up @@ -56,7 +59,7 @@ class BasketMutateOffMarketTest extends VuuServerTestCase {
And("assert the basket trading constituent table has not changed sides")
assertVpEq(filterByVp(vpBasketTradingCons, updates)) {
Table(
("quantity", "side", "instanceId", "instanceIdRic", "basketId", "ric", "description", "notionalUsd", "notionalLocal", "venue", "algo", "algoParams", "pctFilled", "weighting", "priceSpread", "limitPrice", "priceStrategyId", "filledQty","orderStatus"),
("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")
Expand Down Expand Up @@ -194,5 +197,149 @@ class BasketMutateOffMarketTest extends VuuServerTestCase {
}
}
}

Scenario("Check updating trade basket side with no change does not update constituents side") {
withVuuServer(PriceModule(), BasketModule()) {
vuuServer =>

vuuServer.login("testUser", "testToken")

GivenBasketTradeExist(vuuServer, ".FTSE", "chris-001")

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("chris-001", "side", "Buy", vpBasketTrading, 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 the basket trading table has not changed side....")
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, "Buy")
)
}

And("assert the basket trading constituent table has not changed sides")
assertVpEq(filterByVp(vpBasketTradingCons, updates)) {
Table(
("quantity", "side", "instanceId", "instanceIdRic", "basketId", "ric", "description", "notionalUsd", "notionalLocal", "venue", "algo", "algoParams", "pctFilled", "weighting", "priceSpread", "limitPrice", "priceStrategyId"),
(10L, "Buy", "chris-001", "chris-001.BP.L", ".FTSE", "BP.L", "Beyond Petroleum", null, null, null, -1, null, null, 0.1, null, null, 2),
(10L, "Sell", "chris-001", "chris-001.BT.L", ".FTSE", "BT.L", "British Telecom", null, null, null, -1, null, null, 0.1, null, null, 2),
(10L, "Buy", "chris-001", "chris-001.VOD.L", ".FTSE", "VOD.L", "Vodafone", null, null, null, -1, null, null, 0.1, null, null, 2)
)
}
}
}
}

Feature("Basket Trading Constituent Join Service Test Case") {
Scenario("Update selected trade constituent sides when menu action is triggered") {
withVuuServer(PriceModule(), BasketModule()) {
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", "bidSize", "ask", "askSize"),
(10L, "Buy", "chris-001", "chris-001.BP.L", ".FTSE", "BP.L", "Beyond Petroleum", null, null, null, -1, null, null, 0.1, null, null, 2, 2.1, 1, 2.4, 1),
(10L, "Sell", "chris-001", "chris-001.BT.L", ".FTSE", "BT.L", "British Telecom", null, null, null, -1, null, null, 0.1, null, null, 2, null, null, null, null),
(10L, "Sell", "chris-001", "chris-001.VOD.L", ".FTSE", "VOD.L", "Vodafone", null, null, null, -1, null, null, 0.1, null, null, 2, 1.1, 1, 1.4, 1)
)
}
}
}
}

def uuid = java.util.UUID.randomUUID.toString
def GivenBasketTradeExist(vuuServer: TestVuuServer, basketId: String, basketTradeName: String): Unit = {
val basketProvider = vuuServer.getProvider(BasketModule.NAME, BasketTable)
basketProvider.tick(".FTSE", Map(B.Id -> ".FTSE", B.Name -> ".FTSE 100", B.NotionalValue -> 1000001, B.NotionalValueUsd -> 1500001))

val constituentProvider = vuuServer.getProvider(BasketModule.NAME, 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, BasketTable)
val basketService = vuuServer.getViewPortRpcServiceProxy[BasketServiceIF](vpBasket)
basketService.createBasket(basketId, basketTradeName)(vuuServer.requestContext)
}

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 GivenBasketTradeConstituents(tableContainer: TableContainer, tradeId: String, ricToSide: Map[String, String]): Unit = {
val table = tableContainer.getTable(BasketModule.BasketTradingConstituentTable)
ricToSide.foreach(item => {
val (ric, side) = item
val row = TestDataFactory.createBasketTradingConstituentRow(tradeId, ric, side)
table.processUpdate(row.key, row, clock.now())
})
}

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?

}
Original file line number Diff line number Diff line change
@@ -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,
)
)
}
}

0 comments on commit c4c6afb

Please sign in to comment.