使用 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

OpenedByTradeIDClosedTradeID仍然是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 笔交易

正在申请:

因此,我应该得到:

 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

SQL DEMO

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以上,所以我不必一直处理选择特定日期的交易。