커뮤니티
내가 만든 전략들과 지식을 공유하고 토론합니다.

폭락장 현금보유비중 조절 - 켈리의 법칙 적용

소포클레스 2017.09.11 08:48 조회수  2514 추천 4

안녕하세요. 아래 3% 손절 로직은 연속적이지 않음으로 손절이 불완전 합니다. 다시말해 두달 연속 3%하락할때는 손절이 발생하지 않습니다. 주식 +현금의 총평가액을 기준으로 비교하기 때문입니다. 이미 지난달에 100%손절 했기 때문에 시장이 한번 더 하락하더라도 총평가액의 변동이 없어서 그렇습니다. 이런 단점을 제거하기 위해 Kospi 지수로 비교하는 로직으로 바꾸었습니다.


지수가 폭락할 때만 현금보유비중의 조정이 발생합니다. 예를들어 한달에 9%혹은 세달에 13% 정도 빠져야 손절이 발생합니다. 이정도면 폭락이라 할만하죠.


아래 주석사이의 STOCK_WEIGHT 부분을  0으로 하면 100% 손절이 발생하고 0.5로 하면 현금보유비중이 50%가 됩니다. 저는 0.5를 추천합니다. 왜냐하면 첫달에 10%빠져도 다음달에 추세가 이어질지 아닐지 누구도 예측할수 없기 때문입니다.


주사위를 10번 던져서 모두 1이 나와도 다음번에 또 1이 나올 확률은 1/6입니다. 주식 시장도 마찬가지 입니다. 한달에 10% 떨어져도 다음달에 반등할 확률은 50% 입니다. 그래서 하락장이라 하더라도 켈리의 법칙인 0.5(50%)를 추천합니다. 하지만 큰 금액을 투자하신다거나 간이 작으신 분들은 100% 손절하세요. 특히 MDD에 민감하신 분들요^^


현금보유비중을 0%으로 하면 수익이 줄어들 수는 있으나 마음은 편합니다. STOCK_WEIGHT 부분을 50%와 0%로 바꿔가면서 비교해 보세요. 아마도 테스트 기간을 2000년 ~ 2017년 으로 하신다면 현금보유비중 0% 보다는 50%가 수익이 좋을 겁니다. 


폭락장을 피해가려면 아래 부분을 여러분의 로직에 추가하시면 됩니다. 


//현금비중 조절 로직 시작

...

//현금비중 조절 로직 끝


아래는 미국발 폭락장인 리먼브라더스 사태로 테스트한 결과입니다. 테스트 기간은 코스피 지수가 폭락을 시작해서 이전 주가로 완전히 회복하는 2008년 1월초 ~2010년 9월 말로 하였습니다.

kospi 지수는 반토막이 났는데 우리 로직은 2.5% 도 안딸어 졌네요.


두서 없는 글 끝까지 읽어주셔서 감사합니다. 


댓글 10
치명적이진 않지만 로직에 버그가 있네요. currentValue/monthValue4 < 0.83 부분의 monthValue4을 monthValue5로 바꿔주십시오.
소포클레스 2017.09.11 09:47
많은 영감 얻어갑니다 좋은글 감사드립니다^^
코딩왕초보 2017.09.13 21:05
코딩초보라 질문좀 드리겠습니다~
1.손절 후에는 다음달 리밸런싱 구간에서 다시 100프로 매수가 들어가는거죠??ㅎ
2.현금보유 50프로로 로직 짤 경우 첫달 후 두번째달도 손절구간이면 50프로 비중으로만 계속 리밸런싱 유지되는거죠??
코딩왕초보 2017.09.14 08:47
그리고 귀찮게 해드려 죄송합니다.
손절 기준을 인덱스 말고 제 계좌수익률로 잡을 수 있을까요?
예를 들어 한달 기준 총자산이 0.9 세달 기준 0.87 이런식으루.. 감사합니다. 
코딩왕초보 2017.09.14 09:02
공유 고맙습니다. 이해를 못하는 부분 좀 문의드리겠습니다.
4행의, var preMonthtotalEquity = 0; 이것은 무엇을 의미하나요?
현금비중의 로직 안에 STOCK_WEIGHT가 49행에, 58행에 두 군데에 있습니다. 아마 58행에서 조절하란 말 같은데요.
그럼 49행의 STOCK_WEIGHT는 어떤 역할을 하는 것인지요?
(2행의 STOCK_WEIGHT과는 또 어떻게 다른 것인지요?)
칸트 2017.09.14 14:09
@코딩왕초보 5개 구간을 검사하는 겁니다. 
1.한달 하락률
2.두달 하락율
3.세달 하락율
4.네달 하락율
5.다섯달 하락율
이렇게 5구간을 검토해서 하나라도 걸리면 폭락구간으로 설정되어 현금보유 비중대로 손절 됩니다.

