scala - 解析订单文件,按股票代码分组并计算平均订单价格和数量
问题描述
我有一个平面文件,其中包含给定日期的股票购买清单。
Symbol Quantity Price ACTION Datetime
ABC 100 5.25 BUY 20210609 09:20:10
ABC 100 5.35 SELL 20210609 09:20:10
ABC 100 5.25 BUY 20210609 09:20:10
ABC 100 5.20 BUY 20210609 09:20:10
DEF 500 1.25 BUY 20210609 09:20:10
ABC 200 5.50 SELL 20210609 09:20:10
DEF 250 1.50 SELL 20210609 09:20:10
DEF 250 1.00 SELL 20210609 09:20:10
概括
Order#1 ABC 100 ...
Order#2 ABC 200 ...
Order#3 DEF 500 ...
我将每一行表示为一个案例类,并且我已经在 Map 中加载了一个数据: 1. 键是股票代码 2. 集合是该股票代码的所有交易
case class Order(symbol: String, time: Instant, quantity: Int, price: BigDecimal)
case class CompletedOrder(symbol: String, quantity: Int, avgPrice: BigDecimal)
val transactions: Map[String, Seq[Transaction]] = ???
我想遍历给定股票的所有订单,并将它们组合在一起,这样每次买卖都将被视为已完成的交易。
对于每个符号,交易将按日期时间排序。所以基本上逻辑如下:
1. If there is a Buy order, keep adding the quantity to the symbol in an existing CompletedOrder, create if necessary
2. If there is a Sell order, subtract the quantity until you reach 0.
If you reach 0, consider the order to be completed.
现在我可以在 forloop 中执行上述操作,但我很好奇如何以更实用的方式执行此操作,例如使用 a
.fold()
并返回 a Seq[CompleteOrder]
。
解决方案
您可以执行以下操作:
使用 a foldLeft
,其中您的累加器是 aSeq[CompleteOrder]
和 a的元组,Map[symbol, (quantity, avgPrice)]
然后对于每个元素,您可以从Map中删除某些内容并将新的CompleteOrder附加到Seq,或者将新条目添加到Map。
import java.time.Instant
sealed trait Action extends Product with Serializable
object Action {
final case object Buy extends Action
final case object Sell extends Action
}
final case class Transaction(symbol: String, time: Instant, quantity: Int, price: BigDecimal, action: Action)
final case class CompletedOrder(symbol: String, quantity: Int, avgPrice: BigDecimal)
def reduceTransactions(data: List[Transaction]): List[CompletedOrder] = {
val (completedOrders, remainingTransactions) =
data.sortBy(_.time).foldLeft(List.empty[CompletedOrder] -> Map.empty[String, (Int, BigDecimal)]) {
case ((completedOrdersAcc, transactionsMap), Transaction(symbol, _, quantity, price, action)) =>
action match {
case Action.Buy =>
completedOrdersAcc -> transactionsMap.updatedWith(key = symbol) {
case None =>
Some(quantity -> price)
case Some((prevQuantity, prevAvgPrice)) =>
val newQuantity = prevQuantity + quantity
val newAvgPrice = ((prevAvgPrice * prevQuantity) + (quantity + price)) / newQuantity
Some(newQuantity, newAvgPrice)
}
case Action.Sell =>
transactionsMap.get(key = symbol) match {
case Some((totalQuantity, avgPrice)) if (totalQuantity == quantity) =>
(CompletedOrder(symbol, quantity, avgPrice) :: completedOrdersAcc) ->
transactionsMap.removed(key = symbol)
case Some((totalQuantity, avgPrice)) if (totalQuantity > quantity) =>
(CompletedOrder(symbol, quantity, avgPrice) :: completedOrdersAcc) ->
transactionsMap.updated(key = symbol, value = ((totalQuantity - quantity), avgPrice))
case _ =>
// Invalid transaction, panic!
throw new IllegalStateException("Illegal transaction!")
}
}
}
if (remainingTransactions.nonEmpty) {
// Is this an error? Or should those also be returned?
println("Remaining transactions:")
remainingTransactions.valuesIterator.foreach(println)
}
completedOrders.reverse
}
您可能需要调整此函数的许多方面,例如如何处理错误和调整平均值的计算。您甚至可能更喜欢使用尾递归函数而不是foldLeft
; 可能您还应该为每种情况将大主体拆分为多个可重用和可测试的功能。
无论如何,请随时提出您可能有的任何问题。
推荐阅读
- xml - 我怎样才能使这个布局背景完全变黑?
- mongodb - 如何从 mongo 管道的同一阶段访问属性?
- openssl - 从 EVP_PKEY 到缓冲区的 OpenSSL 公钥
- java - 如何从 C/C++ 在 JNI for Java 中创建 UTF16 字符串?
- tcl - 变量未在 openOCD 目标事件过程中设置
- java - m2Eclips 插件:解决工作区工件 - 这是一个好习惯吗
- google-app-engine - 是否可以将 Google App Engine Flexi 与 Cloud Nat 连接起来
- python - Python:使用按钮和标签显示从函数返回的文本
- powershell - PowerShell:根据动态接口索引添加新路由
- reactjs - 如何测试包含时间参考的 REDUX 动作