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

절대모멘텀 (최적화 시리즈6)

소포클레스 2021.11.22 17:53 조회수  3130 추천 7

이글의 내용으로 대중에게 글을 쓰거나 방송을 한다면 출처를 밝혀주기 바란다.


흔히 “한국의 소형주는 모멘텀 전략이 먹히지 않는다”라고들 한다. 그래서 많은 퀀트 투자자들이 소형주에 대해서는 모멘텀을 적용할것을 포기했다. 이런 좋지 않은 경향은 최근에 입문한 퀀트들 사이에서도 급속히 퍼져나가고 있다. 그로 인해 늘어나는 MDD와 줄어드는 샤프 Ratio는 퀀트 투자들에게 지속적인 손해를 입히고 있다. 이런 잘못된 관행으로 인한 손실을 해결하는 방법은 소형주 퀀트에 모멘텀(모멘텀 스코어/가속 모멘텀 스코어/절대 모멘텀/듀얼 모멘텀) 전략을 추가하는 것이다. 


절대모멘텀이란?

주로 Index에 적용되는 전략으로, 1년전 인덱스의 가격보다 현재의 가격이 낮다면 주식을 전량 매도하고, 현금화 하는 전략이다. 최악의 순간을 피하는 방어적인 전략이지만, 반대로 공격적인 전략이기도 하다. 왜냐하면 1년전 인덱스의 가격보다 현재의 가격이 높다면 자산배분 없이 주식 비중이 100%가 되기 때문이다.  


적용방법

원래는 1년전 인덱스의 가격보다 현재의 인덱스 가격이 낮다면, 폭락장으로 진입한 것으로 본다.따라서 주식을 전량을 매도하여 현금 비중이 100%가 되어야 한다. 하지만 지난번에 소개한 상대적으로 안전한 자산배분이 방법이 있다. 그 방법에 따라서 주식(소형주퀀트) 비중을 30% 로 하였다. 필요한 사람은 절대모멘텀 함수인 AbsoluteMomentum 의 주식 비중 수정하여 사용하기 바란다. 하지만 효율적 투자선에 따르면 주식 비중이 15~20% 보다 낮으면 오히려 샤프 Ratio가 감소하는 경향을 보인다. 따라서 너무 극단적으로 주식비중을 줄이지는 말기 바란다. 


과최적화 회피하기

1년 절대모멘텀이 마이너스라도 단기 절대 모멘텀(2달)이 플러스라면, 주식 투자로 돌아가게 된다. 두달 모멘텀이 플러스 라면 더 이상 주식 시장을 폭락장으로 볼 수 없기 때문이다. 그런데 2008년 ~ 2009년의 미국발 금융위기 상황에서 테스트 해보면 2009년 2~3월에 휩소를 만나서 손해를 보게 된다. 이런 것을 피하려고 소스를 수정하지 말길 바란다. 과최적화에 빠질 수 있기 때문이다. 절대모멘텀을 적용하면 코로나로 인해 주식시장이 바닥을 찍은 후인 2020 4월에 주식 비중을 100%로 하고 싶더라도 할 수 없다. 두달 모멘텀이 음수이기 때문이다. 마찬가지로 이런 손해들을 피하려고 수정하면 과최적화가 발생하게 된다. 논리를 세웠으면 수익률에 따라 소스를 수정하면 안되고, 꾸준히 적용해야 한다.   


소형주 인덱스를 사용한 이유는?

절대모멘텀은 시장 지수를 참조하지 않고 Kopsi 소형주 인덱스를 참조 하였다. 모멘텀 스코어는시장 전체의 분위기를 반영하기 때문에 Kospi 인덱스가 적당 하지만, 절대 모멘텀은 반대로 자신(퀀트 전략)의 절대모멘텀을 참조하는 것이 가장 좋다. 하지만 현재는 복수의 계좌를 허용하지 않기 때문에 Kospi 소형주 인덱스로 대신하였다. 언젠가 복수의 계좌를 허용한다면 자신과 똑같이 움직는 계좌의 절대모멘텀으로 변경하기 바란다.   


결과는?

