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

평균모멘텀스코어 적용 관련 질문

지구본 2021.01.24 11:08 조회수  330 추천 0

(Prophit) 님의 슈퍼벨류모멘텀 vs 슈퍼스타벨류 게시글에서 참조하여 만들어봤는데요,

아무래도, 코딩 자체를 처음해보는거라, 조언이 필요할 것 같습니다.

종목 비중을 평균모멘텀 스코어로 결정하고, 전체 종목의 평균모멘텀스코어의 평균을 주식비중으로 결정하는 전략을 백테스트 해보고 싶습니다.

아래와 같이 알고리즘을 짜는 것이 맞는지, 확인 부탁드리겠습니다..

매수 알고리즘에 stock weight를 평균모멘텀스코어의 평균으로 적용한 부분이 특히 확인이 필요할 것 같습니다.


var account_0, account_1, account_2;
var basket, basket_1;

var STOCK_WEIGHT = 0.95;  // 주식 비율 현금 보유를 0%
var STOCK_WEIGHT2 = 0.95;

var MAX_SIZE_1 = 20;         // 각 바스켓에 최대 20주 편입

var port_num = 20; // 각 포트 최대 종목수
var cutline = 4; // 20% 이하시 현금


// 이 전략이 초기화되면 Initialize 함수가 호출됩니다.
function initialize() {
  //  IQEnvironment.simulationMethod = SimulationMethod.day;    //종가매수 - 시가가 튀어서 매수 안되는 일이 없도록
 //   IQEnvironment.stockTax = 0.013;    // 주식 세금 1.3% (슬리피지 1%포함)    
    
    account_0 = IQAccount.getDefaultAccount();
basket = new Basket(account_0, 20, IQEnvironment.aum * STOCK_WEIGHT);   
    basket.setPortfolioBuilder(portfolioBuilder_1);        
    
    account_1 = IQAccount.addAccount('0000-0000-01', '가속 슈퍼스타k', IQEnvironment.aum);
    account_2 = IQAccount.addAccount('0000-0000-02', '슈퍼스타K', IQEnvironment.aum);
}