그리고 두번째달도 당연히 5구간을 검사하기때문에 손절 됩니다.

마지막으로  코딩왕초보님 계좌수익률 기준으로 일주안에 올려드리겠습니다. 
소포클레스 2017.09.15 01:16
@칸트 
preMonthtotalEquity 는 없어도 되는 것입니다. 삭제해야 되는데 놔두었군요.
STOCK_WEIGHT은 49행의 것이 초기화 입니다. 즉 아래 58행에 걸리지 않으면 주식비중이 99%로됩니다. 3행에서는 STOCK_WEIGHT에 0.99로 하는 것이 맞습니다. 실수네요.
한국의 폭락장은 12개월 이상의 장기모멘텀이 듣질 않기 때문에 5가지 단기 모멘텀을 매월 관찰한 것입니다^^ 
소포클레스 2017.09.15 02:10
@코딩왕초보
요청하신 로직 드립니다. 점심시간이라 테스트 하지 못하고 보냅니다.
아마 문제 없이 수행될겁니다.

var basket0, basket1;                //주식 종목들을 담을 Basket
var account0, account1;
var MAX_SIZE = 20;                   //각 바스켓에 최대 20주 편입
var STOCK_WEIGHT = 0.99;             //주식비중 
var basket_num = 0;                  //관찰대상/손절대상 혹은 진입대상 포트폴리오 구분자. 0 이면 관찰대상, 1 이면 3%손절 및 진입대상   
var copyPortfolio;                   //자바스크립트 수행속도를 빠르게 하기 위해 포트폴리오를 복제함. 이렇게 하지 않고 portfolioBuilder 함수를 두번 CALL하는 사람이 있는데, 두배 느려짐. 
var currentValue, monthValue1, monthValue2, monthValue3, monthValue4, monthValue5; //관찰대상 포트폴리오의 주식 + 현금 총평가액을 현재월~ 5개월 전까지 저장하기 위한 용도임 

// 이 전략이 초기화되면 Initialize 함수가 호출됩니다.
function initialize() {
    IQEnvironment.simulationMethod = SimulationMethod.day; //종가매매로 변경
    account0 = IQAccount.getDefaultAccount();
    account0.accountName = "관찰대상 포트폴리오";
    basket0 = new Basket(account0, MAX_SIZE, IQEnvironment.aum * STOCK_WEIGHT);
    basket0.setPortfolioBuilder(portfolioBuilder);
    
    account1 = IQAccount.addAccount('0000-0000-01', '폭락장 현금비중조절 포트폴리오', IQEnvironment.aum);                
    basket1 = new Basket(account1, MAX_SIZE, IQEnvironment.aum * STOCK_WEIGHT);        
    basket1.setPortfolioBuilder(portfolioBuilder);       
    
}

//portfolioBuilder 함수에서 사용할 필터링 함수를 정의합니다.
function stockFilter(stock) {
    if (stock.getMarketCapital() === 0) { return false; }
    if (stock.getFundamentalTotalAsset() === 0) { return false; } 
    if (stock.getClose() === 0) { return false; }                      // filter out delisted stocks
    if (stock.getTradingValue() === 0) { return false; }               // 거래정지 중인 종목 제외
    if (stock.market != 1 || stock.isETF) { return false; }            // KOSPI만 & ETF 제외
    if (stock.getFundamentalNetProfit() <= 0) { return false; }        // 당기 순이익 0 이하 종목 제외
    if (stock.getFundamentalTotalEquity <= 0) { return false; }        // 당기 자본총계 0 이하 종목 제외
    return true;
}