가속 모멘텀 스코어에 비해 년 수익률은 17% 올라갔고, MDD도 10% 올라갔다. 이런 경우 샤프 Ratio가 높은 것을 선택하면 된다. 절대 모멘텀의 MDD 가 높다고 생각하거나 심장이 약한 사람들은 다음번 최적화 시리즈 까지 기다리기 바란다. 하지만 자산배분이나 모멘텀 전략을 적용하지 않고 MDD를 획기적으로 낮추는 방법은 기대하기 힘들다.


결론

영구포트폴리오/사계절 등의 정적자산배분의 비율대로 투자하고 있다면 이글은 별로 상관이 없다. 하지만 자산배분을 하지 않을 계획이라면, 모멘텀(가속 모멘텀 스코어/절대 모멘텀/듀얼 모멘텀)은 MDD를 줄이고, 샤프 Ratio를 높일 수 있는 가장 확실한 방법이다. 또한 모멘텀을 사용한다면, 최악의 순간에 손절을 하여 다시는 주식투자를 하지 않겠다고 결심하는 사람들의 수를 줄일 수 있다. 이런 트라우마는 평생 사람을 괴롭히며, 자신 뿐만 아니라 주위 사람과 가족들도 투자를 못하게 만든다. 


행동하는 사람만이 주식시장에서 오래 살아남을 수 있다. 퀀트 전략에 모멘텀이 없는가? 언제 올지 모르는 최악의 순간을 대비하여 오늘부터 모멘텀을 적용해보는 것은 어떨까? 


댓글 18
아 절대모멘텀이 나왔군요 ~~~ 인사이트 있는 전략을 공유해 주시니 감사합니다.
david yang 2021.11.22 22:28
david yang님 출근 하셨네요. 개근상 드렸으년 좋겠습니다. 감사합니다 
소포클레스 2021.11.22 22:37
감사합니다~
많이 공부됩니다. 
jiun song 2021.11.23 10:07
jiun song님 감사드립니다 ^^
소포클레스 2021.11.23 17:35
언제나 좋은 글 감사합니다. ^-^)=b
푸른주전자 2021.11.23 17:41
푸른주전자님 오셨네요. 저번에 맥쿼리인프라 감사했습니다^^
소포클레스 2021.11.23 21:06
소포클레스님, 혹시 시간 되실 때 4개의 전략을 각각 게시글로 부탁드려도 될까요?
시리즈의 게시글들의 본문 내용은 이해가 되는데요.
어느 정도 눈에 익어서 코드문을 보면 전체적인 흐름은 이제 어렴풋하게 보이는데,
각 전략에 해당하는 코딩구문하고 (글과의) 매칭이 안 되네요.ㅠ
그래서 무식하게 배워야 할 것 같아서요.
펼쳐 놓고 각 전략의 코드문을 비교하면서 공부해가면 좀 괜찮지 않을까 생각에..
언제까지 물어보기만 할 수도 없는 노릇이고..
참, (우연히 검색하다) 블로그 시작하신 것 같으시던데 거기서도 자주 뵙기를 바랍니다.
고맙습니다.
칸트 2021.12.29 19:03
칸트님 안녕하세요. 답변드리겠습니다.
1. 자산배분 : https://www.intelliquant.ai/article/999?forum=0 의 스크립트를 참조하시면 됩니다.

2. 모멘텀스코어 : 아래에  새로운 댓글에 스크립트를 드리겠습니다.

3. 가속모멘텀스코어 : 2번 모멘텀 스코어 전략 스크립트에서 304번 라인을 아래것으로 대체하면 가속 모멘텀 스코어가 됩니다. 단순히 'N'을 'Y'로 바꾼것에 불과합니다. 
var AccelMomWeight = MomentumRatio(MomentumScore, MonthCount, 'Y') ; 

