【问题标题】:Creating a function to calculate realized gains of stock using FIFO method based on transactional history使用基于交易历史的 FIFO 方法创建一个函数来计算股票的已实现收益
【发布时间】:2021-08-28 09:03:47
【问题描述】:

我是 google app 脚本的新手,并试图尽可能多地学习,但我偶然发现了一些我头疼的东西.....

我的目标是创建一个函数,在给定交易历史的输入数据的情况下,以“先进先出”的会计风格(即先进先出)计算股票的已实现收益。

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

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

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

这是我的数据: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;$10</td>
        <td>&nbsp;5</td>
    </tr>
    <tr>
        <td>&nbsp;Feb</td>
        <td>&nbsp;Buy</td>
        <td>&nbsp;$15</td>
        <td>&nbsp;8</td>
    </tr>
    </tbody>
</table>

例如,如果以 23 美元的价格售出 7 个数量,则会导致:

<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;$10</td>
        <td>&nbsp;$13</td>
    </tr>
    <tr>
        <td>&nbsp;Feb</td>
        <td>&nbsp;2</td>
        <td>&nbsp;$15</td>
        <td>&nbsp;$8</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;$80</td>
    </tr>
    <tr>
        <td>&nbsp;Total sell price</td>
        <td>&nbsp;$161</td>
    </tr>
    <tr>
        <td>&nbsp;Total profit (or loss)</td>
        <td>&nbsp;$81</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 循环来遍历每一行并查看是否有购买并根据数量推送购买,推送在一个空数组内。但是,我想学习如何为每个安全单独而不是整个范围执行此操作。我认为也许一个对象是一个解决方案,但还没有弄清楚。

【问题讨论】:

  • 能否请您分解并简化您的问题?
  • @idfurw 对问题进行了编辑以试图澄清。

标签: google-apps-script stock fifo


【解决方案1】:

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

在第一次交互中,过滤 Transaction 表以仅显示您想要的票证(就像您在示例脚本中所做的那样)。继续从上到下交互表格(您可以使用getRange()/getValues(),如示例中所示)。现在,对于每一行 Buypush() 的价格是买入股票的倍数。因此,例如,如果您以 313 美元的价格购买了 3 股,您应该推送313, 313, 313。对于每个卖出订单,您只需shift() 数组并计算返回值(买入价)和卖出价之间的差额。

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


更新

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

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

【讨论】:

  • 非常感谢您的建议。我认为您的方法很有意义,这是一个很好的答案。但是,我很难实现您的建议。我将编辑我的问题,向您展示我为执行您的计划而创建的代码。也许您可以添加一些建议。
  • 我阅读了您的更新和新代码。如果您需要的唯一更改是分别管理每个安全性,那么您只需要进行很小的更改。在创建input 时,在设置范围之后,您应该使用createFiltersetColumnFilterCriteria 创建一个过滤器,以便只关注所需的安全性。请测试此方法并告诉我它是否有效。
  • 感谢您的建议。但是,我似乎无法弄清楚如何做到这一点。例如,我将使用什么筛选条件?我可以尝试使用“whenTextCointains(text)”,但通常我不会事先知道证券的名称。
  • 我假设您已经知道证券的名称,在这种情况下,您使用该过滤器是正确的。如果您事先不知道名称,可以使用一种解决方法。首先,您应该获取数组中的所有证券,然后删除重复项。请在更新中查看我的示例。
猜你喜欢
  • 2021-02-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多