Создание функции для расчета реализованной прибыли по акциям с использованием метода FIFO, основанного на истории транзакций

#google-apps-script #stock #fifo

Вопрос:

Я новичок в Google app script и пытаюсь узнать как можно больше, но я наткнулся на что-то, что выше моего понимания…..

Моя цель состоит в том, чтобы создать функцию, которая вычисляет реализованную прибыль по акциям, учитывая входные данные истории транзакций, в стиле бухгалтерского учета «FIFO», что означает первый вход, первый выход.

Формула для расчета реализованной прибыли относительно проста

 (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

Any idea’s in the right direction would be very welcome.

EDIT: Trying to clarify the question. Wish to do this:

 <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>amp;nbsp;Jan</td>
        <td>amp;nbsp;Buy</td>
        <td>amp;nbsp;$10</td>
        <td>amp;nbsp;5</td>
    </tr>
    <tr>
        <td>amp;nbsp;Feb</td>
        <td>amp;nbsp;Buy</td>
        <td>amp;nbsp;$15</td>
        <td>amp;nbsp;8</td>
    </tr>
    </tbody>
</table> 

For example if then 7 quantity sold for $23, it would result in:

 <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>amp;nbsp;Jan</td>
        <td>amp;nbsp;5</td>
        <td>amp;nbsp;$10</td>
        <td>amp;nbsp;$13</td>
    </tr>
    <tr>
        <td>amp;nbsp;Feb</td>
        <td>amp;nbsp;2</td>
        <td>amp;nbsp;$15</td>
        <td>amp;nbsp;$8</td>
    </tr>
    </tbody>
</table> 

TOTALS:

 <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>amp;nbsp;Total buy price</td>
        <td>amp;nbsp;$80</td>
    </tr>
    <tr>
        <td>amp;nbsp;Total sell price</td>
        <td>amp;nbsp;$161</td>
    </tr>
    <tr>
        <td>amp;nbsp;Total profit (or loss)</td>
        <td>amp;nbsp;$81</td>
    </tr>
    <tbody>
</table> 

ПРАВКА 2: Основываясь на ответе Жака, я создал следующий код:

 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, которая нажимает на основе количества номеров. Затем в функции fifo я создаю цикл for, чтобы просмотреть каждую строку и посмотреть, есть ли покупка, и нажимаю на покупку в зависимости от количества, нажатие находится внутри пустого массива. Однако я хотел бы узнать, как это сделать для каждой безопасности отдельно, а не для всего диапазона. Я думаю, что, возможно, объект-это решение, но еще не понял его.

Комментарии:

1. Не могли бы вы, пожалуйста, разбить и упростить свой вопрос?

2. @idfurw Внес правку в вопрос, чтобы попытаться уточнить.

Ответ №1:

Изучив ваш код и примерный лист, я вижу, что вы очень близки к достижению своей цели. Вам нужно только закодировать функцию, которая взаимодействует с вашей таблицей транзакций, чтобы получить данные для вашей таблицы желаемых результатов.

В первом взаимодействии отфильтруйте таблицу транзакций, чтобы показать только нужный вам билет (как вы сделали в примере сценария). Перейдите к взаимодействию с таблицей сверху вниз (вы можете использовать getRange() / getValues() , как показано в вашем примере). Теперь для каждой строки покупки push() цена увеличивается в столько раз, сколько было куплено акций. Так, например, если вы купили 3 акции по 313 долларов, вам следует нажать 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)];
 

Комментарии:

1. Большое спасибо за ваше предложение. Я думаю, что ваш метод имеет большой смысл, и это отличный ответ. Однако мне трудно добиться того, что вы предложили. Я отредактирую свой вопрос, чтобы показать вам код, который я создал до сих пор для выполнения вашего плана. Возможно, вы можете добавить несколько предложений.

2. Я прочитал ваше обновление и новый код. Если единственное изменение, которое вам нужно, — это управлять каждой системой безопасности отдельно, вам нужно всего лишь небольшое изменение. При input создании, сразу после установки диапазона, вы должны создать фильтр с createFilter и setColumnFilterCriteria для того, чтобы сосредоточиться только на желаемой безопасности. Пожалуйста, протестируйте этот подход и дайте мне знать, работает ли он.

3. Спасибо за предложение. Однако я, похоже, не могу понять, как это сделать. Например, какие критерии фильтрации я бы использовал? Я могу попробовать использовать » whenTextCointains(текст)», но обычно я бы заранее не знал названия ценных бумаг.

4. Я предполагал, что вы уже знали названия ценных бумаг, в этом случае вы правы, используя этот фильтр. Если вы не знаете имена заранее, есть обходной путь, который вы можете использовать. Сначала вы должны принять все меры безопасности в массиве, а затем удалить дубликаты. Пожалуйста, посмотрите мой пример в обновлении.