4. 절대모멘텀:  https://www.intelliquant.ai/article/1008?forum=0 에서 필요 없는 부분을 삭제하시면 됩니다. 필요없는 부분을 말씀드리겟습니다. 
9~10번 라인 삭제
14~20번 라인 삭제
MomentumRatio 함수 삭제
MomScore 함수 삭제   
349~352 라인 삭제 
356~358 라인 삭제
362 라인 삭제
이렇게 하시면 절대모멘텀 전략만 남게 됩니다.
감사합니다.
소포클레스 2021.12.30 14:23
var basket1; // 주식 종목들을 관리하는 Basket 객체
var account1;
var stock_num = 20;             // 주식 종목 수
var stock_weight = 0.3 ;       // 자산배분시 주식 비중 
var valueRatio = 0.2;           // 저평가된 상위 20%만 취한다 
var isFirst = true;             // 시뮬레이션 시작일에 바로 포트폴리오 신규 구성을 하기 위해 사용될 상태 변수
var valueWeightRatio2 = 0.5;    // 가치가중 50%
var powerNumber = 1;            // 숫자를 높게주면 가치가중 효과를 증폭시킴. 기본값 1
var preMomentumScore = 0;
var MonthCount = 12;             //모멘텀 개월수. 12로 주면 12개의 모멘텀을 측정하여 점수를 주식비중을 결정한다.
var rsiPeriod = 10;             //기술적 지표 RSI의 기간 설정. 대체로 사용되는 값은 9일, 14~15일, 25~28일 등이다.(위키백과)
//초기화 함수 
function initialize() {
    account1 = IQAccount.getDefaultAccount();
    account1.accountName = "1. 자산배분+모멘텀스코어";          
    basket1 = new Basket(account1, stock_num, IQEnvironment.aum * stock_weight);        
    IQDate.addRebalSchedule(IQDate.setMonthlyStart(1));
}
//number형에서 null을 0으로 고침
function nvl( value ){  
    if( value === null || isNaN( value )) {return 0;}
    return value;
}; 
function cap(stock) {return stock.getMarketCapital() * 1000;}
// PBR역 구하기
function bp(stock) { 
    return ( stock.getFundamentalTotalEquity()  )/ cap(stock); }
// PCR역 구하기
function cp(stock) { 
    return ( stock.getFundamentalOperatingCashFlow()  )/ cap(stock); }
// PSR역 구하기
function sp(stock) { 
    return ( stock.getFundamentalRevenue() )/ cap(stock); }
// PER역 구하기
function ep(stock) { 
    return ( stock.getFundamentalNetProfit()  )/ cap(stock); }
// POR역 구하기
function op(stock) { 
    return ( stock.getFundamentalOperatingIncome()  )/ cap(stock); }
// 영업이익증가액/시가총액
function pir(stock) { 
    stock.loadPrevData(1, 4, 0);
    var oIncome0 = stock.getFundamentalOperatingIncome() ;
    var oIncome4 = stock.getFundamentalOperatingIncome(4) ;
    if ( oIncome0 <= 0 || oIncome4 < 0 ) {return -99999999999999;}
    var vreturn = (oIncome0-oIncome4)/cap(stock);  
    return  vreturn 
}
//EV/EBIT 역 구하기
function eveb(stock) { if ( stock.getFundamentalEV() <= 0) {return -99999999999999;}
    return ( stock.getFundamentalEBIT()  )/ stock.getFundamentalEV(); }
// PGR 구하기 : (매출액 - 매출원가)/시총 
function gp(stock) {
    var Revenue = stock.getFundamentalRevenue();          //매출액
    var SalesCost = nvl(stock.getFundamentalSalesCost()); //매출원가
    if(SalesCost === 0) {return 0;}  
    if(Revenue === 0) {return -99999999999999;}  
  return  ((Revenue - SalesCost) * 4 )/cap(stock);
}
// GPA 구하기 : 매출총이익/자산총계 
function gpa(stock) {
    var Revenue = stock.getFundamentalRevenue();          //매출액
    var SalesCost = nvl(stock.getFundamentalSalesCost()); //매출원가
    if(SalesCost === 0) {return 0;}  
    if(Revenue === 0) {return -99999999999999;}  
  return  ((Revenue - SalesCost) * 4)/stock.getFundamentalTotalAsset();
}
function roa(stock) { 
    return stock.getROA() ;  }
