For code/output blocks: Use ``` (aka backtick or grave accent) in a single line before and after the block. See: http://commonmark.org/help/

Monte Carlos and CAR25 Simulation by Backtrader (Credit to Howard Bandy)



  • Credit to CAR25 by Howard Bandy. I think Howard provides a good way to measure the required fraction (sizing) for any stock simulation.
    I have created the monte carlos as Howard Bandy mentioned in his book. Hope it helps BT-ers as a quick start to have the Monte Carlos simulation. :)

    Figures as follow:
    I have randomly distributed the trade and set the data started as 1-1-1990. It will need some entry and exit price in your previous trade record as reference point. It will adjust the monte['fraction'] according to the simulated DD90 value.

    alt text
    alt text
    alt text

    Code as follow:

    def runOneMonte(_,row,consoFeeds,monte,params):
    	stockName,exchange,indName=row[:3]
    	cerebro = bt.Cerebro()
    	cerebro.broker.setcash(100000)
    	simParams={'stockName':stockName,'exchange':exchange,'indicator':indName}
    	#analyzer
    	cerebro.addanalyzer(continueITDDAnalyzer,_name='contITDD')
    	#addCommission
    	IB = params['commission'](commission=0.0)
    	cerebro.broker.addcommissioninfo(IB)
    	#SetBroker
    	cerebro.broker.set_checksubmit(False)
    
    	#Shuffle and reindex Data
    	#random.seed(1)
    	datas=list(itertools.chain.from_iterable(itertools.repeat(consoFeeds,max(int(monte['tradeTime']/len(consoFeeds)),1))))
    	shuffledDatas=random.sample(datas, len(datas))
    	barlen = sum(len(f)-1 for f in shuffledDatas)
    	consoFeed=pd.concat(shuffledDatas)
    	mcIndex=pd.date_range(start=datetime.datetime.strptime('1990-01-01','%Y-%m-%d'),periods=len(consoFeed),freq='D')
    	consoFeed.set_index(mcIndex,drop=True,inplace=True)
    	_feed=bt.feeds.PandasData(dataname=consoFeed[['open','high','low','close','volume']])
    	cerebro.adddata(_feed,name=stockName)
    	tradeFeed = consoFeed[['BuyPrice','SellPrice']]
    	cerebro.addstrategy(sizingStra, monte['fraction'],tradeFeed)
    	cerebro.strats[0][0][0].contITDDSet={'ITDD':0,'ITDDLength':0,'initV':100000,'peak':float('-inf'),'ddlen':-1,'dd':0}
    	strategies=cerebro.run()
    	contITDDSet=strategies[0].analyzers.contITDD.get_analysis()
    	contITDDSet['CARReturn']=(cerebro.broker.getvalue()/contITDDSet['initV'] - 1)*100*252/barlen
    	return contITDDSet
    
    def calculateDD90(row,params,monte,consoFeeds,q,loopRound=6):
    	stockName=row.Index
    	masterLogger=logging.getLogger('Master')
    	loopTime=0
    	while loopTime<loopRound:
    		_prevFraction=0
    		_loopCount=0
    		if params['numMulti'] >1:
    			print('{}-{}: Start Pooling with Monte fraction:{}'.format(stockName,row.indicator, monte['fraction']))
    			pool = multiprocessing.Pool(params['numMulti'], worker_init, [q])
    			#Run 1000 monte carlos
    			startTime=datetime.datetime.now()
    			with pool as p:
    				simResSet=pd.DataFrame.from_dict(pool.starmap(runOneMonte,zip(range(0,monte['NumberSim']),itertools.repeat(list(row)),itertools.repeat(consoFeeds),itertools.repeat(monte),itertools.repeat(params))))
    			pool.close()
    			pool.join()
    			timeRequired=datetime.datetime.now()-startTime
    			print('{}-{}: Loop Time required for runOneMonte:{}'.format(stockName,row.indicator,timeRequired))
    		else:
    			print('{}:Start Single Thread Simulation'.format(stockName))
    			simResSet = pd.DataFrame([runOneMonte(i,list(row), consoFeeds,monte,params) for i in range(0,monte['NumberSim'])])
    		DD90 = np.quantile(simResSet['ITDD'] , 0.9, interpolation='nearest')
    		DD90 = simResSet[simResSet['ITDD']==DD90].to_dict(orient='records')[0]
    		print('{}-{}:Test Result: Fraction ={}, DD90={}, CAR25={}'.format(stockName,row.indicator,monte['fraction']*100,DD90['ITDD'],DD90['CARReturn']))
    		if abs(monte['drawdownLimit'] - DD90['ITDD']) < monte['accuracyTolerance'] or abs(monte['fraction'] - _prevFraction) < 0.01 or _loopCount >5:
    			return DD90
    		else:
    			_prevFraction = monte['fraction']
    			_loopCount+=1
    			monte['fraction']=monte['fraction']*monte['drawdownLimit']/(DD90['ITDD'])
    			if monte['fraction']>1 :
    				print('Overwhelming sizing Occurred')
    				return DD90
    		loopTime+=1
    	return DD90
    
    def setMonte(row,params,monte):
    	#Read Order time and Trade Price and prepare the feeds, dont follow below script as it is minimized
    	consoFeeds=[]
    	for record in records:
    		consoFeeds.append(consoFeed)
    
    	#Multi-processing
    	_stockStartTime=datetime.datetime.now()
    	DD90=calculateDD90(row,params,monte,consoFeeds,q)
    	timeRequired=datetime.datetime.now()-_stockStartTime
    	print('{}: Total Loop Time required:{}\n================================='.format(row.Index,timeRequired))
    	DD90.update({'fraction':monte['fraction'],'stockName':row.Index,'exchange':row.exchange,'indicator':row.indicator})
    	return DD90
    

Log in to reply
 

});