//portfolioBuilder 함수에서 사용할 필터링 함수를 정의합니다.
function stockFilter(stock) {
    if (stock.getClose() === 0) { return false; }                      // 가격 0인 종목 = 상폐종목 제외
    if (stock.getTradingVolume() === 0) { return false; }               // 거래량 0인 종목 = 거래정지 중인 종목 제외    
    if (stock.isETF) { return false; }            // ETF 제외
    
    if (stock.name.substr(stock.name.length -3) === "ETN") { return false; } //스팩 제외  
    if (stock.name.substr(stock.name.length -4) === "투자신탁") { return false; } //스팩 제외  
    if (stock.name.substr(stock.name.length -2) === "스팩") { return false; } //스팩 제외
    if (stock.name.substr(stock.name.length -3) === "스팩2") { return false; } //스팩 제외    
    if (stock.name.substr(stock.name.length -2) === "우B") { return false; } //우선주 제외    
    if (stock.name.substr(stock.name.length -2) === "우C") { return false; } //우선주 제외  
    if (stock.name.substr(stock.name.length -1) === "우") {
        if (stock.name === "연우") { return true;} else { return false; }     }//우선주 제외     
    if (stock.name.substr(stock.name.length -3) === "우선주") { return false; } //우선주 제외          
    if (stock.name.substr(stock.name.length -1) === ")") { return false; } //우선주 제외  
    if (stock.name.substr(stock.name.length -2) === "1호") { return false; } //스팩 제외    
    if (stock.name.substr(stock.name.length -2) === "2호") { return false; } //스팩 제외  
    if (stock.name.substr(stock.name.length -2) === "3호") { return false; } //스팩 제외  
    if (stock.name.substr(stock.name.length -2) === "4호") { return false; } //스팩 제외  
    if (stock.name.substr(stock.name.length -2) === "5호") { return false; } //스팩 제외  
    if (stock.name.substr(stock.name.length -2) === "6호") { return false; } //스팩 제외  
    if (stock.name.substr(stock.name.length -2) === "7호") { return false; } //스팩 제외  
    if (stock.name.substr(stock.name.length -2) === "8호") { return false; } //스팩 제외  
    if (stock.name.substr(stock.name.length -2) === "9호") { return false; } //스팩 제외   
    if (stock.name.substr(stock.name.length -3) === "10호") { return false; } //스팩 제외 
    if (stock.name.substr(stock.name.length -3) === "개발1") { return false; } //스팩 제외     
    if (stock.name.substr(stock.name.length -4) === "SPAC") { return false; } //스팩 제외   
    if (stock.name.substr(0, 2) === "QV") { return false; } //ETN 제외  
    if (stock.name.substr(0, 4) === "TRUE") { return false; } //ETN 제외      
    if (stock.name.substr(0, 3) === "삼성 ") { return false; } //ETN 제외  
    if (stock.name.substr(0, 5) === "미래에셋 ") { return false; } //ETN 제외  
    if (stock.name.substr(0, 3) === "신한 ") { return false; } //ETN 제외  
    if (stock.name.substr(0, 3) === "대신 ") { return false; } //ETN 제외 
    //if (stock.getAdjClose(0) < stock.getAdjClose(252)) {return false;} //12개월 모멘텀 음수 제외
    //if (stock.getAdjClose(0) < stock.getAdjClose(63)) {return false;} //3개월 모멘텀 음수 제외
//재무데이터 없는 경우 제외
if (bp(stock) === 0 || cp(stock) === 0 || ep(stock) === 0 || op(stock) === 0 || sp(stock) === 0 || gpa4(stock) === 0) { return false;}
    var filterPER = (stock.getPER() > 0); //PER 값이 마이너스인 경우 제외
    var filterPBR = (stock.getPBR() >0); //PBR 값이 마이너스인 경우 제외
    var filterTradingValue = (stock.getTradingValue() > 100); // 일거래대금 1억 이상
    var filterNCAV = ((stock.getFundamentalCurrentAsset()-stock.getFundamentalTotalLiability())>stock.getMarketCapital()*1.5); //순유동자산비율 150% 이상
    //var filterCap = (stock.capLevel == 4); //소형주만 포함(1:대형 2:중형 4:소형 8:kospi 200)
    return (filterPER &&filterPBR && filterNCAV && filterTradingValue);
    
}

//////////////////////////////////////////////////////

function cap(stock) {return stock.getNoOfShare() * stock.getClose() / 1000;}
// PBR역순 구하기
function bp(stock) { if (stock.getFundamentalTotalEquity() === 0) {return 0;}
    return stock.getFundamentalTotalEquity() / cap(stock); }
// PCR역순 구하기
function cp(stock) { if (stock.getFundamentalOperatingCashFlow() === 0) {return 0;}
    return  stock.getFundamentalOperatingCashFlow() / cap(stock); }
// PER역순 구하기
function ep(stock) { if (stock.getFundamentalNetProfit() === 0) {return 0;}
    return  stock.getFundamentalNetProfit() / cap(stock); }
// POR역순 구하기
function op(stock) { if (stock.getFundamentalOperatingIncome() === 0) {return 0;}
    return  stock.getFundamentalOperatingIncome() / cap(stock); }
// PSR역순 구하기
function sp(stock) { if (stock.getFundamentalRevenue() === 0) {return 0;}
    return  stock.getFundamentalRevenue() / cap(stock); }
// 볼린저밴드 상단-하단 구하기
function bb(stock) {
    var band=stock.getBollingerBand(252,1,1);
    return (band.upper - band.lower) / cap(stock);}

