...
This commit is contained in:
@@ -374,8 +374,6 @@ object AutoTradingManager {
|
||||
}
|
||||
|
||||
suspend fun sellingAfterMarketOnePrice(tradeService: KisTradeService,balance : UnifiedBalance,marketCode : String = "Y") {
|
||||
|
||||
// if (true) return
|
||||
balance.holdings.forEach { holding ->
|
||||
if (BLACKLISTEDSTOCKCODES.contains(holding.code)){
|
||||
println("❌ 차단 처리된 주식 : ${holding.name}")
|
||||
@@ -641,19 +639,27 @@ object AutoTradingManager {
|
||||
if (AUTOSELL) balance?.let { resumePendingSellOrders(KisTradeService, it) }
|
||||
return balance
|
||||
} else {
|
||||
// listOf<String>("Y","X").forEach { code ->
|
||||
// val now = LocalTime.now()
|
||||
// val currentMinute = now.minute
|
||||
// if((now.hour == 16 || now.hour == 17) && (currentMinute % 10 == 3 || currentMinute % 10 == 9)) {
|
||||
// if (lastForceCheckMinute != currentMinute) {
|
||||
// listOf<String>("Y","X").forEach { code ->
|
||||
// KisTradeService.fetchIntegratedBalance(code).getOrNull()?.let {
|
||||
// sellingAfterMarketOnePrice(KisTradeService, it, code)
|
||||
// }
|
||||
// delay(1000)
|
||||
// }
|
||||
// lastForceCheckMinute = currentMinute // 실행 완료 기록
|
||||
// }
|
||||
// }
|
||||
//
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun executeMarketLoop() {
|
||||
val balance = checkBalance()
|
||||
if (AUTOSELL) balance?.let { resumePendingSellOrders(KisTradeService, it) }
|
||||
// if (AUTOSELL) balance?.let { resumePendingSellOrders(KisTradeService, it) }
|
||||
|
||||
val myCash = balance?.deposit?.replace(",", "")?.toLongOrNull() ?: 0L
|
||||
val myHoldings = balance?.holdings?.filter { it.quantity.toInt() > 0 }?.map { it.code }?.toSet() ?: emptySet()
|
||||
@@ -733,16 +739,20 @@ object AutoTradingManager {
|
||||
}
|
||||
}
|
||||
else if((now.hour == 16 || now.hour == 17) && (currentMinute % 10 == 3 || currentMinute % 10 == 9)) {
|
||||
// if (lastForceCheckMinute != currentMinute) {
|
||||
// TradingLogStore.addAnalyzer(
|
||||
// " - ",
|
||||
// " - ",
|
||||
// "⏰ [강제 스케줄 실행] 오후 ${now.hour}시 ${currentMinute}분 - 보유주식 시간외 단일가 또는 대체마켓 체크를 시작합니다.",
|
||||
// true
|
||||
// )
|
||||
// checkBalance(false)
|
||||
// lastForceCheckMinute = currentMinute // 실행 완료 기록
|
||||
// }
|
||||
if (lastForceCheckMinute != currentMinute) {
|
||||
TradingLogStore.addAnalyzer(
|
||||
" - ",
|
||||
" - ",
|
||||
"⏰ [강제 스케줄 실행] 오후 ${now.hour}시 ${currentMinute}분 - 보유주식 시간외 단일가 또는 대체마켓 체크를 시작합니다.",
|
||||
true
|
||||
)
|
||||
listOf<String>("Y","X").forEach { code ->
|
||||
KisTradeService.fetchIntegratedBalance(code).getOrNull()?.let {
|
||||
sellingAfterMarketOnePrice(KisTradeService, it, code)
|
||||
}
|
||||
}
|
||||
lastForceCheckMinute = currentMinute // 실행 완료 기록
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1155,7 +1165,7 @@ class TechnicalAnalyzer {
|
||||
val disparityDaily = (currentPrice / ma20Daily) * 100
|
||||
|
||||
if (disparityDaily > 115.0) { // 20일선보다 15% 이상 떠 있으면 감점 시작
|
||||
val penalty = ((disparityDaily - 115.0) * 0.5).toInt() // 초과분 1%당 2점 감점
|
||||
val penalty = ((disparityDaily - 115.0) * 0.3).toInt() // 초과분 1%당 2점 감점
|
||||
short -= penalty
|
||||
ultra -= (penalty / 2) // 초단기에도 영향
|
||||
println("⚠️ [과열 감점] 일봉 이격도(${String.format("%.1f", disparityDaily)}%): -${penalty}점")
|
||||
@@ -1166,8 +1176,8 @@ class TechnicalAnalyzer {
|
||||
if (weekly.size >= 3) {
|
||||
val weeklyChange = calculateChange(weekly.takeLast(3))
|
||||
if (weeklyChange > 30.0) { // 3주간 30% 이상 급등 시
|
||||
mid -= 10
|
||||
short -= 5
|
||||
mid -= 6
|
||||
short -= 3
|
||||
println("⚠️ [과열 감점] 주봉 급등(${String.format("%.1f", weeklyChange)}%): -10점")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,71 +111,75 @@ object DynamicNewsScraper {
|
||||
fun extractSmartContentWithLineFilter(page: Page): String {
|
||||
val script = """
|
||||
() => {
|
||||
// 1. 선제적 노이즈 제거: 분석에 방해되는 태그들을 DOM에서 아예 삭제
|
||||
const junkTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'IFRAME', 'SVG', 'HEADER', 'FOOTER', 'NAV'];
|
||||
document.querySelectorAll(junkTags.join(',')).forEach(el => el.remove());
|
||||
// 1. 선제적 노이즈 제거: ASIDE(사이드바/광고) 태그 추가
|
||||
const junkTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'IFRAME', 'SVG', 'HEADER', 'FOOTER', 'NAV', 'ASIDE'];
|
||||
document.querySelectorAll(junkTags.join(',')).forEach(el => el.remove());
|
||||
|
||||
const MIN_LINE_LENGTH = 10;
|
||||
const MIN_TOTAL_LENGTH = 100;
|
||||
const CONSECUTIVE_THRESHOLD = 2;
|
||||
const MIN_LINE_LENGTH = 10;
|
||||
const MIN_TOTAL_LENGTH = 100;
|
||||
const CONSECUTIVE_THRESHOLD = 2;
|
||||
|
||||
// 2. 라인별 정제 함수 (짧은 라인 연속 시 예외 처리)
|
||||
const getRefinedText = (el) => {
|
||||
// 실제 텍스트만 추출하여 라인별로 분리
|
||||
const lines = el.innerText.split('\n').map(l => l.trim()).filter(l => l.length > 0);
|
||||
let resultLines = [];
|
||||
let tempBuffer = [];
|
||||
let consecutiveShort = 0;
|
||||
// 2. 라인별 정제 함수 (예외 처리 강화)
|
||||
const getRefinedText = (el) => {
|
||||
if (!el || !el.innerText) return "";
|
||||
const lines = el.innerText.split('\n').map(l => l.trim()).filter(l => l.length > 0);
|
||||
let resultLines = [];
|
||||
let tempBuffer = [];
|
||||
let consecutiveShort = 0;
|
||||
|
||||
lines.forEach(line => {
|
||||
if (line.length <= MIN_LINE_LENGTH) {
|
||||
consecutiveShort++;
|
||||
tempBuffer.push(line);
|
||||
} else {
|
||||
if (consecutiveShort < CONSECUTIVE_THRESHOLD) {
|
||||
resultLines = resultLines.concat(tempBuffer);
|
||||
}
|
||||
resultLines.push(line);
|
||||
tempBuffer = [];
|
||||
consecutiveShort = 0;
|
||||
}
|
||||
});
|
||||
|
||||
lines.forEach(line => {
|
||||
if (line.length <= MIN_LINE_LENGTH) {
|
||||
consecutiveShort++;
|
||||
tempBuffer.push(line);
|
||||
} else {
|
||||
// 짧은 줄이 연속되지 않았을 때만 버퍼를 결과에 합침
|
||||
if (consecutiveShort < CONSECUTIVE_THRESHOLD) {
|
||||
resultLines = resultLines.concat(tempBuffer);
|
||||
}
|
||||
resultLines.push(line);
|
||||
tempBuffer = [];
|
||||
consecutiveShort = 0;
|
||||
return resultLines.join('\n');
|
||||
};
|
||||
|
||||
// 3. 후보 블록 탐색
|
||||
const candidates = Array.from(document.querySelectorAll('div, section, article, p, main, td'))
|
||||
.map(el => ({
|
||||
el: el,
|
||||
refinedText: getRefinedText(el)
|
||||
}))
|
||||
.filter(item => {
|
||||
if (item.refinedText.length < MIN_TOTAL_LENGTH) return false;
|
||||
|
||||
const linkLength = Array.from(item.el.querySelectorAll('a'))
|
||||
.reduce((acc, a) => acc + (a.innerText || "").length, 0);
|
||||
return (linkLength / item.refinedText.length) < 0.3;
|
||||
});
|
||||
|
||||
// 4. 최적의 본문 컨테이너 선정
|
||||
const best = candidates.find(parent =>
|
||||
!candidates.some(child =>
|
||||
parent.el !== child.el &&
|
||||
parent.el.contains(child.el) &&
|
||||
child.refinedText.length > parent.refinedText.length * 0.8
|
||||
)
|
||||
);
|
||||
|
||||
let finalResult = best ? best.refinedText : (candidates.sort((a,b) => b.refinedText.length - a.refinedText.length)[0]?.refinedText || "");
|
||||
|
||||
// ⭐ 5. Fallback 로직: 스마트 추출에 실패했거나 너무 짧으면, 노이즈가 제거된 body 원문을 통째로 반환
|
||||
if (!finalResult || finalResult.length < MIN_TOTAL_LENGTH) {
|
||||
finalResult = getRefinedText(document.body);
|
||||
}
|
||||
});
|
||||
|
||||
// 마지막 남은 버퍼 처리 (본문 끝에 짧은 정보가 있을 경우 대비)
|
||||
if (consecutiveShort < CONSECUTIVE_THRESHOLD) {
|
||||
resultLines = resultLines.concat(tempBuffer);
|
||||
return finalResult;
|
||||
}
|
||||
return resultLines.join('\n');
|
||||
};
|
||||
|
||||
// 3. 후보 블록 탐색 및 텍스트 밀도 기반 분석
|
||||
const candidates = Array.from(document.querySelectorAll('div, section, article, p, main, td'))
|
||||
.map(el => ({
|
||||
el: el,
|
||||
refinedText: getRefinedText(el)
|
||||
}))
|
||||
.filter(item => {
|
||||
if (item.refinedText.length < MIN_TOTAL_LENGTH) return false;
|
||||
|
||||
// 링크 밀도 체크: 기사 본문은 보통 링크보다 텍스트 비중이 훨씬 높음
|
||||
const linkLength = Array.from(item.el.querySelectorAll('a'))
|
||||
.reduce((acc, a) => acc + (a.innerText || "").length, 0);
|
||||
return (linkLength / item.refinedText.length) < 0.3;
|
||||
});
|
||||
|
||||
// 4. 가장 최적의(가장 깊은 계층의) 본문 컨테이너 선정
|
||||
const best = candidates.find(parent =>
|
||||
!candidates.some(child =>
|
||||
parent.el !== child.el &&
|
||||
parent.el.contains(child.el) &&
|
||||
child.refinedText.length > parent.refinedText.length * 0.8
|
||||
)
|
||||
);
|
||||
|
||||
return best ? best.refinedText : (candidates.sort((a,b) => b.refinedText.length - a.refinedText.length)[0]?.refinedText || "");
|
||||
}
|
||||
""".trimIndent()
|
||||
""".trimIndent()
|
||||
|
||||
return page.evaluate(script) as String
|
||||
}
|
||||
@@ -259,8 +263,26 @@ object DynamicNewsScraper {
|
||||
}
|
||||
|
||||
private fun cleanText(text: String): String {
|
||||
return text.replace(Regex("(?m)^.*기자.*$"), "") // 기자 정보 제거
|
||||
.replace(Regex("(?m)^.*무단 전재.*$"), "") // 저작권 문구 제거
|
||||
// 1. 제거할 불필요한 노이즈 키워드 목록
|
||||
val noiseKeywords = listOf(
|
||||
"기자", "무단 전재", "재배포 금지", "Copyright", "ⓒ",
|
||||
"ADVERTISEMENT", "돌아가기", "topstarnews",
|
||||
"구독하기", "제보하기", "광고", "Sponsored"
|
||||
)
|
||||
|
||||
var cleanedText = text
|
||||
|
||||
// 2. 키워드가 포함된 '한 줄 전체'를 삭제
|
||||
for (keyword in noiseKeywords) {
|
||||
// (?im): 대소문자 무시(i) 및 다중행 모드(m)
|
||||
// 해당 키워드가 포함된 라인을 찾아서 빈 문자열로 치환
|
||||
val regex = Regex("(?im)^.*${Regex.escape(keyword)}.*\$")
|
||||
cleanedText = cleanedText.replace(regex, "")
|
||||
}
|
||||
|
||||
// 3. 연속된 빈 줄(엔터)들을 하나의 줄바꿈으로 압축하여 RAG 청크 품질 향상
|
||||
return cleanedText
|
||||
.replace(Regex("\\n{2,}"), "\n")
|
||||
.trim()
|
||||
}
|
||||
}
|
||||
@@ -286,7 +308,9 @@ object SafeScraper {
|
||||
try {
|
||||
withTimeout(25000L) { // 타임아웃 약간 증가
|
||||
val content = DynamicNewsScraper.fetchFullContent(item.originallink)
|
||||
println("📝 [News Raw Text Length] ${item.originallink.take(30)}... : ${content.length}자 추출됨")
|
||||
if (content.isNotBlank() && content.length > 100) {
|
||||
UrlCacheManager.addToCache(item.originallink)
|
||||
RagService.ingestWithChunking(
|
||||
text = content,
|
||||
newsLink = item.originallink,
|
||||
|
||||
@@ -114,7 +114,7 @@ object LlamaServerManager {
|
||||
val pb = ProcessBuilder(command)
|
||||
|
||||
// 2. 윈도우 Vulkan 환경 변수 설정
|
||||
if (isWin && binPath.endsWith("win-x64")) {
|
||||
if (isWin ) {
|
||||
val env = pb.environment()
|
||||
// 특정 GPU 선택 (내장 GPU가 여러 개일 경우)
|
||||
env["GGML_VULKAN_DEVICE"] = "0"
|
||||
@@ -158,10 +158,11 @@ object LlamaServerManager {
|
||||
}
|
||||
if (port == EMBEDDING_PORT){
|
||||
AutoTradingManager.llmNews = true
|
||||
RagService.active()
|
||||
}
|
||||
if (processes.size > 1) {
|
||||
println("[Cache] ${processes.size}")
|
||||
RagService.active()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user