도움말
인텔리퀀트의 사용방법과 메뉴얼입니다.

7. PER + PBR 샘플 분석

이 코드는 인텔리퀀트 스튜디오 사용법을 설명하기 위한 예제입니다. 인텔리퀀트는 이 알고리즘의 수익률을 보장하지 않습니다. 그러나 정량적인 가설 -> 검증(시뮬레이션) -> 실증 과정을 통해 여러분이 보다 안정적이고 높은 수익률을 낼 수 있을거라 확신합니다.

뼈대 전략 및 예제 코드

예제 코드에서는 가치(Value) 팩터들 중에서 대표적인 주가수익비율(PER)과 주가순자산비율(PBR)을 사용하여 투자 종목을 선정하는 방식과 하락장에 대비하여 인버스 ETF 종목을 포트폴리오에 편입하는 방법을 다룹니다.

  • 일정 기준 이상 거래량과 유동성을 가지고 있는 종목 중 PER + PBR 합산 순위가 가장 높은 10종목 매수
  • 한 달에 한 번(매달 15일, 영업일 기준) 종목 교체
  • 안정적인 운용을 위해 전체 자금의 75%를 집행하고 나머지 20%는 인버스 ETF에 진입하여 하락장 대비


예제 코드는 아래와 같이 포트폴리오 초기화, 팩터(factor) 정의, 필터링 함수 정의, 포트폴리오 빌더 함수 정의, 리밸런싱 수행, 시뮬레이션 종료 함수로 구성됩니다.

var stock_basket;               // 주식 종목들을 관리하는 Basket 객체
var short_basket;               // 인버스 ETF 종목들을 관리하는 Basket 객체
var stock_weight = 0.75;        // 주식 비율
var short_weight = 0.2;         // 인버스 ETF 비율, 현금 보유 5%
var stock_num = 10;             // 주식 종목 수


// 1) 포트폴리오 초기화 함수 - 전략이 실행될 때 최초 한번 자동으로 실행
function initialize() {
  stock_basket = new Basket(IQAccount.getDefaultAccount(), stock_num, IQEnvironment.aum * stock_weight);
  stock_basket.setPortfolioBuilder(stockPortfolioBuilder);

  short_basket = new Basket(IQAccount.getDefaultAccount(), 1, IQEnvironment.aum * short_weight);
  short_basket.setPortfolioBuilder(shortPortfolioBuilder);
}


// 2) 팩터(factor) 정의 - 필터링 함수 및 포트폴리오 빌더 함수에서 사용
function getPER(stock) {
  if (stock.getClose() === 0 || stock.getFundamentalNetProfit() === 0) {
    return -1;
  }
  return stock.getMarketCapital() / (stock.getFundamentalNetProfit() * 4);
}

function getPBR(stock) {
  if (stock.getClose() === 0 || stock.getFundamentalTotalEquity() === 0) {
    return -1;
  }
  return stock.getMarketCapital() / (stock.getFundamentalTotalEquity() * 4);
}


// 3) 필터링 함수 정의 - 필터링 조건에 따라 종목들의 포함 여부 판단
function stockFilter(stock) {
  if (stock.market != 1 || stock.isETF) {  //코스닥 종목과 ETF 종목 필터링
      return false;
  }

  var filterMarketCapital = (stock.getMarketCapital() > 50000);	// 시가총액 500억 이상 기준
  var filterTradingValue = (stock.getTradingValue() > 100);     // 일거래대금 1억 이상 기준
  var filterPER = (getPER(stock) > 0);                        	// PER 값이 마이너스인 경우 제외
  var filterPBR = (getPBR(stock) > 0);                        	// PBR 값이 마이너스인 경우 제외

  return (filterMarketCapital &&  filterTradingValue && filterPER && filterPBR);
}


// 4) 포트폴리오 빌더 함수 정의 - 필터링 및 팩터 기반 종목 선정
function stockPortfolioBuilder(targetSize) {
  var universe = IQStock.filter(stockFilter);
  var sortedByPer = universe.slice().sort(function(a,b){return getPER(a)-getPER(b);});
  var sortedByPbr = universe.slice().sort(function(a,b){return getPBR(a)-getPBR(b);});

  universe.forEach(function(stock) {
      stock.setScore('rank_sum', sortedByPer.indexOf(stock) + sortedByPbr.indexOf(stock));
  });

  var factorRank = universe.slice().sort(function(a, b){return a.getScore('rank_sum')-b.getScore('rank_sum');});
  return factorRank.slice(0, targetSize);
}

function shortPortfolioBuilder(targetSize) {
  var inverseETF = IQStock.getStock("A114800");
  return new Array(inverseETF);
}


var enterForInit = false;
var lastRebalMonth = 0; // 이전 리밸런싱을 수행한 달(Month) 저장

// 5) 리밸런싱 수행 - 시뮬레이션 기간 동안 장 마감 후 매일 자동으로 호출되는 함수
function onDayClose(now) {
  if (enterForInit == false || (now.getDate() >= 15 && now.getMonth() != lastRebalMonth)) {
      var currentTotalEquity = IQAccount.getDefaultAccount().getTotalEquity();

      stock_basket.setBudget(currentTotalEquity * stock_weight);
      stock_basket.buildPortfolio();        // 주식 리밸런싱 수행

      short_basket.setBudget(currentTotalEquity * short_weight);
      short_basket.buildPortfolio();        // 인버스 ETF 리밸런싱 수행

      lastRebalMonth = now.getMonth();      // 리밸런싱을 수행한 달(Month) 저장
      enterForInit = true;                  // 최초 진입 플래그를 true로 변경
  }
}

// 6) 시뮬레이션 종료 함수 - 시뮬레이션이 종료될 때 한번 자동으로 호출
function onComplete() {
  logger.debug("계좌 총 평가금액은 " + parseInt(IQAccount.getDefaultAccount().getTotalEquity()) + "원 입니다.");
}
            

1) 포트폴리오 초기화 함수 - 전략이 실행될 때 최초 한번 자동으로 실행

initialize() 함수는 전략 시뮬레이션이 시작할 때 한번 자동으로 호출되며, 바스켓(Basket) 객체를 생성하거나 포트폴리오 빌더 함수를 설정하는 등 시뮬레이션에 필요한 객체들을 생성하거나 변수들을 설정하는 용도로 사용됩니다.


function initialize() {
    // 필요한 바스켓을 선언합니다. 이 바스켓을 통해 종목을 선정하고 주문을 수행합니다.
    // 바스켓을 사용하지 않고 직접 Account 객체에 매수/매도 주문을 낼 수도 있습니다.
    // 그러나 이 예제에서는 바스켓 2개를 선언하여 8:2의 비율로 포트폴리오를 구성하는 방법을 사용합니다.

    // 주식 종목을 담을 Basket 객체입니다. 10개의 종목을 담을 예정이며,
    // 투자 예정 금액은 전체(aum: Asset Under Management) 금액 중 75% 입니다.
    stock_basket = new Basket(IQAccount.getDefaultAccount(), stock_num, IQEnvironment.aum * stock_weight);

    // 주식 포트폴리오 빌더 함수를 설정합니다.
    stock_basket.setPortfolioBuilder(stockPortfolioBuilder);

    // 인버스 ETF 종목을 담을 Basket 객체입니다. 1개의 종목을 담을 예정이며,
    // 투자 예정 금액은 전체(aum: Asset Under Management) 금액 중 20% 입니다.
    short_basket = new Basket(IQAccount.getDefaultAccount(), 1, IQEnvironment.aum * short_weight);

    // 인버스 ETF 포트폴리오 빌더 함수를 설정합니다.
    short_basket.setPortfolioBuilder(shortPortfolioBuilder);
}
      

그리고 initialize() 함수에서 사용하는 변수들(stock_basket, short_basket 등)은 보통 다른 함수에서도 사용하기 위해 전역 변수(Global Variable)로 등록합니다. 전역 변수 사용 방법은 initialize() 함수 밖에서 변수를 먼저 선언(var stock_basket;)한 후 initialize() 함수에서 사용합니다.


2) 팩터(factor) 정의 - 필터링 함수 및 포트폴리오 빌더 함수에서 사용

주가수익비율(PER), 주가순자산비율(PBR) 등의 팩터를 정의하며, 필터링 함수 및 포트폴리오 빌더 함수에서 사용됩니다. 그리고 팩터 정의에서 사용되는 각종 재무 데이터는 팩터 정의 함수의 매개 변수(Parameter)로 전달되는 Stock 객체를 통해 사용할 수 있습니다. 자세한 재무 및 시세 데이터 항목은 도움말 '전략 작성 가이드' 메뉴의 3. 재무 데이터 활용 및 'API 레퍼런스' 메뉴의 Stock 내용을 참고하세요.


// 주가수익비율(PER) 팩터 정의
function getPER(stock) {
  // 해당 종목의 종가 또는 당기순이익이 0인 경우에는 제외합니다.
  if (stock.getClose() === 0 || stock.getFundamentalNetProfit() === 0) {
    return -1;
  }

  //stock.loadPrevData(0, 14, 0);   // 이전 분기 데이터가 필요할때만 사용합니다.

  // 주가수익비율을 계산한 결과를 getPER(stock) 함수를 호출한 곳으로 넘겨줍니다.
  return stock.getMarketCapital() / (stock.getFundamentalNetProfit() * 4);
}

// 주가순자산비율(PBR) 팩터 정의
function getPBR(stock) {
  // 해당 종목의 종가 또는 자본총계 값이 0인 경우에는 제외합니다.
  if (stock.getClose() === 0 || stock.getFundamentalTotalEquity() === 0) {
    return -1;
  }

  //stock.loadPrevData(0, 14, 0);   // 이전 분기 데이터가 필요할때만 사용합니다.

  // 주가순자산비율을 계산한 결과를 getPBR(stock) 함수를 호출한 곳으로 넘겨줍니다.
  return stock.getMarketCapital() / (stock.getFundamentalTotalEquity() * 4);
}
      

예를 들어 주가수익비율은 시가총액(getMarketCapital()) / 당기순이익(getFundamentalNetProfit()) *4로 계산합니다. 여기서 getFundamentalNetProfit()은 getFundamentalNetProfit(0)과 같으며 현재 분기 당기순이익을 의미합니다. 그리고 getFundamentalNetProfit() * 4의 의미는 현재 분기 당기순이익을 1년치 당기순이익으로 가정하고 계산하는 방식입니다.

다른 방식 중에 하나는 (getFundamentalNetProfit(0) + getFundamentalNetProfit(1) + getFundamentalNetProfit(2) + getFundamentalNetProfit(3)) 형태로 이전 분기 당기순이익의 합으로 계산할 수도 있습니다. 여기서 중요한 점은 현재 분기가 아닌 이전 분기 데이터를 사용할 때에는 stock.loadPrevData(0, 14, 0);와 같이 이전 데이터를 먼저 가져온 후에 사용해야합니다. loadPrevData() 함수의 매개 변수는 순서대로 (년, 월, 일)입니다.


3) 필터링 함수 정의 - 필터링 조건에 따라 종목들의 포함 여부 판단

필터링 함수는 시가총액, 일거래 대금 등의 필터링 기준에 따라 주식 종목들을 필터링합니다. 필터링 방법은 해당 종목의 필터링 조건이 true 일 경우(return true) 해당 종목이 포함되고, false 일 경우(return false) 포함되지 않습니다.