// 배열 합계 구하기 함수
function sum(array) {
  var result = 0.0;
  for (var i = 0; i < array.length; i++) {
    result = result + array[i];
  }  
  return result; //null 처리
}
function bnd(stock) {            //볼린저밴드 폭
    stock.loadPrevData(1, 4, 0);
    var retValue = ((stock.getBollingerBand(240, 1, 1).upper / stock.getBollingerBand(240, 1, 1).lower) * 100);
    if (retValue === Infinity || isNaN(retValue)) {
        logger.debug('WARNING: 종목코드 ' + stock.code + '에 대한 지표값이 (' + retValue + ') 입니다. -99999999으로 대체합니다.');
        return -99999999;
    }
    return retValue;
}
function rsi(stock) {           
    stock.loadPrevData(0, 4, 0);
    return stock.getRSI(rsiPeriod);
}
//한달거래금액의 중간값(median) 구하기 Monthly Median Transaction Amount
function MMTA(stock) {
    var arrayMMTA=[];   
    stock.loadPrevData(0,4,0);
    for (var i=0; i <21;i++) {
        arrayMMTA[i] = stock.getTradingValue(i);
    }    
    arrayMMTA.sort(function(a, b) { return a - b; }); // 오름차순    
    return arrayMMTA[10] ;
}
function TradingValueFilter(stock) { 
    var FilterPass_yn = 'Y'
    var thisYear = IQIndex.getIndex("001").getDate(0).getFullYear() ;
    var mata = MMTA(stock);
    if      (thisYear >= 2015  && thisYear < 2016 &&  mata < 64.03)  { FilterPass_yn = 'N';  }   
    else if (thisYear >= 2016  && thisYear < 2017 &&  mata < 68.85)  { FilterPass_yn = 'N';  }   
    else if (thisYear >= 2017  && thisYear < 2018 &&  mata < 74.04)  { FilterPass_yn = 'N';  }   
    else if (thisYear >= 2018  && thisYear < 2019 &&  mata < 79.62)  { FilterPass_yn = 'N';  }   
    else if (thisYear >= 2019  && thisYear < 2020 &&  mata < 85.62)  { FilterPass_yn = 'N';  }   
    else if (thisYear >= 2020  && thisYear < 2021 &&  mata < 92.07)  { FilterPass_yn = 'N';  }   
    else if (thisYear >= 2021                     &&  mata < 99   )  { FilterPass_yn = 'N';  }
    return FilterPass_yn;
}
//자산배분 ETF 매수하기
function basketEnter(basket, account, code, ratio) {
    var sse = IQStock.getStock(code); 
    if (sse !== null) {
        var bond_amt = account.getTotalEquity() * ratio   
        var BO_quantity = Math.floor(bond_amt / sse.getAdjClose());  //수량 (종목당 예산 / 수정종가)
        basket.enter(sse, BO_quantity);
    }
}
// 3) 필터링 함수 정의 - 필터링 조건에 따라 종목들의 포함 여부 판단
function stockFilter(stock) {
    if (stock.getMarketCapital() === 0 || stock.getClose() === 0 || stock.getTradingValue() === 0 ) { return false; } //시총 없는 종목 제외, 종가가 0인 종목 제외, 거래정지 중인 종목 제외   
    if (stock.getFundamentalTotalAsset() === 0 || stock.getFundamentalTotalEquity() === 0) { return false; }      // ETF, 우선주 제외(자산총계가 없음)
    if (stock.manage > 0 ) { return false; }                           // 관리종목, 투자유의종목 제외

    if ( stock.getFundamentalCapitalStock() > stock.getFundamentalTotalEquity() )  { return false; } //자본잠식 제외      
    return true;
}
function port_Value(universe, stock_number) {
    var UniverseFilter = universe.slice().filter(function(stock) {        
        if (  TradingValueFilter(stock) === 'N' ) { return false; }    
        return true; } ); 
    var sortedBybp   = UniverseFilter.slice().sort( function(a, b) { return bp(b)   - bp(a);    }); 
    var sortedByeveb = UniverseFilter.slice().sort( function(a, b) { return eveb(b) - eveb(a);  });     
    var sortedBygpa  = UniverseFilter.slice().sort( function(a, b) { return gpa(b)  - gpa(a);   });     
    
    UniverseFilter.forEach( function(stock) {
        stock.setScore('rank_sum', 
                         sortedBybp.indexOf(stock) 
                       + sortedByeveb.indexOf(stock) 
                       + sortedBygpa.indexOf(stock) 
                      ); 
    });

    var Port_Rank = UniverseFilter.slice().sort( function(a, b) {
        return a.getScore('rank_sum') - b.getScore('rank_sum');  });  
    
    var port_cut = Port_Rank.slice(0,  Math.floor(Port_Rank.length * valueRatio) );      
    var sortedByCap = port_cut.slice().sort(function(a,b){return cap(a) - cap(b);});
    
    return  sortedByCap.slice(0, stock_number );       
}
//동일비중 투자는 stockWeight 0으로 설정하고, 0보다 크면 가치가중으로 매수함 
function Port_Control(basket, account, universe, stockWeight, value_weight, pcrRatio, psrRatio, perRatio, porRatio, pirRatio, bndRatio, rsiRatio) {
    var TOT_PSR_SCORE, TOT_PCR_SCORE, TOT_PER_SCORE, TOT_POR_SCORE, TOT_PIR_SCORE, TOT_BND_SCORE, TOT_RSI_SCORE;    
    var PCR_SCORE = [];
    var PSR_SCORE = [];
    var PER_SCORE = [];
    var POR_SCORE = [];
    var PIR_SCORE = [];
    var BND_SCORE = [];
    var RSI_SCORE = [];   
    var i = -1;
    universe.forEach(function(stock) {
            var vsp = sp(stock);
            if (vsp <= 0) {vsp = 0;}
            PSR_SCORE[PSR_SCORE.length] = Math.pow(vsp, powerNumber);     

            var vcp = cp(stock);
            if (vcp <= 0) {vcp = 0;}
            PCR_SCORE[PCR_SCORE.length] = Math.pow(vcp, powerNumber);           

            var vep = ep(stock);
            if (vep <= 0) {vep = 0;}
            PER_SCORE[PER_SCORE.length] = Math.pow(vep, powerNumber);           
        
            var vop = op(stock);  
            if (vop <= 0) {vop = 0;}
            POR_SCORE[POR_SCORE.length] = Math.pow(vop, powerNumber);      
        
            var vpir = pir(stock);  
            if (vpir <= 0) {vpir = 0;}
            PIR_SCORE[PIR_SCORE.length] = Math.pow(vpir, powerNumber);   
        
            var vbnd =  1/bnd(stock);
            if (vbnd <= 0) {vbnd = 0;}
            BND_SCORE[BND_SCORE.length] = Math.pow(vbnd, powerNumber);  
        
            var vrsi = 100 - rsi(stock);
            if (vrsi > 100) {vrsi = 0;}
            RSI_SCORE[RSI_SCORE.length] = Math.pow(vrsi, powerNumber);         
    } );
    
    var TotalEquity = account.getTotalEquity();
    var port_Budget = TotalEquity * stockWeight;                     //전체금액중 주식에 투자할 금액
    var stock_Budget =  (TotalEquity * stockWeight)/universe.length; //동일 비중시 종목당 예산

    TOT_PCR_SCORE = sum(PCR_SCORE);
    TOT_PSR_SCORE = sum(PSR_SCORE);
    TOT_PER_SCORE = sum(PER_SCORE);
    TOT_POR_SCORE = sum(POR_SCORE);
    TOT_PIR_SCORE = sum(PIR_SCORE);
    TOT_BND_SCORE = sum(BND_SCORE);
    TOT_RSI_SCORE = sum(RSI_SCORE);
    if (TOT_PCR_SCORE === 0 )  {pcrRatio = 0 ; TOT_PCR_SCORE = 1;} //2003년 6월 이전은 PCR 이 없음으로 가치가중에서 제거한다.
    if (TOT_BND_SCORE === 0 )  {bndRatio = 0 ; TOT_BND_SCORE = 1;} //2001년 2월 이전은 1년 볼린저밴드 스코어가 없음으로 가치가중에서 제거한다.

    basket.reset();
    i = -1;
    universe.forEach(function(stock) {
        i = i + 1;
        var total_stock_budget =  port_Budget * (
            (   (PSR_SCORE[i]    / TOT_PSR_SCORE) * psrRatio +
                (PCR_SCORE[i]    / TOT_PCR_SCORE) * pcrRatio +
                (PER_SCORE[i]    / TOT_PER_SCORE) * perRatio +
                (POR_SCORE[i]    / TOT_POR_SCORE) * porRatio +
                (PIR_SCORE[i]    / TOT_PIR_SCORE) * pirRatio +
                (BND_SCORE[i]    / TOT_BND_SCORE) * bndRatio +
                (RSI_SCORE[i]    / TOT_RSI_SCORE) * rsiRatio
            ) / (pcrRatio  + psrRatio + perRatio + porRatio + pirRatio + bndRatio + rsiRatio)
        ) ;         
        
        var quantity;
        var same_weight_ratio = 1 - value_weight
        if (value_weight === 0) {  quantity = Math.floor( stock_Budget / stock.getAdjClose() ); } //동일비중 수량 (종목당 예산 / 수정종가)       
        else {                     quantity = Math.floor( (total_stock_budget * value_weight + (port_Budget * same_weight_ratio)/universe.length )/ stock.getAdjClose() ); }  //수량 (종목당 예산 / 수정종가)
        basket.enter(stock, quantity);
        var stock_ratio = ((quantity*stock.getAdjClose())/TotalEquity ) * 100;
        if (stock_ratio > 10) {logger.info('단일 종목의 비중이 10%를 넘음. 비중: ' + stock_ratio.toPrecision(4) + '%' + ' , 종목수: ' + universe.length + ' , 전략명: ' + account.accountName)}
                                     
    } );             
    AssetAllocation(basket, account, stockWeight);
}
// 주식을 매수하고 남은 비중으로 자산배분(자산배분은 2011년 3월 부터 가능)
function AssetAllocation(basket, account, stockWeight){   
    
    if ( stockWeight < 0.95 ) {
            var gold_ratio  = (1 - stockWeight) * 0.15 ;                            //골드 비중 : (1 - 주식비중) * 15%. ex) 주식비중 30% 인 경우 대략 15% 
            var bond_ratio  = (1 - stockWeight) - gold_ratio - 0.02 ;//채권 비중 : 1 -  주식비중 - 골드비중. 2% 는 현금 

            var US10Y_Tresaury = IQStock.getStock('A305080'); //TIGER 미국채10년선물 305080 2018년 08월 30일
            var KR10Y_Tresaury = IQStock.getStock('A148070'); //KOSEF 국고채10년 148070 2011년 10월 20일
            var USD            = IQStock.getStock('A138230'); //KOSEF 미국달러선물 138230 2011년 02월
            var KR_GOLD        = IQStock.getStock('A132030'); //KODEX 골드선물(H) 2010년 10월 01일     
            var KR3Y_Tresaury  = IQStock.getStock('A114100'); //KBSTAR 국고채3년 114100 2009년 07월 29일
            
            if (KR3Y_Tresaury == null) { 
                logger.debug("자산 배분을 수행할 수 없습니다. 투자 기간을 2009년 8월 이후로 조정하세요.");
                gold_ratio = 0;
            }
            else if (KR3Y_Tresaury.getClose() > 0 && KR_GOLD == null) {
                basketEnter(basket, account, 'A114100', bond_ratio + gold_ratio) ; //KBSTAR 국고채3년
                gold_ratio = 0;
            }
            else if (KR_GOLD.getClose() > 0  && USD == null) {
                basketEnter(basket, account, 'A114100', bond_ratio ) ;             //KBSTAR 국고채3년 
            }
            else if (USD.getClose() > 0 && KR10Y_Tresaury == null) { 
                basketEnter(basket, account, 'A138230', bond_ratio * 0.5) ;        //KOSEF 미국달러선물  
                basketEnter(basket, account, 'A114100', bond_ratio * 0.5) ;        //KBSTAR 국고채3년
            } 
            else if (KR10Y_Tresaury.getClose() > 0 && US10Y_Tresaury == null) { 
                basketEnter(basket, account, 'A148070', bond_ratio * 0.5) ;        //KOSEF 국고채10년 
                basketEnter(basket, account, 'A138230', bond_ratio * 0.5) ;        //KOSEF 미국달러선물 
            }                    
            else if (US10Y_Tresaury.getClose() > 0 ) { 
                basketEnter(basket, account, 'A305080', bond_ratio )   ;           //TIGER 미국채10년선물 
            }
        
            if (gold_ratio > 0) { 
            	basketEnter(basket, account, 'A132030', gold_ratio ) ;             //KODEX 골드선물(H)               
            }       
    }
}
// 모멘텀 스코어로 주식비중을 결정한다.
function MomentumRatio(MomScore, totalMonth, accelMom_yn){
    var StockRatio;
    if (accelMom_yn === 'Y' && MomScore > preMomentumScore) { StockRatio = (MomScore + (MomScore - preMomentumScore) )/totalMonth; } //전월에 비해 모멘텀 스코어가 커졌으면 1점 추가
    else {StockRatio = MomScore/totalMonth;}
    if (StockRatio >= 1) {StockRatio = 0.98;}
    if (StockRatio < 0 ) {StockRatio = 0   ;}
    return StockRatio;
}
function MomScore(MonCount) {
    var Kospi = IQIndex.getIndex('001');  //001 : KOSPI
    var current_price = Kospi.getClose();
    var Kospi_Momenum = [];
    Kospi.loadPrevData(0, 16, 0);
    for (var i = 1; i <= MonCount; i++) {  //12개월 모멘텀 측정        
        if (current_price > Kospi.getClose(i * 21)) { Kospi_Momenum[i-1] = 1;}
        else {Kospi_Momenum[i-1] = 0;}
    }
    return sum(Kospi_Momenum);
}
// 리밸런싱 수행 
function onDayClose(now) {
    if (IQDate.isRebalancingDay(now) || isFirst === true) {
        var universe = IQStock.filter(stockFilter);        
        var port_value        = port_Value(universe, stock_num);
        var MomentumScore = MomScore(MonthCount);                            //MonthCount 만큼의 개월의 모멘텀을 측정한 점수       
        var MomentumWeight = MomentumRatio(MomentumScore, MonthCount, 'N') ; //모멘텀 스코어로 주식비중을 결정한다.
        
                                                       //주식비중,      가치가중비율,   pcrRatio, psrRatio, perRatio, porRatio, pirRatio, bndRatio, rsiRatio
        Port_Control(basket1, account1, port_value,   MomentumWeight, valueWeightRatio2, 1,        1,       1,        1,        1,        1,       1); 
         
        isFirst = false;    
        preMomentumScore = MomentumScore;
    }
}
// 시뮬레이션 종료 함수
function onComplete() {
    logger.debug("계좌 총 평가금액은 " + IQUtil.getNumberWithCommas(Math.floor(account1.getTotalEquity())) + " 입니다.");
//    IQLive.addPortfolio(stock_basket, stock_weight);
}
소포클레스 2021.12.30 14:24
소포클레스님 번거로우실텐데, 진심으로 고맙습니다.
공부해 가면서 숙달하도록 노력하겠습니다. 나중에 궁금한 내용 여쭤보겠습니다.
칸트 2022.01.03 18:48
소포클레스님, 제가 이해한게 맞는지 확인부탁드립니다.
절대모멘텀(4번) 얘기입니다.
뼈대 전략이, 주식 30%, 금 10.5%, 채권 57.5%, 현금 2% 전략입니다.
이중 주식 30%는 가치가중으로 20종목으로 포트구성을 하게 되는데,
이들 종목은 bp+ev/ebit+gpa의 합으로 1차적으로 뽑고, 이중 시총 하위 20%가 유니버스에 넣어진다.
이 20종목은 주식 30% 예산 중 15%는 가치가중(0.5)으로 구성하며,
나머지 15%는 동일비중(0.5)으로 계산하여 구성한다.
단, 이 때에 절대모멘텀 양수일 때에는 주식 100%로 전체 포트폴리오가 구성되며,
음수일 때에는 초기에 설정한 주식 30%, 금 10.5%, 채권 57.5%, 현금 2%전략이 된다.
맞을까요? (제가 이해한게 맞다고 해주세요. ㅠㅠ)
최근 3년간 볼 때에 채권은 미국채선물만 보유하더라고요.
(만약 멀지 않은 백테스트 시기면) 혹 코드문에서 다른 채권들이 필요한 이유가 있을까요?
칸트 2022.01.03 20:28
칸트님 안녕하세요. bp+ev/ebit+gpa의 합으로 우량 가치주 상위 20%(대략300종목)를 뽑고 거기서 시총이 제일 작은 20종목을 뽑는 겁니다. 나머지는 칸트님 말씀하신 대로 입니다. 
그리고 미국채 이외의 채권이 있는 이유는 미국채가 최근(2018년 8월 30일)에 나왔기 때문입니다. 그 이전에 테스트 하려면 다른 채권이 있어야 하기 때문입니다. 2018년 9월 부터 테스트 하려면 다른 채권은 필요없습니다.
소포클레스 2022.01.04 11:47
칸트님 제가 한가지 빠트렸습니다. ㅠㅠ
1년짜리 절대모멘텀이 음수라도 2달짜리 절대모멘텀이 양수라면 100% 주식으로 됩니다. 참고하세요
소포클레스 2022.01.04 11:57
소포클레스님 늦었습니다(계속 쳐다만 보느라고...). 고맙습니다.
318줄에 국채3년물은 어떤 의미가 있을까요?
그게 없어도 현재가<252&42일 가격이라면 , { MomentmRatio = 0.3; } 하라고 하면 될거 같아서요.
그리고 '1년짜리 절대모멘텀이 음수라도 2달짜리 절대모멘텀이 양수라면 100% 주식을 담으라'라는 코드문은 몇 번째 줄에 있을까요?
77번째 줄의 함수 sum(array)는 무슨 역할을 할까요? (짐작컨데 Port_Control 함수에서의 동일비중/가치가중 구문을 위한 것일거라 생각은 드는데..) 아.. 머리 쥐 나네요. ㅎㅎ ㅠㅠ
고맙습니다.
칸트 2022.01.06 17:36
칸트님 안녕하세요. 국채 3년물이 자산배분 할수 있는 첫 ETF 입니다. 이 ETF 나오기 전에는 자산배분을 할 수 없었습니다. 그래서 318줄을 보면 국채3년 물이 없으면 주식을 방어(자산배분)할 수 없음으로 주식 30% 가 아니라 주식 0% 로 한겁니다. 즉 자산배분을 할 수 없을 때 절대모멘텀에 걸리면 현금 100% 로 되는 로직입니다. 

