问题描述
我当时正在处理个人投资工作表,并思考了使之有用并能够计算每个资产余额和当前平均值所需的最少信息。
我看到了几个示例,其中大多数依赖于借方和贷方列,或使用单行来添加买/卖价格;第一种情况需要至少两行来说明钱的去向/去向;第二个很难表达部分卖出(例如买入100只股票,卖出50只股票,然后再买入50只;当前头寸和平均买入价是多少?)
考虑到这一点,我想到了I described here
考虑以下数据集:
SELECT *
FROM (
VALUES (1,'2020-08-01','MBIT','POCKET','MONEY',100.00,'Added $100 from my pocket into MBIT broker'),(2,'2020-08-02','XPTO',0.50,'Bought 0.50 XPTO for $100 (thus rate=200)'),(3,'2020-08-03',125.00,'Sold 0.50 XPTO for $125 (thus rate=250,$25 profit)'),(4,'2020-08-04',0.35,85.00,'Bought 0.35 XPTO for $85 (thus rate=~242.85)'),(5,'2020-08-05','WXYZ',1.75,20.00,'Bought 1.75 WXYZ for $20 (rate~=11.43)'),(6,'2020-08-06',1.85,15.00,'Bought 1.85 WXYZ for $15 (new total = 3.6 for $35,rate~=9.72)'))
Entries([Order#],Date,Broker,Debit,Credit,Quantity,Value,Description)
我想计算每个经纪人/资产的当前平均值和余额;该怎么做?
解决方法
第一步是确定应评估为货币的资产(即固定汇率为1),并存储其他资产的当前报价:
WITH Coins AS
( SELECT *
FROM (
VALUES ('POCKET',1,1.00),-- Pocket and money always have $1 price
('MONEY',-- Pocket and money always have $1 price
('XPTO',200.00),-- Current rate for this asset
('WXYZ',8.50))
Entries(Name,IsMoney,[Current])
)
然后,我们需要从给定的数据集(名为RawData
)中拆分每一行,因此我们可以为贷方部分留一行,并为借方留一行。我们还需要计算每个非货币资产的汇率:
,Accounting AS
( SELECT ROW_NUMBER() OVER (ORDER BY Date,Order#,Quantity) Sequence,[Order#],Date,Broker,Account,Description,CASE WHEN Coins.IsMoney = 1 THEN Value ELSE Quantity END Quantity,Value /
CASE WHEN Coins.IsMoney = 1 THEN Value ELSE Quantity END Rate
FROM ( SELECT [Order#],Debit AS Account,-Quantity Quantity,-Value Value,Description
FROM RawData
UNION ALL
SELECT [Order#],Credit AS Account,+Quantity Quantity,+Value Value,Description
FROM RawData
) Accounting
LEFT
JOIN Coins
ON Coins.Name = Accounting.Account
)
在SuperUser question comments上,我发现计算平均价格会比较棘手,特别是考虑到我需要忽略未平仓头寸。
由于出售资产余额的操作与购买资产余额的操作并不直接相关,所以我陷入了困境。
转折点是当我意识到我不需要匹配这些东西时:我需要的是找出余额何时为零,并仅使用其最新的“版本”。
所以我转向了窗口函数;这里的挑战是确定何时重置天平。进行了一些谷歌搜索,使我进入T-SQL Feature Request: Add RESET WHEN Clause to Reset Window Partition,作者巧妙地描述了请求功能如何帮助进行更简单的查询,并给出了在Microsoft员工点头批准时如何克服它们的提示。
,Balance AS
( SELECT *,MAX(BalanceVersion) OVER (PARTITION BY Broker,Account) LatestVersion
FROM ( SELECT *,SUM(BalanceReset) OVER (
PARTITION BY Broker,Account
ORDER BY Sequence) BalanceVersion
FROM ( SELECT *,CASE WHEN LAG(Total) OVER (
PARTITION BY Broker,Account
ORDER BY Sequence) = 0
THEN 1
ELSE 0
END BalanceReset
FROM ( SELECT *,SUM (Quantity) OVER (
PARTITION BY Broker,Account
ORDER BY Sequence) AS Total
FROM Accounting
) Account
) Reset
) Versions
)
把这件事分开:
- 让我们首先计算每种资产的运行总计(
Account
子选择) - 我们使用
LAG
function(Reset
子选择)来确定余额为零的时间 - 现在让我们使用重置标识符将余额分组(
Versions
子选择)来计算新的运行总计 - 最后一步是确定每个经纪人/资产组的最新版本
通过识别“数据岛”,我们又回到了游戏中!
现在让我们计算未平仓头寸的正确平均值和最终余额:
,Final AS
( SELECT Broker,SUM(Quantity) Quantity,SUM(CASE WHEN IsMoney = 1 THEN 1.000 ELSE [Rate] * Quantity END) /
SUM(CASE WHEN IsMoney = 1 THEN 1.000 ELSE Quantity END) Rate
FROM Balance
WHERE BalanceVersion = LatestVersion
GROUP
BY Broker,Account
)
建立正确的XPTO
平衡需要所有窗口函数疯狂;对于该计算,我们不能使用第一个买/卖操作,因为它会将头寸清零,我们需要重新开始以获取正确的平均买入价。
其余查询仅是将当前资产价格与计算得出的余额和平均价格放在一起的问题:
SELECT Final.Broker,Final.Account,Final.Quantity,Final.Rate [Original],Coins.[Current] [Current],(Coins.[Current] / Final.Rate)-1 [%],(Coins.[Current] * Final.Quantity) [Value]
FROM Final
LEFT
JOIN Coins
ON Coins.Name = Final.Account
ORDER
BY Final.Broker,Final.Account
查看我们可以看到的数据:
- 我们向该经纪人投资了多少(价值
POCKET
- 我们在经纪人中闲置了多少钱(价值
MONEY
- 余额
WXYZ
- 我们首先以$ 20的价格购买了1.75 WXYZ(汇率:20 / 1.75〜= 11.43)
- 然后我们以15美元(价格:15 / 1.85〜= 8.10)购买了更多的1.85 WXYZ
- 因此正确的平均价格为(20 + 15)/(1.75 + 1.85)〜= 9.72
- 余额
XPTO
- 如果考虑所有操作,则平均值为171.43
- 这是错误的,因为如果我们购买了一定数量的资产并出售了所有资产,那么这些价格不会影响新的购买价格
- 因此,该资产的正确平均值约为〜242.85
结论:在这种假设的情况下:
- 我们投入了100美元;
- 获利25美元;
- 购买了一些XPTO
- 买了一些WXYZ
- WXYZ市场价格下跌,我们买了更多
- XPTO自上次购买以来已下降12.57%
- 考虑到前两次收购的平均价格,WXYZ下跌了17.65%
- 如果我们将最后一列中的所有值相加,我们可以看到仍然有$ 5.60的利润