15
15
# 在此前提下,对本软件的使用同样需要遵守 Apache 2.0 许可,Apache 2.0 许可与本许可冲突之处,以本许可为准。
16
16
# 详细的授权流程,请联系 [email protected] 获取。
17
17
18
- from collections import UserDict
18
+ from collections import UserDict , deque
19
19
from datetime import date
20
- from typing import Dict , Iterable , Tuple , Optional
20
+ from decimal import Decimal
21
+ from typing import Dict , Iterable , Tuple , Optional , Deque , List
21
22
22
23
from rqalpha .const import POSITION_DIRECTION , POSITION_EFFECT
23
24
from rqalpha .environment import Environment
@@ -91,6 +92,10 @@ def __init__(self, order_book_id, direction, init_quantity=0, init_price=None):
91
92
92
93
self ._direction_factor = 1 if direction == POSITION_DIRECTION .LONG else - 1
93
94
95
+ self ._queue = PositionQueue ()
96
+ if init_quantity :
97
+ self ._queue .handle_trade (init_quantity , self ._env .trading_dt .date ())
98
+
94
99
@property
95
100
def order_book_id (self ):
96
101
# type: () -> str
@@ -193,7 +198,8 @@ def get_state(self):
193
198
"avg_price" : self ._avg_price ,
194
199
"trade_cost" : self ._trade_cost ,
195
200
"transaction_cost" : self ._transaction_cost ,
196
- "prev_close" : self ._prev_close
201
+ "prev_close" : self ._prev_close ,
202
+ "position_queue" : list (self ._queue .queue )
197
203
}
198
204
199
205
def set_state (self , state ):
@@ -209,6 +215,7 @@ def set_state(self, state):
209
215
self ._trade_cost = state .get ("trade_cost" , 0 )
210
216
self ._transaction_cost = state .get ("transaction_cost" , 0 )
211
217
self ._prev_close = state .get ("prev_close" )
218
+ self ._queue .set_sate (state .get ("position_queue" , []))
212
219
213
220
def before_trading (self , trading_date ):
214
221
# type: (date) -> float
@@ -225,6 +232,7 @@ def apply_trade(self, trade):
225
232
# 返回总资金的变化量
226
233
self ._transaction_cost += trade .transaction_cost
227
234
if trade .position_effect == POSITION_EFFECT .OPEN :
235
+ self ._queue .handle_trade (trade .last_quantity , self ._env .trading_dt .date ())
228
236
if self ._quantity < 0 :
229
237
self ._avg_price = trade .last_price if self ._quantity + trade .last_quantity > 0 else 0
230
238
else :
@@ -235,6 +243,7 @@ def apply_trade(self, trade):
235
243
return (- 1 * trade .last_price * trade .last_quantity ) - trade .transaction_cost
236
244
elif trade .position_effect == POSITION_EFFECT .CLOSE :
237
245
# 先平昨,后平今
246
+ self ._queue .handle_trade (- trade .last_quantity , self ._env .trading_dt .date ())
238
247
self ._old_quantity -= min (trade .last_quantity , self ._old_quantity )
239
248
self ._quantity -= trade .last_quantity
240
249
self ._trade_cost -= trade .last_price * trade .last_quantity
@@ -255,6 +264,15 @@ def update_last_price(self, price):
255
264
def calc_close_today_amount (self , trade_amount , position_effect ):
256
265
return 0
257
266
267
+ @property
268
+ def position_queue (self ):
269
+ # type: () -> deque[tuple[date, int]]
270
+ """
271
+ 获取持仓队列,返回每笔持仓的开仓时间和数量
272
+ 格式为 [(开仓日期, 持仓数量), ...]
273
+ """
274
+ return self ._queue .queue
275
+
258
276
@property
259
277
def _open_orders (self ):
260
278
# type: () -> Iterable[Order]
@@ -263,6 +281,69 @@ def _open_orders(self):
263
281
yield order
264
282
265
283
284
+ class PositionQueue :
285
+ def __init__ (self ):
286
+ self ._queue : Deque [Tuple [date , int ]] = deque ()
287
+
288
+ def set_sate (self , state : List [Tuple [date , int ]]):
289
+ self ._queue = deque (state )
290
+
291
+ def handle_trade (self , delta_quantity , trading_date : date , close_today : bool = False ):
292
+ if delta_quantity == 0 :
293
+ return
294
+ if self ._queue and self ._queue [0 ][1 ] * delta_quantity < 0 :
295
+ # 从正平仓到负或者从负平仓到正
296
+ if close_today :
297
+ # 强制先平今
298
+ d , qty_in_queue = self ._queue [- 1 ]
299
+ if d >= trading_date :
300
+ if abs (qty_in_queue ) <= abs (delta_quantity ):
301
+ delta_quantity += qty_in_queue
302
+ self ._queue .pop ()
303
+ else :
304
+ self ._queue [- 1 ] = (d , qty_in_queue + delta_quantity )
305
+ delta_quantity = 0
306
+ while delta_quantity and self ._queue :
307
+ d , qty_in_queue = self ._queue [0 ]
308
+ if abs (qty_in_queue ) <= abs (delta_quantity ):
309
+ # 当前持仓批次全部平掉
310
+ delta_quantity += qty_in_queue
311
+ self ._queue .popleft ()
312
+ else :
313
+ # 当前持仓批次部分平掉
314
+ self ._queue [0 ] = (d , qty_in_queue + delta_quantity )
315
+ delta_quantity = 0
316
+ if delta_quantity != 0 :
317
+ # queue 消耗光了,delta_quantity 还没消耗完
318
+ self ._queue .append ((trading_date , delta_quantity ))
319
+ else :
320
+ # 开仓
321
+ if self ._queue and self ._queue [- 1 ][0 ] == trading_date :
322
+ # 当日已有开仓,更新数量
323
+ _ , quantity = self ._queue .pop ()
324
+ self ._queue .append ((trading_date , quantity + delta_quantity ))
325
+ else :
326
+ # 当日首次开仓
327
+ self ._queue .append ((trading_date , delta_quantity ))
328
+
329
+ def handle_split (self , ratio : Decimal , expected_quantity ):
330
+ self ._queue = deque (
331
+ (d , round (Decimal (q ) * ratio )) for d , q in self ._queue
332
+ )
333
+ diff = expected_quantity - sum (q for _ , q in self ._queue )
334
+ if diff != 0 :
335
+ # 有可能因为 round 的原因导致差1股
336
+ self ._queue [- 1 ] = (self ._queue [- 1 ][0 ], self ._queue [- 1 ][1 ] + diff )
337
+
338
+
339
+ def clear (self ):
340
+ self ._queue .clear ()
341
+
342
+ @property
343
+ def queue (self ):
344
+ return self ._queue .copy ()
345
+
346
+
266
347
class PositionProxy (metaclass = PositionProxyMeta ):
267
348
__repr_properties__ = (
268
349
"order_book_id" , "positions"
0 commit comments