使用基于交易历史的 FIFO 方法创建一个函数来计算股票的已实现收益

Creating a function to calculate realized gains of stock using FIFO method based on transactional history

我是 google 应用程序脚本的新手,正在尝试尽可能多地学习,但我遇到了一些我无法理解的事情......

我的目标是创建一个函数,根据交易历史的输入数据,以先进先出的会计风格“FIFO”计算股票的已实现收益。

计算已实现收益的公式相对简单

(Total Sell price - Original Cost ) = Realized gains/loss 
Realized Gains/Loss / Original Cost = Profit/loss (%)

如果有人在三个不同的时间以三个不同的价格 (x1,x2,x3) 购买了一只股票,而您希望计算 'realized gains' - 您是采用 x1、x2 还是 x3 的原始成本? FIFO 样式将采用 X1,因为它是 'first in, first out'。这很重要,因为在美国有一种基于所有已实现收益的资本利得税。

这是我的数据:https://docs.google.com/spreadsheets/d/1V7CpCxBH0lg6wi1TAhfZJP5gXE8hj7ivQ8_ULxLSLgs/edit?usp=sharing

const ss = SpreadsheetApp.getActiveSpreadsheet();
const historySheet = ss.getSheetByName('Blad1');

function fifoProject () {

  let input = historySheet.getDataRange().getValues();
  let onlyBuyRows = input.filter(row => row[2] == 'Buy');
  let roundedTotal, priceQuantityMultiplication;
  let onlyColABDF = onlyBuyRows.map(row => {
    roundedTotal = Math.round(row[5] * 100) / 100;
    priceQuantityMultiplication = row[3] * roundedTotal;
    return [
      row[0], row[1], row[3], roundedTotal, priceQuantityMultiplication
  ]});
  let sorted = onlyColABDF.sort((a, b) => {
    if (a[1] > b[1]) return 1;
    if (a[1] < b[1]) return -1;
    if (a[0] > b[0]) return 1;
    if (a[0] < b[0]) return -1;
    return 0;
  });
  let arrayBuyData = [];
  arrayBuyData.push(sorted);
  console.log(arrayBuyData);
  //ss.getSheetByName('output').getRange(1, 1, sorted.length, sorted[0].length).setValues(sorted)

 let input2 = historySheet.getRange(2, 1, historySheet.getLastRow() - 1, 4).getValues();
  let onlySellRows = input2.filter(row => row[2] == 'Sell');
  let sellTotalArray = []
  let addAllSellObject = onlySellRows.reduce((acc, curr) => {
    if (curr[1] in acc) {
      acc[curr[1]] += curr[3]
    } else {
      acc[curr[1]] = curr[3]
    }
    return acc;
  }, {});

  let addAllSellArray = Object.entries(addAllSellObject).sort((a, b) => {
    if (a[0] > b[0]) return 1;
    if (a[0] < b[0]) return -1;
    return 0;
  })
  sellTotalArray.push(addAllSellArray);
  console.log(sellTotalArray[0][1]);
  }

这是我认为函数应该做的:

https://ibb.co/Pr0gC1S

我们非常欢迎任何方向正确的想法。

编辑:试图澄清这个问题。希望这样做:

<style>
    .demo {
        border:1px solid #C0C0C0;
        border-collapse:collapse;
        padding:5px;
    }
    .demo th {
        border:1px solid #C0C0C0;
        padding:5px;
        background:#F0F0F0;
    }
    .demo td {
        border:1px solid #C0C0C0;
        padding:5px;
    }
</style>
<table class="demo">
    <caption></caption> <thead> <tr>        <th>Date</th>
        <th>Type</th>
        <th>Amount p.p</th>
        <th>Quantity</th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>&nbsp;Jan</td>
        <td>&nbsp;Buy</td>
        <td>&nbsp;</td>
        <td>&nbsp;5</td>
    </tr>
    <tr>
        <td>&nbsp;Feb</td>
        <td>&nbsp;Buy</td>
        <td>&nbsp;</td>
        <td>&nbsp;8</td>
    </tr>
    </tbody>
</table>

例如,如果 7 件以 23 美元的价格售出,则结果为:

<style>
    .demo {
        border:1px solid #C0C0C0;
        border-collapse:collapse;
        padding:5px;
    }
    .demo th {
        border:1px solid #C0C0C0;
        padding:5px;
        background:#F0F0F0;
    }
    .demo td {
        border:1px solid #C0C0C0;
        padding:5px;
    }
</style>
<table class="demo">
    <caption></caption> <thead> <tr>        <th>Date</th>
        <th>Amount</th>
        <th>Buy price p.p</th>
        <th>Profit p.p</th>
    </tr>   </thead>    <tbody> <tr>
        <td>&nbsp;Jan</td>
        <td>&nbsp;5</td>
        <td>&nbsp;</td>
        <td>&nbsp;</td>
    </tr>
    <tr>
        <td>&nbsp;Feb</td>
        <td>&nbsp;2</td>
        <td>&nbsp;</td>
        <td>&nbsp;</td>
    </tr>
    </tbody>
</table>

总计:

<style>
    .demo {
        border:1px solid #C0C0C0;
        border-collapse:collapse;
        padding:5px;
    }
    .demo th {
        border:1px solid #C0C0C0;
        padding:5px;
        background:#F0F0F0;
    }
    .demo td {
        border:1px solid #C0C0C0;
        padding:5px;
    }
</style>
<table class="demo">
    <caption></caption> <thead> <tr>        <th><br></th>
        <th><br></th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>&nbsp;Total buy price</td>
        <td>&nbsp;</td>
    </tr>
    <tr>
        <td>&nbsp;Total sell price</td>
        <td>&nbsp;1</td>
    </tr>
    <tr>
        <td>&nbsp;Total profit (or loss)</td>
        <td>&nbsp;</td>
    </tr>
    <tbody>
</table>

编辑 2:根据 Jacques 的回答,我创建了以下代码:

const ss = SpreadsheetApp.getActiveSpreadsheet();
const historySheet = ss.getSheetByName('Blad1');
    
    
// Function which pushes item x amount of times based on a number
    function pushNumber (number, array, item) {
      for(var i = 0; i<number; i++){
        array.push(item);
      }
    }
    
    function shiftNumber (number, array, item) {
      for(var i = 0; i<number; i++){
        array.shift(item);
      }
    }
    
    function projectFIFO () {
    let input = historySheet.getRange(3,1,historySheet.getLastRow()-1, 5).getValues();
    for (var i = 0; i<security.length; i++) {
      let action = row[2].toString();
      let quantity = Number(row[3]);
      let price = Number(row[4]);
      let emptyArray = [];
      // console.log(quantity);
      for (security of array) 
      if (action === 'Buy') {
        let numberPush = quantity;
        pushNumber(quantity, emptyArray, price);
      };
      console.log(emptyArray);
    };
    }

基本上我创建了一个辅助函数 pushNumber,它根据 Number 的数量进行推送。然后在 fifo 函数中,我创建了一个 for 循环来遍历每一行,看看是否有买入并根据数量推送买入,推送在一个空数组中。但是,我想学习如何分别为每个证券而不是整个范围执行此操作。我想也许一个对象是一个解决方案,但还没有想出来。

在研究了您的代码和示例后 Sheet 我发现您非常接近实现您的目标。您只需编写一个与您的 Transactions table 交互的函数,以获得所需结果的数据 table.

在第一次交互中,过滤 Transaction table 以仅显示您想要的票证(就像您在示例脚本中所做的那样)。继续从上到下交互table(可以使用getRange()/getValues() as shown in your example). Now, for every Buy row, push() the price as many times as shares bought. So for example if you bought 3 shares at 3, you should push 313, 313, 313. For every Sell order you only have to shift()数组,计算返回值(买入价)和卖出价的差值。

通过这种方法,您将有效地开发管理买卖订单的 FIFO 逻辑,因此它们之间的差额将是已实现的利润。如果您对此方法有疑问,请随时发表评论。


更新

您可以使用此方法从数组中删除重复项。如果您事先不知道证券的名称,这将很有用。

var allTickers = ['AAPL', 'AAPL', 'GOOG', 'MSFT', '2222.SR', 'MSFT', 'AMZN',
  'AAPL', '2222.SR', 'MSFT', 'AMZN', '2222.SR'
]
var uniqueTickers = [...new Set(allTickers)];