Forge Fate
퀀트·데이터 연구

실험 기록과 보고서를 자동으로 남기기: 성공과 실패를 함께 보존하는 최소 도구

약 8분

「나만의 퀀트 리서치 시스템 만들기」 7편입니다. 지난 편에서는 모델과 기준 전략을 비교할 조건을 정했습니다. 이번에는 작은 합성 입력을 실행하고, 무엇을 넣었으며 어떤 결과나 오류가 남았는지 보존하는 도구를 살펴봅니다.

먼저 실행 범위를 분명히 하겠습니다. 프로젝트 제공 실행기록에 따르면 SPY·IEF·GLD 실제 가격 원자료는 여전히 미확보이며, 6편의 모델 학습과 백테스트도 실행하지 않았습니다. 이번에 실제로 실행한 것은 합성 확률의 비중변환과 실행기록·보고서 도구입니다.

제공 기록에는 2026-09-21, 호스트 Python 3.14.5, 표준라이브러리 환경에서 checks PASS가 남았다고 명시되어 있습니다. 아래 실행 사실은 이 요청에 포함된 프로젝트 기록에 근거합니다. 보존된 코드와 폴더를 직접 열거나 재실행한 독립 검증 결과는 아니며, Python 공식 문서는 사용한 API의 동작을 설명하는 외부 근거로 구분합니다.

성공 한 번, 실패 한 번을 나란히 읽기

프로젝트 제공 실행기록의 두 사례를 비교하면, 이 도구가 남기는 증거가 드러납니다.

항목성공 실행실패 실행
실행 IDd711f515-8380-4bca-9112-23ef651f6bf6330fd103-4ddc-41ce-9620-7ae15bae1003
입력SYN_A=0.60, SYN_B=0.45, SYN_C=0.70별도 입력 A=1.20
임계값·검증임계값 0.55 적용[0, 1] 범위 위반
비중 결과A 0.5, B 0, C 0.5, 현금 0결과 없음
상태·예외비중변환 성공FAILED, ValueError
실패 사유해당 없음probabilities must be nonempty and within [0, 1]
result.json생성미생성

이 확률은 합성 입력이며 모델 출력이나 ETF 예측 결과가 아닙니다. A·B·C는 각각 SYN_A·SYN_B·SYN_C에 대응하는 설명용 표기입니다. 실제 JSON 키의 정확한 형태는 원파일을 열람하지 않아 확인하지 않았습니다. 현금 0도 현금 비중이며, 현금수익률이나 전략수익률을 뜻하지 않습니다.

표를 읽을 때는 성공 결과와 실패 사유를 함께 보아야 합니다. 실패 실행에는 계산 결과가 없습니다. 따라서 보고서에서도 그 자리를 수익률 0으로 채우지 않고, 실패 상태와 이유를 보여 주도록 합니다. 이번 사례에서 확인할 대상은 임계값의 투자 유효성이 아니라 입력이 결과 또는 오류로 이어지고, 그 사실이 파일에 남는 경로입니다.

실행마다 폴더 하나를 만든다

제공 기록에 따른 상대 폴더구조는 다음과 같습니다. runs의 정확한 상위 경로는 확인되지 않았습니다.

text
runs/├── d711f515-8380-4bca-9112-23ef651f6bf6/│   ├── inputs.json│   ├── config.json│   ├── record.json│   ├── result.json│   └── report.md└── 330fd103-4ddc-41ce-9620-7ae15bae1003/    ├── inputs.json    ├── config.json    ├── record.json    └── report.md

inputs.json은 입력, config.json은 임계값 등 설정을 보존합니다. record.json은 실행 식별자와 환경·상태·오류를 연결합니다. 성공했을 때만 result.json을 만들고, report.md는 저장된 기록과 결과를 다시 읽어 생성합니다. 이는 프로젝트 제공 저장 규칙입니다.

실행 폴더는 uuid4()로 만든 ID를 사용하고 mkdir(exist_ok=False)로 생성합니다. uuid4()는 무작위 UUID를 만들며, 해당 mkdir 설정은 대상 폴더가 이미 있으면 FileExistsError를 발생시킵니다.[S4][S1]

여기서 폴더 분리의 목적은 재실행 때 기존 산출물을 우연히 덮어쓰는 경로를 막는 것입니다. 이 생성 규칙에 이후 파일 수정을 차단하는 기능까지 포함된 것은 아닙니다.[S1] UUID를 썼다는 이유로 절대적인 충돌 불가능성이나 변조 방지를 주장해서도 안 됩니다.[S4]

핵심 코드: 식별자·바이트·시각을 남기기

