-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathShipan-MultiFactor.py
More file actions
1804 lines (1510 loc) · 69.8 KB
/
Shipan-MultiFactor.py
File metadata and controls
1804 lines (1510 loc) · 69.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import numpy as np
import pandas as pd
import talib
from prettytable import PrettyTable
import types
import urllib2
import smtplib
from email.mime.text import MIMEText
from email.header import Header
import datetime
import time
import enum
from jqdata import gta
from jqlib.technical_analysis import *
def pick_strategy(buy_count):
g.strategy_memo = '首席质量因子'
pick_config = [
[True, '', '多因子范围选取器', Pick_financial_data, {
'factors': [
FD_Factor('valuation.pe_ratio', min=0, max=200), # 200 > pe > 0
FD_Factor('valuation.pb_ratio', min=0), # pb_ratio > 0
FD_Factor('valuation.ps_ratio', max=2.5) # ps_ratio < 2.5
]
}],
[True, '', '多因子过滤器', Filter_financial_data, {
'filters': [
FD_Filter('valuation.pe_ratio',sort=SortType.asc, percent=40),
FD_Filter('valuation.pb_ratio',sort=SortType.asc, percent=40),
]
}],
[True, '', '过滤创业板', Filter_gem, {}],
[True, '', '过滤ST,停牌,涨跌停股票', Filter_common, {}],
[True, '', '权重排序', SortRules, {
'config': [
[True, '', '流通市值排序', Sort_financial_data, {
'factor': 'valuation.circulating_market_cap',
'sort': SortType.asc
, 'weight': 50}],
[True, '', '首席质量因子排序', Sort_gross_profit, {
'sort': SortType.desc,
'weight': 100}],
[True, '20volumn', '20日成交量排序', Sort_volumn, {
'sort': SortType.desc
, 'weight': 10
, 'day': 20}],
[True, '60volumn', '60日成交量排序', Sort_volumn, {
'sort': SortType.desc
, 'weight': 10
, 'day': 60}],
[True, '120volumn', '120日成交量排序', Sort_volumn, {
'sort': SortType.desc
, 'weight': 10
, 'day': 120}],
[True, '180volumn', '180日成交量排序', Sort_volumn, {
'sort': SortType.desc
, 'weight': 10
, 'day': 180}],
]}
],
[True, '', '获取最终选股数', Filter_buy_count, {
'buy_count': buy_count # 最终入选股票数
}],
]
pick_new = [
[True, '_pick_stocks_', '选股', Pick_stocks, {
'config': pick_config,
'day_only_run_one': True
}]
]
return pick_new
#白名单,股票均在这个list里面选取
def white_list():
return get_index_stocks("399951.XSHE") + get_index_stocks("000300.XSHG") + get_index_stocks("000001.XSHG")
# ==================================策略配置==============================================
def select_strategy(context):
buy_count = 5
adjust_days = 7
# **** 这里定义log输出的类类型,重要,一定要写。假如有需要自定义log,可更改这个变量
g.log_type = Rule_loger
# 判断是运行回测还是运行模拟
g.is_sim_trade = context.run_params.type == 'sim_trade'
index2 = '000016.XSHG' # 大盘指数
index8 = '399333.XSHE' # 小盘指数
''' ---------------------配置 调仓条件判断规则-----------------------'''
# 调仓条件判断
adjust_condition_config = [
[True, '_time_c_', '调仓时间', Time_condition, {
'times': [[14, 50]], # 调仓时间列表,二维数组,可指定多个时间点
}],
[True, '', '止损策略', Group_rules, {
'config':[
[True, '_Stop_loss_by_price_', '指数最高低价比值止损器', Stop_loss_by_price, {
'index': '000001.XSHG', # 使用的指数,默认 '000001.XSHG'
'day_count': 160, # 可选 取day_count天内的最高价,最低价。默认160
'multiple': 2.2 # 可选 最高价为最低价的multiple倍时,触 发清仓
}],
[True, '', '多指数20日涨幅止损器', Mul_index_stop_loss, {
'indexs': [index2, index8],
'min_rate': 0.005
}],
[True, '', '个股止损器', Stop_loss_win_for_single, {
# 止损止盈后是否保留当前的持股状态
'keep_position': True,
# 动态止盈和accumulate_win不能一起使用
'accumulate_loss': -0.1,
# 'accumulate_win': 0.2,
'dynamic_stop_win': True,
'dynamic_threshod': 0.05,
'dynamic_sense': 0.1 # 0 < sense,越接近0越灵敏,止盈出局越快
}],
]
}],
[True, '', '调仓日计数器', Period_condition, {
'period': adjust_days, # 调仓频率,日
}],
]
adjust_condition_config = [
[True, '_adjust_condition_', '调仓执行条件的判断规则组合', Group_rules, {
'config': adjust_condition_config
}]
]
''' --------------------------配置 选股规则----------------- '''
pick_new = pick_strategy(buy_count)
''' --------------------------配置 4 调仓规则------------------ '''
# # 通达信持仓字段不同名校正
col_names = {'可用': u'可用', '市值': u'参考市值', '证券名称': u'证券名称', '资产': u'资产'
, '证券代码': u'证券代码', '证券数量': u'证券数量', '可卖数量': u'可卖数量', '当前价': u'当前价', '成本价': u'成本价'
}
adjust_position_config = [
[True, '', '卖出股票', Sell_stocks, {}],
[True, '', '买入股票', Buy_stocks, {
'buy_count': buy_count # 最终买入股票数
}],
[True, '_Show_postion_adjust_', '显示买卖的股票', Show_postion_adjust, {}],
]
adjust_position_config = [
[True, '_Adjust_position_', '调仓执行规则组合', Adjust_position, {
'config': adjust_position_config
}]
]
''' --------------------------配置 辅助规则------------------ '''
# 优先辅助规则,每分钟优先执行handle_data
common_config_list = [
[True, '', '设置系统参数', Set_sys_params, {
'benchmark': '000300.XSHG' # 指定基准为次新股指
}],
[True, '', '手续费设置器', Set_slip_fee, {}],
[True, '', '持仓信息打印器', Show_position, {}],
[True, '', '统计执行器', Stat, {}],
]
common_config = [
[True, '_other_pre_', '预先处理的辅助规则', Group_rules, {
'config': common_config_list
}]
]
# 组合成一个总的策略
g.main_config = (common_config
+ adjust_condition_config
+ pick_new
+ adjust_position_config)
# ===================================聚宽调用==============================================
def initialize(context):
# 策略配置
select_strategy(context)
# 创建策略组合
g.main = Strategy_Group({'config': g.main_config
, 'g_class': Global_variable
, 'memo': g.strategy_memo
, 'name': '_main_'})
g.main.initialize(context)
# 打印规则参数
g.main.log.info(g.main.show_strategy())
# 按分钟回测
def handle_data(context, data):
# 保存context到全局变量量,主要是为了方便规则器在一些没有context的参数的函数里使用。
g.main.g.context = context
# 执行策略
g.main.handle_data(context, data)
# 开盘
def before_trading_start(context):
log.info("==========================================================================")
g.main.g.context = context
g.main.before_trading_start(context)
# 收盘
def after_trading_end(context):
g.main.g.context = context
g.main.after_trading_end(context)
g.main.g.context = None
# 进程启动(一天一次)
def process_initialize(context):
try:
g.main.g.context = context
g.main.process_initialize(context)
except Exception as e:
log.error(str(e))
pass
# 这里示例进行模拟更改回测时,如何调整策略,基本通用代码。
def after_code_changed(context):
try:
g.main
except Exception as e:
log.error(str(e))
print '更新代码->原先不是OO策略,重新调用initialize(context)。'
initialize(context)
return
try:
print '=> 更新代码'
select_strategy(context)
g.main.g.context = context
g.main.update_params(context, {'config': g.main_config})
g.main.after_code_changed(context)
g.main.log.info(g.main.show_strategy())
except Exception as e:
log.error('更新代码失败:' + str(e) + '\n重新创建策略')
# initialize(context)
pass
'''=================================基础类======================================='''
# '''----------------------------共同参数类-----------------------------------
# 1.考虑到规则的信息互通,完全分离也会增加很大的通讯量。适当的约定好的全局变量,可以增加灵活性。
# 2.因共同约定,也不影响代码重用性。
# 3.假如需要更多的共同参数。可以从全局变量类中继承一个新类并添加新的变量,并赋于所有的规则类。
# 如此达到代码重用与策略差异的解决方案。
# '''
class Rule_loger(object):
def __init__(self, msg_header):
try:
self._owner_msg = msg_header + ':'
except Exception as e:
log.error(str(e))
self._owner_msg = '未知规则:'
def debug(self, msg, *args, **kwargs):
log.debug(self._owner_msg + msg, *args, **kwargs)
def info(self, msg, *args, **kwargs):
log.info(self._owner_msg + msg, *args, **kwargs)
def warn(self, msg, *args, **kwargs):
log.warn(self._owner_msg + msg, *args, **kwargs)
def error(self, msg, *args, **kwargs):
log.error(self._owner_msg + msg, *args, **kwargs)
class Global_variable(object):
context = None
_owner = None
stock_pindexs = [0] # 指示是属于股票性质的子仓列表
op_pindexs = [0] # 提示当前操作的股票子仓Id
buy_stocks = [] # 选股列表
sell_stocks = [] # 卖出的股票列表
# 以下参数需配置 Run_Status_Recorder 规则进行记录。
is_empty_position = True # True表示为空仓,False表示为持仓。
run_day = 0 # 运行天数,持仓天数为正,空仓天数为负
position_record = [False] # 持仓空仓记录表。True表示持仓,False表示空仓。一天一个。
def __init__(self, owner):
self._owner = owner
''' ==============================持仓操作函数,共用================================'''
# 开仓,买入指定价值的证券
# 报单成功并成交(包括全部成交或部分成交,此时成交量大于0),返回True
# 报单失败或者报单成功但被取消(此时成交量等于0),返回False
# 报单成功,触发所有规则的when_buy_stock函数
def open_position(self, sender, security, value, pindex=0):
cur_price = get_close_price(security, 1, '1m')
if math.isnan(cur_price):
return False
# 通过当前价,四乘五入的计算要买的股票数。
amount = int(round(value / cur_price / 100) * 100)
new_value = amount * cur_price
order = order_target_value(security, new_value, pindex=pindex)
if order != None and order.filled > 0:
# 订单成功,则调用规则的买股事件 。(注:这里只适合市价,挂价单不适合这样处理)
self._owner.on_buy_stock(security, order, pindex)
return True
return False
# 按指定股数下单
def order(self, sender, security, amount, pindex=0):
cur_price = get_close_price(security, 1, '1m')
if math.isnan(cur_price):
return False
position = self.context.portfolio.long_positions[security] if self.context is not None else None
_order = order(security, amount, pindex=pindex)
if _order != None and _order.filled > 0:
# 订单成功,则调用规则的买股事件 。(注:这里只适合市价,挂价单不适合这样处理)
if amount > 0:
self._owner.on_buy_stock(security, _order, pindex)
elif position is not None:
self._owner.on_sell_stock(position, _order, pindex)
return _order
return _order
# 平仓,卖出指定持仓
# 平仓成功并全部成交,返回True
# 报单失败或者报单成功但被取消(此时成交量等于0),或者报单非全部成交,返回False
# 报单成功,触发所有规则的when_sell_stock函数
def close_position(self, sender, position, is_normal=True, pindex=0):
security = position.security
order = order_target_value(security, 0, pindex=pindex) # 可能会因停牌失败
if order != None:
if order.filled > 0:
self._owner.on_sell_stock(position, order, is_normal, pindex)
if security not in self.sell_stocks:
self.sell_stocks.append(security)
return True
return False
# 清空卖出所有持仓
# 清仓时,调用所有规则的 when_clear_position
def clear_position(self, sender, context, pindexs=[0]):
pindexs = self._owner.before_clear_position(context, pindexs)
# 对传入的子仓集合进行遍历清仓
for pindex in pindexs:
if context.portfolio.long_positions:
sender.log.info(("[%d]==> 清仓,卖出所有股票") % (pindex))
for stock in context.portfolio.long_positions.keys():
position = context.portfolio.long_positions[stock]
self.close_position(sender, position, False, pindex)
# 调用规则器的清仓事件
self._owner.on_clear_position(context, pindexs)
# 通过对象名 获取对象
def get_obj_by_name(self, name):
return self._owner.get_obj_by_name(name)
# 调用外部的on_log额外扩展事件
def on_log(sender, msg, msg_type):
pass
# 获取当前运行持续天数,持仓返回正,空仓返回负,ignore_count为是否忽略持仓过程中突然空仓的天数也认为是持仓。或者空仓时相反。
def get_run_day_count(self, ignore_count=1):
if ignore_count == 0:
return self.run_day
prs = self.position_record
false_count = 0
init = prs[-1]
count = 1
for i in range(2, len(prs)):
if prs[-i] != init:
false_count += 1 # 失败个数+1
if false_count > ignore_count: # 连续不对超过 忽略噪音数。
if count < ignore_count: # 如果统计的个数不足ignore_count不符,则应进行统计True或False反转
init = not init # 反转
count += false_count # 把统计失败的认为正常的加进去
false_count = 0 # 失败计数清0
else:
break
else:
count += 1 # 正常计数+1
if false_count > 0: # 存在被忽略的噪音数则累回来,认为是正常的
count += false_count
false_count = 0
return count if init else -count # 统计结束,返回结果。init为True返回正数,为False返回负数。
# ''' ==============================规则基类================================'''
# 指定规则的类型,在Strategy_Group的 handle_data里起作用
class Rule_Level(enum.Enum):
Normal = 0 # 普通规则
Prior = 1 # 优先执行的规则
Finally = 2 # 在执行完其它规则后,必需执行的规则
# ''' ==============================规则基类================================'''
class Rule(object):
g = None # 所属的策略全局变量
name = '' # obj名,可以通过该名字查找到
memo = '' # 默认描述
log = None
# 执行是否需要退出执行序列动作,用于Group_Rule默认来判断中扯执行。
is_to_return = False
@property
def level(self):
return self._params.get('level', Rule_Level.Normal)
def __init__(self, params):
self._params = params.copy()
# self.g = None # 所属的策略全局变量
# self.name = '' # obj名,可以通过该名字查找到
# self.memo = '' # 默认描述
# self.log = None
# # 执行是否需要退出执行序列动作,用于Group_Rule默认来判断中扯执行。
# self.is_to_return = False
pass
# 更改参数
def update_params(self, context, params):
self._params = params.copy()
pass
def initialize(self, context):
pass
def handle_data(self, context, data):
pass
def before_trading_start(self, context):
self.is_to_return = False
pass
def after_trading_end(self, context):
self.is_to_return = False
pass
def process_initialize(self, context):
pass
def after_code_changed(self, context):
pass
@property
def to_return(self):
return self.is_to_return
# 卖出股票时调用的函数
# price为当前价,amount为发生的股票数,is_normail正常规则卖出为True,止损卖出为False
def on_sell_stock(self, position, order, is_normal, pindex=0):
pass
# 买入股票时调用的函数
# price为当前价,amount为发生的股票数
def on_buy_stock(self, stock, order, pindex=0):
pass
# 清仓前调用。
def before_clear_position(self, context, pindexs=[0]):
return pindexs
# 清仓时调用的函数
def on_clear_position(self, context, pindexs=[0]):
pass
# handle_data没有执行完 退出时。
def on_handle_data_exit(self, context, data):
pass
# record副曲线
def record(self, **kwargs):
if self._params.get('record', False):
record(**kwargs)
def set_g(self, g):
self.g = g
def __str__(self):
return self.memo
# ''' ==============================策略组合器================================'''
# 通过此类或此类的子类,来规整集合其它规则。可嵌套,实现规则树,实现多策略组合。
class Group_rules(Rule):
rules = []
# 规则配置list下标描述变量。提高可读性与未来添加更多规则配置。
cs_enabled, cs_name, cs_memo, cs_class_type, cs_param = range(5)
def __init__(self, params):
Rule.__init__(self, params)
self.config = params.get('config', [])
pass
def update_params(self, context, params):
Rule.update_params(self, context, params)
self.config = params.get('config', self.config)
def initialize(self, context):
# 创建规则
self.rules = self.create_rules(self.config)
for rule in self.rules:
rule.initialize(context)
pass
def handle_data(self, context, data):
for rule in self.rules:
rule.handle_data(context, data)
if rule.to_return:
self.is_to_return = True
return
self.is_to_return = False
pass
def before_trading_start(self, context):
Rule.before_trading_start(self, context)
for rule in self.rules:
rule.before_trading_start(context)
pass
def after_trading_end(self, context):
Rule.after_code_changed(self, context)
for rule in self.rules:
rule.after_trading_end(context)
pass
def process_initialize(self, context):
Rule.process_initialize(self, context)
for rule in self.rules:
rule.process_initialize(context)
pass
def after_code_changed(self, context):
# 重整所有规则
# print self.config
self.rules = self.check_chang(context, self.rules, self.config)
# for rule in self.rules:
# rule.after_code_changed(context)
pass
# 检测新旧规则配置之间的变化。
def check_chang(self, context, rules, config):
nl = []
for c in config:
# 按顺序循环处理新规则
if not c[self.cs_enabled]: # 不使用则跳过
continue
# print c[self.cs_memo]
# 查找旧规则是否存在
find_old = None
for old_r in rules:
if old_r.__class__ == c[self.cs_class_type] and old_r.name == c[self.cs_name]:
find_old = old_r
break
if find_old is not None:
# 旧规则存在则添加到新列表中,并调用规则的更新函数,更新参数。
nl.append(find_old)
find_old.memo = c[self.cs_memo]
find_old.log = g.log_type(c[self.cs_memo])
find_old.update_params(context, c[self.cs_param])
find_old.after_code_changed(context)
else:
# 旧规则不存在,则创建并添加
new_r = self.create_rule(c[self.cs_class_type], c[self.cs_param], c[self.cs_name], c[self.cs_memo])
nl.append(new_r)
# 调用初始化时该执行的函数
new_r.initialize(context)
return nl
def on_sell_stock(self, position, order, is_normal, new_pindex=0):
for rule in self.rules:
rule.on_sell_stock(position, order, is_normal, new_pindex)
# 清仓前调用。
def before_clear_position(self, context, pindexs=[0]):
for rule in self.rules:
pindexs = rule.before_clear_position(context, pindexs)
return pindexs
def on_buy_stock(self, stock, order, pindex=0):
for rule in self.rules:
rule.on_buy_stock(stock, order, pindex)
def on_clear_position(self, context, pindexs=[0]):
for rule in self.rules:
rule.on_clear_position(context, pindexs)
def before_adjust_start(self, context, data):
for rule in self.rules:
rule.before_adjust_start(context, data)
def after_adjust_end(self, context, data):
for rule in self.rules:
rule.after_adjust_end(context, data)
# 创建一个规则执行器,并初始化一些通用事件
def create_rule(self, class_type, params, name, memo):
obj = class_type(params)
# obj.g = self.g
obj.set_g(self.g)
obj.name = name
obj.memo = memo
obj.log = g.log_type(obj.memo)
# print g.log_type,obj.memo
return obj
# 根据规则配置创建规则执行器
def create_rules(self, config):
# config里 0.是否启用,1.描述,2.规则实现类名,3.规则传递参数(dict)]
return [self.create_rule(c[self.cs_class_type], c[self.cs_param], c[self.cs_name], c[self.cs_memo]) for c in
config if c[self.cs_enabled]]
# 显示规则组合,嵌套规则组合递归显示
def show_strategy(self, level_str=''):
s = '\n' + level_str + str(self)
level_str = ' ' + level_str
for i, r in enumerate(self.rules):
if isinstance(r, Group_rules):
s += r.show_strategy('%s%d.' % (level_str, i + 1))
else:
s += '\n' + '%s%d. %s' % (level_str, i + 1, str(r))
return s
# 通过name查找obj实现
def get_obj_by_name(self, name):
if name == self.name:
return self
f = None
for rule in self.rules:
if isinstance(rule, Group_rules):
f = rule.get_obj_by_name(name)
if f != None:
return f
elif rule.name == name:
return rule
return f
def __str__(self):
return self.memo # 返回默认的描述
# 策略组合器
class Strategy_Group(Group_rules):
def initialize(self, context):
self.g = self._params.get('g_class', Global_variable)(self)
self.memo = self._params.get('memo', self.memo)
self.name = self._params.get('name', self.name)
self.log = g.log_type(self.memo)
self.g.context = context
Group_rules.initialize(self, context)
def handle_data_level(self, context, data, level):
for rule in self.rules:
if rule.level != level:
continue
rule.handle_data(context, data)
if rule.to_return and not isinstance(rule, Strategy_Group): # 这里新增控制,假如是其它策略组合器要求退出的话,不退出。
self.is_to_return = True
return
self.is_to_return = False
pass
def handle_data(self, context, data):
self.handle_data_level(context, data, Rule_Level.Prior)
self.handle_data_level(context, data, Rule_Level.Normal)
self.handle_data_level(context, data, Rule_Level.Finally)
# 重载 set_g函数,self.g不再被外部修改
def set_g(self, g):
if self.g is None:
self.g = g
'''==================================调仓条件相关规则========================================='''
# '''===========带权重的退出判断基类==========='''
class Weight_Base(Rule):
@property
def weight(self):
return self._params.get('weight', 1)
# '''-------------------------调仓时间控制器-----------------------'''
class Time_condition(Weight_Base):
def __init__(self, params):
Weight_Base.__init__(self, params)
# 配置调仓时间 times为二维数组,示例[[10,30],[14,30]] 表示 10:30和14:30分调仓
self.times = params.get('times', [])
def update_params(self, context, params):
Weight_Base.update_params(self, context, params)
self.times = params.get('times', self.times)
pass
def handle_data(self, context, data):
hour = context.current_dt.hour
minute = context.current_dt.minute
self.is_to_return = not [hour, minute] in self.times
pass
def __str__(self):
return '调仓时间控制器: [调仓时间: %s ]' % (
str(['%d:%d' % (x[0], x[1]) for x in self.times]))
# '''-------------------------调仓日计数器-----------------------'''
class Period_condition(Weight_Base):
def __init__(self, params):
Weight_Base.__init__(self, params)
# 调仓日计数器,单位:日
self.period = params.get('period', 3)
self.day_count = 0
def update_params(self, context, params):
Weight_Base.update_params(self, context, params)
self.period = params.get('period', self.period)
def handle_data(self, context, data):
self.log.info("调仓日计数 [%d]" % (self.day_count))
self.is_to_return = self.day_count % self.period != 0
self.day_count += 1
pass
def on_sell_stock(self, position, order, is_normal, pindex=0):
if not is_normal:
# 个股止损止盈时,即非正常卖股时,重置计数,原策略是这么写的
self.day_count = 0
pass
# 清仓时调用的函数
def on_clear_position(self, context, new_pindexs=[0]):
self.day_count = 0
pass
def __str__(self):
return '调仓日计数器:[调仓频率: %d日] [调仓日计数 %d]' % (
self.period, self.day_count)
class Stop_loss_win_for_single(Rule):
def __init__(self, params):
self.accumulate_loss = params.get('accumulate_loss', None)
self.accumulate_win = params.get('accumulate_win', None)
self.dynamic_stop_win = params.get('dynamic_stop_win', False)
self.dynamic_threshod = params.get('dynamic_threshod', 0.1)
self.dynamic_sense = params.get('dynamic_sense', 0.2)
self.keep_position = params.get('keep_position', False)
pass
def update_params(self, context, params):
self.accumulate_loss = params.get('accumulate_loss', self.accumulate_loss)
self.accumulate_win = params.get('accumulate_win', self.accumulate_win)
self.dynamic_stop_win = params.get('dynamic_stop_win', self.dynamic_stop_win)
self.dynamic_threshod = params.get('dynamic_threshod', self.dynamic_threshod)
self.dynamic_sense = params.get('dynamic_sense', self.dynamic_sense)
self.keep_position = params.get('keep_position', self.keep_position)
def caculate_return(self, context, price, stock):
cost = context.portfolio.positions[stock].avg_cost
if cost != 0:
return (price-cost)/cost
else:
return None
# 计算股票累计收益率(从建仓至今)
def security_accumulate_return(self, context, data, stock):
current_price = data[stock].price
return self.caculate_return(context, current_price, stock)
def get_dynamic_win_stop(self, context, data, stock):
position = context.portfolio.positions[stock];
delta = context.current_dt - position.init_time
# 得到过去这些时间内每分钟最高的价格
hist1 = get_price(stock, start_date=position.init_time, end_date=context.current_dt, frequency='1m', fields=["avg"], skip_paused=True)
high_price = hist1['avg'].max();
high_margin_return = self.caculate_return(context, high_price, stock)
if high_margin_return != None and self.dynamic_threshod < high_margin_return:
dynamic_stop_margin = pow(high_margin_return, self.dynamic_sense) * high_margin_return
return dynamic_stop_margin
else:
return 0
def handle_data(self, context, data):
for stock in context.portfolio.positions.keys():
accumulate_return = self.security_accumulate_return(context,data,stock);
# 动态止盈
if self.dynamic_stop_win:
dynamic_stop_margin = self.get_dynamic_win_stop(context, data, stock)
if accumulate_return > 0 and accumulate_return < dynamic_stop_margin:
position = context.portfolio.long_positions[stock]
# 平仓,并且 is_normal=False, 需要重新调仓
self.log.warn('{0} 该股累计涨幅超过动态止盈点{1}%, 目前为{2}%,执行平仓,并且重新开始调仓'.format(show_stock(position.security), dynamic_stop_margin*100, accumulate_return*100))
self.g.close_position(self, position, self.keep_position)
# 静态止损止盈
if accumulate_return != None \
and ( (self.accumulate_loss !=None and accumulate_return < self.accumulate_loss) \
or (self.dynamic_stop_win == False and self.accumulate_win !=None and accumulate_return > self.accumulate_win) ):
position = context.portfolio.long_positions[stock]
# 平仓,并且 is_normal=False, 需要重新调仓
self.log.warn('{0} 该股累计{1}超过{2}%,执行平仓,并且重新开始调仓'.format(show_stock(position.security), "涨幅" if accumulate_return > 0 else "跌幅", (self.accumulate_win if accumulate_return > 0 else self.accumulate_loss)*100))
self.g.close_position(self, position, self.keep_position)
def __str__(self):
s = '个股止损器:'
if self.dynamic_stop_win:
s += '[动态止盈方案:启动阈值:{0}%, 灵敏度:{1}, 保持持仓:{2}]'.format(self.dynamic_threshod*100, self.dynamic_sense, self.keep_position)
elif self.accumulate_win != None:
s += '[参数: 止盈点为{0}%]'.format(self.accumulate_win * 100)
if self.accumulate_loss != None:
s += '[参数: 止损点为{0}%]'.format(self.accumulate_loss * 100)
return s
class Stop_loss_by_price(Rule):
def __init__(self, params):
self.index = params.get('index', '000001.XSHG')
self.day_count = params.get('day_count', 160)
self.multiple = params.get('multiple', 2.2)
self.is_day_stop_loss_by_price = False
def update_params(self, context, params):
self.index = params.get('index', self.index)
self.day_count = params.get('day_count', self.day_count)
self.multiple = params.get('multiple', self.multiple)
def handle_data(self, context, data):
# 大盘指数前130日内最高价超过最低价2倍,则清仓止损
# 基于历史数据判定,因此若状态满足,则当天都不会变化
# 增加此止损,回撤降低,收益降低
if not self.is_day_stop_loss_by_price:
h = attribute_history(self.index, self.day_count, unit='1d', fields=('close', 'high', 'low'),
skip_paused=True)
low_price = h.low.min()
high_price = h.high.max()
if high_price > self.multiple * low_price and h['close'][-1] < h['close'][-4] * 1 and h['close'][
-1] > h['close'][-100]:
# 当日第一次输出日志
self.log.info("==> 大盘止损,%s指数前%s日内最高价超过最低价%s倍, 最高价: %f, 最低价: %f" % (
get_security_info(self.index).display_name, self.day_count, self.multiple, high_price, low_price))
self.is_day_stop_loss_by_price = True
if self.is_day_stop_loss_by_price:
self.g.clear_position(self, context, self.g.op_pindexs)
self.is_to_return = self.is_day_stop_loss_by_price
def before_trading_start(self, context):
self.is_day_stop_loss_by_price = False
pass
def __str__(self):
return '大盘高低价比例止损器:[指数: %s] [参数: %s日内最高最低价: %s倍] [当前状态: %s]' % (
self.index, self.day_count, self.multiple, self.is_day_stop_loss_by_price)
# '''-------------多指数N日涨幅止损------------'''
class Mul_index_stop_loss(Rule):
def __init__(self, params):
Rule.__init__(self, params)
self._indexs = params.get('indexs', [])
self._min_rate = params.get('min_rate', 0.01)
self._n = params.get('n', 20)
def update_params(self, context, params):
Rule.__init__(self, params)
self._indexs = params.get('indexs', [])
self._min_rate = params.get('min_rate', 0.01)
self._n = params.get('n', 20)
def handle_data(self, context, data):
self.is_to_return = False
r = []
for index in self._indexs:
gr_index = get_growth_rate(index, self._n)
self.log.info('%s %d日涨幅 %.2f%%' % (show_stock(index), self._n, gr_index * 100))
r.append(gr_index > self._min_rate)
if sum(r) == 0:
self.log.warn('不符合持仓条件,清仓')
self.g.clear_position(self, context, self.g.op_pindexs)
self.is_to_return = True
def after_trading_end(self, context):
Rule.after_trading_end(self, context)
for index in self._indexs:
gr_index = get_growth_rate(index, self._n - 1)
self.log.info('%s %d日涨幅 %.2f%% ' % (show_stock(index), self._n - 1, gr_index * 100))
def __str__(self):
return '多指数20日涨幅损器[指数:%s] [涨幅:%.2f%%]' % (str(self._indexs), self._min_rate * 100)
'''=========================选股规则相关==================================='''
# '''==============================选股 query过滤器基类=============================='''
class Filter_query(Rule):
def filter(self, context, data, q):
return None
# '''==============================选股 stock_list过滤器基类=============================='''
class Filter_stock_list(Rule):
def filter(self, context, data, stock_list):
return None
# '''-----------------选股组合器-----------------------'''
class Pick_stocks(Group_rules):
def __init__(self, params):
Group_rules.__init__(self, params)
self.has_run = False
def handle_data(self, context, data):
try:
to_run_one = self._params.get('day_only_run_one', False)
except Exception as e:
log.error(str(e))
to_run_one = False
if to_run_one and self.has_run:
self.log.info('设置一天只选一次,跳过选股。')
return
# 执行 filter query
q = None
for rule in self.rules:
if isinstance(rule, Filter_query):
q = rule.filter(context, data, q)
print "执行了: %s" % rule
stock_list = list(get_fundamentals(q)['code']) if q != None else white_list()
stock_list = intersect(stock_list, white_list())
print "选股得到%s只股票" % len(stock_list)
# 执行 Filter_stock_list
for rule in self.rules:
if isinstance(rule, Filter_stock_list):
stock_list = rule.filter(context, data, stock_list)
self.g.buy_stocks = stock_list
if len(self.g.buy_stocks) > 5:
tl = self.g.buy_stocks[0:5]
else:
tl = self.g.buy_stocks[:]
self.log.info('选股:\n' + join_list(["[%s]" % (show_stock(x)) for x in tl], ' ', 10))
self.has_run = True
def before_trading_start(self, context):
self.has_run = False
def __str__(self):
return self.memo
# 选取财务数据的参数
# 使用示例 FD_Factor('valuation.market_cap',None,100) #先取市值小于100亿的股票
# 注:传入类型为 'valuation.market_cap'字符串而非 valuation.market_cap 是因 valuation.market_cap等存在序列化问题!!
# 具体传入field 参考 https://www.joinquant.com/data/dict/fundamentals
class FD_Factor(object):
def __init__(self, factor, **kwargs):
self.factor = factor
self.min = kwargs.get('min', None)
self.max = kwargs.get('max', None)
# 过滤财务数据参数
# 使用示例 FD_Filter('valuation.pe_ratio',sort=SortType.desc,percent=80) 选取市盈率最大的80%
class FD_Filter(object):
def __init__(self, factor, **kwargs):
self.factor = factor
self.sort = kwargs.get('sort', SortType.asc)
self.percent = kwargs.get('percent', None)
# 根据多字段财务数据一次选股,返回一个Query
class Pick_financial_data(Filter_query):
def filter(self, context, data, q):
if q is None:
q = query(valuation,balance,cash_flow,income,indicator)
# q = query(valuation)
for fd_param in self._params.get('factors', []):
if not isinstance(fd_param, FD_Factor):
continue
if fd_param.min is None and fd_param.max is None:
continue
factor = eval(fd_param.factor)
if fd_param.min is not None:
q = q.filter(
factor > fd_param.min
)
if fd_param.max is not None:
q = q.filter(
factor < fd_param.max
)
order_by = self._params.get('order_by', None)
if order_by is not None: