T-SQL 中的 FIFO 利润计算

时间:2021-01-28 14:59:29

标签: sql sql-server tsql

我需要一些关于单个查询的帮助或指针,它可以根据下面附加的数据集上的 FIFO(先进先出)原则为每笔股票交易建立利润/亏损。应与 SQL Server 2016+ 和 Azure SQL 兼容。

示例数据如下所示:

<头>
股票名称 交易日期 转码 数量 价格美元 TotalAmountUSD
StockABC 2017-12-11 11:16:11.000 购买 2364,444444 0,114323 270,310382
股票XYZ 2017-12-11 11:16:11.000 购买 2364,444444 0,114323 270,310382
StockABC 2017-12-14 14:16:24.000 1000 0,158849 158,849
股票XYZ 2017-12-14 14:16:24.000 1000 0,158849 158,849
StockABC 2017-12-14 19:38:46.000 700 0,198934 139,2538
股票XYZ 2017-12-14 19:38:46.000 700 0,198934 139,2538
StockABC 2017-12-15 09:38:09.000 664,4444444 0,207171 137,65362

我需要一个计算,计算每笔交易的利润。

例如如果我以 5 价买入 10 股股票,然后再买入 10 股至 6 价,我以 9 价卖出所有 15 股,我需要计算利润,考虑买入的价格。

我曾尝试使用 CTE 的光标等,但我只能找到有关数量总计的帮助。

整体数据量较少(总共> 50000行),所以不要太在意性能。

可以从下面插入测试数据:

CREATE TABLE dbo.Stock 
(
    Currency VARCHAR(50),
    TransactionDate DATETIME,
    TranCode VARCHAR(5), 
    Quantity NUMERIC(38,18),
    Price NUMERIC(38,18),
    CostBasis NUMERIC(38,18)
)