그리고 316줄에 1년 모멘텀과 두 달 모멘텀이 모두 음수일때만 절대모멘텀이 되는 것을 볼 수 있습니다. 

SUM은 array라는 배열의 숫자값을 모두 더하여 합계를 구하는 함수입니다. 단순히 숫자 배열을 던지면 합계를 반환하는 함수입니다. 말씀하신대로 Port_Control 함수에서 가치가중을 구할 때 씁니다.

자바스크립트 코딩을 공부하시려면 세가지 방법이 있습니다. 간간이 열리는 인텔리퀀트의 유료강의를 들으시거나 프리미엄멤버십에 몇달 가입하여 개인적인 코딩자문을 받으시거나 유튜브강의를 듣는 방법입니다.
유튜브는 "JavaScript 입문 수업 (생활코딩)" 을 추천합니다. 아래 링크로 가시면 됩니다.
https://www.youtube.com/watch?v=PZIPsKgWJiw&list=PLuHgQVnccGMA4uSig3hCjl7wTDeyIeZVU
소포클레스 2022.01.07 11:12
소포클레스님 안녕하세요.  제공하신 알고리즘을 보고 있는데 하나 궁금한점이 있어서 질문드립니다.
스코어를 부여할때 쓰는 함수들을 보면(예들 들어서 bp, cp, sp) 전부 마켓캡이 분모로 들어가 있는데 분자로 들어가야 하는게 아닌가요?

예를 들면

// PER역 구하기
function ep(stock) { 
    return ( stock.getFundamentalNetProfit()  )/ cap(stock); }

에서 cap(stock)/stock.getFundamentalNetProfit() 로 바뀌어야 하지 않나요?
답변 미리 감사드립니다.
Chris 2023.01.10 15:48
제가 잘못봤어요. "PER 역" 이라고 되어있네요. =)
Chris 2023.01.10 16:53
Chris 님 다행입니다^^
소포클레스 2023.01.12 15:55
댓글 등록을 위해서 로그인해주세요.
 
최신 게시글