Skip to content

Commit 118e3f0

Browse files
committed
fix some backtesting bugs
1 parent 5c795a5 commit 118e3f0

24 files changed

Lines changed: 807 additions & 26608 deletions

README.rst

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,16 @@ http://www.quantfans.com/doc/quantdigger/
3131

3232
依赖库
3333
------
34-
* Python (2.x, **暂不支持3.x**)
35-
* pandas
36-
* python-dateutil
3734
* matplotlib
3835
* numpy
39-
* TA-Lib
4036
* logbook
37+
* pandas
38+
* progressbar
39+
* python-dateutil
4140
* pyqt (可选)
41+
* Python (2.x, **暂不支持3.x**)
4242
* tushare_ (可选, 一个非常强大的股票信息抓取工具)
43+
* TA-Lib
4344

4445

4546
策略组合DEMO

demo/.DS_Store

-10 KB
Binary file not shown.

demo/data/.DS_Store

-6 KB
Binary file not shown.

demo/search2.py

Lines changed: 0 additions & 46 deletions
This file was deleted.

quantdigger/datasource/data.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ def get_bars(self, strpcon , dt_start="1980-1-1", dt_end="2100-1-1"):
196196
Returns:
197197
SourceWrapper.
198198
"""
199+
## @TODO 不存在数据时可能返回一个空的list, 虽然不影响程序执行。
199200
if isinstance(dt_start, str):
200201
dt_start = datetime.strptime(dt_start, "%Y-%m-%d")
201202
if isinstance(dt_end, str):

quantdigger/datastruct.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -389,15 +389,30 @@ def trading_interval(cls, contract):
389389

390390
@classmethod
391391
def long_margin_ratio(cls, strcontract):
392-
return cls.info.ix[strcontract.upper(), 'long_margin_ratio']
392+
try:
393+
return cls.info.ix[strcontract.upper(), 'long_margin_ratio']
394+
except KeyError:
395+
print "Can't not find contract: %s" % strcontract
396+
return 1
397+
#assert(False)
393398

394399
@classmethod
395400
def short_margin_ratio(cls, strcontract):
396-
return cls.info.ix[strcontract.upper(), 'short_margin_ratio']
401+
try:
402+
return cls.info.ix[strcontract.upper(), 'short_margin_ratio']
403+
except KeyError:
404+
print "Can't not find contract: %s" % strcontract
405+
return 1
406+
#assert(False)
397407

398408
@classmethod
399409
def volume_multiple(cls, strcontract):
400-
return cls.info.ix[strcontract.upper(), 'volume_multiple']
410+
try:
411+
return cls.info.ix[strcontract.upper(), 'volume_multiple']
412+
except KeyError:
413+
print "Can't not find contract: %s" % strcontract
414+
return 1
415+
#assert(False)
401416

402417

403418
class Period(object):
@@ -510,6 +525,7 @@ def __init__(self, trans):
510525
else:
511526
self._margin_ratio = Contract.short_margin_ratio(strcon)
512527
self._volume_multiple = Contract.volume_multiple(strcon)
528+
self.symbol = str(trans.contract)
513529

514530
def profit(self, new_price):
515531
""" 根据最新价格计算持仓盈亏。
@@ -539,10 +555,9 @@ def position_margin(self, new_price):
539555
new_price (float): 最新价格。
540556
541557
Returns:
542-
float. 保证金占用
558+
float. 证金占用/市值
543559
"""
544-
price = self.cost if self.contract.is_stock else new_price
545-
return price * self.quantity * self._margin_ratio * self._volume_multiple
560+
return new_price * self.quantity * self._margin_ratio * self._volume_multiple
546561

547562
def __str__(self):
548563
rst = " contract: %s\n direction: %s\n cost: %f\n quantity: %d\n closeable: %d\n" % \

quantdigger/engine/blotter.py

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ def __init__(self, blotters, dcontexts, strpcon, i):
2828
for key, value in dcontexts.iteritems():
2929
self._dcontexts[key] = value
3030

31-
def name(self, j):
32-
return self._blts[j].name
31+
def name(self, j=None):
32+
if j != None:
33+
return self._blts[j].name
34+
return self._blts[0].name
3335

3436
def transactions(self, j=None):
3537
""" 第j个策略的所有成交明细, 默认返回组合的成交明细。
@@ -147,6 +149,7 @@ def technicals(self, j=None, strpcon=None):
147149
return rst
148150

149151
def data(self, strpcon=None):
152+
## @TODO execute_unit._parse_pcontracts()
150153
""" 周期合约数据, 只有向量运行才有意义。
151154
152155
Args:
@@ -155,10 +158,10 @@ def data(self, strpcon=None):
155158
Returns:
156159
pd.DataFrame. 数据
157160
"""
158-
pcon = self._main_pcontract
159-
if strpcon:
160-
pcon = PContract.from_string(strpcon)
161-
return self._dcontexts[pcon].raw_data
161+
if not strpcon:
162+
strpcon = self._main_pcontract
163+
strpcon = strpcon.upper()
164+
return self._dcontexts[strpcon].raw_data
162165

163166
def _update_positions(self, current_positions, deal_positions, trans):
164167
""" 根据交易明细计算开平仓对。 """
@@ -270,6 +273,7 @@ def __init__(self, name, events_pool, settings={ }):
270273
def all_holdings(self):
271274
""" 账号历史情况,最后一根k线处平所有仓位。"""
272275
if self.positions:
276+
#assert False
273277
self._force_close()
274278
return self._all_holdings
275279

