백테스트 숫자를 파고들었다 — 그리고 안 좋은 소식이었다 실제 60일 워크포워드 백테스트 자산 곡선 (초기자본 1000만원) 지난 글에서 이 자율매매 엔진은 로컬 LLM이 결정하는 게 아니라 규칙 엔진( _guard_decisions )이 최종 결정을 내린다고 썼다. 손절/익절 강제, 국면별 매수 차단, 종목당 비중 한도 같은 것들이다. 그런데 규칙이 "안전하다"는 것과 그 규칙 조합이 "돈을 번다"는 것은 완전히 다른 질문이다. 이번 글은 그 질문에 정면으로 답한 기록이다. 워크포워드 백테스트는 뭘 하는 코드인가 run_walk_forward_backtest 는 과거 가격 히스토리를 하루하루 순회하면서 그날 시점까지의 데이터만 가지고 신호를 계산한다(미래 데이터를 미리 들여다보는 룩어헤드 편향을 막기 위해서다). 하루 루프 안에서 벌어지는 일은 이렇다. 보유 포지션마다 당일 시가 기준 수익률을 계산해서 손절(-5%)/익절(+12%)/기술점수 급락(-25 이하) 조건에 걸리면 매도 남은 슬롯( AI_BACKTEST_MAX_POSITIONS , 기본 8개) 안에서 기술점수 25 이상이면서 전일 급등 15% 미만인 종목을 점수 순으로 매수 매수 가능 종목들의 기술점수가 죄다 약세 쪽으로 쏠려 있으면(양의 점수 비중이 35% 이하) 그날은 아예 신규 매수를 걸지 않음 모든 체결에 슬리피지·수수료·매도세를 반영한 체결가( _fill_price )를 적용 이렇게 만들어진 자산 곡선에서 최종적으로 다음 지표를 뽑는다: 총수익률, 동일가중 벤치마크 대비 알파, 최대낙폭, 승률, 손익비(profit factor = 이익 합 / 손실 합) , 회전율(turnover = 누적 거래대금 / 초기자본) . 실제로 돌려본 숫자 (2026-09-01 기준) 기간 수익률 벤치마크 알파 최대낙폭 승률 손익비 회전율 판정 60일 +2.81% +26.77% -23.95%p -16.25% 30.4% 0.95 1223% fail 120일 +2.81% +18.43% -15.62%p -16.25% 30.4% 0.95 1223% fail 252일 +2.81% +18.43% -15.62%p -16.25% 30.4% 0.95 1223% fail 120일과 252일 결과가 똑같이 나온 건 버그가 아니라 단순한 이유다 — DB에 쌓인 가격 히스토리가 아직 64거래일치밖에 없어서, 120일 60일 워크포워드 백테스트 전략 성과 스코어카드 과 252일을 요청해도 실제로는 있는 데이터만큼만 돌기 때문이다. 데이터가 쌓일수록 세 기간의 숫자는 갈라질 것이다. _verdict 함수의 판정 기준은 이렇다: 최대낙폭이 -20% 이하이거나 알파가 -10%p 이하면 fail , 최대낙폭 -12% 이하이거나 알파가 음수면 warn , 나머지는 pass . 세 기간 전부 알파가 -15~-24%p 구간이라 가차없이 fail 이다. 숫자가 말하는 것 손익비 0.95는 이익 거래로 번 돈보다 손실 거래로 잃은 돈이 근소하게 더 크다는 뜻이다. 1 밑이면 거래를 반복할수록 평균적으로 까먹는 구조라는 뜻이고, 승률 30%라는 낮은 숫자와 합쳐 읽으면 "적게 이기고 크게 잃는" 패턴은 아니고 그냥 전반적으로 이기는 확률 자체가 낮다는 뜻에 가깝다. 회전율 1223%는 60거래일 동안 초기 자본의 12배가 넘는 금액이 매매로 오갔다는 뜻이다. 슬리피지 5bp + 수수료 1.5bp + 매도세 15bp를 합친 왕복 비용이 대략 0.2% 안팎인데, 회전율이 이렇게 높으면 이 작은 비용도 누적되면서 알파를 깎아먹는다. 규칙 자체의 방향성이 맞다 해도, 이 정도 회전율이면 거래 비용만으로 상당한 손해를 보고 시작하는 셈이다. 결론적으로 지금 조합(기술점수 임계값 25, 손절 -5%/익절 +12%, 8종목 분산)은 이 60 64일 구간에서 시장을 못 이겼다. 벤치마크(그냥 종목 50개를 동일 비중으로 사서 들고만 있는 것)가 +18 27%인데 실제 전략은 +2.8%다. 백테스트가 실제로 배포를 막아준 사례 이전 글들에서 다룬 로테이션 매도(기술 전망이 약해진 보유 종목을 선제적으로 정리하는 기능)와 거래량 상한 캡을 KIS 실계좌 엔진에 붙이면서, 로컬 시뮬레이터 엔진에도 같은 기능을 켤지 검토한 적이 있다. 코드를 켜기 전에 먼저 백테스트로 검증해봤는데, 결과가 알파 -48%p 안팎, 승률 33%, 손익비 0.89로 지금 숫자보다 더 나빴다. 그래서 로컬 엔진에는 이 기능을 켜지 않고 보류했다. 이게 이 검증 파이프라인의 실질적인 쓸모다 — "이 아이디어 괜찮아 보이는데?"라는 직감이 있어도, 백테스트 숫자가 더 나쁘게 나오면 배포하지 않는다. 코드를 다 짜놓고도 안 쓴 기능이 있다는 게 이상하게 들릴 수 있는데, 그게 이 파이프라인이 제 역할을 했다는 증거이기도 하다. 이 백테스트도 완벽하진 않다 체결가를 항상 당일 시가로 가정한다. 실제로는 그 가격에 그 수량이 다 체결된다는 보장이 없다 — 이전 글에서 다룬 유동성 문제가 여기엔 반영 안 돼 있다. 과거 데이터 위에서 잘 안 나온 조합이 미래에도 안 나온다는 보장은 없고, 반대로 과거에 잘 나왔다고 미래에도 잘 나온다는 보장도 없다. 워크포워드 방식이 룩어헤드는 막아주지만 과최적화까지 막아주진 않는다. 지금 데이터가 64거래일뿐이라 표본이 작다. 승률 30%, 손익비 0.95는 이 표본 안에서의 숫자이지, 통계적으로 안정된 장기 기대값은 아니다. 그래도 숫자를 감추지 않고 그대로 공개하는 이유는 똑같다. 파이프라인을 만드는 것과 그 파이프라인이 돈을 버는 것은 별개의 문제라는 걸, 이 프로젝트를 하는 나 스스로도 계속 확인하고 싶어서다. 이 프로젝트는 학습/실험 목적의 모의투자 시스템이며, 실제 투자 조언이 아닙니다. 코드는 개인 레포에서 관리 중입니다. GitHub 저장소: sunwoo8478/kis-paper-trading
1. Git / GitHub Git & GitHub를 사용해야하는 이유 버전 관리(Version Control)을 위해 버전 관리란? 파일이나 코드의 변경 이력을 체계적으로 기록하고 관리하는 것 언제, 누가, 무엇을 바꿨는지 알 수 있고, 필요하면 과거 상태로 되돌릴 수 있음 Git과 GitHub의 차이 1) Git 파일 복사와 달리 변경 내용이 커밋(commit) 단위로 기록됨 언제든 특정 시점으로 되돌릴 수 있음 여러 사람이 동시에 작업 가능 변경 이력과 책임이 명확함 2) Git vs. GitHub Git - 버전 관리를 위한 도구 (프로그램) & 내 컴퓨터에서 동작 GitHub - Git 저장소를 올려두는 웹 서비스 & 코드 공유 및 협업 플랫폼 (Pull Request, Issue, Review 기능 제공) Git은 버전 관리 엔진, GitHub는 협업과 공유를 위한 서비스 Repository 파일과 그 변경 이력을 함께 관리하는 저장소 프로젝트 전체와 모든 버전 기록이 들어 있음 로컬 저장소 : 내 컴퓨터에 있는 저장소 - 프로젝트 폴더로 이동 ex) git init 실행 -> 해당 폴더가 Git 레포지토리가 됨 원격 저장소 : GitHub 같은 서버에 있는 저장소 ex) GitHub에서 새 Repository 생성 - git clone으로 내 컴퓨터에 복사 Commit(커밋) 변경 사항을 하나의 기록으로 저장하는 단위 (=하나의 버전) "무엇을 왜 바꿨는지"를 메시지로 남김 특정 시점의 프로젝트 상태를 저장 되돌아갈 수 있는 기준점 역할 커밋 생성 : git commit -m "회원가입 기능 추기" Branch 독립적으로 작업할 수 있는 작업 공간 기존 코드를 건드리지 않고 기능 개발 가능 여러 기능을 동시에 개발할 수 있음 작업이 끝나면 병합(merge) 브랜치 생성 : git branch feat/login 브랜치 전환 : git switch feat/login 2. git 기본 명령어 git config Git의 기본 동작을 설정하는 명령어 전역 설정 : 모든 저장소에 적용 git config --global user.name"홍길동" git config --global user.email" hong@example.com " 로컬 설정 : 특정 저장소에만 적용 - 프로젝트마다 다른 계정이나 역할을 써야 할 떄 사용 (회사 프로젝트, 개인 프로젝트 구분) git config user.name"홍길동" git config user.email" hong@example.com " git init 현재 폴더를 Git 레포지토리로 초기화 .git 폴더가 생성. 현재 폴더를 기준으로 버전 관리 시작 git status 현재 레포지토리의 상태를 확인 수정된 파일 / 스테이징(staging) 여부 / 커밋 가능한 변경 사항 확인 git add 변경된 파일을 Staging Area로 올리는 명령어 커밋에 포함할 변경 사항을 선택하는 단계 (아직 커밋이 생성된 것은 아님) git commit Staging Area에 있는 변경 사항을 하나의 버전으로 기록 git commit -m"커밋 메시지" 커밋은 되돌릴 수 있는 기준점 역할 (ex) 게임 save file) 작성 규칙 (권장 규칙) 1) 명확하고 간결하게 작성 2) 동사로 시작 3) 한 커밋 = 한 작업 ex) Add login API / Fix password validation bug / Refactor user service git push (내보내기/업로드) 로컬 컴퓨터에서 작업하고 커밋한 변경사항을 원격 저장소(GitHub)로 올려보내는 명령어 내 코드를 팀원들과 공유하거나 GitHub 저장소에 최종 반영할 때 사용 파일 수정 후 변경 사항 스테이징 - git add. 로컬 저장소에 커밋 메시지 작성 - git commit -m "Feat: Add login feature" 원격 저장소(origin)의 main 브랜치로 푸시 - git push origin main git pull (가져오기 + 병합/다운로드) 원격 저장소(GitHub)에 업데이트된 최신 코드를 내 로컬 컴퓨터로 가져와서 현재 작업 중인 브랜치에 병합(Merge)하는 명령어 팀원이 새로 올린 코드를 받아오거나, 다른 환경에서 작업한 최신 상태를 내 컴퓨터에 반영할 때 사용 현재 작업 중인 브랜치 확인 - git branch 원격 저장소(origin)의 main 브랜치 최신 코드 불러와 병합 - git pull origin main 3. Branch git branch 명령어 1) Branch(브랜치) 기존 작업과 분리된 독립적인 작업 흐름 기능 개발, 실험, 수정 작업을 안전하게 진행할 수 있음 협업과 안정적인 배포를 위해 브랜치를 잘 관리하는 것이 중요 2) 주요 명령어 브랜치 목록 확인 : git branch 브랜치 생성 : git branch feature/ui 브랜치 전환 : git switch feature/ui 브랜치 관리(main, develop, feature, hotfix, release) main : 배포 가능한 안정 버전 dev : 개발 중인 기능을 테스트하는 버전 feature/* : 기능 개발 hotfix/* : 긴급 수정 release : 배포 전 최종 QA 및 버그 수정 / 출시 준비 작업 Fast-forward vs. 3-way merge 1) Fast-forward merge : 브랜치가 직선으로 이어질 때 / 단순히 커밋 포인터만 이동 2) 3-way merge : 브랜치가 갈라졌다가 다시 합쳐질 때 / 병합 커밋이 생성 분기가 없으면 Fast-forward, 분기가 있으면 3-way merge Merge conflict 같은 파일의 같은 부분을 서로 다르게 수정하면 충돌이 발생 해결 방법 1) 충돌 파일 확인 2) 충돌 코드 직접 수정 3) 새로운 커밋 생성 Git은 자동 병합을 시도하고, 판단이 필요한 경우만 충돌로 알려줌 Git 레포지토리 초기화 $ git init $ echo "hello" > test.txt $ git add. $ git commit -m "first commit" feature 브랜치 생성 $ git branch feature $ git switch feature 같은 파일 수정 $ echo "hello from feature" > test.txt $ git add. $ git commit -m "update from feature" main에서 다시 수정 $ git switch main $ echo "hello from main" > test.txt $ git commit -am "update from main" $ git merge feature # 병합 시도 $ cat test.txt # 충돌 내용 확인 파일 수정 $ vi test.txt $ git commit -am "resolve merge conflict" $ git log --oneline --graph
Travelling from the airport to a spiritual destination should be comfortable, convenient, and well planned. If you are landing at Rajkot Airport and planning to visit Somnath, booking a Rajkot Airport to Somnath Taxi can make your journey easier from the moment you arrive. Instead of depending on public transport or arranging a cab after landing, you can reserve a private taxi and travel directly to Somnath. Anjani Cabs provides taxi services for travellers looking for a convenient way to travel from Rajkot Airport to Somnath. Whether you are travelling alone, with family, or in a group, a private cab gives you the flexibility to travel according to your schedule. Comfortable Vehicles for Your Journey Choosing the right vehicle can make a noticeable difference during a long-distance journey. Anjani Cabs offers well-maintained cars suitable for different travel requirements, depending on availability and passenger needs. For individual travellers or small families, comfortable sedan options can be suitable. Travellers requiring additional space can choose larger vehicles such as SUVs. Clean interiors, air conditioning, comfortable seating, luggage space, and professional driving support are important features for a relaxed airport-to-destination journey. A suitable vehicle also allows passengers to keep their luggage safely while enjoying a comfortable ride throughout the trip. Rajkot Airport to Somnath Taxi Booking Made Easy Planning your journey in advance can save time and reduce last-minute travel stress. Rajkot Airport to Somnath Taxi Booking with Anjani Cabs can be arranged by sharing your travel details, such as pickup date, arrival time, number of passengers, and preferred vehicle type. Once your requirements are confirmed, you can plan your airport pickup accordingly. Advance booking is particularly useful when travelling during weekends, holidays, festivals, or peak pilgrimage periods when transportation demand may be higher. For booking assistance or travel enquiries, you can contact Anjani Cabs at +91-9725356821 . Rajkot Airport to Somnath Cabs for Different Travel Needs Rajkot Airport to Somnath Cabs can be a practical choice for different types of travellers. Families can enjoy a private ride with enough room for passengers and luggage, while couples can travel without having to coordinate with other passengers. Tourists can also use private cabs when they want greater flexibility during their Gujarat trip. If you are planning to visit other attractions around Somnath or continue your journey to another destination, discuss your itinerary with the cab provider while making your booking. Factors That Affect Taxi Pricing The cost of a Rajkot Airport to Somnath taxi service can vary depending on several factors. Vehicle category is one of the primary considerations because sedans, SUVs, and larger vehicles may have different rates. Other factors can include the travel date, pickup and drop-off locations, trip type, number of passengers, additional stops, waiting time, and seasonal demand. One-way and round-trip bookings can also have different pricing structures. For this reason, it is better to confirm the complete fare and inclusions before beginning your journey. This helps you understand the booking clearly and avoid unexpected costs. Rajkot Airport to Somnath Cab Service for a Stress-Free Journey A reliable Rajkot Airport to Somnath cab service can make airport travel much more convenient. Instead of searching for transportation after landing, your ride can be planned in advance according to your arrival schedule. A private cab also gives you greater control over your travel experience. You can sit comfortably, carry your luggage without sharing limited space with strangers, and travel directly towards your destination. For travellers arriving after a long flight, this convenience can make the journey to Somnath considerably more comfortable. Benefits of Booking a Rajkot Airport to Somnath One Way Cab A Rajkot Airport to Somnath One Way Cab is useful when Somnath is your final destination and you do not need the same taxi for the return journey. It allows you to book transportation specifically for the airport-to-Somnath route. One-way taxi travel can be convenient for tourists following a multi-city Gujarat itinerary. You can reach Somnath comfortably and make separate transportation arrangements for your next destination. It is also a practical option for travellers who prefer a direct private ride rather than combining multiple modes of transportation. How to Book Your Taxi with Anjani Cabs Booking your taxi can be completed with a few simple steps: Contact Anjani Cabs at +91-9725356821 . Share your Rajkot Airport pickup date and arrival time. Provide the number of passengers and luggage requirements. Select a suitable vehicle according to your travel needs. Confirm the fare, pickup details, and trip type. Keep the booking information handy for your airport pickup. Providing accurate flight and contact details can help make the pickup process smoother. FAQs About Rajkot Airport to Somnath Taxi 1. How can I book a Rajkot Airport to Somnath Taxi? You can contact Anjani Cabs at +91-9725356821 and share your pickup date, airport arrival time, passenger count, and vehicle requirements to enquire about your booking. 2. Can I book a Rajkot Airport to Somnath One Way Cab? Yes, travellers who only need transportation from Rajkot Airport to Somnath can enquire about a Rajkot Airport to Somnath One Way Cab according to their travel requirements. 3. Are Rajkot Airport to Somnath Cabs suitable for families? Yes. Private cabs can be convenient for families because they provide dedicated space for passengers and luggage and allow the group to travel together. 4. What affects the cost of a Rajkot Airport to Somnath taxi service? Pricing may depend on the vehicle type, travel date, trip type, passenger requirements, additional stops, waiting time, and other booking conditions. Confirming the fare before the journey is recommended. 5. Why should I book a Rajkot Airport to Somnath cab service in advance? Advance booking helps you plan your airport transfer before arrival and can reduce the stress of finding transportation after landing, particularly during busy travel periods. Conclusion A comfortable airport transfer can set the right tone for your Somnath trip. With a private Rajkot Airport to Somnath Taxi , you can enjoy direct travel, comfortable seating, luggage space, and greater flexibility throughout the journey. Whether you need a one-way ride, family transportation, or a convenient airport transfer, Anjani Cabs can help you plan your journey according to your requirements.
지난 시간까지 Oracle Database 19c Software와 ORCL Database를 생성하고 기본적인 Oracle Architecture를 살펴봤다. 이번 시간에는 Oracle Database에 접속하여 SQL*Plus의 주요 관리 명령어를 실습 하고, Oracle Architecture에서 Redo Log, Control File, Data File의 역할과 구조 를 조금 더 자세히 살펴봤다. 이후 Database가 시작될 때 어떤 File을 어떤 순서로 읽는지 확인하면서 STARTUP의 NOMOUNT → MOUNT → OPEN 단계와 각 단계에서 확인할 수 있는 상태 및 장애 범위 를 정리했다. 이번 시간의 주요 내용은 다음과 같다. SQL*Plus 접속 및 Database 상태 확인 ↓ 같은 User의 여러 Session 확인 ↓ SQL*Plus 주요 명령 / Script / SPOOL 실습 ↓ Online Redo Log / ARCHIVELOG / Control File / Data File ↓ 실습용 dcuser 생성 ↓ SHUTDOWN ↓ NOMOUNT → MOUNT → OPEN ↓ 각 단계의 Process / User 접속 / 장애 범위 확인 1. MobaXterm 접속이 안 될 때 IP 확인 실습 환경의 VMware Network는 DHCP를 사용하기 때문에 VM을 껐다 켜면 IP가 변경될 수 있다. VMware의 Linux Terminal에서: nmcli 를 실행하여 현재 IP를 확인한다. IP가 변경되었다면: MobaXterm → 기존 Session 우클릭 → Edit Session → Remote Host 수정 을 통해 새로운 IP로 변경하면 된다. MobaXterm 왼쪽에 표시되는 Session Name 은 실제 접속 IP와는 별개의 Bookmark 이름 이다. Session Name ≠ Remote Host IP 2. Oracle Database 접속 Tool 수업에서는 Oracle Database에 접속하기 위한 대표적인 Tool로 SQL*Plus 와 SQL Developer 를 소개했다. SQL*Plus Oracle Database Software가 설치되면 함께 설치되는 Command Line 기반 Tool이다. Oracle Engine 설치 ↓ SQL*Plus도 함께 설치 DBA나 Engineer가 Database를 관리할 때 많이 사용하는 Tool이다. SQL Developer Oracle에서 무료로 제공하는 GUI 기반 Tool이다. 별도로 Oracle 사이트에서 다운로드하여 사용하며, 개발자가 SQL 작성이나 Object 조회 등을 할 때 사용할 수 있다. 현업에서는 SQL Developer뿐 아니라: Orange TOAD DBeaver ... 와 같은 Tool도 많이 사용한다. 이번 수업에서는 주로 SQL*Plus 를 이용하여 Oracle을 관리했다. 3. Linux User와 Oracle Database User 구분 Oracle을 관리할 때 가장 먼저 구분해야 하는 것이 OS User와 Database User 이다. Linux OS User ├─ root ├─ oracle └─ ... Group ├─ oinstall ├─ dba └─ ... Oracle Database User ├─ SYS ├─ SYSTEM └─ 일반 Database User 즉: oracle → Linux OS User SYS / SYSTEM → Oracle Database User 이다. Oracle 설치 환경에서는 대표적으로 다음과 같은 Group을 사용한다. oinstall → Oracle Software 설치와 Inventory 관리에 사용하는 Group dba → Oracle Database의 관리자 권한과 연결되는 OSDBA Group 실습 환경에서는 oracle OS User가 dba Group에 포함되어 있고, Oracle 설치 시 해당 Group을 OSDBA Group으로 사용하도록 구성했다. 따라서 Local Server에서 oracle User는 OS Authentication을 이용하여: sqlplus / as sysdba 와 같이 SYSDBA 권한으로 접속할 수 있다. 단순화하면: Linux oracle User ↓ dba(OSDBA) Group ↓ OS Authentication ↓ SYSDBA 접속 구조이다. 왜 SYSDBA에 이러한 접속 방식이 필요한지는 뒤의 NOMOUNT / MOUNT / OPEN 단계 에서 다시 확인한다. 4. su - oracle 과 Oracle 환경변수 Oracle 관련 작업을 할 때는 oracle OS User로 전환한다. su - oracle → oracle User의 Login 환경까지 적용 su oracle → User만 변경 → Login 환경이 정상적으로 적용되지 않을 수 있음 oracle User의 Home Directory에는 다음 File이 존재한다. /home/oracle/.bash_profile 이 File에는 Oracle 관련 환경변수가 설정되어 있다. 대표적으로: ORACLE_BASE ORACLE_HOME ORACLE_SID PATH 가 있다. ORACLE_BASE Oracle 관련 관리 File과 Directory의 기준 경로이다. 이번 실습에서는: /u01/app/oracle 을 사용했다. ORACLE_HOME Oracle Software, 즉 Oracle Engine이 실제 설치된 경로 이다. /u01/app/oracle/product/19.0.0/dbhome_1 수업에서는: Oracle Engine Oracle Software Oracle Home 을 비슷한 의미로 사용했다. ORACLE_SID 현재 Server에서 작업할 Oracle Instance를 식별하는 값 이다. 이번 실습에서는: ORACLE_SID=ORCL 을 사용했다. ORCL 은 특별한 고정 이름이 아니라 실습에서 지정한 Sample 이름이다. 실제 업무 환경에서는 업무에 따라 다른 이름을 사용할 수 있다. PATH Linux에서 Command를 입력했을 때 실행 File을 검색할 Directory를 지정한다. Oracle 환경에서는 PATH에: $ORACLE_HOME/bin 이 포함되어 있기 때문에 어느 Directory에서든: sqlplus 를 실행할 수 있다. 5. SQL*Plus는 어떻게 실행되는가? su - oracle 을 하면 .bash_profile 을 통해 Oracle 환경변수를 읽게 된다. 이후: sqlplus 를 입력하면 Linux Shell은 PATH 를 검색한다. sqlplus 입력 ↓ PATH 검색 ↓ $ORACLE_HOME/bin ↓ sqlplus 실행 File 발견 ↓ SQL*Plus 실행 현재 환경을 직접 확인할 수도 있다. 예를 들어: $ command -v sqlplus /u01/app/oracle/product/19.0.0/dbhome_1/bin/sqlplus 와 같이 출력된다. sqlplus -v 는 Database에 접속하는 것이 아니라 SQL*Plus Program 자체가 정상적으로 실행되는지만 확인하는 명령 이다. SQL*Plus 실행 성공 ≠ Database 접속 성공 6. sqlplus: command not found 가 발생한다면 수업에서는 단순히 Error를 해결하는 것보다 어느 단계에서 문제가 발생했는지 추적하는 것 이 중요하다고 강조했다. 예를 들어: sqlplus 실행 시: sqlplus: command not found 가 발생한다면 다음 순서로 확인할 수 있다. su - oracle로 접속했는가? ↓ .bash_profile이 적용되었는가? ↓ ORACLE_HOME이 올바른가? ↓ PATH에 $ORACLE_HOME/bin이 존재하는가? ↓ sqlplus 실행 File이 실제로 존재하는가? ↓ 그래도 문제가 있다면 Oracle Engine 자체 확인 즉 Error가 발생했다고 바로 Oracle을 재설치하는 것이 아니라 실패한 단계부터 범위를 좁혀야 한다. 7. SYS와 SYSTEM의 인증 방식 Oracle Database에는 대표적인 관리자 User로: SYS SYSTEM 이 존재한다. 하지만 두 계정은 이번 수업에서 서로 다른 방식으로 접속했다. SYSDBA Local Server의 oracle OS User에서: sqlplus / as sysdba 를 사용했다. / 는 User Name과 Database Password를 직접 입력하지 않고 Local OS Authentication을 이용한다는 의미이다. SYSTEM SYSTEM은 Password Authentication 방식으로 접속했다. sqlplus system 또는 수업 실습에서는: sqlplus system/<password> 형태도 사용했다. SYSTEM은 관리자 계정이지만 SYSDBA와 같은 Local OS Authentication 접속을 사용한 것이 아니라 Database Password를 이용하여 인증했다. 이 차이에 대한 이유는 뒤의 Startup 실습에서 다시 확인한다. 8. Background Process로 Instance가 떠 있는지 확인 Oracle Database에 접속하기 전에 Linux에서 Oracle Background Process를 확인했다. 수업에서는 여러 Background Process 중 입력하기 편한 SMON 을 사용했다. ps -ef | grep smon Database Instance가 실행 중이라면 다음과 비슷한 Process를 확인할 수 있다. ora_smon_ORCL 하지만 여기서 중요한 점이 있다. SMON Process가 존재함 = Database가 정상적으로 OPEN됨 은 아니다. 뒤의 Startup 실습에서 확인하듯 NOMOUNT 상태에서도 SGA와 Background Process는 이미 생성된다. ( SHUTDOWN 상태에서 존재하지 않는다) 따라서 Process는 Instance가 어느 정도 올라와 있는지 빠르게 확인하는 수단 이고, Database의 정확한 상태는 별도로 확인해야 한다. 9. SQL*Plus 접속 후 Database 상태 확인 SYSDBA로 접속했다. sqlplus / as sysdba 그리고 실제 Database Instance 상태를 확인했다. SELECT instance_name, status FROM v$instance; 정상적으로 OPEN된 상태에서는: INSTANCE_NAME STATUS ---------------- ------------ ORCL OPEN 과 같이 나타난다. 즉: Linux Process 확인 → Instance Process 존재 여부 확인 V$INSTANCE.STATUS → 현재 Oracle Instance 상태 확인 으로 구분할 수 있다. 교수님은 특히 Background Process만 보고 “Database가 정상적으로 올라와 있다”고 판단하면 안 된다고 강조했다. 10. 같은 User라도 Session은 각각 생성된다 이번 시간에는 Terminal을 3개 열어 같은 Database User로 여러 Session을 연결한 뒤 V$SESSION 에서 확인하는 실습 을 진행했다. Terminal A → SYSTEM Terminal B → SYSTEM Terminal C → SYSDBA SYSTEM Password를 잊은 경우에는 SYSDBA에서 Password를 다시 지정할 수도 있다. 수업에서는 예시로: ALTER USER system IDENTIFIED BY oracle; 를 보여줬다. 이후 첫 번째와 두 번째 Terminal에서 각각 SYSTEM으로 접속했다. sqlplus system/<password> 세 번째 Terminal에서는 SYSDBA로 접속했다. sqlplus / as sysdba 이후 SYSDBA Session에서 V$SESSION 을 조회하여 SYSTEM으로 접속한 Session들을 확인했다. SELECT sid, serial#, username, status, program FROM v$session WHERE username = 'SYSTEM'; 같은 SYSTEM User로 접속했더라도 각각 다른 Session이므로 SID , SERIAL# 값이 서로 다르게 나타난다. 출력이 너무 길어 보기 어려웠기 때문에 Column Format도 변경했다. COL username FORMAT A15 COL program FORMAT A40 그리고: / 를 입력하여 SQL Buffer에 남아 있던 V$SESSION 조회 SQL을 다시 실행했다. 즉 이번 실습을 통해: Database User ≠ Session 하나의 Database User → 여러 Session 동시 생성 가능 이라는 점을 확인할 수 있었다. 11. 반복 SQL을 Script로 저장하고 @ 로 실행 DBA 업무에서는 같은 조회 SQL을 반복해서 사용하는 경우가 많다. 매번 긴 SQL을 다시 입력하는 대신 File로 저장해 두고 실행할 수 있다. 수업에서는 oracle User의 Home 아래에 Script를 관리할 Directory를 만들었다. mkdir -p /home/oracle/sql -p 옵션은 만약 상위 폴더가 없으면 같이 생성하게 하는 옵션이다. 이후 반복해서 사용할 Session 확인 SQL을: check_local_connection.sql 이라는 File로 저장했다. SQL*Plus에서는 @ 를 이용하여 SQL Script File을 실행할 수 있다. @파일경로 예를 들어: @/home/oracle/sql/check_local_connection.sql 과 같이 실행하면 Script File 안에 저장된 SQL이 순서대로 실행된다. 즉: 반복해서 사용할 SQL 작성 ↓ SQL Script File로 저장 ↓ @파일경로 ↓ Script 실행 형태로 사용할 수 있다. 긴 조회 SQL을 매번 직접 입력할 필요가 없기 때문에 반복적인 관리 작업에서 유용하다. 12. SP2-0310 원래 Script의 올바른 경로는 @/home/oracle/sql/check_local_connection.sql 이다. 만약 @/home/oracle/check_local_connection.sql 와 같이 다른 경로를 입력하면 SP2-0310: unable to open file ... Error가 발생했다. ORA- → Oracle Database / Oracle 관련 Error SP2- → SQL*Plus 자체에서 발생한 Error command not found → Linux Shell에서 명령을 찾지 못한 Error 단, 이번 Error의 원인은 OS File 경로에 있었지만 Error를 출력한 Layer는 SQL*Plus라고 구분하면 된다. 13. 주요 SQL*Plus 명령 이번 시간에는 SQL*Plus에서 자주 사용하는 명령들을 소개하고 실제로 여러 명령을 사용해봤다. 명령 약자 기능 CONNECT CONN Database 접속 변경 SHOW - SQL*Plus 환경 또는 접속 정보 확인 DESCRIBE DESC Object 구조 확인 LIST L SQL Buffer 확인 RUN R Buffer 내용을 표시한 뒤 실행 CHANGE C 문자열 치환 EDIT ED Editor에서 SQL 수정 SPOOL SPO SQL*Plus 출력 File 저장 COLUMN COL Column 출력 형식 설정 HOST ! SQL*Plus에서 OS Command 실행 CLEAR SCREEN CL SCR 화면 정리 @ @ SQL Script 실행 SET - SQL*Plus 환경 설정 EXIT - SQL*Plus 종료 14. CONN 으로 접속 User 변경 SQL*Plus를 종료하지 않고 접속 User를 변경할 수 있다. 먼저 현재 User: SHOW USER SYS였다면: USER is "SYS" SYSTEM으로 변경: CONN system/<password> 다시 확인: SHOW USER USER is "SYSTEM" 즉 매번: EXIT 한 뒤 다시 SQL Plus를 실행할 필요 없이 같은 SQL Plus 안에서 Connection을 변경할 수 있다. 15. SQL Buffer - L , / , R SQL*Plus는 최근 SQL 또는 PL/SQL Block 하나를 SQL Buffer에 보관한다. 예: SELECT instance_name, status FROM v$instance; L L Buffer에 저장되어 있는 SQL을 표시한다. 1 SELECT instance_name, status 2* FROM v$instance SQL을 실행하지는 않는다. / / 현재 SQL Buffer에 들어 있는 SQL을 바로 실행한다. R R Buffer 내용을 화면에 표시한 뒤 SQL을 실행한다. 정리하면: L → Buffer 확인 / → Buffer SQL 바로 실행 R → Buffer SQL 표시 후 실행 이다. 16. SPOOL 로 작업 내용 저장 DBA 작업에서는 어떤 명령을 수행했는지 기록을 남기는 것이 중요하다. 수업에서는 간단히: SPOOL /home/oracle/sql/a.txt 로 기록을 시작했다. 그 이후 SQL이나 Comment를 입력했다. 예: -- 세션 확인 명령 SELECT instance_name, status FROM v$instance; 기록을 종료할 때: SPOOL OFF 를 사용했다. 그리고 SQL*Plus에서 바로 Linux의 cat Command를 실행했다. ! cat /home/oracle/sql/a.txt File에 SQL과 실행 결과가 저장되어 있는 것을 확인했다. 즉: SPOOL File ↓ SQL*Plus 작업 ↓ SPOOL OFF ↓ 작업 기록 File 생성 이다. 17. SQL*Plus 출력 Format 설정 Database의 User 정보를 조회했다. SELECT username, account_status FROM dba_users; 처음에는 Column 폭이나 Page 크기 때문에 출력이 보기 불편했다. 현재 설정 확인: SHOW LINESIZE SHOW PAGESIZE 이후 출력 폭을 변경했다. SET LINESIZE 120 SET PAGESIZE 100 Column 폭도 지정했다. COL username FORMAT A25 COL account_status FORMAT A18 그리고: / 로 같은 SQL을 다시 실행하여 출력이 정리된 것을 확인했다. A25 는 문자 값을 화면에서 25자리 폭으로 표시하겠다는 의미이다. 18. ED 로 SQL Buffer 수정 앞에서 실행한: SELECT username, account_status FROM dba_users; SQL을 Editor에서 직접 수정했다. ED 를 입력하면 VI Editor가 열렸다. 기존 SELECT Column 뒤에: default_tablespace 를 추가했다. ED를 실행하면 SQL*Plus는 현재 SQL Buffer 내용을 기본 편집용 File인 afiedt.buf에 저장한 뒤 외부 Editor를 실행한다. Editor에서 수정 후 저장하고 종료하면 변경된 내용이 다시 SQL Buffer에 반영된다. 19. 행 번호를 이용한 SQL Buffer 수정 현재 Buffer 확인: L 기존 SQL이 2번 Line까지 있을 때 3번 Line을 추가했다. 3 WHERE username = 'SYSTEM' 이렇게 하면 세 번째 Line에 WHERE 절이 추가된다. 실행: / 이후 SYSTEM User만 조회되는 것을 확인했다. 20. C 로 문자열 치환 이번에는 WHERE 조건의: SYSTEM 을: SYS 로 변경했다. C 를 이용하여 특정 문자열만 바꿀 수도 있다. C/SYSTEM/SYS/ SYSTEM → SYS 로 문자열을 치환한다. 정리하면 SQL*Plus에서 SQL을 수정하는 방법은 3가지가 있다. ED → SQL 여러 줄을 제대로 편집할 때 Line 번호 입력 → 짧은 한 줄을 추가/통째로 수정할 때 C → 현재 줄에서 단어 몇 개만 고칠 때 21. DESC 로 Object 구조 확인 마지막으로 Object의 구조를 확인하는 방법을 실습했다. DESC dba_users DESC 는 실제 Data 행을 조회하는 명령이 아니라 해당 Object에: 어떤 Column이 존재하는가 Data Type은 무엇인가 NULL 제약 여부는 어떠한가 등의 구조를 확인하는 명령이다. DBA가 모든 Dictionary View의 Column을 외울 수는 없기 때문에 필요한 경우 DESC 로 구조를
Strengthening Lockout/Tagout: From Energy Isolation to Digital Control Lockout/Tagout (LOTO) is a critical workplace control designed to prevent one of the most dangerous events during maintenance: hazardous energy being released without warning. Despite established procedures, incidents linked to LOTO failures can still occur in manufacturing plants, refineries, utilities, and food processing environments. The weakness is often not a missing procedure. More commonly, controls are poorly applied, information is overlooked, or an isolation is accepted without proving it effective. As a result, more organizations are turning to digital safety systems to improve visibility, responsibility, and control throughout the isolation process. Understanding LOTO Lockout/Tagout is a structured process used to disconnect machinery from hazardous energy before servicing, maintenance, or repair begins. Depending on the equipment, that energy may come from electrical systems, hydraulic pressure, compressed air, heat, or stored mechanical force. A standard LOTO process involves stopping the equipment, identifying and isolating every energy source, applying locks and warning tags, checking that hazardous energy has been eliminated, and ensuring that re energization can only be carried out by authorized personnel. These steps help protect workers while reducing equipment damage and operational disruption. Why LOTO Controls Break Down Industrial incidents repeatedly reveal familiar weaknesses in LOTO practices across departments, work areas, and shifts. Incomplete Energy Isolation A team may disconnect the primary electrical supply but fail to recognize another active hazard, such as hydraulic pressure, compressed air, residual heat, or stored mechanical energy. Leaving just one source uncontrolled can result in a sudden release while maintenance work is underway. Skipping Verification Turning a switch off does not prove a zero energy state. Workers need to verify the isolation directly. Depending on labels, assumptions, or verbal confirmation instead of testing can allow hazardous energy to remain unnoticed. Weak Contractor Coordination Multiple contractors can introduce gaps in communication and responsibilities. Outside workers may overlook site specific LOTO requirements, fail to apply suitable personal locks, or misunderstand who controls isolation and re energization at the worksite. Informal Training A brief verbal explanation cannot establish competency on its own. Workers need equipment specific guidance, documented understanding, and clear instructions about locks, isolation, and authorized re energization. Production Pressure Pressure to restart equipment can encourage shortcuts. Workers may hurry through verification or assume another person has completed a required isolation step. Key Lessons From Industrial Incidents Repeated LOTO incidents highlight several essential practices: • Every maintenance worker involved should apply a personal lock. • Zero energy must be confirmed before work begins. • Procedures need to identify all energy sources, including stored energy. • Contractor activities should follow the same isolation expectations as site personnel. • Job safety analyses should be reassessed whenever conditions or energy states change. Effective LOTO is not simply paperwork. It depends on verification, communication, coordination, and oversight from task start until equipment is safely returned to service. How Digital LOTO and PTW Systems Help Paper based processes can make isolation conditions, approvals, audits, and contractor involvement harder to track across locations or shifts. Digital Permit to Work (PTW) and LOTO systems can place these activities in one centralized workflow, giving teams clearer visibility and control. This can make responsibilities easier to review before work starts and after isolation changes. Digital systems can support the management of: • Electronic lockout records • Real time permit status • Contractor coordination • Audit ready documentation • Equipment isolation verification • Training and competency records • Incident reporting and corrective actions An integrated HSE system can improve communication between operations, maintenance, and safety teams while reducing administrative delays. Creating a Stronger LOTO Culture Preventing hazardous energy incidents requires LOTO to become part of everyday work, not a compliance activity performed only when required. Regular audits, refresher training, consistent isolation practices, and digital monitoring can help reinforce reliable safety behavior. Structured energy isolation can strengthen compliance readiness, coordination, and worker confidence. Most importantly, stronger LOTO controls can help reduce the likelihood of severe injuries that may affect employees, operations, and business continuity. Read More @ https://toolkitx.com/blogsdetails.aspx?title=loto-failures-real-industrial-incidents
실제로 돌아가는 대시보드의 실시간 시장 스캐너 하루 동안 만난 버그 5개와 해결 순서 "로컬 LLM으로 자동매매 시켜보면 재밌겠다"에서 시작했다 KOSPI/KOSDAQ 모의투자 대시보드를 FastAPI + SQLite, Next.js로 만들고 있다. 로컬에서 돌아가는 LLM(Ollama + EXAONE)이 실시간으로 매수·매도 판단을 내리는 구조다. 자체 시뮬레이터로만 굴리다가 오늘 한국투자증권(KIS) Open API의 모의투자 서버에 실제로 붙여봤는데, 코드보다 개념 정리에서 더 오래 헤맸다. 그 기록을 남긴다. 구조 backend/ FastAPI + SQLite ai/ 자율매매 엔진 (로컬 / KIS 두 개, 독립 운용) execution/ 주문 실행 계층 (SimulatedExecutor / KisPaperExecutor) integrations/kis.py KIS Open API 클라이언트 api/ REST 엔드포인트 frontend/ Next.js + shadcn/ui 시세 조회와 주문 실행을 인터페이스로 분리해뒀다. 나중에 시뮬레이터를 실제 브로커로 갈아끼울 때 다른 코드는 안 건드리게 하려는 목적이었는데, 실제로 KIS를 붙일 때 이 결정 덕을 많이 봤다. "모의투자"가 사실 두 개다 KIS Open API를 처음 만지면 거의 반드시 헷갈리는 지점이 있다. "모의투자"라는 이름을 쓰는 서비스가 완전히 별개로 두 개 있다. 하나는 Open API 모의투자(VTS, openapivts.koreainvestment.com )로, 개발자용 상시 테스트 서버다. 앱키만 있으면 바로 쓸 수 있고 잔고·주문은 API로만 조회된다. 다른 하나는 KIS 웹·MTS 앱에서 신청하는 프로모션 "모의투자 대회"로, 완전히 다른 시스템이다. 계좌번호가 우연히 같은 걸 쓸 수 있게 되어 있어서, 처음엔 "내가 대회 순위에 안 뜨는 게 버그인가?" 하고 한참 찾아봤다. API로 낸 주문은 대회 순위에 절대 반영 안 된다. 서로 다른 시스템이라 그렇다. 오늘 만난 버그들 1. SQLite가 "database is locked"를 뱉는다 자율매매 엔진이 백그라운드 스레드에서 5분마다 DB를 건드리는데, 동시에 API 요청이 들어오면 락이 걸렸다. 기본 SQLite는 동시 쓰기에 관대하지 않다. conn.execute("PRAGMA journal_mode=WAL") conn.execute("PRAGMA busy_timeout=5000") 새 커넥션 열 때마다 이 두 줄만 추가하면 된다. WAL 모드는 리더가 라이터를 막지 않게 해주고, busy_timeout은 락이 걸려도 바로 에러 내지 않고 잠깐 기다렸다 재시도하게 해준다. 2. 죽은 커넥션을 재사용해서 RemoteDisconnected KIS 클라이언트가 requests.Session() 하나를 프로세스 전체에서 재사용하는 구조였다. 오래 살아있는 세션의 keep-alive 커넥션을 KIS 서버가 일방적으로 끊어버리는 경우가 있는데, 그 상태에서 재사용하면 RemoteDisconnected 가 터진다. from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry retry = Retry(total=2, connect=2, read=2, backoff_factor=0.3) session.mount("https://", HTTPAdapter(max_retries=retry)) 이거 하나로 간헐적 에러가 거의 다 사라졌다. 대신 죽은 커넥션을 새로 맺느라 응답이 몇 초 더 걸리는 트레이드오프는 생긴다. 3. 대량 시장가 주문이 미체결로 쌓인다 자율매매 엔진이 예산을 한 종목에 다 밀어넣으려다, 그 종목의 실제 유동성보다 훨씬 큰 수량을 시장가로 냈다. 절반도 안 체결되고 나머지는 미체결로 남았는데, 더 큰 문제는 이 미체결 주문 하나 때문에 엔진 전체가 다음 판단을 멈춰버렸다는 거다. 종목의 평균 거래량 대비 참여율로 주문 수량 자체를 상한선 걸어서 해결했다. 시장이 실제로 소화 가능한 물량만 주문하게 하니 미체결 문제가 거의 사라졌다. 4. 잔고 정산 지연 때문에 현금이 마이너스로 찍혔다 엔진이 "지금 남은 현금"을 기준으로 다음 매수 수량을 계산하는데, KIS 잔고 API의 정산 반영이 실시간이 아니었다. 사이클마다 "아직 현금 있네" 하고 계속 사들이다가, 실제로는 이미 다 써버린 상태가 됐다. 계좌의 추정 인출가능금액이 마이너스까지 갔다. 매수 직전에 KIS가 그 순간 실시간으로 계산해주는 진짜 주문가능금액을 한 번 더 확인하는 걸로 고쳤다. 우리가 계산한 예상치가 아니라, 브로커가 확인해주는 값을 최종 권위로 쓰는 게 맞다. 5. useSWR에 fetcher 함수를 잘못 넘긴 프론트엔드 실수 이건 좀 창피하다. // 버그 useSWR("/api/kis/buying-power", getKisBuyingPower); // 수정 useSWR("/api/kis/buying-power", () => getKisBuyingPower()); SWR은 fetcher(key) 형태로 호출한다. 첫 번째 인자를 종목코드로 기대하는 함수에 SWR 키 문자열이 그대로 들어가버려서, 실제로는 code=/api/kis/buying-power 라는 요청이 나가고 있었다. 로그에 이게 200번 넘게 쌓인 걸 보고서야 알아챘다. 백테스트 결과는 화려하지 않았다 여기까지 고치고 "그래서 이 AI가 돈은 버냐"가 궁금해서 워크포워드 백테스트를 돌렸다. 결과를 그대로 옮긴다. 수익률 -2.77% 벤치마크 대비 알파 큰 폭 마이너스 승률 21.7% 손익비 0.45 회전율 317% 단순 기술지표 스코어링 + 손절/익절만으로는 잦은 매매 비용을 이길 엣지가 없다는 뜻이다. 이 숫자를 그대로 공개하는 이유는, 파이프라인을 만드는 것과 그 파이프라인이 알파를 만드는 것은 완전히 다른 문제라는 걸 스스로 계속 확인하고 싶어서다. 오늘 배운 것 브로커 API 연동은 코드보다 개념 정리에서 더 오래 걸린다. 멀티스레드에서 SQLite를 쓴다면 WAL 모드는 옵션이 아니라 기본값이어야 한다. 그리고 자동매매 시스템에서는 "우리가 계산한 값"과 "브로커가 실제로 확인해주는 값"이 다를 수 있다는 걸 항상 염두에 둬야 한다 — 최종 주문 직전엔 후자를 믿는다. 다음 글에서는 로컬 LLM을 자율매매 판단에 붙인 구조를 좀 더 깊게 다뤄볼 생각이다. 이 프로젝트는 학습·실험 목적의 모의투자 시스템이며 투자 조언이 아닙니다. GitHub 저장소: sunwoo8478/kis-paper-trading
시장 개요 포춘 비즈니스 인사이트에 따르면, 전 세계 UHT 우유 시장 규모는 2025년 828억 6천만 달러 로 평가되었습니다 . 이 시장은 2026년 847억 3천만 달러 에서 2034년 1,278억 6천만 달러 로 성장할 것으로 예상되며 , 예측 기간 동안 연평균 성장률(CAGR)은 5.28%를 기록할 것으로 전망됩니다. 아시아 태평양 지역은 2025년 기준 35.02% 의 시장 점유율로 세계 시장을 주도했습니다 . 이러한 분석 결과는 "UHT 우유 시장, 2026-2034"라는 제목의 새로운 연구 보고서에 포함되어 있습니다. 분석 결과, 주요 유제품 가공업체들이 UHT(초고온 살균) 제품군을 확대하고 있는 것으로 나타났습니다. 이러한 시장 성장은 전 세계 우유 생산량 증가, 도시 인구 증가, 그리고 냉장 물류망이 고르지 않은 지역에서의 효율적인 유제품 유통 필요성 증가에 기인합니다. 이러한 추세는 전 세계적으로 편리하고 상온 보관이 가능한 유제품에 대한 강력한 수요를 보여줍니다. 시장 보고서에 소개된 주요 업체: 락탈리스(프랑스) 네슬레 SA (스위스) 다논 SA (프랑스) 프리슬란트캄피나(네덜란드) 알라 푸드(덴마크) 폰테라 협동조합 그룹(뉴질랜드) 중국 멍뉴 유제품 유한회사(중국) 내몽골 이리 산업 그룹 유한회사(중국) GCMMF / Amul (인도) 사푸토 주식회사(캐나다) 세그먼트 제품 유형별로 시장은 일반 UHT 우유, 가향 UHT 우유, 강화 UHT 우유 및 기타로 구분됩니다. 일반 UHT 우유는 가정, 소매 및 외식업 분야에서 가장 널리 소비되는 표준 우유 형태로 자리매김하여 시장 점유율 1위를 차지하며 시장을 주도하고 있습니다. 지방 함량에 따라 시장은 전지방, 저지방, 탈지방으로 분류됩니다. 전지방 UHT 우유는 풍부한 질감과 진한 맛, 그리고 특히 아시아, 유럽, 라틴 아메리카에서 가정 소비에 대한 폭넓은 선호도로 인해 시장을 선도하고 있습니다. 전 세계 대규모 생산 기반을 바탕으로 소 우유 부문이 시장을 주도하고 있습니다 . 시장은 원료에 따라 소, 물소, 염소 및 기타로 나뉩니다. 소 우유 부문은 상업적 우유 생산에서 압도적인 비중을 차지하고, 체계적인 유제품 수집 시스템을 갖추고 있으며, 대규모 UHT 가공에 적합하기 때문에 시장 성장을 이끌고 있습니다. 포장 유형별로 시장은 PET 병, 카톤, 테트라팩, 벌크 포장으로 나뉩니다. 테트라팩은 우수한 유통기한 덕분에 시장을 주도하고 있으며, 뛰어난 차단성, 보관 용이성, 무균 충전 적합성, 상온 유통망 전반에 걸친 효율적인 판매 가능성 등의 장점을 제공합니다. 지리적으로는 북미, 유럽, 아시아 태평양, 남미, 중동 및 아프리카 지역을 대상으로 시장을 조사합니다. 자세한 시장 분석 정보는 다음 링크를 참조하세요: https://www.fortunebusinessinsights.com/uht-milk-market-116147 보도 범위 이 보고서는 다음과 같은 내용을 제공합니다. 시장의 주요 성장 동력, 제약 요인, 기회 및 잠재적 과제. 지역 발전 상황에 대한 종합적인 분석. 주요 업계 참여 기업 목록. 시장 참여자들이 채택한 주요 전략. 최근 업계 동향에는 제품 출시, 파트너십, 합병 및 인수 등이 포함됩니다. 운전자 및 안전장치 전 세계 원유 공급량 증가와 편리성에 대한 수요 증가가 시장 성장을 견인할 전망입니다. UHT 우유는 소매 유통망 현대화가 진행되고 유통기한이 긴 제품을 선호하는 소비자가 많은 국가에서 특히 인기가 높으며, 슈퍼마켓, 전자상거래, 기관 유통 채널에 적합합니다. 하지만 일부 시장에서 소비자들이 냉장 보관된 신선한 우유의 맛을 선호하는 경향과 엄격하고 자본 집약적인 무균 가공 및 포장 요건이 시장 성장을 저해할 수 있습니다. 지역별 분석 아시아 태평양 지역의 급속한 도시화와 유제품 소비 확대가 시장 성장을 견인하고 있습니다. 아시아 태평양 지역은 UHT 우유 시장에서 지배적인 점유율을 차지하고 있으며, 예측 기간 동안 가장 빠르게 성장하는 지역으로 전망됩니다. 이러한 성장은 급속한 도시화, 가처분 소득 증가, 그리고 중국과 인도처럼 인구 밀도가 높고 지리적으로 다양한 시장에서 유통기한이 긴 유제품이 갖는 상업적 이점 덕분입니다. 유럽은 잘 정립된 유제품 소비 패턴과 대규모 조직화된 소매 네트워크를 보유한 성숙한 시장입니다. 이 지역의 성장은 특히 남유럽과 서유럽에서 UHT 우유가 식료품 소매 시장에 깊숙이 자리 잡은 데 기인하며, 이 지역 소비자들은 상온 우유에 매우 익숙합니다. UHT 우유 시장의 향후 성장 전망 UHT 우유 시장은 편리하고 상온 보관이 가능한 유제품에 대한 수요 증가와 개발도상국의 현대식 소매 유통망 확장에 힘입어 견조한 성장세를 보이고 있습니다. 시장의 미래는 부가가치 혁신에 의해 더욱 좌우될 것이며, 특히 영양 강화, 맛 첨가, 무유당, 기타 기능성 UHT 우유 제품에 대한 수요가 증가하고 있습니다. 이러한 제품들은 건강을 중시하는 소비자들의 니즈를 충족시키고, 학교 급식 프로그램 및 수출 시장에서 새로운 가능성을 열어줍니다. 많은 지역에서 냉장 물류가 여전히 어려운 과제로 남아 있는 가운데, 상온 보관 유제품의 전략적 중요성이 더욱 커지고 있으며, 이는 무균 포장 및 가공 기술에 대한 투자를 촉진하고 있습니다. 경쟁 환경 브랜드 강점과 유통기한이 긴 제품 혁신에 집중하여 시장 입지를 강화해야 합니다. 시장은 락탈리스, 네슬레, 다논과 같은 주요 업체들이 브랜드 강점, 대규모 우유 공급망, 광범위한 소매 유통망을 바탕으로 경쟁하는 비교적 과점화된 형태입니다. 이들 선도 기업들은 부가가치가 높은 흰 우유, 다양한 맛의 제품, 포장 혁신에 투자하여 성장을 가속화하고 있습니다. 국경을 넘어 사업을 확장하고, 프리미엄 제품을 출시하며, 운영 효율성을 높이는 데 집중하는 이들의 적극적인 전략은 시장 성장의 원동력이 되고 있습니다. 주요 산업 동향 2026년 1월: 태국의 락타소이(Lactasoy Co.)는 새로운 브랜드 베네핏(Benefitt)으로 SIG의 유연한 무균 충전 기술을 사용하여 국내 최초로 고단백 UHT 우유를 출시했으며, 이를 통해 상온 보관이 가능한 카톤 용기에 담아 더욱 폭넓게 유통할 수 있게 되었습니다. 2025년 11월: 알마라이 그룹의 자회사인 베이티 푸드 인더스트리는 이집트 최초로 PET 병에 담긴 UHT 우유를 출시하며 현지 유제품 업계에 혁신을 가져왔습니다. 2025년 10월: 나렌드라 모디 인도 총리는 라자스탄주 빌와라 우유 생산자 협동조합에 위치한 라자스탄 최초의 UHT 우유 가공 및 무균 포장 공장을 화상으로 개소했습니다. 2025년 4월: 프랑스 유제품 회사 말로 데어리(Malo Dairy)는 엘로팩(Elopak)의 퓨어팩 센스(Pure-Pak Sense) 포장 플랫폼을 사용하여 지속 가능하고 상온 보관이 가능한 UHT 우유를 카톤 용기에 담아 출시했습니다. 2020년 10월: 콜하푸르에 본사를 둔 유제품 협동조합인 고쿨은 도시 소비자를 위한 장기 보관 제품으로 포지셔닝된 UHT 처리 우유 브랜드 '고쿨 셀렉트'를 출시했습니다. 일본 시장 트렌드를 살펴보세요 - 최신 UHT 우유 시장 분석 자료를 확인하세요
LLM 제안과 규칙 엔진(_guard_decisions)이 나뉘는 판단 흐름 시세 조회와 주문 실행을 분리한 자율매매 아키텍처 지난 글에서는 한국투자증권 Open API 연동 과정에서 만난 버그들을 다뤘다. 이번엔 조금 다른 이야기다. 이 자율매매 엔진은 판단을 어디서, 어떻게 내리는가. 그리고 그 판단을 얼마나 믿어도 되는가. 결론부터 말하면, 이 시스템에서 로컬 LLM은 매매를 결정하지 않는다 . 후보를 제안할 뿐이고, 실제로 주문이 나갈지 말지는 전부 규칙 기반 코드가 정한다. 이 글은 왜 그렇게 설계했는지, 그리고 그 경계선이 코드에서 정확히 어디에 그어져 있는지에 대한 기록이다. 왜 로컬 LLM인가 Ollama로 EXAONE 3.5 7.8B 모델을 돌린다. 클라우드 LLM API를 안 쓰는 이유는 단순하다. 매 사이클(5분)마다 계좌 잔고와 보유 종목, 기술적 지표를 프롬프트에 실어 보내야 하는데, 이걸 외부 API로 매번 보내면 지연 시간도 늘고 계좌 정보가 외부로 나간다. 로컬에서 돌리면 네트워크 의존이 없고, API 요금 걱정 없이 5분마다 마음껏 호출할 수 있다. 대신 대가가 있다. 7.8B급 로컬 모델은 GPT-4급 모델보다 추론 품질이 떨어진다. 이 트레이드오프를 감안하고 설계한 게 다음 절의 핵심이다 — 모델이 틀려도 시스템이 무너지지 않게 만드는 것 . 판단이 흐르는 경로 한 사이클( run_cycle )이 실행될 때 벌어지는 일을 순서대로 적으면 이렇다. 1. 후보 종목 압축 _rank_candidates 가 DB에 있는 후보 종목 전체의 가격 히스토리를 계산해서 기술적 편향 점수( technical_bias.score )를 매긴다. 절대값이 25 미만인 약한 신호는 버리고, 남은 것 중 점수 절대값이 큰 순서로 최대 12개만 추린다. 여기서 이미 대부분의 종목이 걸러진다 — LLM은 전체 종목을 보는 게 아니라 이미 한 번 필터링된 12개짜리 요약을 받는다. 2. 프롬프트 구성 _build_prompt 가 총자산, 현금, 누적수익률, 최대낙폭, 시장 국면, 보유 종목, 그리고 앞서 추린 후보 목록을 텍스트 한 덩어리로 만든다. 프롬프트 안에는 이런 지시문도 박혀 있다. 시장 국면 {market_regime}. structural_decline/recession_rebalance/bearish 국면에서는 신규 매수를 제안하지 마라. 위험 한도를 지키면서 가용 현금을 전액 분산하도록 매수 후보를 충분히 선택하라. 3. 모델 호출 ask_local_model 이 Ollama의 /api/generate 에 system 프롬프트와 user 프롬프트를 실어 던진다. temperature 0.1 로 낮게 고정해서 매 사이클마다 결과가 크게 요동치지 않게 한다. 응답 안에서 json ... 블록을 정규식으로 뽑아내는데( extract_json_block ), 이 파싱이 실패하면 그냥 decisions 가 빈 리스트로 처리된다 — 모델이 이상한 형식으로 답해도 시스템이 에러로 죽지 않고 조용히 "이번엔 제안 없음"으로 넘어간다. 핵심: LLM은 제안만 하고, 결정은 코드가 한다 모델이 뽑아낸 decisions 는 그대로 실행되지 않는다. _guard_decisions 라는 규칙 엔진을 반드시 통과해야 한다. 이 함수가 하는 일은 크게 두 갈래다. 첫째, LLM과 무관하게 강제로 발동하는 룰이 있다. 보유 종목의 수익률이 손절 기준(기본 -5%)에 닿으면 LLM이 뭐라고 하든 무조건 매도 결정을 만들어 넣는다. 익절 기준(기본 +12%)에 닿아도 마찬가지로 절반 매도를 강제한다. 이 두 개는 모델의 제안 목록과 별도로, 코드가 직접 만들어서 앞에 끼워 넣는 결정이다. 둘째, LLM이 제안한 매수/매도 후보들은 아래 조건을 전부 통과해야 실제 주문으로 이어진다. 일일 손실 한도(기본 -3%)를 넘긴 상태면 신규 매수 전체 차단 시장 국면이 bearish / structural_decline / recession_rebalance 로 분류되면 신규 매수 전체 차단 기술점수가 25 미만이면 차단 ( weak_signal ) 당일 등락률이 +15% 이상이면 추격 매수 차단 ( price_spike ) 종목당 비중이 설정된 최대치를 넘으면 차단 ( position_limit ) 최근 체결한 종목이면 쿨다운 시간 동안 재매수 차단 ( cooldown ) 목표 노출 비중(시장 국면별로 계산되는 값)에 이미 도달했으면 차단 ( target_exposure_reached ) 사이클당 최대 주문 개수를 넘으면 차단 ( cycle_order_limit ) 차단된 항목은 그냥 버려지는 게 아니라 blocked_decisions 에 사유 코드와 함께 기록된다. 그래서 "이번 사이클에 왜 안 샀는지"를 나중에 그대로 다시 읽을 수 있다. 실제로 이 로그 덕분에 이전 글에서 다룬 대량 미체결 문제(유동성 무시한 사이징)를 원인까지 추적할 수 있었다. 이 이중 구조를 한 문장으로 요약하면: LLM은 "이 종목이 좋아 보인다"고 말할 수 있을 뿐이고, "실제로 사도 되는지"는 8개 넘는 하드코드 조건이 전부 통과해야 결정된다. 모델이 헛소리를 해도 손절/익절 강제 룰과 국면 차단, 비중 한도가 그 위에 얹혀 있어서 계좌 전체가 한 번의 나쁜 추론으로 무너지지 않는다. 이것도 한계가 있다 규칙 엔진이 안전판 역할을 하긴 하지만, 만능은 아니다. 모델이 보는 건 텍스트 요약뿐이다. 호가창 깊이, 실시간 뉴스, 거시 지표는 프롬프트에 없다. 가격 히스토리에서 뽑은 기술적 지표와 손익 숫자가 전부다. 규칙 자체가 틀릴 수 있다. 손절/익절 임계값, 국면 분류 로직, 비중 한도 — 이 숫자들은 전부 사람이 정한 값이다. 실제로 이전 글에서 워크포워드 백테스트를 돌려보니 이 조합의 수익률이 마이너스였다. 규칙이 안전하다는 것과 규칙이 돈을 번다는 것은 다른 문제다. 파싱 실패는 조용히 넘어간다. JSON 추출이 실패하면 그냥 제안이 없는 걸로 처리되는데, 이건 안전한 방향의 실패이긴 하지만 모델이 왜 실패했는지는 로그만 보고는 알기 어렵다. 7.8B 모델의 추론 한계. 프롬프트에 명시한 "국면별 매수 금지" 같은 지시를 무시하고 매수를 제안하는 경우도 있다 — 그래서 애초에 국면 차단을 프롬프트 지시가 아니라 _guard_decisions 코드 레벨에서 한 번 더 강제한다. 모델을 신뢰하지 않고 이중으로 막은 것 자체가 이 한계를 인정한 설계다. 정리하면, 이 프로젝트에서 로컬 LLM의 역할은 "똑똑한 트레이더"가 아니라 "정량 필터를 통과한 후보 중에서 그럴듯한 이유를 골라주는 조수"에 가깝다. 실제 안전장치는 전부 평범한 if문으로 짜여 있다. 다음 글에서는 이 규칙들이 실전에서 어떤 결과를 냈는지, 백테스트 숫자를 좀 더 파고들어 볼 예정이다. 이 프로젝트는 학습/실험 목적의 모의투자 시스템이며, 실제 투자 조언이 아닙니다. 코드는 개인 레포에서 관리 중입니다. GitHub 저장소: sunwoo8478/kis-paper-trading
버튼에 링크 걸기 링크를 걸 트랜잭션 화면에 가서 다이얼로그에 버튼 추가 후 Click 메서드 기본적인 로직은 a. IASLINKS 클래스의 인스턴스 변수 선언 b. SETPARAMETERS 와 ADDCOLVALUE 로 값 넘겨주기 c. CREATELINK() 로 링크 생성 ->->-> d. 링크 대상 트랜잭션의 TransCalled e. 값 넘겨준 거 받아서 활용 상세 코드 설명 SETPARAMETERS 참고 801 /* IASLINKS 클래스의 인스턴스 변수 생성 */ GLOBAL: IASLINKS LINKREC; /* 선택된 행의 MEMBERID가 존재하는지 확인 후 실행 */ IF NKMEMBERINFO8100LIST_SELECTED == 1 THEN LINKREC.SETPARAMETERS('NKMEMBERINFO8100', 'NKMEMBERINFO8100LIST', '', '', 'EDUT804NK', '', '0'); LINKREC.ADDCOLVALUE('COMPANY', NKMEMBERINFO8100LIST_COMPANY); LINKREC.ADDCOLVALUE('MEMBERID', NKMEMBERINFO8100LIST_MEMBERID); SYS_LINK = LINKREC.CREATELINK(); LINKREC.EXECUTELINK(SYS_LINK); ELSE /* 선택된 행이 없는 경우 안내 */ MESSAGE BAS E2000 WITH '선택된 회원이 없습니다.'; ENDIF; 804 GLOBAL: IASLINKS LINKEXC; LOCAL: STRING SCCOMPANY, STRING SCMEMBERID; REPGROUP = GETFORMREPORTGROUP(); QUERYID = GETFORMQUERY(); QITEMS = SEARCHHEADER00039.GETREPORTTABLE(); IF SYS_LINK != '' THEN IF LINKEXC.PARSELINK(SYS_LINK) THEN SCCOMPANY = LINKEXC.GETCOLVALUE('COMPANY'); SCMEMBERID = LINKEXC.GETCOLVALUE('MEMBERID'); /* 1. 다이얼로그 구조를 QITEMS에 먼저 로드 */ GETSEARCHCRITERIA DIALOG SYS_CURRENTDIALOG TO QITEMS; /* 2. 로드된 QITEMS의 파라미터 값 변경 */ REPQUERY00039REC.SETPARAMVALUES(REPGROUP, QUERYID, QITEMS, 'NKRENTHEADER8100', 'COMPANY', SCCOMPANY); REPQUERY00039REC.SETPARAMVALUES(REPGROUP, QUERYID, QITEMS, 'NKRENTHEADER8100', 'MEMBERID', SCMEMBERID); THIS.SEARCH(); IF NKRENTHEADER8100LIST_ROWCOUNT == 1 THEN MOVE 1 TO NKRENTHEADER8100LIST_SELECTED; ENDIF; IF NKRENTHEADER8100LIST_SELECTED THEN CNKRENTHEADER8100REC.SETMODI(2); THIS.CALLDETAIL(); ELSE MESSAGE BAS E2000 WITH '대여정보가 존재하지 않습니다'; ENDIF; ENDIF; ENDIF; 주의해야할점 SCCOMPANY 넘겨줄 때 변수 선언 하기 REPGROUP = GETFORMREPORTGROUP(); QUERYID = GETFORMQUERY(); GETSEARCHCRITERIA DIALOG SYS_CURRENTDIALOG TO QITEMS; 설정 나머지는 그 이후 동작 설정