INSERT INTO dbo.Stock (Currency, TransactionDate, TranCode, Quantity, Price, CostBasis)
    SELECT * 
    FROM
        (SELECT 
             'StockABC' AS Currency,
             'Dec 11 2017 11:16AM' AS TransactionDate,
             'BUY' AS TranCode, 
             '2364.444444440000000000' AS Quantity, 
             '0.114323000000000000' AS Price,   
             '270.310382000000000000' AS CostBasis 
         UNION
         SELECT 
             'StockABC' AS Currency,    
             'Dec 14 2017  2:16PM' AS TransactionDate,  
             'SELL' AS TranCode,    
             '1000.000000000000000000' AS Quantity, 
             '0.158849000000000000' AS Price,   
             '158.849000000000000000' AS CostBasis 
         UNION
         SELECT 
             'StockABC' AS Currency,    
             'Dec 14 2017  7:38PM' AS TransactionDate,  
             'SELL' AS TranCode,    
             '700.000000000000000000' AS Quantity,  
             '0.198934000000000000' AS Price,   
             '139.253800000000000000' AS CostBasis 
         UNION
         SELECT 
             'StockABC' AS Currency,    
             'Dec 15 2017  9:38AM' AS TransactionDate,  
             'SELL' AS TranCode,    
             '664.444444440000000000' AS Quantity,  
             '0.207171000000000000' AS Price,   
             '137.653620000000000000' AS CostBasis 
         UNION
         SELECT 
             'StockABC' AS Currency,    
             'Dec 18 2017 11:34AM' AS TransactionDate,  
             'BUY' AS TranCode, 
             '1334.531919170000000000' AS Quantity, 
             '0.489515000000000000' AS Price,   
             '653.273392000000000000' AS CostBasis 
         UNION
         SELECT 
             'StockABC' AS Currency,    
             'Dec 21 2017  9:02PM' AS TransactionDate,  
             'SELL' AS TranCode,    
             '334.531919170000000000' AS Quantity,  
             '0.473771000000000000' AS Price,   
             '158.491522000000000000' AS CostBasis 
         UNION
         SELECT 
             'StockABC' AS Currency,    
             'Dec 26 2017 10:44AM' AS TransactionDate,  
             'BUY' AS TranCode, 
             '400.000000000000000000' AS Quantity,  
             '0.440974000000000000' AS Price,   
             '176.389600000000000000' AS CostBasis 
         UNION

SELECT 'StockABC' AS Currency,  'Dec 26 2017 10:45AM' AS TransactionDate,   'SELL' AS TranCode, '399.000000000000000000' AS Quantity,   '0.438398000000000000' AS Price,    '174.920802000000000000' AS CostBasis 
         UNION

SELECT 'StockABC' AS Currency,  'Dec 30 2017 11:34AM' AS TransactionDate,   'SELL' AS TranCode, '500.000000000000000000' AS Quantity,   '0.633751000000000000' AS Price,    '316.875500000000000000' AS CostBasis 
         UNION

SELECT 'StockABC' AS Currency,  'Jan  3 2018  5:45PM' AS TransactionDate,   'SELL' AS TranCode, '20.000000000000000000' AS Quantity,    '1.056470000000000000' AS Price,    '21.129400000000000000' AS CostBasis 
         UNION

SELECT 'StockABC' AS Currency,  'Jan 10 2018  8:48AM' AS TransactionDate,   'BUY' AS TranCode,  '292.310860920000000000' AS Quantity,   '0.750911000000000000' AS Price,    '219.499441000000000000' AS CostBasis 
         UNION

SELECT 'StockABC' AS Currency,  'Mar  6 2018  6:51PM' AS TransactionDate,   'BUY' AS TranCode,  '700.495360820000000000' AS Quantity,   '0.283468000000000000' AS Price,    '198.568019000000000000' AS CostBasis 
         UNION

SELECT 'StockABC' AS Currency,  'Mar 21 2018  3:42PM' AS TransactionDate,   'SELL' AS TranCode, '1472.806221740000000000' AS Quantity,  '0.217339000000000000' AS Price,    '320.098231000000000000' AS CostBasis 
         UNION

SELECT 'StockABC' AS Currency,  'Apr 11 2018  6:03PM' AS TransactionDate,   'BUY' AS TranCode,  '910.127737230000000000' AS Quantity,   '0.164000000000000000' AS Price,    '149.260949000000000000' AS CostBasis 
         UNION

SELECT 'StockABC' AS Currency,  'Apr 16 2018  7:53AM' AS TransactionDate,   'SELL' AS TranCode, '450.000000000000000000' AS Quantity,   '0.235482000000000000' AS Price,    '105.966900000000000000' AS CostBasis 
         UNION

SELECT 'StockABC' AS Currency,  'Apr 24 2018  8:15PM' AS TransactionDate,   'SELL' AS TranCode, '460.127737230000000000' AS Quantity,   '0.297748000000000000' AS Price,    '137.002114000000000000' AS CostBasis 
         UNION

SELECT 'StockABC' AS Currency,  'Jul 13 2018  8:08PM' AS TransactionDate,   'BUY' AS TranCode,  '1500.009200180000000000' AS Quantity,  '0.133000000000000000' AS Price,    '199.501224000000000000' AS CostBasis 
         UNION

SELECT 'StockABC' AS Currency,  'Jul 22 2018  5:08PM' AS TransactionDate,   'SELL' AS TranCode, '500.000000000000000000' AS Quantity,   '0.174302000000000000' AS Price,    '87.151000000000000000' AS CostBasis 
         UNION

SELECT 'StockABC' AS Currency,  'Aug  5 2018  5:34PM' AS TransactionDate,   'SELL' AS TranCode, '1000.009200180000000000' AS Quantity,  '0.127404000000000000' AS Price,    '127.405172000000000000' AS CostBasis 
         UNION

SELECT 'StockABC' AS Currency,  'Sep 22 2018  1:29PM' AS TransactionDate,   'BUY' AS TranCode,  '1810.263568330000000000' AS Quantity,  '0.082654000000000000' AS Price,    '149.625525000000000000' AS CostBasis                     
         UNION 

SELECT 'StockABC' AS Currency,  'Nov 25 2018  2:55AM' AS TransactionDate,   'BUY' AS TranCode,  '3000.000000000000000000' AS Quantity,  '0.035000000000000000' AS Price,    '105.000000000000000000' AS CostBasis 
         UNION

SELECT 'StockABC' AS Currency,  'Feb 11 2019  9:07AM' AS TransactionDate,   'BUY' AS TranCode,  '1782.043635700000000000' AS Quantity,  '0.042000000000000000' AS Price,    '74.845833000000000000' AS CostBasis 
         UNION

SELECT 'StockABC' AS Currency,  'Mar  9 2019  2:19PM' AS TransactionDate,   'BUY' AS TranCode,  '4190.000000000000000000' AS Quantity,  '0.043930000000000000' AS Price,    '184.066700000000000000' AS CostBasis 
         UNION

SELECT 'StockABC' AS Currency,  'Apr 25 2019  5:41PM' AS TransactionDate,   'BUY' AS TranCode,  '2261.000000000000000000' AS Quantity,  '0.075000000000000000' AS Price,    '169.575000000000000000' AS CostBasis 
         UNION

SELECT 'StockABC' AS Currency,  'Sep  9 2019  5:06PM' AS TransactionDate,   'BUY' AS TranCode,  '2200.000000000000000000' AS Quantity,  '0.045927000000000000' AS Price,    '101.039400000000000000' AS CostBasis ) A

