第一步是确定应该被评估为货币的资产(即固定利率为 1)并存储其他资产的当前报价:
WITH Coins AS
( SELECT *
FROM (
VALUES ('POCKET', 1, 1.00), -- Pocket and money always have $1 price
('MONEY' , 1, 1.00), -- Pocket and money always have $1 price
('XPTO' , 0, 200.00), -- Current rate for this asset
('WXYZ' , 0, 8.50))
Entries(Name, IsMoney, [Current])
)
然后我们需要从给定的数据集(命名为RawData)中拆分每一行,这样我们就可以有一行用于贷方部分,另一行用于借方部分。我们还需要计算每种非货币资产的费率:
, Accounting AS
( SELECT ROW_NUMBER() OVER (ORDER BY Date, Order#, Quantity) Sequence,
[Order#], Date, Broker, Account, IsMoney, 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#], Date, Broker, Debit AS Account,
-Quantity Quantity, -Value Value, Description
FROM RawData
UNION ALL
SELECT [Order#], Date, Broker, 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,作者精彩地描述了请求功能如何帮助进行更简单的查询,并给出了如何在微软人员点赞的同时克服它们的提示。
, 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 subselect)
- 我们使用
LAG function(Reset 子选择)识别余额何时变为零
- 让我们计算一个新的运行总计,现在使用重置标识符对余额进行分组 (
Versions subselect)
- 最后一步是确定每个经纪人/资产组的最新版本
通过识别“数据孤岛”,我们又回到了游戏中!
现在让我们计算未平仓头寸的正确平均值和最终余额:
, Final AS
( SELECT Broker, Account,
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 美元购买了更多 1.85 WXYZ(汇率:15/1.85 ~=8.10)
- 因此正确的平均价格为 (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 美元的利润