답변완료
시스템식 검증 부탁드립니다.
// =========================================================
// 1. 전역 변수 설정 및 매매 전략 스위치/설정 섹션
// =========================================================
// --- 기본 프로그램 설정 ---
var timerInterval = 5; // 5초 타이머 간격 (매 5초마다 특정 작업을 수행하도록 설정)
var HHMMSS = 0; // 현재 시분초 저장을 위한 변수
// --- 종목 관리 변수 ---
var MKList = []; // 검색 결과 종목 코드를 저장할 배열
var MK = []; // 실제 MarketData 객체(매수된 종목 등)가 담길 배열
var req = 0; // MKList에서 몇 번째 종목을 요청 중인지 나타내는 인덱스
// --- [? 중요 ?] 예수금 및 매수 설정 ---
// 초기 자산 설정 (전략 내에서 관리하거나 가상으로 설정할 금액)
// 실제 계좌 잔고는 Account1.Balance 등으로 확인됩니다.
var INITIAL_ASSET_AMOUNT = 5000000; // 500만원 (예시)
// 1회 매수 시 투입할 금액 (동일 종목 매수 시 한 번에 진입할 금액)
var ONE_TIME_BUY_AMOUNT = 100000; // 10만원
// 수익/손실 잔고 확인은 Main_OnRcvMarketData 또는 Main_OnTimer 내에서
// Account1 객체를 통해 실시간으로 확인하고 로그 출력
// --- [? 중요 ?] 매매 조건 활성화/비활성화 스위치 ---
var ENABLE_TRADING = true; // true: 전체 매매 로직 활성화, false: 모든 매매 중지
// 각 매수 조건 활성화/비활성화 스위치 (true: 활성화, false: 비활성화)
var USE_BUY_TYPE1_DNLINE = true; // 매수Type_1: 볼린저밴드 하단선 상향 돌파
var USE_BUY_TYPE2_MA_HIGH_CROSS = true; // 매수Type_2: 고가 5일MA가 고가 20일MA 상향 돌파
var USE_BUY_TYPE3_C_MA05_HIGH = true; // 매수Type_3: 종가 고가 5일MA 상향 돌파 AND 종가 CumulativeAvg 아래
var USE_BUY_TYPE4_C_MA20H_MIDL = true; // 매수Type_4: 종가 고가 20일MA & 미드라인 상향 돌파
var USE_BUY_TYPE5_ONCE = true; // 매수Type_5: 일단 (CrossDownFlag_MA05_MA20 && C < CumulativeAvg && C CrossUp CrossUp_DnLine_추적)
var USE_BUY_TYPE6_C_UP_기준고가 = true; // 매수Type_6: 현재가 UpLine 위에 있고, 이전 기준고가 돌파
var USE_BUY_TYPE7_THRICE = true; // 매수Type_7: 볼린저밴드 상단선 상향 돌파
var USE_BUY_TYPE8_TWICE_CUM_AVG = true; // 매수Type_8: 누적평균선 상향 돌파
var USE_BUY_TYPE9_BB_CONTRACTION_BREAKOUT = true; // 매수Type_9: BB 수축 후 상단선 돌파
// 각 매도 조건 활성화/비활성화 스위치 (true: 활성화, false: 비활성화)
var USE_SELL_TYPE1_THRICE_BREAKDOWN = true; // 매도Type_1: 삼단 고가 위에서 MA05_High 또는 UpLine 하향 돌파 시
var USE_SELL_TYPE2_KIJUN_HIGH_BREAKDOWN = true; // 매도Type_2: 갱신된 기준고가 라인 하향 돌파 시
// --- [? 중요 ?] 1일 최대 거래 횟수 제한 설정 ---
var MAX_TRADES_PER_DAY = 10; // 하루에 총 매수/매도 거래를 포함한 최대 거래 횟수 (매수 1회, 매도 1회가 각각 1회로 카운트)
var dailyTradeCount = 0; // 당일 총 거래 횟수 카운터
var lastTradeDate = ""; // 마지막 거래일 (YYYYMMDD 형식, 초기화 및 날짜 변경 감지에 사용)
// --- [? 중요 ?] 종목검색기 이름 설정 ---
// 예스스팟에 등록된 검색식 이름과 정확히 일치해야 합니다.
var POWER_SEARCH_NAME = "##5분봉 기준 고가 돌파";
// 청산 손절은 시스템설정으로 진행 (예스스팟 자동매매 설정창에서 설정 권장)
// --- [? 추가된 기능 ?] 검색된 종목 중 매수할 종목 개수 설정 ---
// 검색된 종목 리스트(MKList)에서 실제로 MarketData를 요청하고 매수를 시도할 종목의 최대 개수
// 1로 설정하면 검색된 종목 중 가장 첫 번째 종목만 매수를 시도합니다.
var MAX_REQUEST_MARKET_DATA_COUNT = 1; // 기본값: 1개 종목만 매수 시도
// =========================================================
// 2. 예스트레이더 전략 지표 및 상태 변수 (내부 사용)
// =========================================================
var BBPeriod = 60; // 볼린저밴드 기간
var BBMul = 2; // 볼린저밴드 배수
var MAPeriod = 20; // MA 기간 (20일 이동평균선에 사용)
var CumulativePeriod = 240; // 누적 평균 계산에 사용할 봉 개수 (약 1년 기준)
// 지표 값 및 상태 저장을 위한 변수
var 기준 = 0;
var 기준봉_신호발생 = false;
var 기준_고가 = 0; // 계단식 유지
var 기준_중심 = 0; // 계단식 유지
var 기준_저가 = 0; // 계단식 유지
var MidLine = 0;
var UpLine = 0;
var DnLine = 0;
var StdDevVal = 0;
var MA20 = 0;
var MA20_High = 0;
var MA05_High = 0;
var Signal1Occurred = false; // 체크 또는 준비 신호 발생 여부 플래그 (계단식 유지)
var CrossUpOccurredClose = 0; // 현재 봉에서 종가가 20MA 상향 돌파 시 종가 저장
var CumulativeAvg = 0; // 누적 평균 값을 저장할 변수 (계산 시 필요)
var Decline_Low = 0; // 하락 신호 발생 시 저가를 저장할 변수 (계단식 유지)
var BBWidth = 0; // 볼린저밴드 폭 (UpLine - DnLine)
var BBWidth_Prev = 0; // 이전 봉의 볼린저밴드 폭
var BBExpanded = false; // 볼린저밴드가 팽창했는지 여부 (계단식 유지)
var BBContracted = false; // 볼린저밴드가 수축했는지 여부 (계단식 유지)
var ThriceSignalHigh = 0; // 삼단 신호 발생 시의 고가 저장 (계단식 유지)
var CrossUp_DnLine_추적 = 0; // 준비 신호 발생 시 종가를 저장 (계단식 유지)
var CrossUpFlag_MA05_MA20 = false; // MA05_High가 MA20_High를 상향 돌파했는지 여부 (계단식 유지)
var CrossDownFlag_MA05_MA20 = false; // MA05_High가 MA20_High를 하향 돌파했는지 여부 (계단식 유지)
var CrossUpFlag_C_DnLine = false; // 종가가 DnLine을 상향 돌파했는지 여부 (계단식 유지)
// =========================================================
// 3. 자바스크립트 헬퍼 함수 (지표 계산 및 CrossUp/CrossDown 구현)
// =========================================================
// 이동평균선 계산 함수
// dataArray: [현재봉, 1봉전, 2봉전...] 순서의 데이터 배열
function calculateMA(dataArray, period) {
if (dataArray.length < period) return 0;
var sum = 0;
for (var i = 0; i < period; i++) {
sum += dataArray[i];
}
return sum / period;
}
// 표준편차 계산 함수
// dataArray: [현재봉, 1봉전, 2봉전...] 순서의 데이터 배열
function calculateSTD(dataArray, period) {
if (dataArray.length < period) return 0;
var sum = 0;
for (var i = 0; i < period; i++) {
sum += dataArray[i];
}
var mean = sum / period;
var sumOfSquares = 0;
for (var i = 0; i < period; i++) {
sumOfSquares += Math.pow(dataArray[i] - mean, 2);
}
return Math.sqrt(sumOfSquares / period);
}
// 상향 돌파 체크 함수
// currentVal: 현재 값, prevVal: 이전 값
// currentRefVal: 현재 기준 값, prevRefVal: 이전 기준 값
function isCrossUp(currentVal, prevVal, currentRefVal, prevRefVal) {
return prevVal < prevRefVal && currentVal > currentRefVal;
}
// 하향 돌파 체크 함수
function isCrossDown(currentVal, prevVal, currentRefVal, prevRefVal) {
return prevVal > prevRefVal && currentVal < currentRefVal;
}
// MarketData에서 최근 N개의 데이터를 배열로 추출하는 헬퍼 함수
// MarketData 객체의 과거 데이터 접근 방식에 따라 수정이 필요할 수 있습니다.
// 여기서는 MarketData.Close[i] 와 같은 배열 인덱싱 방식을 가정합니다.
function getRecentData(marketDataField, period) {
var arr = [];
for (var i = 0; i < period; i++) {
// marketDataField[i]가 존재하는지 확인 (데이터 부족 시 undefined)
if (typeof marketDataField[i] !== undefined) {
arr.push(marketDataField[i]);
} else {
break; // 데이터가 부족하면 루프 중단
}
}
return arr; // [현재봉, 1봉전, 2봉전...] 순서로 반환
}
// =========================================================
// 4. 프로그램 시작 시 실행되는 함수: Main_OnStart()
// =========================================================
function Main_OnStart()
{
var d = new Date();
HHMMSS = d.getHours()*10000 + d.getMinutes()*100 + d.getSeconds();
var today = "" + d.getFullYear() + (d.getMonth() + 1 < 10 ? "0" : "") + (d.getMonth() + 1) + (d.getDate() < 10 ? "0" : "") + d.getDate();
lastTradeDate = today; // 프로그램 시작 시 오늘 날짜로 초기화
Main.MessageList(HHMMSS,"|자동매매 시작");
Main.SetTimer(1, timerInterval*1000); // 1번 타이머를 (5초 * 1000) = 5000ms 간격으로 설정
}
// =========================================================
// 5. 타이머가 울릴 때마다 실행되는 함수: Main_OnTimer(nEventID)
// =========================================================
function Main_OnTimer(nEventID)
{
var d = new Date();
HHMMSS = d.getHours()*10000 + d.getMinutes()*100 + d.getSeconds();
var today = "" + d.getFullYear() + (d.getMonth() + 1 < 10 ? "0" : "") + (d.getMonth() + 1) + (d.getDate() < 10 ? "0" : "") + d.getDate();
// 날짜가 바뀌면 일일 총 거래 횟수 초기화
if (dailyTradeCount > 0 && today !== lastTradeDate) {
dailyTradeCount = 0;
lastTradeDate = today;
Main.MessageList(HHMMSS, "|일일 총 거래 횟수 초기화");
}
// 1번 타이머 (종목 검색 요청)
if (nEventID == 1 && HHMMSS >= 90000) // 오전 9시 이후에만 검색
{
Main.MessageList(HHMMSS,"|종목검색요청: " + POWER_SEARCH_NAME);
Main.ReqPowerSearch(POWER_SEARCH_NAME); // 설정된 검색기 이름 사용
}
// 2번 타이머 (종목 객체 재요청 - API 제한 발생 시)
if (nEventID == 2)
{
Main.KillTimer(2); // 2번 타이머 중지
Main.MessageList(HHMMSS,"|종목객체 재요청");
Main.ReqMarketData(MKList[req]); // req 인덱스에 해당하는 종목코드를 재요청
}
}
// =========================================================
// 6. 종목 검색 결과가 도착했을 때 실행되는 함수: Main_OnRcvItemList(aItemList, nCount)
// =========================================================
function Main_OnRcvItemList(aItemList, nCount)
{
var d = new Date();
HHMMSS = d.getHours()*10000 + d.getMinutes()*100 + d.getSeconds();
Main.KillTimer(1); // 1번 타이머 중지 (검색 중복 방지)
Main.MessageList(HHMMSS,"|종목검색완료|검색된종목수:", nCount);
MKList = []; // 이전 검색 결과 초기화
if (nCount > 0)
{
// MAX_REQUEST_MARKET_DATA_COUNT에 따라 검색된 종목 수를 제한
var limitCount = Math.min(nCount, MAX_REQUEST_MARKET_DATA_COUNT);
Main.MessageList(HHMMSS,"|매수 시도할 최대 종목 수:", limitCount);
// 아직 매수된 종목객체(MK)가 전혀 없는 경우
if (MK.length == 0)
{
// 제한된 개수만큼 MKList에 저장
for (var i = 0; i < limitCount; i++) {
MKList.push(aItemList[i]);
}
}
else
{
// 이미 매수된 종목이 있을 경우, 중복되지 않는 종목 중 제한된 개수만 MKList에 추가
var addedCount = 0;
for (var a = 0; a < nCount; a++)
{
if (addedCount >= limitCount) break; // 설정된 개수만큼 추가했으면 중단
var Add = true;
for (var b = 0; b < MK.length; b++)
{
if (aItemList[a] == MK[b].code) // 이미 MK에 있는 종목이면
{
Add = false;
break;
}
}
if (Add == true)
{
MKList.push(aItemList[a]);
addedCount++;
}
}
}
Main.MessageList(HHMMSS,"|요청할 종목객체수 (필터링 후):",MKList.length);
}
// 새로 요청할 종목이 없다면, 1번 타이머 재설정 (다음 검색 대기)
if (MKList.length == 0)
{
Main.SetTimer(1, timerInterval*1000);
}
else // 새로 요청할 종목이 있으면 MarketData 요청 시작
{
req = 0; // 요청 인덱스 초기화
Main.MessageList(HHMMSS,"|종목객체요청",MKList[req]);
Main.ReqMarketData(MKList[req]); // 실제 종목 객체 생성 요청
}
}
// =========================================================
// 7. 종목 객체(실시간 데이터)가 도착했을 때 실행되는 함수: Main_OnRcvMarketData(MarketData)
// =========================================================
function Main_OnRcvMarketData(MarketData)
{
var d = new Date();
HHMMSS = d.getHours()*10000 + d.getMinutes()*100 + d.getSeconds();
if (MarketData.code == MKList[req]) // 수신된 MarketData 객체의 code가 현재 요청 중인 종목과 같다면
{
Main.MessageList(HHMMSS,"|종목객체수신:",MarketData.code);
// --- 실시간 데이터 및 이전 데이터 추출 ---
var C = MarketData.Close[0]; // 현재 종가
var H = MarketData.High[0]; // 현재 고가
var L = MarketData.Low[0]; // 현재 저가
var O = MarketData.Open[0]; // 현재 시가
var V = MarketData.Volume[0]; // 현재 거래량
var prev_C = MarketData.Close[1]; // 1봉 전 종가
var prev_H = MarketData.High[1]; // 1봉 전 고가
var prev_L = MarketData.Low[1]; // 1봉 전 저가
var prev_O = MarketData.Open[1]; // 1봉 전 시가
var prev_V = MarketData.Volume[1]; // 1봉 전 거래량
// 필요한 기간만큼의 과거 데이터 추출
var closes = getRecentData(MarketData.Close, Math.max(BBPeriod, MAPeriod, CumulativePeriod, 5) + 2);
var highs = getRecentData(MarketData.High, Math.max(MAPeriod, 5) + 2);
var prev_closes = closes.slice(1); // 1봉 전부터 시작하는 종가 배열
var prev_highs = highs.slice(1); // 1봉 전부터 시작하는 고가 배열
// 데이터 부족 시 지표 계산 및 매매 로직 스킵
if (closes.length < Math.max(BBPeriod, MAPeriod) || highs.length < Math.max(MAPeriod, 5) || prev_closes.length < Math.max(BBPeriod, MAPeriod) || prev_highs.length < Math.max(MAPeriod, 5)) {
Main.MessageList(HHMMSS, "|데이터 부족 (지표 계산 및 매매 스킵)", MarketData.code);
// 데이터가 충분하지 않으면 다음 종목 요청으로 넘어감
MK.push(MarketData);
req = req + 1;
if (req < MKList.length) {
Main.MessageList(HHMMSS,"|종목객체요청",MKList[req]);
var S = Main.ReqMarketData(MKList[req]);
if (S == -1) { Main.MessageList(HHMMSS,"|종목객체생성 15초제한"); Main.SetTimer(2, 15000); }
} else { Main.MessageList(HHMMSS,"|종목객체생성끝"); Main.SetTimer(1, timerInterval*1000); }
return;
}
// --- 예스트레이더 계산 섹션 로직 ---
기준 = (H + L + O) / 3 * V / 100000000; // 기준 값 계산
// 볼린저밴드 및 이동평균선 계산
MidLine = calculateMA(closes, BBPeriod);
StdDevVal = calculateSTD(closes, BBPeriod);
UpLine = MidLine + (StdDevVal * BBMul);
DnLine = MidLine - (StdDevVal * BBMul);
MA20 = calculateMA(closes, MAPeriod);
MA20_High = calculateMA(highs, MAPeriod);
MA05_High = calculateMA(highs, 5);
// 이전 봉 지표 값 계산
var prev_MidLine = calculateMA(prev_closes, BBPeriod);
var prev_DnLine = prev_MidLine - (calculateSTD(prev_closes, BBPeriod) * BBMul);
var prev_UpLine = prev_MidLine + (calculateSTD(prev_closes, BBPeriod) * BBMul);
var prev_MA20 = calculateMA(prev_closes, MAPeriod);
var prev_MA20_High = calculateMA(prev_highs, MAPeriod);
var prev_MA05_High = calculateMA(prev_highs, 5);
// 볼린저밴드 폭과 추세 계산
BBWidth = UpLine - DnLine;
BBWidth_Prev = prev_UpLine - prev_DnLine;
// BBExpanded 및 BBContracted 상태 업데이트 (계단식)
var currentBBExpanded = BBExpanded; // 현재 봉의 상태를 임시 저장
var currentBBContracted = BBContracted;
if (BBWidth > BBWidth_Prev) {
currentBBExpanded = true;
currentBBContracted = false;
} else if (BBWidth < BBWidth_Prev) {
currentBBExpanded = false;
currentBBContracted = true;
} // else 문 없음: 이전 상태 유지 (기존 값 유지)
BBExpanded = currentBBExpanded; // 전역 변수 업데이트
BBContracted = currentBBContracted;
// 플래그 변수 업데이트: MA05_High와 MA20_High (계단식)
var currentCrossUpFlag_MA05_MA20 = CrossUpFlag_MA05_MA20;
var currentCrossDownFlag_MA05_MA20 = CrossDownFlag_MA05_MA20;
if (isCrossUp(MA05_High, prev_MA05_High, MA20_High, prev_MA20_High)) {
currentCrossUpFlag_MA05_MA20 = true;
currentCrossDownFlag_MA05_MA20 = false;
} else if (isCrossDown(MA05_High, prev_MA05_High, MA20_High, prev_MA20_High)) {
currentCrossUpFlag_MA05_MA20 = false;
currentCrossDownFlag_MA05_MA20 = true;
}
CrossUpFlag_MA05_MA20 = currentCrossUpFlag_MA05_MA20;
CrossDownFlag_MA05_MA20 = currentCrossDownFlag_MA05_MA20;
// 플래그 변수 업데이트: 종가(C)와 DnLine (계단식)
var currentCrossUpFlag_C_DnLine = CrossUpFlag_C_DnLine;
if (prev_H < prev_DnLine && isCrossUp(C, prev_C, DnLine, prev_DnLine)) {
currentCrossUpFlag_C_DnLine = true;
} else { // 조건 불만족시 초기화 (예스트레이더 동작 확인 필요)
currentCrossUpFlag_C_DnLine = false;
}
CrossUpFlag_C_DnLine = currentCrossUpFlag_C_DnLine;
// 20MA 상향 돌파 시점 종가 누적 계산을 위한 값 설정 (계단식)
var currentCrossUpOccurredClose = CrossUpOccurredClose; // 이전 값 유지
if (prev_H < prev_MA20 && isCrossUp(C, prev_C, MA20, prev_MA20)) {
currentCrossUpOccurredClose = C;
} else {
currentCrossUpOccurredClose = 0; // 돌파가 없으면 0으로 리셋 (예스트레이더 로직 따름)
}
CrossUpOccurredClose = currentCrossUpOccurredClose;
// 누적 평균선 계산 (이 부분의 구현이 MarketData 과거 데이터 접근 방식에 따라 가장 크게 달라질 수 있습니다.)
// 가장 안정적인 방법은 별도의 배열에 MarketData.Close[i]와 MarketData.MA20[i]를 저장하고 직접 순회하는 것입니다.
var tempSumOfCloses = 0;
var tempCrossUpCount = 0;
// 임시 로직: 최근 CumulativePeriod 봉을 돌면서 가상으로 CrossUpOccurredClose 값을 추적
// 실제 구현에서는 MarketData.Close[i]와 MarketData.MA20[i]의 관계를 정확히 봐야 함.
for (var i = 0; i < CumulativePeriod; i++) {
// 이 부분은 예스스팟의 MarketData 객체가 과거 지표 값을 제공하는 방식에 따라
// 매우 정교한 구현이 필요합니다. 현재는 단순화된 형태입니다.
// 예: if (MarketData.GetNthData(i+1, "High") < MarketData.GetNthData(i+1, "MA20") &&
// isCrossUp(MarketData.GetNthData(i, "Close"), MarketData.GetNthData(i+1, "Close"),
// MarketData.GetNthData(i, "MA20"), MarketData.GetNthData(i+1, "MA20"))) {
// tempSumOfCloses += MarketData.GetNthData(i, "Close");
// tempCrossUpCount += 1;
// }
}
if (tempCrossUpCount > 0) {
CumulativeAvg = tempSumOfCloses / tempCrossUpCount;
} else {
CumulativeAvg = 0;
}
// 볼린저밴드 하단선 상향 돌파 시 종가 추적 라인 계산 (계단식)
var currentCrossUp_DnLine_추적 = CrossUp_DnLine_추적;
if (prev_H < prev_DnLine && isCrossUp(C, prev_C, DnLine, prev_DnLine)) {
currentCrossUp_DnLine_추적 = C;
} else { // 조건 불만족시 초기화 (예스트레이더 동작 확인 필요)
currentCrossUp_DnLine_추적 = 0;
}
CrossUp_DnLine_추적 = currentCrossUp_DnLine_추적;
// 기준봉 신호 발생 조건 (계단식)
var prev_기준 = (MarketData.High[1] + MarketData.Low[1] + MarketData.Open[1]) / 3 * MarketData.Volume[1] / 100000000;
var prev_prev_기준 = (MarketData.High[2] + MarketData.Low[2] + MarketData.Open[2]) / 3 * MarketData.Volume[2] / 100000000;
기준봉_신호발생 = (기준 >= 1) && (O < C) && (C - O > (H - C) * 1.2) && (기준 >= ((prev_기준 + prev_prev_기준) / 2) * 3);
// 기준선 설정 (계단식)
var current_기준_고가 = 기준_고가;
var current_기준_중심 = 기준_중심;
var current_기준_저가 = 기준_저가;
if (기준봉_신호발생) {
current_기준_고가 = H;
current_기준_중심 = (H + L) / 2;
current_기준_저가 = L;
}
기준_고가 = current_기준_고가;
기준_중심 = current_기준_중심;
기준_저가 = current_기준_저가;
// 하락 신호 처리 (계단식)
// Signal1Occurred는 매수Type_2에서 true로 설정되어야 함.
var currentSignal1Occurred = Signal1Occurred;
var currentDecline_Low = Decline_Low;
if (currentSignal1Occurred && isCrossDown(C, prev_C, MA20, prev_MA20)) {
currentSignal1Occurred = false;
currentDecline_Low = L;
} else { // 조건 불만족시 초기화 (예스트레이더 동작 확인 필요)
currentDecline_Low = 0;
}
Signal1Occurred = currentSignal1Occurred;
Decline_Low = currentDecline_Low;
// --- 매수 조건 체크 및 주문 실행 ---
var current_shares = Account1.GetPosition(MarketData.code).Amount; // 현재 보유 수량
var current_price_ask3 = MarketData.Ask(3); // 매수 시 호가 (매도3호가)
// 전체 매매 활성화 및 일일 거래 횟수 제한 확인
if (ENABLE_TRADING && dailyTradeCount < MAX_TRADES_PER_DAY) {
// 매수Type_1: 볼린저 하단선 상향 돌파
if (USE_BUY_TYPE1_DNLINE && current_shares == 0 && prev_H < prev_DnLine && isCrossUp(C, prev_C, DnLine, prev_DnLine)) {
Account1.OrderBuy(MarketData.code, Math.floor(ONE_TIME_BUY_AMOUNT / current_price_ask3), current_price_ask3, 0);
Main.MessageList(HHMMSS, "|매수주문-Type1", MarketData.code, "수량: " + Math.floor(ONE_TIME_BUY_AMOUNT / current_price_ask3), "가격: " + current_price_ask3);
dailyTradeCount++;
}
// 매수Type_2: 고가 5일MA가 고가 20일MA 상향 돌파
if (USE_BUY_TYPE2_MA_HIGH_CROSS && current_shares == 0 && prev_MA05_High < prev_MA20_High && isCrossUp(MA05_High, prev_MA05_High, MA20_High, prev_MA20_High)) {
Signal1Occurred = true; // 체크 신호 발생 플래그 설정
Account1.OrderBuy(MarketData.code, Math.floor(ONE_TIME_BUY_AMOUNT / current_price_ask3), current_price_ask3, 0);
Main.MessageList(HHMMSS, "|매수주문-Type2", MarketData.code, "수량: " + Math.floor(ONE_TIME_BUY_AMOUNT / current_price_ask3), "가격: " + current_price_ask3);
dailyTradeCount++;
}
// 매수Type_3: 종가 고가 5일MA 상향 돌파 AND 종가 CumulativeAvg 아래
if (USE_BUY_TYPE3_C_MA05_HIGH && current_shares == 0 && prev_H < prev_MA05_High && isCrossUp(C, prev_C, MA05_High, prev_MA05_High) && (C < CumulativeAvg) && CumulativeAvg > 0) { // CumulativeAvg가 0이면 조건 충족 안함
Account1.OrderBuy(MarketData.code, Math.floor(ONE_TIME_BUY_AMOUNT / current_price_ask3), current_price_ask3, 0);
Main.MessageList(HHMMSS, "|매수주문-Type3", MarketData.code, "수량: " + Math.floor(ONE_TIME_BUY_AMOUNT / current_price_ask3), "가격: " + current_price_ask3);
dailyTradeCount++;
}
// 매수Type_4: 종가 고가 20일MA & 미드라인 상향 돌파
if (USE_BUY_TYPE4_C_MA20H_MIDL && current_shares == 0 && prev_H < prev_MA20_High && prev_H < prev_MidLine && isCrossUp(C, prev_C, MA20_High, prev_MA20_High) && isCrossUp(C, prev_C, MidLine, prev_MidLine)) {
Account1.OrderBuy(MarketData.code, Math.floor(ONE_TIME_BUY_AMOUNT / current_price_ask3), current_price_ask3, 0);
Main.MessageList(HHMMSS, "|매수주문-Type4", MarketData.code, "수량: " + Math.floor(ONE_TIME_BUY_AMOUNT / current_price_ask3), "가격: " + current_price_ask3);
dailyTradeCount++;
}
// 매수Type_5: 일단 (하락라인 돌파)
if (USE_BUY_TYPE5_ONCE && current_shares == 0 && CrossDownFlag_MA05_MA20 && (C < CumulativeAvg) && CumulativeAvg > 0 && isCrossUp(C, prev_C, CrossUp_DnLine_추적, CrossUp_DnLine_추적)) {
Account1.OrderBuy(MarketData.code, Math.floor(ONE_TIME_BUY_AMOUNT / current_price_ask3), current_price_ask3, 0);
Main.MessageList(HHMMSS, "|매수주문-Type5", MarketData.code, "수량: " + Math.floor(ONE_TIME_BUY_AMOUNT / current_price_ask3), "가격: " + current_price_ask3);
dailyTradeCount++;
}
// 매수Type_6: 현재가 UpLine 위에 있고, 이전 기준고가 돌파
if (USE_BUY_TYPE6_C_UP_기준고가 && current_shares == 0 && (C > UpLine) && isCrossUp(C, prev_C, 기준_고가, 기준_고가)) {
Account1.OrderBuy(MarketData.code, Math.floor(ONE_TIME_BUY_AMOUNT / current_price_ask3), current_price_ask3, 0);
Main.MessageList(HHMMSS, "|매수주문-Type6", MarketData.code, "수량: " + Math.floor(ONE_TIME_BUY_AMOUNT / current_price_ask3), "가격: " + current_price_ask3);
dailyTradeCount++;
}
// 매수Type_7: 볼린저밴드 상단선 상향 돌파
var currentThriceSignalHigh = ThriceSignalHigh; // 현재 봉의 ThriceSignalHigh 임시 저장
if (USE_BUY_TYPE7_THRICE && current_shares == 0 && prev_H < prev_UpLine && isCrossUp(C, prev_C, UpLine, prev_UpLine)) {
Account1.OrderBuy(MarketData.code, Math.floor(ONE_TIME_BUY_AMOUNT / current_price_ask3), current_price_ask3, 0);
Main.MessageList(HHMMSS, "|매수주문-Type7", MarketData.code, "수량: " + Math.floor(ONE_TIME_BUY_AMOUNT / current_price_ask3), "가격: " + current_price_ask3);
currentThriceSignalHigh = H; // 삼단 신호 발생 시 현재 봉의 고가 저장
dailyTradeCount++;
}
ThriceSignalHigh = currentThriceSignalHigh; // 전역 변수 업데이트
// 매수Type_8: 누적평균선 상향 돌파
if (USE_BUY_TYPE8_TWICE_CUM_AVG && current_shares == 0 && CumulativeAvg > 0 && prev_H < CumulativeAvg && isCrossUp(C, prev_C, CumulativeAvg, CumulativeAvg)) {
Account1.OrderBuy(MarketData.code, Math.floor(ONE_TIME_BUY_AMOUNT / current_price_ask3), current_price_ask3, 0);
Main.MessageList(HHMMSS, "|매수주문-Type8", MarketData.code, "수량: " + Math.floor(ONE_TIME_BUY_AMOUNT / current_price_ask3), "가격: " + current_price_ask3);
dailyTradeCount++;
}
// 매수Type_9: BB 수축 후 상단선 돌파
if (USE_BUY_TYPE9_BB_CONTRACTION_BREAKOUT && current_shares == 0 && BBExpanded && BBContracted && prev_H < prev_UpLine && isCrossUp(C, prev_C, UpLine, prev_UpLine)) {
Account1.OrderBuy(MarketData.code, Math.floor(ONE_TIME_BUY_AMOUNT / current_price_ask3), current_price_ask3, 0);
Main.MessageList(HHMMSS, "|매수주문-Type9", MarketData.code, "수량: " + Math.floor(ONE_TIME_BUY_AMOUNT / current_price_ask3), "가격: " + current_price_ask3);
dailyTradeCount++;
}
}
// --- 매도 조건 체크 및 주문 실행 ---
var current_shares_sell = Account1.GetPosition(MarketData.code).Amount; // 현재 보유 수량
var current_price_bid3 = MarketData.Bid(3); // 매도 시 호가 (매수3호가)
var avg_entry_price = Account1.GetPosition(MarketData.code).AvgPrice; // 평균 매수 단가
if (ENABLE_TRADING && dailyTradeCount < MAX_TRADES_PER_DAY && current_shares_sell > 0) { // 보유 수량이 있고, 거래 제한 내에서만 매도 조건 검토
// 매도Type_1: 삼단 고가 위에서 MA05_High 또는 UpLine 하향 돌파 시 전량 매도
if (USE_SELL_TYPE1_THRICE_BREAKDOWN && ThriceSignalHigh > 0 && prev_C > ThriceSignalHigh) { // 이전 봉 종가가 삼단 고가 위에 있었는지
if ((C > UpLine) && (isCrossDown(C, prev_C, MA05_High, prev_MA05_High) || isCrossDown(C, prev_C, UpLine, prev_UpLine))) {
Account1.OrderSell(MarketData.code, current_shares_sell, current_price_bid3, 0); // 전량 매도
Main.MessageList(HHMMSS, "|매도주문-Type1", MarketData.code, "수량: " + current_shares_sell, "가격: " + current_price_bid3);
var tradeProfitLoss = (current_price_bid3 - avg_entry_price) * current_shares_sell;
Main.MessageList(HHMMSS, "|매도Type1 수익/손실:", tradeProfitLoss.toFixed(0) + "원");
dailyTradeCount++;
}
}
// 매도Type_2: 갱신된 기준고가 라인 하향 돌파 시 매도
if (USE_SELL_TYPE2_KIJUN_HIGH_BREAKDOWN && C < 기준_고가) {
Account1.OrderSell(MarketData.code, current_shares_sell, current_price_bid3, 0); // 전량 매도
Main.MessageList(HHMMSS, "|매도주문-Type2", MarketData.code, "수량: " + current_shares_sell, "가격: " + current_price_bid3);
var tradeProfitLoss = (current_price_bid3 - avg_entry_price) * current_shares_sell;
Main.MessageList(HHMMSS, "|매도Type2 수익/손실:", tradeProfitLoss.toFixed(0) + "원");
dailyTradeCount++;
}
}
// --- 잔고 확인 및 로그 출력 ---
var totalBalance = Account1.Balance; // 현재 예수금 (주문 가능 금액)
var totalEvaluationProfitLoss = 0;
var positions = Account1.GetPositionList(); // 현재 보유 종목 리스트
for (var i = 0; i < positions.length; i++) {
var pos = positions[i];
// 현재 종목이 MarketData.code와 일치하는 경우의 현재가 사용, 아니면 GetPrice 등으로 최신가 가져와야 함.
// 여기서는 단순화하여 현재 MarketData.code의 현재가를 사용 (모든 보유 종목에 대해 정확하진 않을 수 있음)
var currentPosPrice = (pos.code == MarketData.code) ? C : Account1.GetPrice(pos.code).Current;
totalEvaluationProfitLoss += (currentPosPrice - pos.AvgPrice) * pos.Amount;
}
Main.MessageList(HHMMSS, "|현재 잔고 | 예수금: " + totalBalance.toFixed(0) + "원 | 총 평가손익: " + totalEvaluationProfitLoss.toFixed(0) + "원");
// --- 다음 종목 객체 요청 로직 ---
MK.push(MarketData); // 해당 종목의 MarketData 객체를 MK 배열에 추가
req = req + 1; // 다음 종목 요청 인덱스를 1 증가
// MAX_REQUEST_MARKET_DATA_COUNT를 초과하지 않고, 아직 요청할 종목이 남아 있다면
if (req < MKList.length && req < MAX_REQUEST_MARKET_DATA_COUNT)
{
Main.MessageList(HHMMSS,"|다음 종목객체요청",MKList[req]);
var S = Main.ReqMarketData(MKList[req]); // 다음 종목 요청
// 15초에 60번 이상 요청하면 시간제한(-1) 받을 수 있음
if (S == -1)
{
Main.MessageList(HHMMSS,"|종목객체생성 15초제한. 잠시 후 재시도.");
Main.SetTimer(2, 15000); // 제한에 걸리면 2번 타이머를 15초 후로 설정하여 재시도
}
}
else // 모든 요청을 끝냈거나 설정된 최대 개수를 초과했을 때
{
Main.MessageList(HHMMSS,"|모든 종목객체 생성 완료 또는 최대 요청 개수 도달. 다음 검색 대기.");
Main.SetTimer(1, timerInterval*1000); // 다시 1번 타이머(검색)를 5초 간격으로 재시작
}
}
}
// =========================================================
// 8. 체결 정보 수신 시 실행되는 함수: Main_OnRcvChegyul(Chegyul)
// =========================================================
function Main_OnRcvChegyul(Chegyul) {
// 체결 정보 로그 출력 (예: 매수/매도, 종목코드, 수량, 가격 등)
Main.MessageList(HHMMSS, "|체결알림:", Chegyul.code, Chegyul.gubun == "1" ? "매수" : "매도", Chegyul.qty, "주", Chegyul.price, "원");
// 이 함수 내에서 별도로 보유 수량이나 평균 단가를 업데이트할 필요는 없습니다.
// Account1.GetPosition()을 통해 실시간으로 최신 정보를 조회할 수 있습니다.
}
안녕하세요 고생이많으십니다!!
위의 코드는 제미나이랑 같이만든건대요
스팟실행시 구동되고 종목검색 되고 매수종목선정후 매수준비까지 하는데 매수가안되고
계속 검색 반복합니다
계좌연결 확인했고 별다른 오류 메세지는 안뜨는 상황이고요
처음 실행시 약3-5분간 데이터객체 머라고 뜨다가 검색 시작하고요
번거로우시겠지만 검증 및 수정 부탁드립니다!!
그럼 수고하세요!!