fund_grid_trading.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. from jqdata import *
  2. from jqlib.technical_analysis import *
  3. import numpy as np
  4. import pandas as pd
  5. import datetime as dt
  6. # 初始化函数,设定基准等等
  7. def initialize(context):
  8. # 设定沪深300作为基准
  9. set_benchmark('000300.XSHG')
  10. # 开启异步报单
  11. set_option('async_order', True)
  12. # 开启动态复权模式(真实价格)
  13. set_option('use_real_price', True)
  14. # 是否未来函数
  15. set_option("avoid_future_data", True)
  16. # 初始化全局变量
  17. g.observation_period = 1260 # 默认观察周期为5年(约252个交易日)
  18. g.total_portions = 100 # 总资金分成100份
  19. g.buy_thresholds = {
  20. 0.3: 1, # 相对位置30%买入1份
  21. 0.2: 2, # 相对位置20%买入2份
  22. 0.1: 3, # 相对位置10%买入3份
  23. 0.0: 5 # 相对位置最低买入5份
  24. }
  25. g.profit_target = 0.5 # 50%的目标收益率
  26. g.take_profit_portion = 0.05 # 每次卖出获利5%的部分
  27. # 记录买入历史和持仓信息
  28. g.buy_records = {} # 记录每个基金在不同阈值的买入状态
  29. g.position_records = {} # 记录每个基金的持仓成本和数量
  30. g.profit_records = {} # 记录已经获利的部分
  31. # 新增:交易信息存储字典
  32. g.trading_records = {
  33. 'normal_positions': {}, # 正常网格交易持仓
  34. 'profit_positions': {} # 获利后保留的持仓
  35. }
  36. # 设置交易费用
  37. set_order_cost(OrderCost(close_tax=0.000, open_commission=0.00025, close_commission=0.00025, min_commission=0), type='fund')
  38. # 设定定时运行函数
  39. run_weekly(before_market_open, 1, '09:20')
  40. run_daily(market_open, '09:31')
  41. run_daily(check_profit, '14:30')
  42. # run_daily(print_position_info, '15:30')
  43. def before_market_open(context):
  44. # 获取目标基金列表
  45. g.target_funds = [
  46. '512880.XSHG', '512980.XSHG', '513050.XSHG', '159938.XSHE',
  47. '512070.XSHG', '515180.XSHG', '513520.XSHG', '159952.XSHE',
  48. '162412.XSHE', '510300.XSHG', '510050.XSHG', '510500.XSHG',
  49. '588080.XSHG', '588000.XSHG', '159901.XSHE', '159915.XSHE',
  50. '159919.XSHE', '159922.XSHE', '159920.XSHE', '159985.XSHE',
  51. '501018.XSHG', '162411.XSHE'
  52. ]
  53. # 初始化当日需要检查的基金列表
  54. g.check_funds = []
  55. # log.info("\n=== 周一基金检查 ===")
  56. # log.info(f"观察周期: {g.observation_period}个交易日")
  57. # log.info("\n基金历史位置统计:")
  58. # 记录所有基金的相对位置,用于计算平均值
  59. all_positions = []
  60. for fund in g.target_funds:
  61. try:
  62. df = get_price(fund, count=g.observation_period, end_date=context.previous_date, fields=['close'])
  63. current_price = df['close'][-1]
  64. hist_prices = df['close'].values
  65. # 计算相对于历史最高价和最低价的位置
  66. hist_high = np.max(hist_prices)
  67. hist_low = np.min(hist_prices)
  68. price_position = (current_price - hist_low) / (hist_high - hist_low) if hist_high != hist_low else 0
  69. all_positions.append(price_position)
  70. # 获取基金名称
  71. fund_info = get_security_info(fund)
  72. fund_name = fund_info.display_name if fund_info else fund
  73. # log.info(f"基金 {fund}({fund_name}): | 当前价格: {format(current_price, '.4f')}| 相对位置: {format(price_position * 100, '.2f')}%| 价格区间: {format(hist_low, '.4f')} - {format(hist_high, '.4f')}")
  74. if price_position <= 0.35: # 如果当前价格在相对位置40%以下
  75. g.check_funds.append(fund)
  76. # log.info(" 状态: 需要每日检查 ✓")
  77. # else:
  78. # log.info(" 状态: 暂不检查 ✗")
  79. except Exception as e:
  80. # log.error(f"获取基金{fund}数据失败: {str(e)}")
  81. continue
  82. # 输出统计信息
  83. if all_positions:
  84. avg_position = np.mean(all_positions)
  85. median_position = np.median(all_positions)
  86. low_position_count = sum(1 for p in all_positions if p <= 0.3)
  87. # log.info("\n=== 统计信息 ===")
  88. # log.info(f"平均相对位置: {format(avg_position * 100, '.2f')}% | 中位相对位置: {format(median_position * 100, '.2f')}% | 低位基金数量: {low_position_count} (相对位置≤30%)")
  89. if g.check_funds:
  90. # log.info("\n=== 需要每日检查的基金 ===")
  91. for fund in g.check_funds:
  92. fund_info = get_security_info(fund)
  93. fund_name = fund_info.display_name if fund_info else fund
  94. # log.info(f"- {fund}({fund_name})")
  95. # else:
  96. # log.info("\n没有基金需要每日检查")
  97. # 在统计信息之后,添加交易持仓信息的输出
  98. log.info("\n=== 交易持仓信息 ===")
  99. # 输出正常网格交易持仓
  100. if g.trading_records['normal_positions']:
  101. log.info("正常网格交易持仓:")
  102. for fund, positions in g.trading_records['normal_positions'].items():
  103. fund_info = get_security_info(fund)
  104. fund_name = fund_info.display_name if fund_info else fund
  105. for pos in positions:
  106. log.info(f"标的 {fund}({fund_name}): "
  107. f"买入时间 {pos['buy_time'].strftime('%Y-%m-%d %H:%M')}, "
  108. f"相对位置 {format(pos['price_position'] * 100, '.2f')}%, "
  109. f"买入数量 {format(pos['shares'], '.2f')}, "
  110. f"买入价格 {format(pos['buy_price'], '.4f')}, "
  111. f"买入金额 {format(pos['total_amount'], '.2f')}")
  112. else:
  113. log.info("当前无正常网格交易持仓")
  114. # 输出获利保留持仓
  115. if g.trading_records['profit_positions']:
  116. log.info("\n获利保留持仓:")
  117. for fund, positions in g.trading_records['profit_positions'].items():
  118. fund_info = get_security_info(fund)
  119. fund_name = fund_info.display_name if fund_info else fund
  120. for pos in positions:
  121. log.info(f"标的 {fund}({fund_name}): "
  122. f"原始买入时间 {pos['buy_time'].strftime('%Y-%m-%d %H:%M')}, "
  123. f"卖出时间 {pos['sell_time'].strftime('%Y-%m-%d %H:%M')}, "
  124. f"原始相对位置 {format(pos['price_position'] * 100, '.2f')}%, "
  125. f"买入价格 {format(pos['buy_price'], '.4f')}, "
  126. f"卖出价格 {format(pos['sell_price'], '.4f')}, "
  127. f"卖出数量 {format(pos['sell_shares'], '.2f')}, "
  128. f"卖出金额 {format(pos['sell_amount'], '.2f')}, "
  129. f"保留份额 {format(pos['remaining_shares'], '.2f')}")
  130. else:
  131. log.info("当前无获利保留持仓")
  132. def market_open(context):
  133. # 检查需要买入的基金
  134. for fund in g.check_funds:
  135. try:
  136. # 过滤停牌和新成立基金
  137. if not filter_paused_fund(context, fund) or not filter_new_fund(context, fund):
  138. continue
  139. df = get_price(fund, count=g.observation_period, end_date=context.previous_date, fields=['close'])
  140. current_price = df['close'][-1]
  141. hist_prices = df['close'].values
  142. # 计算相对于历史最高价和最低价的位置
  143. hist_high = np.max(hist_prices)
  144. hist_low = np.min(hist_prices)
  145. price_position = (current_price - hist_low) / (hist_high - hist_low) if hist_high != hist_low else 0
  146. # 如果该基金没有买入记录,初始化记录
  147. if fund not in g.buy_records:
  148. g.buy_records[fund] = {threshold: False for threshold in g.buy_thresholds.keys()}
  149. # 检查每个阈值是否需要买入
  150. for threshold, portions in g.buy_thresholds.items():
  151. if price_position <= threshold and not g.buy_records[fund][threshold]:
  152. # 计算买入金额
  153. portion_value = context.portfolio.total_value / g.total_portions
  154. buy_amount = portion_value * portions
  155. # 执行买入
  156. order = order_target_value_(fund, buy_amount)
  157. if order is not None and order.status == OrderStatus.held and order.filled == order.amount:
  158. g.buy_records[fund][threshold] = True
  159. # 记录持仓信息
  160. if fund not in g.position_records:
  161. g.position_records[fund] = []
  162. g.position_records[fund].append({
  163. 'buy_price': current_price,
  164. 'amount': buy_amount,
  165. 'threshold': threshold
  166. })
  167. # 更新交易记录
  168. if fund not in g.trading_records['normal_positions']:
  169. g.trading_records['normal_positions'][fund] = []
  170. # 获取实际成交信息
  171. filled_shares = order.filled
  172. total_amount = filled_shares * current_price
  173. g.trading_records['normal_positions'][fund].append({
  174. 'buy_time': context.current_dt,
  175. 'price_position': price_position,
  176. 'shares': filled_shares,
  177. 'buy_price': current_price,
  178. 'total_amount': total_amount
  179. })
  180. # log.info(f"买入基金{fund},价格{current_price},金额{buy_amount},相对位置{format(price_position * 100, '.2f')}%,阈值{format(threshold * 100, '.2f')}%")
  181. except Exception as e:
  182. # log.error(f"处理基金{fund}买入失败: {str(e)}")
  183. continue
  184. def check_profit(context):
  185. # 检查所有持仓是否达到盈利目标
  186. for fund in list(g.position_records.keys()): # 使用list复制键,避免运行时修改字典
  187. try:
  188. if not filter_paused_fund(context, fund):
  189. continue
  190. current_price = get_current_data()[fund].last_price
  191. # 检查每个持仓记录
  192. positions_to_remove = [] # 记录需要移除的持仓
  193. for position in g.position_records[fund]:
  194. if current_price >= position['buy_price'] * (1 + g.profit_target):
  195. # 计算需要卖出的份额
  196. # 原始投入金额
  197. original_value = position['amount']
  198. # 目标收益金额(比如10%)
  199. target_profit = original_value * g.take_profit_portion
  200. # 需要卖出的份额 = (原始金额 + 目标收益) / 当前价格
  201. shares_to_sell = (original_value + target_profit) / current_price
  202. # 计算卖出金额
  203. sell_amount = shares_to_sell * current_price
  204. # 执行卖出
  205. order = order_target_value_(fund, -sell_amount)
  206. if order is not None and order.status == OrderStatus.held and order.filled > 0:
  207. # 计算实际获利
  208. actual_profit = sell_amount - (sell_amount / current_price * position['buy_price'])
  209. # 更新持仓记录
  210. position['amount'] -= sell_amount
  211. # 记录获利部分
  212. if fund not in g.profit_records:
  213. g.profit_records[fund] = []
  214. g.profit_records[fund].append({
  215. 'sell_price': current_price,
  216. 'profit_amount': actual_profit,
  217. 'sell_date': context.current_dt,
  218. 'sell_shares': shares_to_sell
  219. })
  220. # 更新交易记录 - 从normal_positions移除卖出部分
  221. if fund in g.trading_records['normal_positions']:
  222. # 找到对应的买入记录
  223. for normal_pos in g.trading_records['normal_positions'][fund]:
  224. if normal_pos['buy_price'] == position['buy_price']:
  225. # 计算剩余份额
  226. remaining_shares = normal_pos['shares'] - shares_to_sell
  227. if remaining_shares > 0:
  228. normal_pos['shares'] = remaining_shares
  229. normal_pos['total_amount'] = remaining_shares * normal_pos['buy_price']
  230. else:
  231. g.trading_records['normal_positions'][fund].remove(normal_pos)
  232. # 添加到获利持仓记录
  233. if fund not in g.trading_records['profit_positions']:
  234. g.trading_records['profit_positions'][fund] = []
  235. g.trading_records['profit_positions'][fund].append({
  236. 'buy_time': normal_pos['buy_time'],
  237. 'sell_time': context.current_dt,
  238. 'price_position': normal_pos['price_position'],
  239. 'buy_price': normal_pos['buy_price'],
  240. 'sell_price': current_price,
  241. 'sell_shares': shares_to_sell,
  242. 'sell_amount': sell_amount,
  243. 'remaining_shares': remaining_shares if remaining_shares > 0 else 0
  244. })
  245. break
  246. # 如果该基金没有正常持仓了,清理记录
  247. if not g.trading_records['normal_positions'][fund]:
  248. del g.trading_records['normal_positions'][fund]
  249. # log.info(f"卖出基金{fund}获利部分: | 买入价格: {format(position['buy_price'], '.4f')}| 当前价格: {format(current_price, '.4f')}| 卖出份额: {format(shares_to_sell, '.2f')}| 卖出金额: {format(sell_amount, '.2f')}| 实际获利: {format(actual_profit, '.2f')}| 剩余金额: {format(position['amount'], '.2f')}")
  250. # 如果剩余金额很小,加入待清理列表
  251. if position['amount'] < 100:
  252. positions_to_remove.append(position)
  253. # 清理需要移除的持仓记录
  254. for position in positions_to_remove:
  255. g.buy_records[fund][position['threshold']] = False
  256. g.position_records[fund].remove(position)
  257. # 如果该基金没有持仓了,清理记录
  258. if not g.position_records[fund]:
  259. del g.position_records[fund]
  260. except Exception as e:
  261. # log.error(f"处理基金{fund}获利了结失败: {str(e)}")
  262. continue
  263. def print_position_info(context):
  264. # 打印当天成交记录
  265. trades = get_trades()
  266. for _trade in trades.values():
  267. print(f'成交记录:{str(_trade)}')
  268. # 打印账户信息
  269. print("\n=== 当前持仓信息 ===")
  270. for position in list(context.portfolio.positions.values()):
  271. security = position.security
  272. cost = position.avg_cost
  273. price = position.price
  274. ret = 100 * (price/cost - 1)
  275. value = position.value
  276. amount = position.total_amount
  277. print(f'代码: {security} | 成本价: {format(cost, ".2f")} | 现价: {price} | 收益率: {format(ret, ".2f")}% | 持仓(份): {amount} | 市值: {format(value, ".2f")}')
  278. # 打印当日盈利记录
  279. print("\n=== 当日获利记录 ===")
  280. for fund in g.profit_records.keys():
  281. if g.profit_records[fund]:
  282. today_profits = [record for record in g.profit_records[fund]
  283. if record['sell_date'].date() == context.current_dt.date()]
  284. if today_profits:
  285. print(f"\n基金{fund}今日获利记录:")
  286. total_profit = sum([record['profit_amount'] for record in today_profits])
  287. print(f"今日获利:{format(total_profit, '.2f')}")
  288. # def after_trading_end(context):
  289. # # 打印每日持仓和盈利信息
  290. # log.info("=== 当日持仓信息 ===")
  291. # for fund in g.position_records.keys():
  292. # if g.position_records[fund]:
  293. # log.info(f"基金{fund}持仓:")
  294. # for pos in g.position_records[fund]:
  295. # log.info(f"买入价格:{pos['buy_price']},持仓金额:{pos['amount']},买入阈值:{pos['threshold']}")
  296. # log.info("=== 当日获利记录 ===")
  297. # for fund in g.profit_records.keys():
  298. # if g.profit_records[fund]:
  299. # total_profit = sum([record['profit_amount'] for record in g.profit_records[fund]])
  300. # log.info(f"基金{fund}获利记录: | 总获利:{total_profit}")
  301. # 交易模块 - 自定义下单
  302. def order_target_value_(security, value):
  303. if value == 0:
  304. log.debug(f"清空持仓 {security}")
  305. else:`
  306. log.debug(f"目标下单 {security},金额 {value}")
  307. return order_target_value(security, value)
  308. # 交易模块 - 开仓
  309. def open_position(security, value):
  310. if value == 0:
  311. return True
  312. order = order_target_value_(security, value)
  313. if order is not None and order.status == OrderStatus.held and order.filled == order.amount:
  314. return True
  315. return False
  316. # 交易模块 - 平仓
  317. def close_position(position):
  318. security = position.security
  319. order = order_target_value_(security, 0)
  320. if order is not None and order.status == OrderStatus.held and order.filled == order.amount:
  321. return True
  322. return False
  323. # 交易模块 - 调整仓位
  324. def adjust_position(context, security, target_value):
  325. if security not in context.portfolio.positions:
  326. if target_value > 0:
  327. return open_position(security, target_value)
  328. else:
  329. position = context.portfolio.positions[security]
  330. if target_value == 0:
  331. return close_position(position)
  332. else:
  333. return order_target_value_(security, target_value)
  334. return False
  335. # 过滤模块 - 过滤停牌基金
  336. def filter_paused_fund(context, security):
  337. current_data = get_current_data()
  338. return not current_data[security].paused
  339. # 过滤模块 - 过滤新成立基金
  340. def filter_new_fund(context, security):
  341. yesterday = context.previous_date
  342. return not yesterday - get_security_info(security).start_date < datetime.timedelta(days=30) # 过滤成立不足30天的基金