아래 코드는 제공된 저장 규칙을 설명하기 위해 새로 작성한 예시입니다. 보존된 실행파일의 원문 발췌나 checks PASS를 받은 코드가 아닙니다. 전체 실행기를 대신하기보다 기록에 필요한 작은 동작을 보여 줍니다.

먼저 실행 폴더를 만드는 부분입니다. 다음 함수는 상위 runs_dir가 준비되어 있다는 조건으로 작성했습니다.

python
import uuiddef create_run_dir(runs_dir):    run_id = str(uuid.uuid4())    run_dir = runs_dir / run_id    run_dir.mkdir(exist_ok=False)    return run_id, run_dir

식별자 생성과 폴더 충돌 감지는 별개의 역할입니다. UUID는 이름을 만들고, mkdir는 그 이름의 폴더가 이미 있는지 확인하며 생성합니다.[S4][S1]

다음은 저장할 JSON 바이트와 해시를 만드는 부분입니다.

python
import hashlibimport jsondef json_bytes(value):    text = json.dumps(        value, sort_keys=True, ensure_ascii=False, indent=2    )    return (text + "\n").encode("utf-8")def save_inputs_and_config(run_dir, inputs, config, code_path):    input_bytes = json_bytes(inputs)    config_bytes = json_bytes(config)    (run_dir / "inputs.json").write_bytes(input_bytes)    (run_dir / "config.json").write_bytes(config_bytes)    return {        "input_hash": hashlib.sha256(input_bytes).hexdigest(),        "config_hash": hashlib.sha256(config_bytes).hexdigest(),        "code_hash": hashlib.sha256(code_path.read_bytes()).hexdigest(),    }

sort_keys=True는 키를 정렬하고, ensure_ascii=False는 필수 이스케이프 대상을 제외한 비ASCII 문자를 그대로 출력합니다. indent=2는 두 칸 들여쓰기입니다.[S2] 마지막 개행을 붙이고 UTF-8로 인코딩하는 것은 프로젝트가 정한 추가 규칙입니다. 예시에서는 저장과 해시 계산에 같은 바이트를 사용하도록 했습니다.

sha256은 바이트의 해시를 계산하고 hexdigest()는 이를 16진수 문자열로 반환합니다.[S3] 입력·설정은 정해진 JSON 표현을, 코드는 파일 바이트 자체를 대상으로 삼는 것이 제공된 규칙입니다.

해시는 내용 식별과 대조에 사용해야 합니다. 이 계산 기능만으로 원자료의 정확성, 출처, 실행환경 보존이나 완전한 연구 재현성이 입증되지는 않습니다.[S3] 내용과 해시를 함께 바꾸는 상황에 대비한 별도 보안체계도 이번 구현 범위에 없습니다.

시각은 다음처럼 표현할 수 있습니다.

python
from datetime import datetime, timezonedef utc_now():    return datetime.now(timezone.utc).isoformat()

이 코드는 시간대 정보가 있는 UTC 시각을 ISO 8601 문자열로 만듭니다.[S5] 시작과 종료 지점에서 각각 호출하도록 구성하되, UTC 표기 자체를 호스트 시계의 정확성 보증으로 해석하지는 않습니다.[S5]

제공 기록에 따르면 record.json에는 다음 항목이 포함됩니다.

묶음기록 내용
식별·시간run_id, started_at, finished_at
환경·내용 식별Python 버전, 입력·설정·코드의 세 해시
상태·오류status, error_type, reason

실제 해시 문자열과 시작·종료 시각값은 제공되지 않았으므로 여기서 만들어 넣지 않습니다. 실행 환경은 제공 기록상 3.14.5이고, 조사한 공식 문서는 3.14.7 기준입니다. 공식 릴리스 페이지에서 두 버전의 존재를 확인할 수 있지만, 그것이 프로젝트 호스트의 실행 버전을 입증하지는 않습니다.[S6][S7]

보고서는 저장된 파일에서 만든다

프로젝트 제공 기록에 따르면 보고서는 메모리에 남아 있는 계산값을 따로 옮기는 대신, 저장된 record.json과 성공 시의 result.json을 다시 읽어 생성합니다.

text
저장된 record.json ──────────────┐                               ├── report.md저장된 result.json ── 성공 시 ──┘

이것이 기록→결과파일→보고서라는 단방향 구조의 뜻입니다. 세 파일을 반드시 한 번씩 정해진 순서로 쓴다는 의미가 아니라, 보고서의 상태와 숫자가 저장된 산출물에서 오도록 한다는 설계입니다.

