TrendBreakoutStrategy_v003.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715
  1. # 导入函数库
  2. from jqdata import *
  3. from jqdata import finance
  4. import pandas as pd
  5. from datetime import date, datetime, timedelta
  6. import re
  7. # 趋势突破交易策略 v003
  8. # 基于均线趋势突破的期货交易策略 - 修复版本
  9. # 设置以便完整打印 DataFrame
  10. pd.set_option('display.max_rows', None)
  11. pd.set_option('display.max_columns', None)
  12. pd.set_option('display.width', None)
  13. pd.set_option('display.max_colwidth', 20)
  14. ## 初始化函数
  15. def initialize(context):
  16. # 设定沪深300作为基准
  17. set_benchmark('000300.XSHG')
  18. # 开启动态复权模式(真实价格)
  19. set_option('use_real_price', True)
  20. log.info('趋势突破交易策略初始化开始')
  21. ### 期货相关设定 ###
  22. # 设定账户为金融账户
  23. set_subportfolios([SubPortfolioConfig(cash=context.portfolio.starting_cash, type='index_futures')])
  24. # 期货类每笔交易时的手续费
  25. set_order_cost(OrderCost(open_commission=0.000023, close_commission=0.000023, close_today_commission=0.0023), type='index_futures')
  26. # 设置期货交易的滑点
  27. set_slippage(StepRelatedSlippage(2))
  28. # 初始化全局变量
  29. g.usage_percentage = 0.8 # 最大持股数量
  30. g.default_days = 10 # 判断趋势线的最小天数
  31. g.continuous_days_length = 5 # 破趋势后的观察天数
  32. g.change_direction_days = 5 # 检查均线穿过的天数范围
  33. g.crossed_symbols_history = {} # 用于存储过去几天的穿越信息
  34. g.trade_history = {} # 初始化交易记录
  35. g.change_fail_history = {} # 初始化换月建仓失败的记录
  36. g.high_low_ma_relations = {}
  37. # 定义默认的保证金比例
  38. g.default_margin_rates = {
  39. 'long': {'A': 0.07, 'AG': 0.04, 'AL': 0.05, 'AO': 0.05, 'AP': 0.08, 'AU': 0.04, 'B': 0.05,
  40. 'BC': 0.13, 'BR': 0.07, 'BU': 0.04, 'C': 0.07, 'CF': 0.05, 'CJ': 0.07, 'CS': 0.07,
  41. 'CU': 0.05, 'CY': 0.05, 'EB': 0.12, 'EC': 0.12, 'EG': 0.05, 'FG': 0.05, 'FU': 0.08,
  42. 'HC': 0.04, 'I': 0.1, 'J': 0.22, 'JD': 0.08, 'JM': 0.22,
  43. 'L': 0.07, 'LC': 0.05, 'LH': 0.1, 'LR': 0.05, 'LU': 0.15, 'M': 0.07, 'MA': 0.05, 'NI': 0.05, 'NR': 0.13, 'OI': 0.05,
  44. 'P': 0.05, 'PB': 0.05, 'PF': 0.1, 'PG': 0.05, 'PK': 0.05,
  45. 'PP': 0.07, 'RB': 0.05, 'RI': 0.05, 'RM': 0.05, 'RU': 0.05,
  46. 'SA': 0.05, 'SC': 0.12, 'SF': 0.05, 'SH': 0.05, 'SI': 0.13, 'SM': 0.05, 'SN': 0.05, 'SP': 0.1, 'SR': 0.05,
  47. 'SS': 0.05, 'TA': 0.05, 'UR': 0.09, 'V': 0.07,
  48. 'Y': 0.05, 'ZC': 0.05, 'ZN': 0.05},
  49. 'short': {'A': 0.07, 'AG': 0.04, 'AL': 0.05, 'AO': 0.05, 'AP': 0.08, 'AU': 0.04, 'B': 0.05,
  50. 'BC': 0.13, 'BR': 0.07, 'BU': 0.04, 'C': 0.07, 'CF': 0.05, 'CJ': 0.07, 'CS': 0.07,
  51. 'CU': 0.05, 'CY': 0.05, 'EB': 0.12, 'EC': 0.12, 'EG': 0.05, 'FG': 0.05, 'FU': 0.08,
  52. 'HC': 0.04, 'I': 0.1, 'J': 0.22, 'JD': 0.08, 'JM': 0.22,
  53. 'L': 0.07, 'LC': 0.05, 'LH': 0.1, 'LR': 0.05, 'LU': 0.15, 'M': 0.07, 'MA': 0.05, 'NI': 0.05, 'NR': 0.13, 'OI': 0.05,
  54. 'P': 0.05, 'PB': 0.05, 'PF': 0.1, 'PG': 0.05, 'PK': 0.05,
  55. 'PP': 0.07, 'RB': 0.05, 'RI': 0.05, 'RM': 0.05, 'RU': 0.05,
  56. 'SA': 0.05, 'SC': 0.12, 'SF': 0.05, 'SH': 0.05, 'SI': 0.13, 'SM': 0.05, 'SN': 0.05, 'SP': 0.1, 'SR': 0.05,
  57. 'SS': 0.05, 'TA': 0.05, 'UR': 0.09, 'V': 0.07,
  58. 'Y': 0.05, 'ZC': 0.05, 'ZN': 0.05}
  59. }
  60. g.multiplier = {
  61. 'A': 10, 'AG': 15, 'AL': 5, 'AO': 20, 'AP': 10, 'AU': 1000, 'B': 10,
  62. 'BC': 5, 'BR': 5, 'BU': 10, 'C': 10, 'CF': 5, 'CJ': 5, 'CS': 10,
  63. 'CU': 5, 'CY': 5, 'EB': 5, 'EC': 50, 'EG': 10, 'FG': 20, 'FU': 10,
  64. 'HC': 10, 'I': 100, 'J': 60, 'JD': 5, 'JM': 100,
  65. 'L': 5, 'LC': 1, 'LH': 16, 'LR': 0.05, 'LU': 10, 'M': 10, 'MA': 10, 'NI': 1, 'NR': 10, 'OI': 10,
  66. 'P': 10, 'PB': 5, 'PF': 5, 'PG': 20, 'PK': 5,
  67. 'PP': 5, 'RB': 10, 'RI': 0.05, 'RM': 10, 'RU': 10,
  68. 'SA': 20, 'SC': 1000, 'SF': 5, 'SH': 30, 'SI': 5, 'SM': 5, 'SN': 1, 'SP': 10, 'SR': 10,
  69. 'SS': 5, 'TA': 5, 'UR': 20, 'V': 5,
  70. 'Y': 10, 'ZC': 0.05, 'ZN': 5
  71. }
  72. # 临时止损检查
  73. run_daily(loss_control, time='21:15:00', reference_security='IF1808.CCFX')
  74. run_daily(loss_control, time='21:45:00', reference_security='IF1808.CCFX')
  75. run_daily(loss_control, time='22:15:00', reference_security='IF1808.CCFX')
  76. run_daily(loss_control, time='22:45:00', reference_security='IF1808.CCFX')
  77. run_daily(loss_control, time='09:15:00', reference_security='IF1808.CCFX')
  78. run_daily(loss_control, time='09:45:00', reference_security='IF1808.CCFX')
  79. run_daily(loss_control, time='10:15:00', reference_security='IF1808.CCFX')
  80. run_daily(loss_control, time='10:45:00', reference_security='IF1808.CCFX')
  81. run_daily(loss_control, time='11:15:00', reference_security='IF1808.CCFX')
  82. run_daily(loss_control, time='13:15:00', reference_security='IF1808.CCFX')
  83. run_daily(loss_control, time='13:45:00', reference_security='IF1808.CCFX')
  84. run_daily(loss_control, time='14:15:00', reference_security='IF1808.CCFX')
  85. run_daily(loss_control, time='14:56:00', reference_security='IF1808.CCFX')
  86. # 收盘前运行
  87. run_daily(before_market_close, time='14:55', reference_security='IF8888.CCFX')
  88. ############################ 主程序中执行函数 ###################################
  89. def before_market_close(context):
  90. """收盘前主策略执行"""
  91. print("-" * 20 + "New day ending!" + "-" * 20)
  92. if len(g.trade_history) > 0:
  93. print(f'当前交易记录: {g.trade_history}, 换月失败记录: {g.change_fail_history}')
  94. # 1. 检查趋势
  95. trend_symbols, daily_data_info = check_trend(context)
  96. print_list_elements("趋势标的", trend_symbols)
  97. # 2. 检查影线穿越
  98. crossed_symbols = check_shadow_cross(context, trend_symbols, daily_data_info)
  99. print_list_elements("穿越标的", crossed_symbols)
  100. # 3. 检查买入条件
  101. buy_symbols = check_buy_condition(context, crossed_symbols)
  102. print_list_elements("买入标的", buy_symbols)
  103. # 4. 执行交易
  104. subportfolio = context.subportfolios[0]
  105. hold_symbols = set(subportfolio.long_positions.keys()) | set(subportfolio.short_positions.keys())
  106. for symbol, line_label, line_type, direction in buy_symbols:
  107. if check_symbol_prefix_match(symbol, hold_symbols):
  108. print(f'已有相似持仓 {symbol},跳过交易')
  109. else:
  110. print(f'无相似持仓 {symbol},执行交易')
  111. value_to_invest = calculate_order_value(context, symbol, direction)
  112. open_position(symbol, value_to_invest, direction, line_label)
  113. def loss_control(context):
  114. """止损控制"""
  115. print("-" * 20 + "止损控制" + "-" * 20)
  116. # 检查是否有正常的夜盘
  117. now = context.current_dt.time()
  118. now_hour = now.hour
  119. if now_hour >= 21:
  120. test_future = get_dominant_future("A")
  121. if test_future:
  122. test_data = attribute_history(test_future, 1, '1m', 'close')
  123. test_hour = test_data.index[0].hour
  124. if test_hour <= 15:
  125. print(f'最近的数据小时为: {test_hour},该夜盘不存在,直接停止')
  126. return
  127. # 先把换月买入失败的重新买入
  128. if len(g.change_fail_history) > 0:
  129. print(f'检查换月失败的{g.change_fail_history}')
  130. for symbol in list(g.change_fail_history.keys()):
  131. direction = g.change_fail_history[symbol]['direction']
  132. value_to_invest = calculate_order_value(context, symbol, direction)
  133. success = open_position(symbol, value_to_invest, direction, g.change_fail_history[symbol].get("line_label", "unknown"))
  134. if success:
  135. del g.change_fail_history[symbol]
  136. # 检查损失
  137. target_time = datetime.strptime('14:55:00', '%H:%M:%S').time()
  138. potential_future_list = get_potential_future_list(context)
  139. # 遍历所有持仓
  140. subportfolio = context.subportfolios[0]
  141. all_positions = list(subportfolio.long_positions.values()) + list(subportfolio.short_positions.values())
  142. for position in all_positions:
  143. if position.security in potential_future_list:
  144. # 检查固定止损
  145. if check_loss_for_close(context, position, position.side):
  146. continue
  147. # 检查基于动态跟踪线的平仓条件
  148. if now > target_time:
  149. check_ma_for_close(context, position, 0.01, 4)
  150. else:
  151. check_ma_for_close(context, position, 0.003, 4)
  152. ############################ 交易模块 ###################################
  153. def order_target_value_(security, value, direction):
  154. """自定义下单"""
  155. if value == 0:
  156. log.debug("平仓 %s" % (security))
  157. else:
  158. log.debug("下单 %s 金额 %f" % (security, value))
  159. return order_target_value(security, value, side=direction)
  160. def open_position(security, value, direction, line_label):
  161. """开仓"""
  162. order = order_target_value_(security, value, direction)
  163. if order is not None and order.filled > 0:
  164. print(f'成功开仓 {security} 方向 {direction} 线标 {line_label}')
  165. g.trade_history[security] = {
  166. 'entry_price': get_current_data()[security].last_price,
  167. 'position_value': value,
  168. 'direction': direction,
  169. 'line_label': line_label,
  170. 'finish_time': order.finish_time
  171. }
  172. return True
  173. return False
  174. def close_position(position, direction):
  175. """平仓"""
  176. security = position.security
  177. order = order_target_value_(security, 0, direction)
  178. if order is not None:
  179. if order.status == OrderStatus.held and order.filled == order.amount:
  180. # 如果成功平仓,从交易历史中移除该标的
  181. if security in g.trade_history:
  182. del g.trade_history[security]
  183. return True
  184. return False
  185. ############################ 策略核心函数 ###################################
  186. def get_potential_future_list(context):
  187. """获取可交易的期货品种列表"""
  188. potential_night_list = ['NI', 'CF', 'PF', 'Y', 'M', 'B', 'SN', 'RM']
  189. potential_day_list = ['JD', 'UR']
  190. if str(context.current_dt.time())[:2] in ('21', '22'):
  191. potential_icon_list = potential_night_list
  192. else:
  193. potential_icon_list = potential_day_list + potential_night_list
  194. potential_future_list = []
  195. for i in potential_icon_list:
  196. dominant_future = get_dominant_future(i)
  197. if dominant_future:
  198. potential_future_list.append(dominant_future)
  199. return potential_future_list
  200. def check_trend(context):
  201. """检查品类的主连是否满足形成趋势的条件"""
  202. print("-" * 20 + "检查趋势" + "-" * 20)
  203. trend_symbols = []
  204. daily_data_info = {}
  205. # 获取可以交易的所有标的主连
  206. potential_future_list = get_potential_future_list(context)
  207. potential_future_list = [item for item in potential_future_list if item not in g.trade_history.keys()]
  208. # 针对所有标的需要的基础数据
  209. ma_crosses_dict = {}
  210. for symbol in potential_future_list:
  211. # 获取50天的收盘价数据
  212. close_data = attribute_history(symbol, 50, '1d', ['close', 'high', 'low', 'open'], df=True)
  213. close_series = close_data['close']
  214. # 计算移动平均线
  215. ma5 = close_series.rolling(window=5).mean()
  216. ma10 = close_series.rolling(window=10).mean()
  217. ma20 = close_series.rolling(window=20).mean()
  218. ma30 = close_series.rolling(window=30).mean()
  219. trend_info = {'symbol': symbol, 'trend_lines': []}
  220. daily_data = []
  221. # 初始化连续天数计数器
  222. continuous_days = {
  223. 'above_ma5': [0] * g.continuous_days_length,
  224. 'below_ma5': [0] * g.continuous_days_length,
  225. 'above_ma10': [0] * g.continuous_days_length,
  226. 'below_ma10': [0] * g.continuous_days_length,
  227. 'above_ma20': [0] * g.continuous_days_length,
  228. 'below_ma20': [0] * g.continuous_days_length,
  229. 'above_ma30': [0] * g.continuous_days_length,
  230. 'below_ma30': [0] * g.continuous_days_length
  231. }
  232. # 获取连续在某一个均线上或者下的天数
  233. for i in range(len(close_series)):
  234. update_continuous_days(continuous_days, close_series[i], ma5[i], ma10[i], ma20[i], ma30[i], g.continuous_days_length)
  235. # 收集每日数据
  236. day_data = {
  237. 'date': close_data.index[i].date(),
  238. 'close': close_series[i],
  239. 'high': close_data['high'][i],
  240. 'low': close_data['low'][i],
  241. 'open': close_data['open'][i],
  242. 'ma5': ma5[i],
  243. 'ma10': ma10[i],
  244. 'ma20': ma20[i],
  245. 'ma30': ma30[i],
  246. 'continuous_above_ma5': continuous_days['above_ma5'].copy(),
  247. 'continuous_below_ma5': continuous_days['below_ma5'].copy(),
  248. 'continuous_above_ma10': continuous_days['above_ma10'].copy(),
  249. 'continuous_below_ma10': continuous_days['below_ma10'].copy(),
  250. 'continuous_above_ma20': continuous_days['above_ma20'].copy(),
  251. 'continuous_below_ma20': continuous_days['below_ma20'].copy(),
  252. 'continuous_above_ma30': continuous_days['above_ma30'].copy(),
  253. 'continuous_below_ma30': continuous_days['below_ma30'].copy()
  254. }
  255. daily_data.append(day_data)
  256. daily_data_info[symbol] = daily_data
  257. # 检查过去一定天数内均线的相交次数
  258. ma_crosses_dict[symbol] = count_ma_crosses(daily_data_info[symbol], g.change_direction_days)
  259. # 检查哪些均线会形成什么类型的趋势
  260. for ma_type in ['ma5', 'ma10', 'ma20', 'ma30']:
  261. above_days = continuous_days[f'above_{ma_type}']
  262. below_days = continuous_days[f'below_{ma_type}']
  263. above_condition = any(day >= g.default_days for day in above_days)
  264. below_condition = any(day >= g.default_days for day in below_days)
  265. if above_condition:
  266. trend_info['trend_lines'].append((ma_type, 'support'))
  267. if below_condition:
  268. trend_info['trend_lines'].append((ma_type, 'resistance'))
  269. if trend_info['trend_lines']:
  270. trend_symbols.append(trend_info)
  271. # 去除掉在一段时间内均线过于频繁交叉的对象
  272. valid_trend_symbols = []
  273. for trend_info in trend_symbols:
  274. symbol = trend_info["symbol"]
  275. ma_crosses = ma_crosses_dict[symbol]
  276. if ma_crosses <= 3:
  277. valid_trend_symbols.append(trend_info)
  278. return valid_trend_symbols, daily_data_info
  279. def check_shadow_cross(context, trend_symbols, daily_data_info):
  280. """检查影线穿越"""
  281. print("-" * 20 + "检查影线穿越" + "-" * 20)
  282. # 检查并进行换月
  283. switch_result = position_auto_switch(context)
  284. if switch_result:
  285. print(f'换月结果: {switch_result}')
  286. today_crossed_symbols = {}
  287. for trend_info in trend_symbols:
  288. symbol = trend_info['symbol']
  289. # 获取昨天的数据
  290. yesterday_data = daily_data_info[symbol][-1]
  291. # 临时存储当前标的的穿越信息
  292. symbol_crosses = []
  293. for line_label, line_type in trend_info['trend_lines']:
  294. yesterday_day = yesterday_data['date']
  295. close = yesterday_data['close']
  296. high = yesterday_data['high']
  297. low = yesterday_data['low']
  298. ma_value = yesterday_data[line_label]
  299. close_ma_difference = (close - ma_value) / ma_value
  300. # 删除指定的键
  301. yesterday_data_copy = yesterday_data.copy()
  302. keys_to_remove = ['continuous_above_ma5', 'continuous_below_ma5', 'continuous_above_ma10', 'continuous_below_ma10', 'continuous_above_ma20', 'continuous_below_ma20', 'continuous_above_ma30', 'continuous_below_ma30']
  303. for key in keys_to_remove:
  304. yesterday_data_copy.pop(key, None)
  305. # 判断上一个交易日完整的数据中是否穿越
  306. if (line_type == 'support' and low < ma_value and close_ma_difference <= 0.0005) or (line_type == 'resistance' and high > ma_value and close_ma_difference >= -0.0005):
  307. symbol_crosses.append({
  308. 'date': yesterday_day,
  309. 'symbol': symbol,
  310. 'line_label': line_label,
  311. 'line_type': line_type,
  312. 'direction': 'long' if line_type == 'support' else 'short',
  313. 'latest_data': yesterday_data_copy
  314. })
  315. print(f"{symbol} 穿越 {line_label} ({line_type})")
  316. # 从符合条件的趋势线中选择数字较大的
  317. if symbol_crosses:
  318. best_cross = max(symbol_crosses, key=lambda x: int(''.join(filter(str.isdigit, x['line_label']))))
  319. update_crossed_symbols_history(context, best_cross)
  320. # 针对出现趋势变化的检查均线顺序、破趋势天数、收盘价和均线的最高点关系
  321. for symbol, records in g.crossed_symbols_history.items():
  322. latest_record = max(records, key=lambda x: x['date'])
  323. # 检查各种条件
  324. relation_check = check_ma_relations(context, symbol)
  325. duration_check, ma_close = check_cross_details(context, symbol)
  326. if relation_check and duration_check and ma_close:
  327. print(f'{symbol}满足所有条件,加入购买清单')
  328. today_crossed_symbols[symbol] = [latest_record]
  329. return [item for sublist in today_crossed_symbols.values() for item in sublist]
  330. def check_buy_condition(context, crossed_symbols):
  331. """检查买入条件"""
  332. print("-" * 20 + "检查买入条件" + "-" * 20)
  333. buy_symbols = []
  334. for crossed_symbol in crossed_symbols:
  335. symbol = crossed_symbol['symbol']
  336. line_label = crossed_symbol['line_label']
  337. line_type = crossed_symbol['line_type']
  338. # 获取最新数据
  339. latest_data = crossed_symbol['latest_data']
  340. ma_value = latest_data[line_label]
  341. close_price = latest_data['close']
  342. today_change = (latest_data['close'] - latest_data['open']) / latest_data['open']
  343. if line_label != "ma5":
  344. if line_type == 'support' and 1.005 * ma_value <= close_price <= 1.02 * ma_value and today_change >= -0.002:
  345. buy_symbols.append((symbol, line_label, 'support', 'long'))
  346. elif line_type == 'resistance' and 0.98 * ma_value <= close_price <= 0.995 * ma_value and today_change <= 0.002:
  347. buy_symbols.append((symbol, line_label, 'resistance', 'short'))
  348. return buy_symbols
  349. ############################ 辅助函数 ###################################
  350. def calculate_order_value(context, security, direction):
  351. """计算可以用于交易的金额"""
  352. current_price = get_current_data()[security].last_price
  353. underlying_symbol = security.split('.')[0][:-4]
  354. # 获取保证金比例
  355. margin_rate = g.default_margin_rates.get(direction, {}).get(underlying_symbol, 0.10)
  356. # 获取合约乘数
  357. multiplier = g.multiplier.get(underlying_symbol, 10)
  358. # 计算单手保证金
  359. single_hand_margin = current_price * multiplier * margin_rate
  360. # 根据单手保证金决定购买手数
  361. if single_hand_margin <= 20000:
  362. total_margin = 20000
  363. else:
  364. total_margin = single_hand_margin
  365. return total_margin
  366. def update_continuous_days(continuous_days, close, ma5, ma10, ma20, ma30, length):
  367. """更新连续天数"""
  368. for key in ['above_ma5', 'below_ma5', 'above_ma10', 'below_ma10', 'above_ma20', 'below_ma20', 'above_ma30', 'below_ma30']:
  369. condition_met = False
  370. if ((key == 'above_ma5' and close > ma5) or
  371. (key == 'below_ma5' and close < ma5) or
  372. (key == 'above_ma10' and close > ma10) or
  373. (key == 'below_ma10' and close < ma10) or
  374. (key == 'above_ma20' and close > ma20) or
  375. (key == 'below_ma20' and close < ma20) or
  376. (key == 'above_ma30' and close > ma30) or
  377. (key == 'below_ma30' and close < ma30)):
  378. condition_met = True
  379. if condition_met:
  380. continuous_days[key].insert(0, continuous_days[key][0] + 1)
  381. else:
  382. continuous_days[key].insert(0, 0)
  383. continuous_days[key] = continuous_days[key][:length]
  384. def count_ma_crosses(future_data, days):
  385. """计算均线交叉次数"""
  386. recent_data = future_data[-days:]
  387. ma5 = [day['ma5'] for day in recent_data]
  388. ma10 = [day['ma10'] for day in recent_data]
  389. ma20 = [day['ma20'] for day in recent_data]
  390. ma30 = [day['ma30'] for day in recent_data]
  391. cross_5_10 = sum([1 for i in range(1, len(ma5)) if ((ma5[-i] > ma10[-i] and ma5[-i-1] < ma10[-i-1]) or
  392. (ma5[-i] < ma10[-i] and ma5[-i-1] > ma10[-i-1]))])
  393. cross_5_20 = sum([1 for i in range(1, len(ma5)) if ((ma5[-i] > ma20[-i] and ma5[-i-1] < ma20[-i-1]) or
  394. (ma5[-i] < ma20[-i] and ma5[-i-1] > ma20[-i-1]))])
  395. cross_5_30 = sum([1 for i in range(1, len(ma5)) if ((ma5[-i] > ma30[-i] and ma5[-i-1] < ma30[-i-1]) or
  396. (ma5[-i] < ma30[-i] and ma5[-i-1] > ma30[-i-1]))])
  397. cross_10_20 = sum([1 for i in range(1, len(ma10)) if ((ma10[-i] > ma20[-i] and ma10[-i-1] < ma20[-i-1]) or
  398. (ma10[-i] < ma20[-i] and ma10[-i-1] > ma20[-i-1]))])
  399. cross_10_30 = sum([1 for i in range(1, len(ma10)) if ((ma10[-i] > ma30[-i] and ma10[-i-1] < ma30[-i-1]) or
  400. (ma10[-i] < ma30[-i] and ma10[-i-1] > ma30[-i-1]))])
  401. cross_20_30 = sum([1 for i in range(1, len(ma20)) if ((ma20[-i] > ma30[-i] and ma20[-i-1] < ma30[-i-1]) or
  402. (ma20[-i] < ma30[-i] and ma20[-i-1] > ma30[-i-1]))])
  403. return cross_5_10 + cross_5_20 + cross_5_30 + cross_10_20 + cross_10_30 + cross_20_30
  404. def update_crossed_symbols_history(context, new_record):
  405. """更新穿越历史记录"""
  406. symbol = new_record['symbol']
  407. if symbol not in g.crossed_symbols_history:
  408. g.crossed_symbols_history[symbol] = []
  409. g.crossed_symbols_history[symbol].append(new_record)
  410. def check_ma_relations(context, symbol):
  411. """检查MA均线之间的关系"""
  412. if symbol not in g.crossed_symbols_history:
  413. return False
  414. latest_record = g.crossed_symbols_history[symbol][-1]
  415. line_label = latest_record['line_label'].lower()
  416. line_type = latest_record['line_type']
  417. MA_values = latest_record['latest_data']
  418. MA5 = MA_values['ma5']
  419. MA10 = MA_values['ma10']
  420. MA20 = MA_values['ma20']
  421. MA30 = MA_values['ma30']
  422. conditions = {
  423. ('ma10', 'resistance'): MA5 > MA10 > MA20 > MA30,
  424. ('ma10', 'support'): MA30 > MA20 > MA10 > MA5,
  425. ('ma20', 'resistance'): (MA30 >= MA20 * 0.999 and MA5 >= MA10 * 0.999) or (MA30 > MA20 > MA10 > MA5),
  426. ('ma20', 'support'): (MA20 >= MA30 * 0.999 and MA10 >= MA5 * 0.999) or (MA5 > MA10 > MA20 > MA30),
  427. ('ma30', 'resistance'): (MA30 >= MA20 * 0.999 and MA5 >= MA10 * 0.999) or (MA30 > MA20 > MA10 > MA5),
  428. ('ma30', 'support'): (MA20 >= MA30 * 0.999 and MA10 >= MA5 * 0.999) or (MA5 > MA10 > MA20 > MA30),
  429. }
  430. return conditions.get((line_label, line_type), False)
  431. def check_cross_details(context, symbol):
  432. """检查穿越详情"""
  433. if symbol not in g.crossed_symbols_history:
  434. return False, False
  435. all_records = g.crossed_symbols_history[symbol]
  436. if not all_records:
  437. return False, False
  438. # 检查穿越天数
  439. first_day = all_records[0]['date']
  440. today = context.current_dt.date()
  441. all_days = get_trade_days(first_day, today)
  442. duration_length = len(all_days)
  443. cross_duration = duration_length <= 6
  444. # 检查价格与均线的关系
  445. ma_close = False
  446. for record in all_records:
  447. line_label = record['line_label']
  448. ma_price = record['latest_data'][line_label]
  449. close = record['latest_data']['close']
  450. ma_close_rate = abs((close - ma_price) / ma_price)
  451. if ma_close_rate <= 0.02:
  452. ma_close = True
  453. return cross_duration, ma_close
  454. def check_loss_for_close(context, position, direction, initial_loss_limit=-4000, loss_increment_per_day=200):
  455. """检查固定止损"""
  456. if position.security not in g.trade_history:
  457. return False
  458. trade_info = g.trade_history.get(position.security, {})
  459. finish_time = trade_info.get('finish_time')
  460. # 获取持仓天数
  461. holding_days = 0
  462. if finish_time:
  463. finish_date = finish_time.date()
  464. current_date = context.current_dt.date()
  465. all_trade_days = get_all_trade_days()
  466. holding_days = sum((finish_date <= d <= current_date) for d in all_trade_days)
  467. # 调整损失限制
  468. adjusted_loss_limit = initial_loss_limit + holding_days * loss_increment_per_day
  469. # 计算盈亏
  470. multiplier = position.value / (position.total_amount * position.price)
  471. if direction == 'long':
  472. revenue = multiplier * (position.price - position.acc_avg_cost) * position.total_amount
  473. else:
  474. revenue = multiplier * (position.acc_avg_cost - position.price) * position.total_amount
  475. if revenue < adjusted_loss_limit:
  476. close_position(position, g.trade_history[position.security]['direction'])
  477. return True
  478. return False
  479. def check_ma_for_close(context, position, offset_ratio, days_for_adjustment):
  480. """根据均线止损"""
  481. security = position.security
  482. if security not in g.trade_history:
  483. return False
  484. trade_info = g.trade_history.get(position.security, {})
  485. finish_time = trade_info.get('finish_time')
  486. line_label = trade_info.get('line_label')
  487. # 获取持仓天数
  488. holding_days = 0
  489. if finish_time:
  490. finish_date = finish_time.date()
  491. current_date = context.current_dt.date()
  492. all_trade_days = get_all_trade_days()
  493. holding_days = sum((finish_date <= d <= current_date) for d in all_trade_days)
  494. # 计算变化率
  495. today_price = get_current_data()[position.security].last_price
  496. avg_daily_change_rate = calculate_average_daily_change_rate(position.security)
  497. historical_data = attribute_history(position.security, 1, '1d', ['close'])
  498. yesterday_close = historical_data['close'].iloc[-1]
  499. today_change_rate = abs((today_price - yesterday_close) / yesterday_close)
  500. # 选择止损均线
  501. close_line = None
  502. if today_change_rate >= 1.5 * avg_daily_change_rate:
  503. close_line = 'ma5'
  504. elif holding_days <= days_for_adjustment:
  505. close_line = line_label
  506. else:
  507. close_line = 'ma5' if today_change_rate >= 1.2 * avg_daily_change_rate else 'ma10'
  508. # 计算MA值并进行平仓判断
  509. ma_values = calculate_ma_values(position.security, [5, 10, 20, 30])
  510. adjusted_ma_value = ma_values[close_line] * (1 + offset_ratio if position.side == 'short' else 1 - offset_ratio)
  511. if (position.side == 'long' and today_price < adjusted_ma_value) or \
  512. (position.side == 'short' and today_price > adjusted_ma_value):
  513. close_position(position, g.trade_history[position.security]['direction'])
  514. return True
  515. return False
  516. def calculate_average_daily_change_rate(security, days=30):
  517. """计算日均变化率"""
  518. historical_data = attribute_history(security, days + 1, '1d', ['close'])
  519. daily_change_rates = abs(historical_data['close'].pct_change()).iloc[1:]
  520. return daily_change_rates.mean()
  521. def calculate_ma_values(security, ma_periods):
  522. """计算MA值"""
  523. historical_data = attribute_history(security, max(ma_periods), '1d', ['close'])
  524. today_price = get_current_data()[security].last_price
  525. close_prices = historical_data['close'].tolist() + [today_price]
  526. ma_values = {f'ma{period}': sum(close_prices[-period:]) / period for period in ma_periods}
  527. return ma_values
  528. def check_symbol_prefix_match(symbol, hold_symbols):
  529. """检查是否有相似持仓"""
  530. symbol_prefix = symbol[:-9]
  531. for hold_symbol in hold_symbols:
  532. hold_symbol_prefix = hold_symbol[:-9]
  533. if symbol_prefix == hold_symbol_prefix:
  534. return True
  535. return False
  536. def print_list_elements(title, elements):
  537. """打印列表元素"""
  538. print(f"{title}:")
  539. for item in elements:
  540. print(item)
  541. ############################ 移仓换月函数 ###################################
  542. def position_auto_switch(context, pindex=0):
  543. """期货自动移仓换月"""
  544. import re
  545. subportfolio = context.subportfolios[pindex]
  546. symbols = set(subportfolio.long_positions.keys()) | set(subportfolio.short_positions.keys())
  547. switch_result = []
  548. for symbol in symbols:
  549. match = re.match(r"(?P<underlying_symbol>[A-Z]{1,})", symbol)
  550. if not match:
  551. continue
  552. dominant = get_dominant_future(match.groupdict()["underlying_symbol"])
  553. if not dominant or dominant <= symbol:
  554. continue
  555. cur = get_current_data()
  556. symbol_last_price = cur[symbol].last_price
  557. dominant_last_price = cur[dominant].last_price
  558. for positions_ in (subportfolio.long_positions, subportfolio.short_positions):
  559. if symbol not in positions_.keys():
  560. continue
  561. position = positions_[symbol]
  562. amount = position.total_amount
  563. side = position.side
  564. # 检查涨跌停限制
  565. if side == "long":
  566. symbol_low_limit = cur[symbol].low_limit
  567. dominant_high_limit = cur[dominant].high_limit
  568. if symbol_last_price <= symbol_low_limit or dominant_last_price >= dominant_high_limit:
  569. continue
  570. else:
  571. symbol_high_limit = cur[symbol].high_limit
  572. dominant_low_limit = cur[dominant].low_limit
  573. if symbol_last_price >= symbol_high_limit or dominant_last_price <= dominant_low_limit:
  574. continue
  575. # 执行移仓换月
  576. order_old = order_target(symbol, 0, side=side)
  577. if order_old is not None and order_old.filled > 0:
  578. order_new = order_target(dominant, amount, side=side)
  579. if order_new is not None and order_new.filled > 0:
  580. # 换月成功,更新交易记录
  581. if symbol in g.trade_history:
  582. g.trade_history[dominant] = g.trade_history[symbol]
  583. del g.trade_history[symbol]
  584. switch_result.append({"before": symbol, "after": dominant, "side": side})
  585. print(f"移仓换月成功: {symbol} -> {dominant}")
  586. else:
  587. # 换月失败,记录失败信息
  588. if symbol in g.trade_history:
  589. g.change_fail_history[dominant] = g.trade_history[symbol]
  590. del g.trade_history[symbol]
  591. print(f"移仓换月失败: {symbol} -> {dominant}")
  592. return switch_result