11from typing import Mapping , NamedTuple , cast , Optional , Union
2+ from collections import defaultdict
23from operator import itemgetter
34
45from pandas import Series , Index , DataFrame
56from numpy import sign , round as np_round , inf
67
78from rqalpha .api import export_as_api
89from rqalpha .apis .api_base import assure_instrument
9- from rqalpha .model .order import AlgoOrder , MarketOrder
10+ from rqalpha .model .order import AlgoOrder , MarketOrder , LimitOrder , OrderStyle
1011from rqalpha .model .order import Order
1112from rqalpha .environment import Environment
1213from rqalpha .const import EXECUTION_PHASE , POSITION_DIRECTION , INSTRUMENT_TYPE , MARKET , DEFAULT_ACCOUNT_TYPE , SIDE , POSITION_EFFECT
1314from rqalpha .core .execution_context import ExecutionContext
14- from rqalpha .utils .exception import RQApiNotSupportedError
15+ from rqalpha .utils .exception import RQApiNotSupportedError , RQInvalidArgument
1516from rqalpha .utils .functools import lru_cache
1617from rqalpha .utils .i18n import gettext as _
1718
@@ -34,12 +35,15 @@ def __init__(
3435 target_weights : Series ,
3536 current_quantities : Series ,
3637 current_closable : Series ,
38+ last_prices : Series | None ,
3739 env : Environment ,
3840 ):
39- if target_weights [target_weights < 0 ].any ():
40- raise ValueError ("target_weights contains negative value: {}" .format (target_weights [target_weights < 0 ]))
41-
4241 index = Index (target_weights .index .union (current_quantities .index ).union (current_closable .index ))
42+ if last_prices is not None :
43+ last_prices = last_prices .reindex (index )
44+ if last_prices .isna ().any ():
45+ raise RQInvalidArgument (_ ("prices of {} is not provided" ).format (last_prices .isna ().index [last_prices .isna ()]))
46+
4347 self ._target_weights = target_weights .reindex (index , fill_value = 0 )
4448 self ._current_quantities = current_quantities .reindex (index , fill_value = 0 )
4549 self ._current_closable = current_closable .reindex (index , fill_value = 0 )
@@ -74,17 +78,22 @@ def __init__(
7478 self ._prices = DataFrame (index = index , columns = ["last" , "limit_up" , "limit_down" ], dtype = float ) # type: ignore
7579 if phase == EXECUTION_PHASE .OPEN_AUCTION :
7680 # 集合竞价阶段,最近的价格是昨收
77- prev_date = env .data_proxy .get_previous_trading_date (env .trading_dt )
78- for order_book_id in index :
79- bars = env .data_proxy .history_bars (order_book_id , 1 , "1d" , ["close" ], prev_date )
80- self ._prices .loc [order_book_id , "last" ] = bars ["close" ][0 ]
81+ if last_prices is not None :
82+ self ._prices ["last" ] = last_prices
83+ else :
84+ prev_date = env .data_proxy .get_previous_trading_date (env .trading_dt )
85+ for order_book_id in index :
86+ bars = env .data_proxy .history_bars (order_book_id , 1 , "1d" , ["close" ], prev_date )
87+ self ._prices .loc [order_book_id , "last" ] = bars ["close" ][0 ]
8188 elif phase == EXECUTION_PHASE .ON_BAR :
8289 if env .config .base .frequency == "1d" :
8390 # TODO:根据算法时间选择最近的分钟线作为估值
8491 # 当前先选择开盘价
8592 for order_book_id in index :
8693 bars = env .data_proxy .history_bars (order_book_id , 1 , "1d" , ["open" , "limit_up" , "limit_down" ], env .trading_dt )
87- self ._prices .loc [order_book_id ] = bars [0 ]
94+ self ._prices .loc [order_book_id ] = list (bars [0 ])
95+ if last_prices is not None :
96+ self ._prices ["last" ] = last_prices
8897 elif env .config .base .frequency == "1m" :
8998 raise NotImplementedError
9099 else :
@@ -148,8 +157,12 @@ def __call__(
148157
149158 if self ._current_quantities .empty and self ._target_weights .empty :
150159 return Series ()
151-
152- safety = self .SAFETY
160+
161+ if self ._target_weights .sum () > 0.95 :
162+ # 如果目标是满仓或者接近满仓,则使用一个较高的 safety 开始下降
163+ safety = self .SAFETY
164+ else :
165+ safety = 1.
153166 last_proportion_diff = inf
154167 last_diff = None
155168 prices = self ._prices_settle_ccy
@@ -175,7 +188,7 @@ def __call__(
175188 proportion_diff = abs (total_proportion - self ._target_weights .sum ())
176189 if cash_consumed < cash_available :
177190 # TODO: 分别计算 A H 股的可用资金
178- if proportion_diff >= last_proportion_diff and last_diff is not None :
191+ if proportion_diff > last_proportion_diff and last_diff is not None :
179192 return last_diff
180193 last_diff = diff
181194 last_proportion_diff = proportion_diff
@@ -187,50 +200,67 @@ def __call__(
187200 EXECUTION_PHASE .OPEN_AUCTION ,
188201 EXECUTION_PHASE .ON_BAR ,
189202)
190- def order_target_portfolio_smart (target_portfolio : Union [Mapping [str , float ], Series ], algo : Optional [AlgoOrder ] = None ):
203+ def order_target_portfolio_smart (
204+ target_portfolio : Union [Mapping [str , float ], Series ],
205+ algo_or_prices : Union [AlgoOrder , dict [str , float ], None ] = None
206+ ):
191207 from rqalpha .mod .rqalpha_mod_sys_accounts .api .api_stock import _order_value
192208
193209 env = Environment .get_instance ()
194- target_weights = {
210+ target_weights = Series ( {
195211 assure_instrument (id_or_ins ).order_book_id : percent for id_or_ins , percent in target_portfolio .items ()
196- }
212+ })
197213 account = env .portfolio .accounts [DEFAULT_ACCOUNT_TYPE .STOCK ]
198214 quantities , closable = {}, {}
199215 for position in account .get_positions ():
200216 quantities [position .order_book_id ] = position .quantity
201217 closable [position .order_book_id ] = position .closable
218+ if isinstance (algo_or_prices , dict ):
219+ style_map : dict [str , OrderStyle ] = {
220+ order_book_id : LimitOrder (price ) for order_book_id , price in algo_or_prices .items ()
221+ }
222+ def _get_style (order_book_id ) -> OrderStyle :
223+ try :
224+ return style_map [order_book_id ]
225+ except KeyError :
226+ raise RQInvalidArgument (_ ("price of {} is needed, which is not provided in the algo_or_prices" ).format (order_book_id ))
227+ prices = Series (algo_or_prices )
228+ else :
229+ style = algo_or_prices or MarketOrder ()
230+ _get_style = lambda order_book_id : style
231+ prices = None
232+
233+ if target_weights [target_weights < 0 ].any ():
234+ raise ValueError ("target_weights contains negative value: {}" .format (target_weights [target_weights < 0 ]))
235+ current_quantities = Series (quantities )
236+ current_quantities = cast (Series , current_quantities [current_quantities != 0 ])
237+ current_closable = Series (closable ).reindex (current_quantities .index , fill_value = 0 )
238+
202239 adjusting = OrderTargetPortfolio (
203240 target_weights = Series (target_weights ),
204- current_quantities = Series (quantities ),
205- current_closable = Series (closable ),
241+ current_quantities = current_quantities ,
242+ current_closable = current_closable ,
243+ last_prices = prices ,
206244 env = env
207245 )(
208246 target_value = env .portfolio .total_value ,
209247 cash_available = account .cash ,
210248 )
211249
212250 orders = []
213- style = algo or MarketOrder ()
251+
214252 # 先平
215253 for order_book_id , delta_quantity in cast (Series , (adjusting [adjusting < 0 ])).items ():
216254 order = env .submit_order (Order .__from_create__ (
217- order_book_id ,
218- abs (delta_quantity ),
219- SIDE .SELL ,
220- style ,
221- POSITION_EFFECT .CLOSE
255+ order_book_id , abs (delta_quantity ), SIDE .SELL , _get_style (order_book_id ), POSITION_EFFECT .CLOSE
222256 ))
223257 if order is not None :
224258 orders .append (order )
225259 # 后开
226260 for order_book_id , delta_quantity in cast (Series , (adjusting [adjusting > 0 ])).items ():
227261 order_book_id = cast (str , order_book_id )
228262 order_to_be_submitted = Order .__from_create__ (
229- order_book_id ,
230- delta_quantity ,
231- SIDE .BUY ,
232- style ,
233- POSITION_EFFECT .OPEN
263+ order_book_id , delta_quantity , SIDE .BUY , _get_style (order_book_id ), POSITION_EFFECT .OPEN
234264 )
235265 order = env .submit_order (order_to_be_submitted )
236266 if order is None :
@@ -241,7 +271,7 @@ def order_target_portfolio_smart(target_portfolio: Union[Mapping[str, float], Se
241271 account .get_position (order_book_id ),
242272 env .data_proxy .instrument_not_none (order_book_id ),
243273 account .cash ,
244- style ,
274+ _get_style ( order_book_id ) ,
245275 zero_amount_as_exception = False
246276 )
247277 if order is not None :
0 commit comments