问题
PROBLEM:
Given some positions in different financial securities, I would like to apply trades to them up to a given date. In doing so I would like to keep track which trades closed the position and opened a new one. The position is CLOSED
when the Position Quantity becomes 0 after a trade is applied. A position is considered OPENED
when the Position Quantity changes from 0 to something.
SETUP: SQL DEMO SOURCE
Let's say I have the following tables:
CREATE TABLE tPosition
(
SecNum INT,
Position INT
)
CREATE TABLE tTrade
(
TradeID INT IDENTITY(1,1),
TradeDate DATETIME,
SecNum INT,
Quantity INT
)
And some sample data:
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;
SAMPLE SCENARIOS/CASES: SQL DEMO SOURCE
Without any trades my result would be (i.e. exactly the position table with 2 extra fields which will be useful later):
SecNum, Position, OpenedByTradeID, ClosedByTradeID
1, 100, NULL, NULL
2, 200, NULL, NULL
3, -300, NULL, NULL
So let's say I apply trades to the positions up to and including 1/1/2016
. This trade with TradeID
:1, will affect the position for securityID
: 1 as 100+(-50)=50 so my result should be:
SecNum, Position, OpenedByTradeID, ClosedByTradeID
1, 50, NULL, NULL
2, 200, NULL, NULL
3, -300, NULL, NULL
The OpenedByTradeID
and ClosedTradeID
are still NULL
because the position hasn't crossed 0 yet.
If I apply trades up to and including 1/2/2016
I should get:
SecNum, Position, OpenedByTradeID, ClosedByTradeID
1, 0, NULL, 2
2, 200, NULL, NULL
3, -300, NULL, NULL
Notice that the position has become 0 so we record the ClosedByTradeID
with the tradeID
that closed this position.
Up to and including 1/3/2016
I should get:
SecNum, Position, OpenedByTradeID, ClosedByTradeID
1, 0, NULL, 2
1, -50, 3, NULL
2, 200, NULL, NULL
3, -300, NULL, NULL
Notice that a new position was opened in securityID
:1 by the latest tradeID
:3 so we mark the OpenedByTradeID
column with TradeID
:3
Up to and including 1/4/2016
I should get:
SecNum, Position, OpenedByTradeID, ClosedByTradeID
1, 0, NULL, 2
1, 0, 3, 4
2, 200, NULL, NULL
3, -300, NULL, NULL
Notice that the position has become 0 so we record the ClosedByTradeID
with the tradeID
that closed this position -- TradeID
:4
Up to and including 1/5/2016
is an edge case. There are 2 things happening here: the application of the trade crosses the 0 mark so a new position needs to be formed AND 2 trades happen on the same day.
Applying:
tradeID
:6 forsecurityID
:3 would adjust the position to -100 (-300 + 200 = -100).- Then we need to apply
tradeID
:7 but that would cross 0 (-100 + 200 = 100 crossing from neg -> pos) so we need to apply the part before crossing 0 to close the position and then start a new position with the remaining amount. - Part of
tradeID
:7 [100] would adjust the position to 0 (-100 + 100 = 0) and close it (would be noted inClosedByTradeID
column) and then part oftradeID
:7 [the remaining 100] would adjust the position to 100 (0 + 100 = 100) [would also get noted inOpenedByTradeID
column].
Therefore, I should get:
SecNum, Position, OpenedByTradeID, ClosedByTradeID
1, 0, NULL, 2
1, 0, 3, 4
2, 200, NULL, NULL
3, 0, NULL, 7
3, 100, 7, NULL
This sounds like an islands problem but I just can't seem to figure out how to write this one.
回答1:
This is just a partial work using recursive. I couldnt finish, but maybe others can have ideas.
I start working with only 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
OUTPUT
回答2:
I think I got it to work using the WHILE
loop approach. Maybe there is a more efficient way but I think this works...
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.: I figured that I can make a temp table with the trades that I need before-hand and I can use that table instead of tTrade in my query above so I don't have to keep dealing with selecting the trades up to a particular date.
来源:https://stackoverflow.com/questions/40920379/generating-all-realized-and-unrealized-trades-using-sql