// GPA 구하기 
function gpa4(stock) { stock.loadPrevData(1, 6, 0);
    if (stock.getFundamentalRevenue(0) === 0 || stock.getFundamentalRevenue(1) === 0 || 
        stock.getFundamentalRevenue(2) === 0 || stock.getFundamentalRevenue(3) === 0) {return 0; }        
    if (stock.getFundamentalSalesCost(0) === 0 || stock.getFundamentalSalesCost(1) === 0 || 
        stock.getFundamentalSalesCost(2) === 0 || stock.getFundamentalSalesCost(3) === 0) {return 0; }        
    if (stock.getFundamentalTotalAsset(0) === 0 || stock.getFundamentalTotalAsset(1) === 0 || 
        stock.getFundamentalTotalAsset(2) === 0 || stock.getFundamentalTotalAsset(3) === 0) {return 0; }                                  
  return (stock.getFundamentalRevenue(0) + stock.getFundamentalRevenue(1) + 
          stock.getFundamentalRevenue(2) + stock.getFundamentalRevenue(3) -
          stock.getFundamentalSalesCost(0) -  stock.getFundamentalSalesCost(1) - 
          stock.getFundamentalSalesCost(2) - stock.getFundamentalSalesCost(3) ) / // stock.getFundamentalTotalAsset() ;
      ((stock.getFundamentalTotalAsset(0) + stock.getFundamentalTotalAsset(1) +
        stock.getFundamentalTotalAsset(2) + stock.getFundamentalTotalAsset(3)) / 4); 
}

/////////////////////////////////////////////////////////////////

// 평균 모멘텀 스코어 구하기 -> 12M 
function M_score(stock) {
    stock.loadPrevData(0, 15, 0);
    var month_days = 21;
    var point = 0;
    for (var i = 1; i < 13; i++) {
        if (stock.getAdjClose(0) > stock.getAdjClose(month_days * i)) {point = point + 1;}
}
    point = point/12;
    return point;
}

// 가속 평균 모멘텀 스코어 구하기

function M_score2(stock) {
    stock.loadPrevData(0, 15, 0);
    var month_days = 21;
    var point = 0;
    for (var i = 1; i < 13; i++) {
        if (stock.getAdjClose(0) > stock.getAdjClose(month_days * i)) {point = point + 1;}
}
    for (var i = 1; i < 3; i++) {
        if (stock.getAdjClose(0) > stock.getAdjClose(month_days * i)) {point = point + 1;}
}
    point = point/12;
    return point;
}
/////////////////////////////////////////////////////////////////

function portfolioBuilder_1(targetSize) { 
var universe = IQStock.filter(stockFilter);  

//    var sortedByPbr = universe.slice().sort( function(a, b) { return bp(b) - bp(a); });
    var sortedByPER = universe.slice().sort( function(a, b) { return ep(b) - ep(a); });
//    var sortedByPor = universe.slice().sort( function(a, b) { return op(b) - op(a); });    
    var sortedByPsr = universe.slice().sort( function(a, b) { return sp(b) - sp(a); });    
//    var sortedByPcr = universe.slice().sort( function(a, b) { return cp(b) - cp(a); }); 
    var sortedByGPA = universe.slice().sort( function(a, b) { return gpa4(b) - gpa4(a); });
    var sortedByBB = universe.slice().sort( function(a,b) {return bb(b)-bb(a);});

    universe.forEach( function(stock) {
        stock.setScore('rank_sum', 
                       sortedByPER.indexOf(stock) + 
                    //   sortedByPbr.indexOf(stock) + 
                       sortedByPsr.indexOf(stock) + 
                    //   sortedByPor.indexOf(stock) + 
                    //   sortedByPcr.indexOf(stock) +
                       sortedByBB.indexOf(stock)+
                       sortedByGPA.indexOf(stock)  
                      ); 
    });    
    
    var sortedByCap = universe.sort( function(a, b) { return cap(a) - cap(b); });    //시총 오름순 정렬
var quantile_e = Math.floor(sortedByCap.length * 0.2);  // 전체 종목의 20% 길이 계산
    var smallCap = sortedByCap.slice(0, quantile_e);        //시총 하위 20%만
    
    var momontum = smallCap.filter(function(stock) {
        stock.loadPrevData(0, 15, 0);  
        if (stock.getAdjClose(252) === 0) { return false; }     // 상장 1년미만 제외             
        return true; } );
                                 
    var modelPortfolio = momontum.slice().sort( function(a, b) { // 팩터 종합 랭킹값으로 정렬
        return a.getScore('rank_sum') - b.getScore('rank_sum');  });
    var final_list = modelPortfolio.slice(0, port_num); // 20종목만 리턴
    
//var final_port = final_list.filter(function(stock) {
      //  if (stock.getAdjClose(0) < stock.getAdjClose(252)) {return false;} // 모멘텀 음수 제외
     //   logger.debug(stock.name);
        //return true; } );  
    
    stock_20 = final_list; // 슈퍼스타K 종목 저장
    momentumPort = final_list;  // 슈퍼밸류 종목 저장
    
 //   if (final_port.length < cutline) { return false; } else { return final_port; }
}
///////////////////////////////////////////////////////////////

