매매 일지 작성 기능 구현
자동 주식 매매 프로그램 매매 일지 작성 기능 기획
개요
본 문서는 자동 주식 매매 프로그램에 매매 일지 작성 및 관리 기능을 추가하기 위한 상세 기획을 설명합니다. 사용자가 날짜별로 매매 기록과 개인적인 분석을 체계적으로 정리하고, 기록된 일지를 PDF 형식으로 출력할 수 있도록 기능을 구현합니다. 이 기능은 사용자의 매매 복기 및 학습에 필수적인 도구가 될 것입니다.
1. 주요 기능 및 모듈
| 기능 번호 | 기능 모듈 | 세부 설명 |
|---|---|---|
| 1 | 날짜별 매매 일지 작성 및 조회 | 사용자가 특정 날짜를 선택하여 일지를 작성하거나 기존 일지를 조회합니다. 일지는 마크다운(Markdown) 문법을 지원하여 텍스트, 리스트, 코드 블록 등을 활용해 풍부하게 작성할 수 있습니다. |
| 2 | 매매 일지 수정 및 삭제 | 이미 작성된 일지 내용을 쉽게 수정하고 저장할 수 있는 기능. 데이터베이스(SQLite 또는 CSV)에 저장된 해당 날짜의 데이터를 업데이트합니다. |
| 3 | 매매 기록 연동 | trader.py 모듈을 통해 실행된 자동/수동 매매 기록(체결 내역)을 불러와 일지 화면 하단에 참고 자료로 자동으로 표시합니다. |
| 4 | 매매 일지 PDF 출력 | 사용자가 특정 날짜 또는 기간을 선택하여 해당 일지 내용을 PDF 파일로 출력하는 기능. 출력된 PDF는 깔끔한 양식으로 정리되어야 합니다. |
2. UI/UX 및 데이터 구조
2.1. UI/UX 흐름 (Streamlit 페이지 구성)
메인 화면: 💰 나의 포트폴리오 탭 옆에 📝 매매 일지 탭을 추가합니다.
- 사이드바:
st.sidebar.date_input을 사용하여 날짜 선택 위젯을 배치합니다. 기본값은 오늘 날짜(Date)로 설정합니다. - 메인 컨텐츠:
- 일지 제목:
<span type="placeholder" placeholder-type="date"></span> 매매 복기와 같이 선택된 날짜가 제목으로 자동 표시됩니다. - 일지 입력 영역:
st.text_area를 사용하여 마크다운을 지원하는 입력창을 제공합니다. - 버튼: “일지 저장/수정”, “일지 삭제”, “PDF 출력” 버튼을 배치합니다.
- 참고 데이터:
trader모듈에서 가져온 해당 날짜의 실제 매매 체결 기록 테이블이 하단에 표시됩니다.
- 일지 제목:
2.2. 데이터 저장 구조 (SQLite 또는 Pandas CSV)
| 필드명 | 데이터 타입 | 설명 | 비고 |
|---|---|---|---|
date | TEXT (YYYY-MM-DD) | 일지 작성 날짜 (Primary Key) | 필수 |
title | TEXT | 일지 제목 | 자동 생성 (날짜) |
content | TEXT | 사용자가 작성한 마크다운 내용 | 필수 |
created_at | DATETIME | 최초 작성 시간 | |
updated_at | DATETIME | 최종 수정 시간 | |
trades_summary | JSON | 해당 날짜의 총 매매 요약 (선택 사항) | 손익 등 |
3. 구현 상세 가이드: PDF 출력 (기능 4)
PDF 출력은 Python의 PdfKit 사용하여 HTML/CSS를 PDF로 변환합니다. 또한 마크다운 내용을 HTML로 변환 후 PDF로 출력하도록 했습니다.
3.1. 필수 라이브러리 설치
1
pip install markdown pdfkit # pdfkit은 wkhtmltopdf 외부 프로그램 필요
markdown, pdfkit은 requirements.txt에 넣어주면 자동으로 설치되지만 wkhtmltopdf는 시스템에 직접 설치해줘야 합니다.
처음에 wkhtmtopdf를 설치하고 사용하려고 하니 자꾸 관련 라이브러리(Qt) 를 올리지 못하는 오류가 발생해서 연관된 라이브러리를 모두 포함하고 있는 정적 wkhtmltopdf를 설치했습니다.
1
2
3
4
5
6
7
cd /tmp
wget -O wkhtmltox.deb \
https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-2/wkhtmltox_0.12.6.1-2.jammy_amd64.deb
sudo dpkg -i wkhtmltox.deb || true
sudo apt-get -f install -y
제대로 설치되었는지 확인해보면
1
2
wkhtmltopdf --version
command -v wkhtmltopdf
한가지 더 추가해야 할것이 있습니다.
한글 폰트를 설치하지 않으면 아래와 같이 한글이 깨져서 PDF가 생성됩니다.
따라서 필요한 한글 폰트도 설치해줍니다.
1
2
3
sudo apt-get update
sudo apt-get install -y fonts-noto-cjk fonts-nanum fontconfig
sudo fc-cache -f -v
설치 후 확인을 해보면
1
fc-list | grep -i -E "noto.*cjk|nanum" | head
3.2. PDF 출력 로직 (modules/pdf_generator.py)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# modules/pdf_generator.py
import datetime
import os
import shutil
from typing import Optional, Tuple
import markdown
import pdfkit
import streamlit as st
def _find_wkhtmltopdf() -> Optional[str]:
"""wkhtmltopdf 실행 파일 경로를 최대한 유연하게 찾는다."""
# 1) 사용자가 명시적으로 지정한 경우
for env_key in ("WKHTMLTOPDF_PATH", "WKHTMLTOPDF_BINARY", "WKHTMLTOPDF"):
p = os.getenv(env_key)
if p and os.path.exists(p):
return p
# 2) PATH에서 찾기
p = shutil.which("wkhtmltopdf")
if p:
return p
# 3) 흔한 설치 위치들 (리눅스/윈도우/맥)
candidates = [
"/usr/bin/wkhtmltopdf",
"/usr/local/bin/wkhtmltopdf",
"/app/.apt/usr/bin/wkhtmltopdf", # Streamlit Cloud apt layer에서 흔히 쓰임
"C:\\Program Files\\wkhtmltopdf\\bin\\wkhtmltopdf.exe",
"C:\\Program Files (x86)\\wkhtmltopdf\\bin\\wkhtmltopdf.exe",
"/opt/homebrew/bin/wkhtmltopdf",
"/usr/local/opt/wkhtmltopdf/bin/wkhtmltopdf",
]
for c in candidates:
if os.path.exists(c):
return c
return None
def _get_pdfkit_config() -> Optional[pdfkit.configuration]:
wk = _find_wkhtmltopdf()
if not wk:
return None
try:
return pdfkit.configuration(wkhtmltopdf=wk)
except Exception:
return None
def create_journal_pdf_bytes(
date, content: str, trades_data=None
) -> Tuple[Optional[bytes], str]:
"""
매매 일지 내용을 받아 PDF 바이너리(bytes)로 생성합니다.
Returns:
(pdf_bytes, filename)
- pdf_bytes: 성공 시 bytes, 실패 시 None
- filename: 다운로드용 파일명
"""
date_str = date.strftime("%Y-%m-%d")
filename = f"{date_str}_journal.pdf"
title = f"[{date_str}] 자동 매매 프로그램 복기 일지"
# 1) Markdown → HTML
# 코드블록/줄바꿈 호환을 위해 extensions를 약간 켬
html_content = markdown.markdown(
content or "",
extensions=["fenced_code", "tables", "nl2br"],
output_format="html5",
)
# 2) trades_data 테이블 (DataFrame 가정)
trades_html = ""
if trades_data is not None:
try:
trades_html = trades_data.to_html(index=False, border=0)
trades_html = f"<h2>실제 매매 기록</h2>{trades_html}"
except Exception:
# DataFrame이 아니거나 to_html 불가하면 무시
trades_html = ""
# 3) HTML 템플릿
generated_at = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
html_template = f"""<!DOCTYPE html>
<html>
<head>
<meta charset=\"utf-8\">
<title>{title}</title>
<style>
body
h1
h2
pre, code
pre code
table
thead th
th, td
.muted
</style>
</head>
<body>
<h1>{title}</h1>
<p class=\"muted\">작성일: {date_str}</p>
<div style=\"margin-top: 20px;\">
{html_content}
</div>
<div style=\"margin-top: 30px;\">
{trades_html}
</div>
<p class=\"muted\" style=\"margin-top: 50px; text-align: right;\">
생성일: {generated_at}
</p>
</body>
</html>"""
# 4) pdfkit 설정 (wkhtmltopdf 경로 포함)
config = _get_pdfkit_config()
if config is None:
st.error(
"wkhtmltopdf를 찾지 못해서 PDF를 생성할 수 없어.\n\n"
"- 로컬: wkhtmltopdf 설치 후 PATH에 잡히게 하거나\n"
"- 서버(Streamlit Cloud): packages.txt에 wkhtmltopdf를 추가하거나\n"
"- 또는 환경변수 WKHTMLTOPDF_PATH 로 바이너리 경로를 지정해줘."
)
return None, filename
# 5) PDF 생성 (output_path=False → bytes 반환)
options = {
"encoding": "UTF-8",
"page-size": "A4",
"margin-top": "12mm",
"margin-right": "12mm",
"margin-bottom": "12mm",
"margin-left": "12mm",
# Streamlit/리눅스에서 로컬 리소스 접근 이슈 방지용
"enable-local-file-access": "",
}
try:
pdf_bytes = pdfkit.from_string(
html_template, False, options=options, configuration=config
)
return pdf_bytes, filename
except Exception as e:
st.error(f"PDF 생성 중 오류 발생: {e}")
return None, filename
# main.py에서 쓰기 좋은 Streamlit 래퍼
def download_journal_pdf(date, content: str, trades_data=None):
"""PDF를 생성하고, 생성된 PDF를 Streamlit 다운로드 버튼으로 제공."""
pdf_bytes, filename = create_journal_pdf_bytes(
date, content, trades_data=trades_data
)
if not pdf_bytes:
return
st.download_button(
label="PDF 파일 다운로드",
data=pdf_bytes,
file_name=filename,
mime="application/pdf",
)
- 한글 출력을 위해 html CSS의 font-family에 “Noto Sans CJK KR”, “Noto Sans KR”, “NanumGothic”, “Nanum Gothic”를 추가했습니다.
main.py 파일에 매매 일지 관련 코드를 추가합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# ... 기존 코드 ...
# -----------------------------------------------------
# TAB 3: 매매 일지
# -----------------------------------------------------
with tab_journal:
st.header("📝 매매 일지")
# 날짜 선택
selected_date = st.date_input("날짜 선택", value=datetime.date.today())
# 일지 내용 입력
journal_content = st.text_area(
"일지 내용 (Markdown 형식)",
placeholder="매매 후기, 분석 내용 등을 입력해주세요.",
height=200,
)
# 실제 매매 기록 (예시)
trades_data = None # 실제 데이터는 여기에 들어가야 함
if st.button("일지 저장 및 PDF 생성"):
if journal_content.strip():
download_journal_pdf(selected_date, journal_content, trades_data)
else:
st.warning("일지를 입력해주세요.")
pass
# ... 기존 코드 ...
Stream Cloud에서 운영할때는 packages.txt라는 파일에 관련 라이브러리와 필요 폰트들을 적어줘야 합니다.
1
2
3
4
wkhtmltopdf
fonts-noto-cjk
fonts-nanum
fontconfig
코드를 올리고 Streamlit app을 Reboot해야 위 사항이 반영됩니다.
3.3 최종 결과물
4. 개발 일정 (매매 일지 기능)
본 일정은 ‘자동 주식 매매 프로그램 개발 계획’ 문서의 최종 단계 이후, 추가 기능 개발 단계에 해당합니다.
| 단계 | 시작일 | 완료일 | 주요 작업 내용 |
|---|---|---|---|
| 데이터 모델 설계 | Date | Date | 매매 일지 데이터베이스(SQLite) 테이블 설계 및 CRUD(Create, Read, Update, Delete) 함수 구현 |
| UI/UX 구현 | Date | Date | app.py에 📝 매매 일지 탭 추가 및 날짜 선택, 마크다운 입력 UI 구현 |
| PDF 출력 통합 | Date | Date | pdf_generator.py 구현 및 app.py의 “PDF 출력” 버튼 연동 |
| 통합 및 테스트 | Date | Date | 자동 매매 기록 연동 테스트 및 최종 버그 수정 |
5. 연락처 및 참고 사항
매매 일지 작성 기능 구현에 대한 문의 사항 또는 피드백은 Person 또는 백흠경 에게 연락 주시기 바랍니다.
PDF 출력을 위한 디자인 템플릿은 File(일지 디자인 초안)을 참고합니다.
일지 기능 최종 검토 및 사용자 시연 미팅은 Calendar event 일정에 진행될 예정입니다.
