首页 > 解决方案 > 解析订单文件,按股票代码分组并计算平均订单价格和数量

问题描述

我有一个平面文件,其中包含给定日期的股票购买清单。

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]

标签: scala

解决方案


您可以执行以下操作:
使用 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; 可能您还应该为每种情况将大主体拆分为多个可重用和可测试的功能。

无论如何,请随时提出您可能有的任何问题。


推荐阅读