function sell_stock(acc) { //전량 매도
    acc.getEggs().forEach(function(egg) {
        acc.sell(egg.code, egg.quantity);
    } );
}

//슈퍼스타K  매수
function SuperStar_stock(acc, universe) {
    var total_score = 0; // 평균모멘텀 스코어 합
    var num_stock = 0;  //종목 수
    universe.forEach(function(stock) {
        var stock_score = M_score(stock);
        stock.setScore('M_score', stock_score); // 스코어 점수 기록
     //   logger.debug(stock.name + " 스코어 = " + stock_score);
        total_score = total_score + stock_score; // 전체스코어에 합산
        num_stock = num_stock + 1;
    } );
    
    var budget_score = total_score/num_stock;  //전체 평균모멘텀 스코어
    STOCK_WEIGHT = budget_score;
     logger.debug(budget_score);
    var port_Budget = acc.getTotalEquity() * STOCK_WEIGHT; // 전체 포트 예산
    
    universe.forEach(function(stock) {
        var Budget_Per = stock.getScore('M_score') // 스코어 불러오기
        var stock_budget = parseInt(port_Budget * (Budget_Per / total_score)); // 종목당 예산 (총예산 * 모멘텀스코어 / 스코어합)
        
     //   logger.debug(stock.name + "의 모멘텀 스코어는 " + Budget_Per + ", 토탈스코어는 " + total_score + ", 예산은 " + stock_budget);
        var quantity = parseInt(stock_budget / stock.getAdjClose());
        acc.buy(stock.code, quantity);
    } );
}

//가속 슈퍼스타K 매수 
function SuperStar2_stock(acc, universe) {
    var total_score2 = 0; // 평균모멘텀 스코어 합
    var num_stock2 = 0; //종목 수
    universe.forEach(function(stock) {
        var stock_score2 = M_score2(stock);
        stock.setScore('M_score2', stock_score2); // 스코어 점수 기록
     //   logger.debug(stock.name + " 스코어 = " + stock_score);
        total_score2 = total_score2 + stock_score2; // 전체스코어에 합산
        num_stock2 = num_stock2 + 1;
    } );
    var budget_score2 = total_score2/num_stock2;
    STOCK_WEIGHT2 = budget_score2;
    var port_Budget = acc.getTotalEquity() * STOCK_WEIGHT2; // 전체 포트 예산
 
    
    universe.forEach(function(stock) {
        var Budget_Per2 = stock.getScore('M_score2') // 스코어 불러오기
        var stock_budget2 = parseInt(port_Budget * (Budget_Per2 / total_score2)); // 종목당 예산 (총예산 * 모멘텀스코어 / 스코어합)
        
     //   logger.debug(stock.name + "의 모멘텀 스코어는 " + Budget_Per + ", 토탈스코어는 " + total_score + ", 예산은 " + stock_budget);
        var quantity2 = parseInt(stock_budget2 / stock.getAdjClose());
        acc.buy(stock.code, quantity2);
    } );
}

///////////////////////////////////////////////////////////////
var momentumPort; // 슈퍼밸류 종목 저장
var stock_20; // 슈퍼스타K 종목 
///////////////////////////////////////////////////////////////
var lastRebalYear = -1; 
var lastRebalMonth = -1; 
var startDate = 1;