function stockFilter(stock) {
  // 코스닥 종목과 ETF 종목들을 제외합니다. 그리고 필요 시 관리코드(manage),
  // 대형/중형/소형 등의 구분코드(capLevel)도 필터링 조건으로 추가할 수 있습니다.
  // 자세한 내용은 Stock 객체 도움말(https://www.intelliquant.co.kr/help/ref/1)을 참고하세요.

  if (stock.market != 1 || stock.isETF) {
      return false;
  }

  var filterMarketCapital = (stock.getMarketCapital() > 50000);	// 시가총액 500억 이상 기준
  var filterTradingValue = (stock.getTradingValue() > 100);     // 일거래대금 1억 이상 기준
  var filterPER = (getPER(stock) > 0);                        	// PER 값이 마이너스인 경우 제외
  var filterPBR = (getPBR(stock) > 0);                        	// PBR 값이 마이너스인 경우 제외

  // 4개 필터링 조건을 모두 만족(true)할 경우에만 해당 종목(stock)이 포함됩니다.
  return (filterMarketCapital &&  filterTradingValue && filterPER && filterPBR);
}
      

위의 예제에서는 시가총액이 500억 이상(stock.getMarketCapital() > 50000)일 경우 filterMarketCapital 변수에 true 값이 저장되며, 4개 필터링 조건(filterMarketCapital, filterTradingValue, filterPER, filterPBR) 모두 true일 경우 해당 종목(stock)이 투자 유니버스(universe)에 포함됩니다.


4) 포트폴리오 빌더 함수 정의 - 필터링 및 팩터 기반 종목 선정

포트폴리오 빌더 함수는 필터링 함수에서 정의된 종목들로 유니버스(universe)로 구성하고 팩터 정의 함수를 통해 종목들을 선정합니다.

전체적인 순서는 필터링 함수로 유니버스를 구성한 후 팩터 정의 함수를 이용하여 팩터 별로 정렬(sort)합니다. 그리고 팩터들의 종합 순위를 구한 후 상위 종목들을 최종 포트폴리오로 구성합니다.


// 주식 포트폴리오 빌더 함수를 정의합니다.
function stockPortfolioBuilder(targetSize) {
  // 필터링 함수(stockFilter)의 필터링 조건에 맞는 종목들이 유니버스(universe) 변수에 저장됩니다.
  var universe = IQStock.filter(stockFilter);

  // 유니버스(universe) 변수의 모든 종목들을 원하는 팩터 값으로 다시 정렬(sort)합니다.
  // 여기서 sort() 함수는 자바스크립트에서 기본적으로 제공하는 함수이며 사용 방법은 이미 정의한 팩터 함수들을 이용하여
  // 오름차순(return getPER(a)-getPER(b);) 또는 내림차순(return getPER(b)-getPER(a);)으로
  // 유니버스 변수의 모든 종목들을 순서에 맞게 재배열합니다.

  var sortedByPer = universe.slice().sort(function(a,b){return getPER(a)-getPER(b);});  // 종목들을 PER 값의 오름차순으로 정렬
  var sortedByPbr = universe.slice().sort(function(a,b){return getPBR(a)-getPBR(b);});  // 종목들을 PBR 값의 오름차순으로 정렬

  // 유니버스의 모든 종목들에 대해 각각(sortedByPer, sortedByPbr)의 팩터 순위(indexOf(stock))를 구하고
  // 해당 stock 객체에 'rank_sum' 이름으로 종합 순위를 저장합니다.

  universe.forEach(function(stock) {
      stock.setScore('rank_sum', sortedByPer.indexOf(stock) + sortedByPbr.indexOf(stock));
  });

  // 유니버스의 모든 종목들을 종합 순위(rank_sum)로 다시 정렬합니다. (오름차순)
  var factorRank = universe.slice().sort(function(a, b){return a.getScore('rank_sum')-b.getScore('rank_sum');});

  // 종합 순위 1위 종목부터 targetSize 만큼 종목을 최종적으로 선정합니다.
  return factorRank.slice(0, targetSize);
}

// 인버스 ETF 포트폴리오 빌더 함수를 정의합니다.
function shortPortfolioBuilder(targetSize) {
  // 인버스 ETF(A114800) 종목을 담고 있는 stock 객체를 inverseETF 변수에 저장합니다.
  var inverseETF = IQStock.getStock("A114800");

  // inverseETF 변수를 배열(Array)에 담아서 전달합니다.
  return new Array(inverseETF);
}
      