@@ -302,8 +306,9 @@ def update_datetime(self, dt):
302306
pos.today = 0
303307
self._datetime = dt
304308

305-
def update_status(self, dt, at_baropen=True):
309+
def update_status(self, dt, at_baropen, append):
306310
""" 更新历史持仓,当前权益。"""
311+
## @TODO open_orders 和 postion_margin分开,valid_order调用前再统计?
307312
# 更新资金历史。
308313
dh = { }
309314
dh['datetime'] = dt
@@ -329,6 +334,11 @@ def update_status(self, dt, at_baropen=True):
329334
# 当前权益 = 初始资金 + 累积平仓盈亏 + 当前持仓盈亏 - 历史佣金总额
330335
dh['equity'] = self._capital + self.holding['history_profit'] + pos_profit - \
331336
self.holding['commission']
337+
## @TODO
338+
# 对于股票,Bar的开盘和收盘点资金验证是一致的。
339+
# 如果期货不一致,成交撮合时候也无法确定确切的时间点,
340+
# ,就无法确定精确的可用资金,可能因此导致cash<0, 就得加
341+
# 强平功能,使交易继续下去。
332342
dh['cash'] = dh['equity'] - margin - order_margin
333343
if dh['cash'] < 0:
334344
for key in self.positions.iterkeys():
@@ -338,24 +348,33 @@ def update_status(self, dt, at_baropen=True):
338348
self.holding['cash'] = dh['cash']
339349
self.holding['equity'] = dh['equity']
340350
self.holding['position_profit'] = pos_profit
341-
if at_baropen:
351+
if append:
342352
self._all_holdings.append(dh)
343353
else:
344354
self._all_holdings[-1] = dh
345355

346356
def update_signal(self, event):
347-
""" 处理策略函数产生的下单事件。 """
357+
""" 处理策略函数产生的下单事件。
358+
359+
可能产生一系列order事件,在bar的开盘时间交易。
360+
"""
348361
assert event.type == Event.SIGNAL
349362
new_orders = []
350363
for order in event.orders:
351-
if self._valid_order(order):
364+
errmsg = self._valid_order(order)
365+
if errmsg == '':
352366
order.datetime = self._datetime
353-
self.api.order(copy.deepcopy(order))
354367
new_orders.append(order)
368+
if order.side == TradeSide.KAI:
369+
self.holding['cash'] -= order.order_margin(self._bars[order.contract].open)
355370
else:
371+
logger.warn(errmsg)
372+
#print len(event.orders), len(new_orders)
356373
continue
357374
self.open_orders.update(new_orders) # 改变对象的值,不改变对象地址。
358375
self._all_orders.extend(new_orders)
376+
for order in new_orders:
377+
self.api.order(copy.deepcopy(order))
359378
for order in new_orders:
360379
if order.side == TradeSide.PING:
361380
pos = self.positions[PositionKey(order.contract, order.direction)]
@@ -412,6 +431,7 @@ def _update_holding(self, trans):
412431
flag = 1 if trans.direction == Direction.LONG else -1
413432
profit = (trans.price-self.positions[poskey].cost) * trans.quantity *\
414433
flag * trans.volume_multiple
434+
#print profit, trans.price, trans.quantity
415435
#if self.name == 'A1': # 平仓调试
416436
#print "***********"
417437
#print self._datetime, profit
@@ -421,28 +441,27 @@ def _update_holding(self, trans):
421441
def _valid_order(self, order):
422442
""" 判断订单是否合法。 """
423443
if order.quantity<=0:
424-
raise TradingError(err="交易数量不能小于0")
444+
return "交易数量要大于0"
425445
# 撤单
426446
if order.side == TradeSide.CANCEL:
427447
if order not in self.open_orders:
428-
raise TradingError(err='撤销失败: 不存在该订单!')
448+
return '撤销失败: 不存在该订单!'
429449
if order.side == TradeSide.PING:
430450
try:
431451
poskey = PositionKey(order.contract, order.direction)
432452
pos = self.positions[poskey]
433453
if pos.closable < order.quantity:
434-
raise TradingError(err='可平仓位不足')
454+
return '可平仓位不足'
435455
except KeyError:
436456
# 没有持有该合约
437457
#logger.warn("不存在合约[%s]" % order.contract)
438-
raise TradingError(err="不存在合约[%s]" % order.contract)
458+
return "不存在合约[%s]" % order.contract
439459
elif order.side == TradeSide.KAI:
440-
new_price = self._bars[order.contract].close
460+
new_price = self._bars[order.contract].open
441461
if self.holding['cash'] < order.order_margin(new_price):
442-
raise TradingError(err='没有足够的资金开仓')
443-
else:
444-
self.holding['cash'] -= order.order_margin(new_price)
445-
return True
462+
#print self.holding['cash'], new_price * order.quantity
463+
return '没有足够的资金开仓'
464+
return ''
446465

447466
def _force_close(self):
448467
""" 在回测的最后一根k线以close价格强平持仓位。 """
@@ -463,7 +482,7 @@ def _force_close(self):
463482
self._update_holding(trans)
464483
self._update_positions(trans)
465484
if force_trans:
466-
self.update_status(trans.datetime, False)
485+
self.update_status(trans.datetime, False, False)
467486
self.positions = { }
468487
return
469488

0 commit comments

Comments
 (0)