//시뮬레이션 기간동안 매일 매일 호출됩니다.
//호출되는 시점이 언제인지는 Date 객체인 now파라메터를 통해 알 수 있습니다.
function onDayClose(now) {

    // 매달 초에 리밸런싱을 수행합니다.
if ((now.getMonth() != lastRebalMonth &&  now.getDate() >= startDate)) {    
        basket.setBudget(account_0.getTotalEquity() * 0);
        
        basket.buildPortfolio(); 
        
        sell_stock(account_1); 
        sell_stock(account_2);         

SuperStar2_stock(account_1, momentumPort);
        SuperStar_stock(account_2, stock_20);
        

        lastRebalMonth = now.getMonth();
}
}
댓글 1
평균모멘텀 계산해서 적용하는 부분은 특별히 오류는 없어 보입니다만, 아무래도 테스트를 여러 번 해 보시면서 확인하셔야 할 것 같습니다.

그 외에, 원래 전략에서 복사된 부분 중 아래 부분은 초창기에 많은 분들이 복사해서 사용한 코드인데, 사실 필요 없거나 간단히 할 수 있는 부분입니다. ETN 종목은 원래부터 저희가 제공을 안하고 있고, 우선주나 스팩 종목은 재무데이터가 없기 때문에 TotalAsset이 0인지 아닌지 만으로도 모두 걸러낼 수 있습니다.

-----------------------------------------------------------------------------------------------------------------
    if (stock.name.substr(stock.name.length -3) === "ETN") { return false; } //스팩 제외  
    if (stock.name.substr(stock.name.length -4) === "투자신탁") { return false; } //스팩 제외  
    if (stock.name.substr(stock.name.length -2) === "스팩") { return false; } //스팩 제외
    if (stock.name.substr(stock.name.length -3) === "스팩2") { return false; } //스팩 제외    
    if (stock.name.substr(stock.name.length -2) === "우B") { return false; } //우선주 제외    
    if (stock.name.substr(stock.name.length -2) === "우C") { return false; } //우선주 제외  
    if (stock.name.substr(stock.name.length -1) === "우") {
        if (stock.name === "연우") { return true;} else { return false; }     }//우선주 제외     
    if (stock.name.substr(stock.name.length -3) === "우선주") { return false; } //우선주 제외          
    if (stock.name.substr(stock.name.length -1) === ")") { return false; } //우선주 제외  
    if (stock.name.substr(stock.name.length -2) === "1호") { return false; } //스팩 제외    
    if (stock.name.substr(stock.name.length -2) === "2호") { return false; } //스팩 제외  
    if (stock.name.substr(stock.name.length -2) === "3호") { return false; } //스팩 제외  
    if (stock.name.substr(stock.name.length -2) === "4호") { return false; } //스팩 제외  
    if (stock.name.substr(stock.name.length -2) === "5호") { return false; } //스팩 제외  
    if (stock.name.substr(stock.name.length -2) === "6호") { return false; } //스팩 제외  
    if (stock.name.substr(stock.name.length -2) === "7호") { return false; } //스팩 제외  
    if (stock.name.substr(stock.name.length -2) === "8호") { return false; } //스팩 제외  
    if (stock.name.substr(stock.name.length -2) === "9호") { return false; } //스팩 제외   
    if (stock.name.substr(stock.name.length -3) === "10호") { return false; } //스팩 제외 
    if (stock.name.substr(stock.name.length -3) === "개발1") { return false; } //스팩 제외     
    if (stock.name.substr(stock.name.length -4) === "SPAC") { return false; } //스팩 제외   
    if (stock.name.substr(0, 2) === "QV") { return false; } //ETN 제외  
    if (stock.name.substr(0, 4) === "TRUE") { return false; } //ETN 제외      
    if (stock.name.substr(0, 3) === "삼성 ") { return false; } //ETN 제외  
    if (stock.name.substr(0, 5) === "미래에셋 ") { return false; } //ETN 제외  
    if (stock.name.substr(0, 3) === "신한 ") { return false; } //ETN 제외  
    if (stock.name.substr(0, 3) === "대신 ") { return false; } //ETN 제외 


인텔리퀀트 2021.01.25 11:14
댓글 등록을 위해서 로그인해주세요.
 
최신 게시글