약어로 쓰는 것들
import backtrader as bt
import backtrader.indicators as btind
import backtrader.feeds as btfeeds
데이터 feed는 다음처럼 설정한다.
data=btfeeds.MyFeed(~)
cerebro.adddata(data)
self.data 는 self.datas[0]과 같다. 또한 명시되지 않으면 디폴트로 사용되는 데이터이기도 함.
self.dataX 는 self.datas[X]와 같다.
data에 대한 중요한 점은, data feed만 data가 아니라는 것이다.
sma1을 만들고 나면, 이 sma1을 sma2의 데이터로 집어 넣을 수 있다. 그리고 기타 등등 이것저것을 데이터로 만들어서 전달할 수 있다. 즉, 데이터 피드와, 그에 대해 처리한 것, 그리고 그에 대한 파생 데이터 모두를 data feed로 사용할 수 있다.
파라미터는 튜플의 튜플로 제공한다.
전략 안에 params = (("period",20),)처럼 넣는다.
params= dict(period=20)처럼 dictionary를 이용할 수 도 있다.
내용물에 대한 접근은 self.params.period처럼 사용한다.
Lines
플랫폼에 있는 거의 모든 object은 lines enabled object임. 사용자 입장에서, 한 개 이상의 line series를 들고 있을 수 있다.(line series 는 value의 배열. value들이 하나의 선 생성.)
line의 좋은 예는 주가 종가 데이터. '원래' 이 모듈을 이용하는 방법은 line에 대한 접근.
class MyStrategy(bt.Strategy):
params = dict(period=20)
def __init__(self):
self.movav = btind.SimpleMovingAverage(self.data, period=self.p.period)
def next(self):
if self.movav.lines.sma[0] > self.data.lines.close[0]:
print('Simple Moving Average is greater than the closing price')
보면, movav, data의 lines.~로 접근한다.
data_name 은 data.lines.name의 줄임이다. 편리하게 사용 가능.
self.data.close, self.movav.sma도 가능은하다. 하지만 line을 access하고 있는지 등이 명확하지 않음.
indicator를 만들고 있을 때, indicator의 line또한 정의해야 한다. param은 튜플 말고도 dictionary로 값을 줄 수 있었지만, 이번에는 tuple로만 가능하다.
class SimpleMovingAverage(Indicator):
lines=('sma',) 처럼 line을 만들어 준다. 튜플의 쉼표는 파이썬의 몇 안되는 불필요한 syntax다.
이렇게 되면, 여기에서 self.lines[0]는 self.lines.sma를 가리킨다.
물론 여기에 대해서도 self.line는 self.lines[0] 을 가리키는등의 짧은 버전이 있다.
data feed안에서 line에 접근할 때는 lines를 생략 가능.
self.data.close[0]처럼 사용 가능하다. (원래는 self.data.lines.close[0]>30)
이건 Indicator에 대해서는 사용할 수 없다. Indicator안의 close가 처리 된 뒤 close라는 이름의 line에 전달되었는지 알 수가 없기 때문이다.
datafeed는 연산을 하지 않으므로 이것이 가능하다.
Lines 연산이 중요한 이유는 여기에 Python len함수를 쓰게 되기 때문. Lines들은 실행 동안 역동적으로 커지고, len을 이용해서 실시간으로 길이를 추적할 것.
Data Feed, 전략, Indicator에 대해 이걸 적용.
Data Feed가 preloaded되었을 때, 추가로 buflen이라는 method가 있음.
Data Feed에서 실제로 가능한 바의 수를 리턴
다시 정리하면 len은 처리가 된 바의 수를, buflen은 Data Feed에 대해 load된 바의 총 수를 돌려준다. 둘이 같다는 것은 preload된 게 없거나, preload된 모든 것을 처리한 경우.
Param, 그리고 line은 inherit이 된다. 제대로된 파이썬 문법과는 좀 다르지만, 파이썬의 모든 inheritance와 이더지도록 생성.
param, line은 모두 multiple inheritance가 지원된다. base class에서 inherit도 된다.
여러 base에서 동일한 param이 있다면, 디폴트로 가장 마지막 class에서의 것을 사용. child에서 같은이름의 param을 재정의시 새 값이 base것을 덮어씀.
line의 경우는 한 버전의 line만 남을 것.
index는 가장 최근 데이터가 언제나 0. 그외에는 -1로 이전 데이터들 접근. 전략은 값을 get만 하고, indicator는 set도 한다.
예시:
def next(self):
self.line[0] = math.fsum(self.data.get(0,size=self.p.period))/self.p.period
get을 이용해서 데이터를 받고, set으로 line에 set하는 예시.
인덱싱을 이렇게 하기 때문에, 기존 파이썬의 슬라이싱은 작동 안한다. 만약에 해야된다면 - 인덱싱만 사용해야 할 것.
당연히, 자체 모듀이 있다.
myslice=self.my_sma.get(ago=0,size=10)처럼, ago로 출발점을 지정해주고 size로 개수를 지정해 준다. 그리고 이 배열을 기존 파이썬 순서로 값을 돌려준다.
delay index이 가능하다고 하는데, 약간 이해가 잘 되지는 않는다.
self.data.close(-1) > self.sma 처럼 하면 -1 만큼 딜레이가 된다.
class MyStrategy(bt.Strategy):
params = dict(period=20)
def __init__(self):
self.movav = btind.SimpleMovingAverage(self.data, period=self.p.period)
self.cmpval = self.data.close(-1) > self.sma
def next(self):
if self.cmpval[0]:
print('Previous close is higher than the moving average')
()안에 delay날짜수가 들어간다.
self.cmpval = self.data.close(-1) > self.sma 이 조건은 새로운 line을 만들어 낸다.
()를 이용하면 line을 묶을 수 있다. sma0 = btind.SMA(self.data0, period=15) 전에 봤던 예시와 같던 방식.
operator에서 기억할 점은, 파이썬이 일부는 덮어쓰기를 허용하지 않아 새로 정의함
And, Or, If, Any, All, Cmp, Max, Min, Sum 등
class MyStrategy(bt.Strategy):
def __init__(self):
sma1 = btind.SMA(self.data.close, period=15)
high_or_low = bt.If(sma1 > self.data.close, self.data.low, self.data.high)
sma2 = btind.SMA(high_or_low, period=15)
이런식이다.
line iterator
iterator라는 이름을 쓰지만, 파이썬의 iterator와는 다르다.
line iterator는 하위 line iterator가 iterate하도록 한 뒤, 스스로 정의된 line을 iterate한다.
iterate안에서는 next 함수가 호출된다. 매 iteration마다 호출된다. line iterator의 minimum period가 지나고 난 뒤 호출된다.
prenext는 line iteator가 만날때 minimum period 전에 호출된다.
nextstart는 minimum period에 도달시 딱 한번 호출되고 디폴트는 next를 호출하는 것.
예를들어, SMA를 만들고 period가 25라면, prenext가 24번, nextstart가 1번, next가 그다음에 data feed가 남은 만큼 실행되는 것.
조금 더 나아가, 25길이 짜리 sma1과, 이를 이용한 20짜리 sma2가 있다고 가정하자.
우선, sma1은 동일하다. sma2는 이렇게 호출된다.
prenext가 우선 43번(sma1을 위한 25번, sma1을 쌓을 18번 +1)
nextstart 1번, next n번
모델이 작동하는 최소 line의 수는 3개
1. Data feed
2. 전략
3. Cerebro
Data feed에는 다음이 들어갈 수 잇따.
1. 다양한 CSV 포맷 및 일반적 CSV 리더
2. 야후 온라인 fetcher
3. Pandas 데이터 프레임 그리고 blaze object에 대한 지원
4. Interactive Broker, Visual Charty Oanda
데이터에 대해 timeframe그리고 compression에 대한 가정이 없다.
import backtrader as bt
import backtrader.feeds as btfeeds
...
datapath = 'path/to/your/yahoo/data.csv'
data = btfeeds.YahooFinanceCSVData(
dataname=datapath,
reversed=True
fromdate=datetime.datetime(2014, 1, 1),
todate=datetime.datetime(2014, 12, 31)
timeframe=bt.TimeFrame.Days,
compression=1,
name='Yahoo'
)
야후 데이터에 대한 예다. reversed까지는 기본 옵션. 시작날짜와 끝 날짜를 정했고, time frame이 일이라는 정보를 제공했다. name은 plot을 위함.
Strategy class는 이 모듈의 가장 핵심. 이 모듈을 쓰고자 한다면, 모든 것은 이 클래스 안에서 이루어진다. 최소한 2개의 함수를 구현해야 한다.
가장 기초 코드다.
class MyStrategy(bt.Strategy):
def __init__(self):
self.sma = btind.SimpleMovingAverage(self.data, period=20)
def next(self):
if self.sma > self.data.close:
self.buy()
elif self.sma < self.data.close:
self.sell()
여기에 start, stop, notify_order등의 method를 overwrite해줄 수 있다.
start는 백테스팅의 시작, stop은 백테스팅의 끝에서 호출되는 method. notify_order는 주문이 들어올 때(죽 buy/sell 요청이 들어오거나 브로커에 의해 주문의 status가 변했을 때.)
추가로 지원되는 함수들이다.
buy/sell/close. 물론 order를 만들어서 전달하는 것도 가능하지만, 이 함수들로 좀 더 쉽게 가능하다.
getposition으로 현재 market 포지션을 얻을 수 있다.
setsizer, getsizer로 stake를 바꿀 수 잇다.
strategy에는 여러 파라미터를 사용할 수 있고, 파이썬의 kwargs를 받는 것과 비슷한 방법으로 받게 된다.
Cerebro가 이제 이를 전부 한 곳에 모은다.
cerebro=bt.Cerebro()
cerebro.adddata(data)
cerebro.addstrategy(MyStrategy,period=25)
cerebro.run()
cerebro.plot()
bt.Cerebro(runonce=True, preload=True)로 옵션을 추가할 수 있다.
plot안에도 여러 옵션이 가능하다.
numfigs로 fig의 수를 정하고
plotter=None을 고치면 디폴트 대신 다른 plotter를 사용한다.
optimization을 할 때는
cerebro.optstrategy(MyStrategy, period=xrange(10,20), factor=3.5)
레인지에 있는건 iterable, =으로 부여하는 건 고정값
Cerebro는 1. 모든 input, 전략, 시장 관측, 분석 등등을 모으고 2. backtesting실행, data feed 3. 결과 반환 4. plot 을 모두 실행한다.
Datafeed에 관한 함수들이 있다.
data관한 함수들
cerebro.adddata(data) : 데이터 추가
cerebro.resampledata(data, timeframe=bt.TimeFrame.Days)
cerebro.replaydatadata(data, timeframe=bt.TimeFrame.Days) : 데이터 샘플링
그 뿐 아니라, 다소 특이한 개념들도 있다.
.addwriter, .addanalyzer, .addobserver
브로커의 경우 디폴트 브로커가 있지만, 덮어쓸 수 있다.
broker=MyBroker()
cerebro.broker=broker처럼 덮어쓰기 가능.
data feed/broker/store provider가 notification을 보내면, Cerebro.notify_store method를 통해 수신 가능.
addnotifycallback(callback)를 통해 얻을 수 있따.
callback(msg, *args, **kwargs)
Cerebro에는 셋의 observer가 초기화 됨
1. Broker : cash 및 포트포리오 가치 추적
2. Trade observer
3. Buy/sell observer
result=cerebro.run(**kwargs)로 얻은 결과 형태는 다양함.
list 하나, 혹은 list of list일 것.
주의점:
충분한 램이 없다면 제대로 안 돌아갈 가능성이 있다.
dynamic memory scheme이 지원되어야 함.