포트폴리오 빌더 함수 사용 방법은 우선 바스켓 객체(Basket)의 setPortfolioBuilder()함수를 통해 포트폴리오 빌더 함수를 등록하고, 바스켓 객체의 buildPortfolio() 함수 실행 시 내부적으로 포트폴리오 빌더 함수가 자동으로 수행됩니다.


5) 리밸런싱 수행 - 시뮬레이션 기간 동안 장 마감 후 매일 자동으로 호출되는 함수

onDayClose(now) 함수는 영업일을 기준으로 매일 자동으로 호출되는 함수입니다. 이 함수에서는 리밸런싱 날짜에 해당하는지를 먼저 판단한 후 맞으면 리밸런싱을 수행합니다.

리밸런싱 주기는 onDayClose()함수로 전달되는 now 객체를 이용하여 일별/월별/년별 등으로 다양하게 설정할 수 있습니다. 그리고 onDayClose() 함수는 매일 실행되기 때문에 리밸런싱 수행 뿐만 아니라 필요에 따라 일별 수익률 또는 누적 수익률 등을 구할수도 있습니다.


var enterForInit = false;  // 시뮬레이션이 처음 시작할 때 최초 한번 리밸런싱(buildPortfolio())이 수행되도록 설정
var lastRebalMonth = 0;    // 이전 리밸런싱을 수행한 달(Month) 저장

// 시뮬레이션 기간 동안 장 마감 후(영업일 기준) 매일 자동으로 호출됩니다.
// 매개 변수로 전달되는 now 객체는 onDayClose() 함수가 실행되는 날짜 정보를 담고 있는 자바스크립트의 Date 객체입니다.
// 그래서 Date 객체가 지원하는 여러 기능들을 활용할 수 있습니다.

function onDayClose(now) {
  // onDayClose() 함수가 처음 실행됐거나 실행된 날짜가 15일인 경우 리밸런싱을 수행합니다.
  // 여기서 15일이 영업일이 아닐 수도 있기 때문에 15일보다 큰 경우로 설정하고 같은 달에는 한번만 실행되도록 설정합니다.

  if (enterForInit == false || (now.getDate() >= 15 && now.getMonth() != lastRebalMonth)) {
      // 디폴트 계좌에서 현재 계좌 평가액을 가져옵니다.
      var currentTotalEquity = IQAccount.getDefaultAccount().getTotalEquity();

      // 현재 계좌 평가액에 주식 투자 비중을 반영하여 주식 포트폴리오 예산을 다시 설정합니다.
      stock_basket.setBudget(currentTotalEquity * stock_weight);

      // 주식 리밸런싱을 수행합니다. 등록된 주식 포트폴리오 빌더 함수가 자동으로 호출됩니다.
      stock_basket.buildPortfolio();
      logger.debug("리밸런싱 실행날짜: " + now.toLocaleDateString() + ", 계좌 평가액: " + totalEquity);

      // 포트폴리오의 현재 계좌 평가액에 인버스 ETF 투자 비중을 반영하여 인버스 ETF 포트폴리오 예산을 다시 설정합니다.
      short_basket.setBudget(currentTotalEquity * short_weight);

      // 인버스 ETF 리밸런싱을 수행합니다. 등록된 인버스 ETF 포트폴리오 빌더 함수가 자동으로 호출됩니다.
      short_basket.buildPortfolio();

      lastRebalMonth = now.getMonth();      // 리밸런싱을 수행한 달(Month) 저장
      enterForInit = true;                  // 최초 진입 플래그를 true로 변경
  }
}
      


6) 시뮬레이션 종료 함수 - 시뮬레이션이 종료될 때 한번 자동으로 호출

onComplete() 함수는 시뮬레이션이 종료될 때 마지막으로 호출되기 때문에 시뮬레이션 기간 동안 onDayClose() 함수 내에서 필요에 따라 계산했던 일별 수익률 또는 로그 수익률 등의 최종 결과를 이곳에서 확인할 수 있습니다.


function onComplete() {
  logger.debug("계좌 총 평가금액은 " + parseInt(IQAccount.getDefaultAccount().getTotalEquity()) + "원 입니다.");
}