Knuth님의 글 (https://www.intelliquant.co.kr/article/870?forum=8)을 보고 인상깊어서 여러모로 만져보던 와중 막힌부분이 있어 질문드립니다.
(Knuth님 코드는 편의상 PER지표와 마켓타이밍 부분 빼고 지웠습니다. )
분기별 데이터가 업데이트 될때 (4 6 9 12월 첫째날) PER로 종목을 선택하고, 코스닥이 3,5,10일 이평선을 동시하회할때 도망가는 알고리즘입니다.
재진입은 코스닥 종가가 3,5,10일 이평선 중 하나라도 상회하면 돌아옵니다.
지금 해결하고자 하는것은 다음과 같습니다.
지금 조건에선 4,6,9,12월 첫째날이 아니더라도 도망갔다가 다시 들어올때 stock_basket.buildPortfolio() 가 수행되고,
다시 돌아오는 시점의 가격기준으로 다시 바스켓이 구성되는지라 리밸런싱일에 고른 종목과 다른녀석들이 바스켓에 들어옵니다.
(가령 4월1일에 A B C D E를 선택했지만 중간에 도망갔다가 4월 15일에 돌아왔다면 그때 기준으로 재정렬해 A B C F G로 종목이 바뀌는...)
리밸런싱일에 A B C D E를 골랐다면 나중에 도망갔다가 다시 들어올때도 A B C D E를 사게 하고싶습니다.
도움말을 보면서 조금씩 수정해봤습니다만 잘 안되서 여러분들의 지혜를 구합니다.
var stock_basket;
var stock_num = 5;
var stock_weight = 0.96; // 주식 비중 (거래비용 고려 현금 4% 확보)
var draw_down_flag = false;
var currentTotalEquity;
var isFirst = true; // 시뮬레이션 시작일에 바로 포트폴리오 신규 구성을 하기 위해 사용될 상태 변수
// 초기화
function initialize() {
stock_basket = new Basket(IQAccount.getDefaultAccount(), stock_num, IQEnvironment.aum * stock_weight);
stock_basket.setPortfolioBuilder(stockPortfolioBuilder);
IQAccount.getDefaultAccount().accountName = "마켓타이밍"
IQDate.addRebalSchedule(IQDate.setYearly(6, 1)); // 1분기 재무재표 리밸런싱
IQDate.addRebalSchedule(IQDate.setYearly(9, 1)); // 2분기 재무재표 리밸런싱
IQDate.addRebalSchedule(IQDate.setYearly(12, 1)); // 3분기 재무재표 리밸런싱
IQDate.addRebalSchedule(IQDate.setYearly(4, 1)); // 4분기 재무재표 리밸런싱
}
/// 평가지표 블록
// PER
function per(stock) {
if (stock.getFundamentalProfit_ownersOfParent() === 0) return -1;
if (stock.getFundamentalNetProfit() === 0) return -1;
var cap = stock.getMarketCapital(0) * 1000
return cap/(stock.getFundamentalProfit_ownersOfParent() * 4) ;
}
// 상승 하락장 판단문
function KOSDAQ_filter() {
var kosdaq = IQIndex.getIndex('301');
var MA_3 = kosdaq.getMA(3);
var MA_5 = kosdaq.getMA(5);
var MA_10 = kosdaq.getMA(10);
var current_price = kosdaq.getClose();
if ((current_price < MA_3 && current_price < MA_5 && current_price < MA_10 )) {
stock_weight = 0;
stock_basket.reset();
draw_down_flag = true;
logger.debug("Out");
} else {
stock_weight = 0.96;
if (draw_down_flag) {
currentTotalEquity = IQAccount.getDefaultAccount().getTotalEquity();
stock_basket.setBudget(currentTotalEquity * stock_weight);
stock_basket.buildPortfolio();
draw_down_flag = false;
logger.debug("In " + stock_weight);}
}
}
// 3) 필터링 함수 정의 - 필터링 조건에 따라 종목들의 포함 여부 판단
function stockFilter(stock) {
if (stock.isETF) return false;
return true;
}
// 4) 포트폴리오 빌더 함수 정의 - 필터링 및 팩터 기반 종목 선정
function stockPortfolioBuilder(targetSize) {
var universe = IQStock.filter(stockFilter);
var sortedByPer = universe.slice().sort(function(b,a){return per(a) - per(b);});
universe.forEach( function(stock) {
stock.setScore('rank_sum', sortedByPer.indexOf(stock));
stock.setScore('buy_price', stock.getAdjClose());
});
var sortedByRankSum = universe.sort(function(b,a) {
return a.getScore('rank_sum') - b.getScore('rank_sum');
})
var modelPortfolio2 = sortedByRankSum.slice(0, stock_num);
return modelPortfolio2;
}
// 5) 리밸런싱 수행 - 시뮬레이션 기간 동안 장 마감 후 매일 자동으로 호출되는 함수
function onDayClose(now) {
KOSDAQ_filter();
if (IQDate.isRebalancingDay(now) || isFirst === true) {
logger.debug('리밸런싱을 진행합니다!');
currentTotalEquity = IQAccount.getDefaultAccount().getTotalEquity();
logger.debug('현재 계좌 평가액 : ' + Math.floor(currentTotalEquity));
if (!draw_down_flag) {
stock_basket.reset();
stock_basket.setBudget(currentTotalEquity * stock_weight);
stock_basket.buildPortfolio();
}
isFirst = false;
}
}
// 6) 시뮬레이션 종료 함수 - 시뮬레이션이 종료될 때 한번 자동으로 호출
function onComplete() {
logger.debug("계좌 총 평가금액은 " + Math.floor(IQAccount.getDefaultAccount().getTotalEquity()) + "원 입니다.");
IQLive.addPortfolio(stock_basket, stock_weight);
}