function portfolioBuilder(targetSize) {

	  switch(basket_num){ 
		   case 0 :         //관찰대상 포트폴리오 실행로직   
		       var universe = IQStock.filter(stockFilter);
		       var sortedByPbr = universe.slice().sort( function(a, b) { return b.getPBR() - a.getPBR(); });
		       var sortedByPer = universe.slice().sort( function(a, b) { return b.getPER() - a.getPER(); });
		       
		       var modelPortfolio = universe.slice().sort( function(a, b) {
		           return b.getScore('rank_sum') - a.getScore('rank_sum');
		       });
		       copyPortfolio = modelPortfolio; //현금비중 조절용 basket을 위해 포트폴리오 copy  		    		    
		       
		       STOCK_WEIGHT = 0.99;
		       currentValue = account0.getTotalEquity();
		       basket0.setBudget(currentValue * STOCK_WEIGHT); 		    
           
		       //현금비중 조절 로직위해 global 변수 입력 
		       monthValue5  = monthValue4;
		       monthValue4  = monthValue3;
		       monthValue3  = monthValue2;
		       monthValue2  = monthValue1;
		       monthValue1  = currentValue;	 
		       break; 
		     
		   case 1 :          //현금비중조절 포트폴리오 실행로직   
		       if ( currentValue/monthValue1 < 0.91 ||currentValue/monthValue2 < 0.89 || currentValue/monthValue3 < 0.87 || currentValue/monthValue4 < 0.85 || currentValue/monthValue5 < 0.83 ) {  STOCK_WEIGHT = 0; } 
		       logger.debug('주식비중 :' + STOCK_WEIGHT * 100 + '%' );
		       basket1.setBudget(account1.getTotalEquity() * STOCK_WEIGHT);
		       break; 
		} 
		
    return copyPortfolio.slice(0, MAX_SIZE);
}

var lastRebalMonth = -1; 
var startDate = 1;

//시뮬레이션 기간동안 매일 매일 호출됩니다.
//호출되는 시점이 언제인지는 Date 객체인 now파라메터를 통해 알 수 있습니다.
function onDayClose(now) {
    
    // 매달 초에 리밸런싱을 수행합니다.
    if ((now.getMonth() != lastRebalMonth &&  now.getDate() >= startDate)) {
        basket_num = 0;       //관찰대상 포트폴리오(손절없음, 주식비중 항상 99%) : 이 포트폴리오가 3% 하락하면 손절,다시 3% 상승하면 진입함
        basket0.setBudget(account0.getTotalEquity() * STOCK_WEIGHT);
        basket0.buildPortfolio();
        
        basket_num = 1;       //현금비중조절 포트폴리오(폭락장에서 현금비중 높아짐) 
        basket1.setBudget(account1.getTotalEquity() * STOCK_WEIGHT);
        basket1.buildPortfolio();

        lastRebalMonth = now.getMonth();
 }

}

그럼 수고하세요.
소포클레스 2017.09.15 13:00
참 코딩왕초보님 PBR과 PER이 낮은 주식은 폭락장에서도 어느정도 버티기 때문에 손절 로직에 잘 걸리지 않습니다. 그렇기 때문에 손절 발생이 몇달 늦게 됩니다. 손해가 커 지지요. 그러니 KOSPI 지수로 손절로직을 모니터링하는것이 더 나아 보입니다. 참고하세요
소포클레스 2017.09.15 13:33
고견, 로직공유, 매우 감사드립니다 ! 복받으실겁니다!!
코딩왕초보 2017.09.16 12:18
댓글 등록을 위해서 로그인해주세요.
 
최신 게시글