使用 sql 生成所有已实现和未实现的交易?
Generating all realized and unrealized trades using sql?
问题:
给定不同金融证券的一些头寸,我想在给定日期之前对它们进行交易。在这样做时,我想跟踪哪些交易关闭了头寸并打开了一个新头寸。申请交易后持仓数量变为0时,持仓为CLOSED
。当头寸数量从 0 变为某个值时,头寸被视为 OPENED
。
设置:SQL DEMO SOURCE
假设我有以下 tables:
CREATE TABLE tPosition
(
SecNum INT,
Position INT
)
CREATE TABLE tTrade
(
TradeID INT IDENTITY(1,1),
TradeDate DATETIME,
SecNum INT,
Quantity INT
)
和一些示例数据:
INSERT INTO tPosition (SecNum, Position)
SELECT 1, 100
UNION
SELECT 2, 200
UNION
SELECT 3, -300
INSERT INTO tTrade (TradeID, TradeDate, SecNum, Quantity)
SELECT 1, '1/1/2016', 1, -50
UNION
SELECT 2, '1/2/2016', 1, -50
UNION
SELECT 3, '1/3/2016', 1, -50
UNION
SELECT 4, '1/4/2016', 1, 50
UNION
SELECT 6, '1/5/2016', 3, 200
UNION
SELECT 7, '1/5/2016', 3, 200;
样本SCENARIOS/CASES:SQL DEMO SOURCE
如果没有任何交易,我的结果将是(即恰好 table 的位置,带有 2 个额外的字段,稍后会有用):
SecNum, Position, OpenedByTradeID, ClosedByTradeID
1, 100, NULL, NULL
2, 200, NULL, NULL
3, -300, NULL, NULL
所以假设我将交易应用于 1/1/2016
之前的头寸并包括在内。与 TradeID
:1 的交易将影响 securityID
: 1 的头寸为 100+(-50)=50 所以我的结果应该是:
SecNum, Position, OpenedByTradeID, ClosedByTradeID
1, 50, NULL, NULL
2, 200, NULL, NULL
3, -300, NULL, NULL
OpenedByTradeID
和ClosedTradeID
仍然是NULL
,因为位置还没有越过0。
如果我应用交易直至并包括 1/2/2016
,我应该得到:
SecNum, Position, OpenedByTradeID, ClosedByTradeID
1, 0, NULL, 2
2, 200, NULL, NULL
3, -300, NULL, NULL
注意仓位已经变成0所以我们记录ClosedByTradeID
和关闭这个仓位的tradeID
。
最多 1/3/2016
我应该得到:
SecNum, Position, OpenedByTradeID, ClosedByTradeID
1, 0, NULL, 2
1, -50, 3, NULL
2, 200, NULL, NULL
3, -300, NULL, NULL
请注意,最近 tradeID
:3 在 securityID
:1 开设了一个新职位,因此我们将 OpenedByTradeID
列标记为 TradeID
:3
最多 1/4/2016
我应该得到:
SecNum, Position, OpenedByTradeID, ClosedByTradeID
1, 0, NULL, 2
1, 0, 3, 4
2, 200, NULL, NULL
3, -300, NULL, NULL
注意仓位已经变成0所以我们记录ClosedByTradeID
和关闭这个仓位的tradeID
-- TradeID
:4
直到并包括 1/5/2016
是一个 边缘情况 。这里发生了 2 件事:交易的应用越过 0 标记,因此需要建立新头寸并且同一天发生 2 笔交易。
正在申请:
tradeID
:6 对于 securityID
:3 会将位置调整为 -100 (-300 + 200 = -100).
- 然后我们需要应用
tradeID
:7 但这会穿过 0(-100 + 200 = 100 从 neg -> pos 穿过)所以我们需要在穿过 0 之前应用该部分以平仓然后用剩余的金额开始新的仓位。
- 部分
tradeID
:7 [100] 会将头寸调整为 0 (-100 + 100 = 0) 并关闭它(将在 ClosedByTradeID
列中注明)然后部分tradeID
:7 [剩余的 100] 会将位置调整为 100 (0 + 100 = 100) [也会在 OpenedByTradeID
列中注明]。
因此,我应该得到:
SecNum, Position, OpenedByTradeID, ClosedByTradeID
1, 0, NULL, 2
1, 0, 3, 4
2, 200, NULL, NULL
3, 0, NULL, 7
3, 100, 7, NULL
这听起来像是一个孤岛问题,但我似乎无法弄清楚如何写这个。
这只是使用递归的部分工作。我没写完,但也许其他人可以有想法。
我开始只使用 SecNum = 1
WITH DirectReports (SecNum, Position, OpenedByTradeID, ClosedByTradeID, level_id, TradeID)
AS (
SELECT SecNum, Position, NULL as OpenedByTradeID, NULL as ClosedByTradeID, 1, null as TradeID
FROM tPosition
WHERE SecNum = 1
UNION ALL
SELECT D.SecNum,
Position + Quantity as Position,
CASE WHEN Position = 0 and Position + Quantity <> 0
THEN T.TradeID
ELSE NULL
END as OpenedByTradeID,
CASE WHEN Position <> 0 and Position + Quantity = 0
THEN T.TradeID
ELSE NULL
END as ClosedByTradeID,
level_id + 1 as level_id,
T.TradeID
FROM DirectReports D
JOIN (SELECT *,
ROW_NUMBER() OVER (partition by SecNum ORDER BY TradeDate) as rn
FROM tTrade
) T
ON D.SecNum = T.SecNum
AND D.level_id = T.rn
)
SELECT *
FROM DirectReports
输出
我想我使用 WHILE
循环方法让它工作了。也许有更有效的方法,但我认为这可行...
DECLARE @result TABLE
(
SecNum INT,
Position INT,
OpenedByTradeID INT,
ClosedByTradeID INT
)
INSERT INTO @result(SecNum, Position)
SELECT SecNum, Position
FROM dbo.tPosition
SELECT *
FROM @result
ORDER BY SecNum
DECLARE @CurTradeID INT
SELECT @CurTradeID = MIN(TradeID) FROM dbo.tTrade
WHILE(@CurTradeID IS NOT NULL)
BEGIN
DECLARE @TradeQty INT, @TradeSecNum INT
SELECT @TradeQty = Quantity, @TradeSecNum = SecNum FROM dbo.tTrade WHERE TradeID = @CurTradeID
DECLARE @OldPos INT = (SELECT Position FROM @result WHERE ClosedByTradeID IS NULL AND SecNum = @TradeSecNum)
-- IF THERE IS NO POSITION
IF (@OldPos IS NULL)
BEGIN
INSERT INTO @result(SecNum, Position, OpenedByTradeID, ClosedByTradeID)
SELECT @TradeSecNum, @TradeQty, @CurTradeID, NULL
END
-- IF THIS TRADE CLOSES THE POSITION
ELSE IF (@OldPos + @TradeQty = 0)
BEGIN
UPDATE @result
SET ClosedByTradeID = @CurTradeID, Position = Position + @TradeQty
WHERE SecNum = @TradeSecNum AND ClosedByTradeID IS NULL
END
-- IF THIS TRADE MAKES THE POSITION CROSS THROUGH 0 i.e. IF TRADE MAKES POSITION CHANGE SIGN
ELSE IF (SIGN(@OldPos + @TradeQty) <> SIGN(@OldPos))
BEGIN
DECLARE @RemainingAmt INT = @TradeQty + @OldPos
UPDATE @result
SET ClosedByTradeID = @CurTradeID, Position = 0
WHERE SecNum = @TradeSecNum AND ClosedByTradeID IS NULL
INSERT INTO @result(SecNum, Position, OpenedByTradeID, ClosedByTradeID)
SELECT @TradeSecNum, @RemainingAmt, @CurTradeID, NULL
END
-- JUST UPDATE THE ACTIVE POSITION
ELSE
BEGIN
UPDATE @result
SET Position = Position + @TradeQty
WHERE SecNum = @TradeSecNum AND ClosedByTradeID IS NULL
END
SELECT @CurTradeID = MIN(TradeID) FROM dbo.tTrade WHERE TradeID > @CurTradeID
END
SELECT *
FROM @result
ORDER BY SecNum
P.S.: 我想我可以用我需要的交易做一个临时 table 我可以在我的查询中使用 table 而不是 tTrade以上,所以我不必一直处理选择特定日期的交易。
问题:
给定不同金融证券的一些头寸,我想在给定日期之前对它们进行交易。在这样做时,我想跟踪哪些交易关闭了头寸并打开了一个新头寸。申请交易后持仓数量变为0时,持仓为CLOSED
。当头寸数量从 0 变为某个值时,头寸被视为 OPENED
。
设置:SQL DEMO SOURCE
假设我有以下 tables:
CREATE TABLE tPosition
(
SecNum INT,
Position INT
)
CREATE TABLE tTrade
(
TradeID INT IDENTITY(1,1),
TradeDate DATETIME,
SecNum INT,
Quantity INT
)
和一些示例数据:
INSERT INTO tPosition (SecNum, Position)
SELECT 1, 100
UNION
SELECT 2, 200
UNION
SELECT 3, -300
INSERT INTO tTrade (TradeID, TradeDate, SecNum, Quantity)
SELECT 1, '1/1/2016', 1, -50
UNION
SELECT 2, '1/2/2016', 1, -50
UNION
SELECT 3, '1/3/2016', 1, -50
UNION
SELECT 4, '1/4/2016', 1, 50
UNION
SELECT 6, '1/5/2016', 3, 200
UNION
SELECT 7, '1/5/2016', 3, 200;
样本SCENARIOS/CASES:SQL DEMO SOURCE
如果没有任何交易,我的结果将是(即恰好 table 的位置,带有 2 个额外的字段,稍后会有用):
SecNum, Position, OpenedByTradeID, ClosedByTradeID
1, 100, NULL, NULL
2, 200, NULL, NULL
3, -300, NULL, NULL
所以假设我将交易应用于 1/1/2016
之前的头寸并包括在内。与 TradeID
:1 的交易将影响 securityID
: 1 的头寸为 100+(-50)=50 所以我的结果应该是:
SecNum, Position, OpenedByTradeID, ClosedByTradeID
1, 50, NULL, NULL
2, 200, NULL, NULL
3, -300, NULL, NULL
OpenedByTradeID
和ClosedTradeID
仍然是NULL
,因为位置还没有越过0。
如果我应用交易直至并包括 1/2/2016
,我应该得到:
SecNum, Position, OpenedByTradeID, ClosedByTradeID
1, 0, NULL, 2
2, 200, NULL, NULL
3, -300, NULL, NULL
注意仓位已经变成0所以我们记录ClosedByTradeID
和关闭这个仓位的tradeID
。
最多 1/3/2016
我应该得到:
SecNum, Position, OpenedByTradeID, ClosedByTradeID
1, 0, NULL, 2
1, -50, 3, NULL
2, 200, NULL, NULL
3, -300, NULL, NULL
请注意,最近 tradeID
:3 在 securityID
:1 开设了一个新职位,因此我们将 OpenedByTradeID
列标记为 TradeID
:3
最多 1/4/2016
我应该得到:
SecNum, Position, OpenedByTradeID, ClosedByTradeID
1, 0, NULL, 2
1, 0, 3, 4
2, 200, NULL, NULL
3, -300, NULL, NULL
注意仓位已经变成0所以我们记录ClosedByTradeID
和关闭这个仓位的tradeID
-- TradeID
:4
直到并包括 1/5/2016
是一个 边缘情况 。这里发生了 2 件事:交易的应用越过 0 标记,因此需要建立新头寸并且同一天发生 2 笔交易。
正在申请:
tradeID
:6 对于securityID
:3 会将位置调整为 -100 (-300 + 200 = -100).- 然后我们需要应用
tradeID
:7 但这会穿过 0(-100 + 200 = 100 从 neg -> pos 穿过)所以我们需要在穿过 0 之前应用该部分以平仓然后用剩余的金额开始新的仓位。 - 部分
tradeID
:7 [100] 会将头寸调整为 0 (-100 + 100 = 0) 并关闭它(将在ClosedByTradeID
列中注明)然后部分tradeID
:7 [剩余的 100] 会将位置调整为 100 (0 + 100 = 100) [也会在OpenedByTradeID
列中注明]。
因此,我应该得到:
SecNum, Position, OpenedByTradeID, ClosedByTradeID
1, 0, NULL, 2
1, 0, 3, 4
2, 200, NULL, NULL
3, 0, NULL, 7
3, 100, 7, NULL
这听起来像是一个孤岛问题,但我似乎无法弄清楚如何写这个。
这只是使用递归的部分工作。我没写完,但也许其他人可以有想法。
我开始只使用 SecNum = 1
WITH DirectReports (SecNum, Position, OpenedByTradeID, ClosedByTradeID, level_id, TradeID)
AS (
SELECT SecNum, Position, NULL as OpenedByTradeID, NULL as ClosedByTradeID, 1, null as TradeID
FROM tPosition
WHERE SecNum = 1
UNION ALL
SELECT D.SecNum,
Position + Quantity as Position,
CASE WHEN Position = 0 and Position + Quantity <> 0
THEN T.TradeID
ELSE NULL
END as OpenedByTradeID,
CASE WHEN Position <> 0 and Position + Quantity = 0
THEN T.TradeID
ELSE NULL
END as ClosedByTradeID,
level_id + 1 as level_id,
T.TradeID
FROM DirectReports D
JOIN (SELECT *,
ROW_NUMBER() OVER (partition by SecNum ORDER BY TradeDate) as rn
FROM tTrade
) T
ON D.SecNum = T.SecNum
AND D.level_id = T.rn
)
SELECT *
FROM DirectReports
输出
我想我使用 WHILE
循环方法让它工作了。也许有更有效的方法,但我认为这可行...
DECLARE @result TABLE
(
SecNum INT,
Position INT,
OpenedByTradeID INT,
ClosedByTradeID INT
)
INSERT INTO @result(SecNum, Position)
SELECT SecNum, Position
FROM dbo.tPosition
SELECT *
FROM @result
ORDER BY SecNum
DECLARE @CurTradeID INT
SELECT @CurTradeID = MIN(TradeID) FROM dbo.tTrade
WHILE(@CurTradeID IS NOT NULL)
BEGIN
DECLARE @TradeQty INT, @TradeSecNum INT
SELECT @TradeQty = Quantity, @TradeSecNum = SecNum FROM dbo.tTrade WHERE TradeID = @CurTradeID
DECLARE @OldPos INT = (SELECT Position FROM @result WHERE ClosedByTradeID IS NULL AND SecNum = @TradeSecNum)
-- IF THERE IS NO POSITION
IF (@OldPos IS NULL)
BEGIN
INSERT INTO @result(SecNum, Position, OpenedByTradeID, ClosedByTradeID)
SELECT @TradeSecNum, @TradeQty, @CurTradeID, NULL
END
-- IF THIS TRADE CLOSES THE POSITION
ELSE IF (@OldPos + @TradeQty = 0)
BEGIN
UPDATE @result
SET ClosedByTradeID = @CurTradeID, Position = Position + @TradeQty
WHERE SecNum = @TradeSecNum AND ClosedByTradeID IS NULL
END
-- IF THIS TRADE MAKES THE POSITION CROSS THROUGH 0 i.e. IF TRADE MAKES POSITION CHANGE SIGN
ELSE IF (SIGN(@OldPos + @TradeQty) <> SIGN(@OldPos))
BEGIN
DECLARE @RemainingAmt INT = @TradeQty + @OldPos
UPDATE @result
SET ClosedByTradeID = @CurTradeID, Position = 0
WHERE SecNum = @TradeSecNum AND ClosedByTradeID IS NULL
INSERT INTO @result(SecNum, Position, OpenedByTradeID, ClosedByTradeID)
SELECT @TradeSecNum, @RemainingAmt, @CurTradeID, NULL
END
-- JUST UPDATE THE ACTIVE POSITION
ELSE
BEGIN
UPDATE @result
SET Position = Position + @TradeQty
WHERE SecNum = @TradeSecNum AND ClosedByTradeID IS NULL
END
SELECT @CurTradeID = MIN(TradeID) FROM dbo.tTrade WHERE TradeID > @CurTradeID
END
SELECT *
FROM @result
ORDER BY SecNum
P.S.: 我想我可以用我需要的交易做一个临时 table 我可以在我的查询中使用 table 而不是 tTrade以上,所以我不必一直处理选择特定日期的交易。