성공 보고서의 비중 0.5를 작성자가 다시 타이핑하는 경로를 만들지 않는 것이 핵심입니다. 저장된 JSON은 json.load나 json.loads로 읽을 수 있습니다.[S2] 보고서 생성 단계는 읽은 결과를 표현하는 역할에 집중하도록 합니다.

실패 보고서는 결과파일 없이 기록에 저장된 상태·예외·사유를 사용합니다. 이번 실패라면 FAILED, ValueError, 범위 위반 사유가 보고서의 본문이 됩니다. 빈 성과표나 임의의 숫자를 넣을 필요가 없습니다.

checks PASS가 확인한 범위

프로젝트 제공 실행기록에는 다음 assert 검사가 통과했다고 명시되어 있습니다.

  1. 성공 실행의 비중이 기대값과 일치했다.
  2. 실패 상태는 FAILED, 예외 종류는 ValueError였다.
  3. 실패 폴더에는 result.json이 없었다.
  4. 성공·실패 폴더가 서로 달랐다.
  5. 두 번째 실행 후 첫 폴더의 모든 파일 바이트가 바뀌지 않았다.
  6. 성공 보고서에 저장 결과와 동일한 본문이 포함됐다.
  7. 실패 보고서에 실패 사유가 포함됐다.

특히 다섯 번째 검사는 해당 재실행이 첫 번째 산출물을 변경하지 않았다는 증거입니다. 이후 수동 수정이나 제삼자의 변경까지 막았다는 뜻은 아닙니다.

제공된 보존 위치는 data/manual-revisions/quant-research-07-records.py와 runs 폴더입니다. 이 경로는 프로젝트가 안내한 위치이며, 이 글에서 직접 열람한 파일 목록은 아닙니다. 검사 통과의 범위도 제공된 두 사례까지로 읽어야 합니다.

현재 도구가 처리하지 못하는 일

제공 기록상 현재 구현이 처리하는 예외는 입력검증의 ValueError입니다. 디스크 오류, 강제종료, 원자적 쓰기, 환경 잠금, 실시장 데이터 스냅샷과 완전한 연구 재현은 미구현입니다. 실행 중단 시 RUNNING 상태가 남을 가능성도 있습니다.

따라서 “실패도 남긴다”는 설명은 이번에 처리한 입력검증 실패에 해당합니다. 모든 장애에서 온전한 기록을 보존한다는 주장으로 넓힐 수 없습니다.

또한 JSON 저장 성공과 확률 검증 성공을 구분해야 합니다. Python json의 기본 설정은 NaN·무한대 같은 비표준 숫자 표현을 허용할 수 있습니다.[S2] 이번 제공 기록에는 NaN, 무한대, 문자열, 임계값 자체의 범위에 대한 검사 결과가 없습니다.

공개 기록에는 허용된 필드만 넣고 API 키·토큰·비공개 원자료를 제외한다는 프로젝트 원칙도 유지합니다. 이번 입력은 비밀정보가 없는 합성값입니다. 이 원칙은 자동 비밀정보 탐지 기능이 구현되었다는 뜻이 아닙니다.

다음 실행에서 직접 대조할 것

독자 실습은 작은 합성 입력의 성공 실행과 A=1.20 같은 범위 위반 실패 실행을 각각 만드는 데서 시작하면 됩니다. 두 폴더의 ID, 상태, 오류 사유, 결과파일 유무를 비교하고, 저장된 기록·결과에서 보고서를 생성해 보세요. 재실행 뒤 이전 폴더의 바이트가 유지되는지, 보고서 본문이 저장 결과와 일치하는지도 대조하도록 권합니다.

실제 시장 실험으로 확장할 때는 특징·레이블 시각, 데이터 기간과 식별자, 분할 경계, 재학습 일정, 체결·비용 설정을 추가할 항목으로 삼겠습니다. 이는 향후 확장 제안이며 현재 도구에 이미 구현된 필드는 아닙니다.

기본연재 마지막인 8편에서는 실제 확보한 산출물, 재실행 가능한 범위, AI 기여에 대한 관찰과 미완료 실데이터 과제를 정리하겠습니다. 실데이터 실증 완료나 생산성 향상 수치를 약속하지 않고, 지금까지 확보한 증거를 기준으로 마무리하겠습니다.

이 도구와 실습은 교육·연구용이며, 투자 추천이나 수익 보장이 아닙니다.

Sources

오류 제보·의견 보내기

글 제목과 주소가 포함된 메일 초안을 엽니다. 내용과 받는 사람을 확인한 뒤 보내주세요.

받는 사람: [email protected]

메일 앱에서 작성

메일 앱이 열리지 않으면 아래 내용을 복사해 평소 쓰는 이메일에 붙여 넣으세요.

문의 안내