2 个答案:

答案 0 :(得分:1)

这是一种简单的方法(有点),它使用名为 dbo.fnTallytally function 为每个项目或数量的“份额”生成 1 行。 'buy_cte' 和 'sell_cte' 都使用 CROSS APPLY dbo.fnTally(1, s.Quantity) 来扩展每个数量项目的行。此外,CTE 都分配了一个名为“trans_rn”的序数 row_number。通过将 'trans_rn' 上的两个 CTE 连接起来,只需将价格相加即可计算出 FIFO 盈利能力。像这样

dbo.fnTally

CREATE FUNCTION [dbo].[fnTally]
/**********************************************************************************************************************
    Jeff Moden Script on SSC: https://www.sqlservercentral.com/scripts/create-a-tally-function-fntally
**********************************************************************************************************************/
        (@ZeroOrOne BIT, @MaxN BIGINT)
RETURNS TABLE WITH SCHEMABINDING AS 
 RETURN WITH
  H2(N) AS ( SELECT 1 
               FROM (VALUES
                     (1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    )V(N))            --16^2 or 256 rows
, H4(N) AS (SELECT 1 FROM H2 a, H2 b) --16^4 or 65,536 rows
, H8(N) AS (SELECT 1 FROM H4 a, H4 b) --16^8 or 4,294,967,296 rows
            SELECT N = 0 WHERE @ZeroOrOne = 0 UNION ALL
            SELECT TOP(@MaxN)
                   N = ROW_NUMBER() OVER (ORDER BY N)
              FROM H8;
;with 
buy_cte(Currency, TransactionDate, Price, trans_rn) as (
    select Currency, TransactionDate, Price,
           row_number() over (partition by Currency order by TransactionDate)
    from #Stock s
         cross apply dbo.fnTally(1, s.Quantity) fn
    where TranCode='BUY'),
sell_cte(Currency, TransactionDate, Price, trans_rn) as (
    select Currency, TransactionDate, Price,
           row_number() over (partition by Currency order by TransactionDate)
    from #Stock s
         cross apply dbo.fnTally(1, s.Quantity) fn
    where TranCode='SELL')
select s.Currency, s.TransactionDate, 
       cast(sum(b.price) as decimal(14,2)) buy_sum, 
       cast(sum(s.price) as decimal(14,2)) sell_sum, 
       cast(sum(s.price-b.price) as decimal(14,2)) profit_sum, 
       count(*) q_sold
from buy_cte b
     join sell_cte s on b.Currency=s.Currency
                    and b.trans_rn=s.trans_rn
                    and b.TransactionDate<s.TransactionDate
group by s.Currency, s.TransactionDate
order by TransactionDate;
Currency    TransactionDate         buy_sum sell_sum    profit_sum  q_sold
StockABC    2017-12-14 14:16:00.000 114.32  158.85      44.53       1000
StockABC    2017-12-14 19:38:00.000 80.03   139.25      59.23       700
StockABC    2017-12-15 09:38:00.000 75.91   137.56      61.65       664
StockABC    2017-12-21 21:02:00.000 163.50  158.24      -5.26       334
StockABC    2017-12-26 10:45:00.000 195.32  174.92      -20.40      399
StockABC    2017-12-30 11:34:00.000 244.76  316.88      72.12       500
StockABC    2018-01-03 17:45:00.000 9.79    21.13       11.34       20
StockABC    2018-03-21 15:42:00.000 633.45  319.92      -313.53     1472
StockABC    2018-04-16 07:53:00.000 73.92   105.97      32.05       450
StockABC    2018-04-24 20:15:00.000 75.44   136.96      61.52       460
StockABC    2018-07-22 17:08:00.000 66.53   87.15       20.62       500
StockABC    2018-08-05 17:34:00.000 133.00  127.40      -5.60       1000

答案 1 :(得分:0)

感谢您的反馈,让我做一些更改。

SELECT Currency,TransactionDate,
SUM(CASE WHEN TranCode = 'SELL' THEN CostBasis * (-1) ELSE CostBasis END )OVER(PARTITION BY Currency ORDER BY TransactionDate ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS TOTAL
FROM #Stock
GROUP BY Currency, TransactionDate, CostBasis, TranCode
相关问题