Files
myProfile/data/learning.json
T
2026-05-31 21:05:59 +09:00

170 lines
48 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
[
{
"id": 3,
"title": "Synology NAS에 Gitea + CI/CD 환경 구축",
"category_id": 15,
"tags": [
"Gitea",
"Docker",
"NAS",
"CI/CD"
],
"content": "## 목표\n\n외부 서비스 의존 없이 개인 개발 인프라 구축.\n\n## 진행 과정\n\n1. Synology Container Manager로 Gitea 컨테이너 띄우기\n2. Reverse Proxy로 외부 도메인 연결\n3. SSH 키 등록 및 첫 푸시 테스트\n\n## 배운 것\n\n- Docker volume 관리의 중요성 (데이터 유실 방지)\n- HTTPS 인증서 자동 갱신 설정\n- 백업 전략 수립",
"created_at": "2026-03-10",
"updated_at": "2026-03-12"
},
{
"id": 4,
"title": "BeatSaber 모작하기 -1",
"category_id": 12,
"tags": [
"Unity",
"VR",
"Game"
],
"content": "## 1. 프로젝트 개선 목표 설정\n\n수업 시간에 배운 비트세이버 프로젝트를 기반으로, 더 높은 완성도를 위해 다음과 같은 개선 목표를 설정한다.\n\n[ 현 재 ]\n\n- 단순 파괴: 스포너에서 생성된 큐브의 {color:#ff4757}색상과 방향이 맞으면 그 자리에서 바로 Destroy 처리{/color}됨.\n\n- 랜덤 스폰: 큐브가 나오는 {color:#ff4757}시점과 위치가 단순히 시간별 랜덤으로 설정{/color}되어 있음.\n\n- 기본 리소스: 검의 모델링이 단순한 막대기 형태로 되어 있어 시각적 몰입감이 떨어짐.\n\n[ 목 표 ]\n\n- 슬라이싱 시스템: 큐브가 사라지는 대신, {color:#54a0ff}검의 궤적에 따라 실제로 잘리는 효과{/color}로 수정.\n\n- 패턴 커스터마이징: 랜덤 스폰이 아닌, {color:#54a0ff}개발자가 직접 의도한 패턴대로 큐브가 나오도록{/color} 수정.\n\n- 에셋 고도화: 무료 에셋을 활용하여 검의 외형을 화려하게 변경.\n---\n\n## 2. 세부 실행 방안\n### 검 에셋 변경 및 적용\n구글링 및 유니티 에셋 스토어를 통해 무료 검 에셋을 확보하여 기존 막대 모델링을 대체 적용.\n\n### 슬라이싱 시스템 구현 (EzySlice 도입)\n기존의 단순 파괴 로직에서 벗어나 실제 메시(Mesh)를 절단하기 위해 외부 라이브러리를 활용한다.\n\n라이브러리 준비\n\nDavidArayan의 EzySlice GitHub 저장소에서 라이브러리 다운로드.\n\n> 인용문을 작성하세요\nhttps://github.com/DavidArayan/ezy-slice\n\n프로젝트 내 Assets/Plugins 폴더를 생성하여 관련 파일 임포트.\n\n코드 분석 및 변경\n\n- 기존 방식: {color:#ff6b9d}Raycast를 쏘아 충돌한 물체의 각도만 체크{/color}하고 Destroy 호출.\n\n- 변경 방식: {color:#00d2d3}Linecast를 사용하여 검의 날 전체 범위를 체크{/color}하고, EzySlice 함수를 호출하여 {color:#00d2d3}잘린 단면 생성 및 물리 효과 부여{/color}.\n---\n\n## 3. 기존 코드 분석 (Before)\n현재 적용되어 있는 Raycast 기반의 단순 파괴 로직이다.\n```\nusing System.ComponentModel.Design.Serialization;\nusing UnityEngine;\n\npublic class Saber : MonoBehaviour\n{\n public LayerMask layer;\n Vector3 prevPos;\n\n void Update()\n {\n RaycastHit hit;\n \n // Raycast를 사용하여 충돌 감지 (위치, 방향, 저장변수, 거리, 레이어)\n if(Physics.Raycast(transform.position, transform.forward, out hit, 1, layer))\n {\n // 현재위치 - 이전 위치 = 이동방향 벡터 계산\n Vector3 v1 = transform.position - prevPos; \n \n // 이동방향(v1)과 큐브의 위쪽 방향(up) 사이의 각도가 130도 이상이면 파괴\n if(Vector3.Angle(v1, hit.transform.up) > 130)\n {\n Destroy(hit.transform.gameObject);\n }\n }\n\n // 다음 프레임 계산을 위해 현재 위치 저장\n prevPos = transform.position;\n }\n}\n```\n\n---\n\n## 4. 주요 변경 및 개선 사항 (Key Changes)\n기존의 단순한 로직을 물리 기반의 정밀한 시스템으로 리팩토링하며 다음과 같은 큰 변화를 주었습니다.\n\n### 1) 충돌 감지 방식의 정밀도 향상 (Raycast → Linecast)\n- 기존: 검의 한 지점에서 정해진 방향으로 광선을 쏘는 Raycast 방식을 사용했습니다. 이는 검이 빠를 경우 물체를 지나쳐버리는 '터널링' 현상이 발생할 수 있었습니다.\n\n- 변경: 검의 손잡이(Start)와 끝(End) 지점을 잇는 Linecast 방식을 도입하여, 검의 전체 면적에 대한 충돌을 실시간으로 체크하도록 개선했습니다.\n\n### 2) 물리 엔진 기반의 속도 측정 (VelocityEstimator)\n- 기존: Update 문에서 프레임 간의 위치 차이를 직접 계산하여 속도를 구했습니다. 이는 프레임 드랍 발생 시 속도 값이 부정확해지는 단점이 있었습니다.\n\n- 변경: 별도의 VelocityEstimator 스크립트를 통해 물리 연산 주기(FixedUpdate)에 맞춘 정확한 속도 벡터를 추출합니다. 이를 통해 일정 속도(swingSpeedThreshold) 이상으로 휘두를 때만 잘리도록 '손맛'을 조절했습니다.\n\n### 3) 외적(Cross Product)을 이용한 동적 절단면 생성\n- 기존: 단순히 충돌 여부와 각도만 체크하여 오브젝트를 통째로 삭제(Destroy)했습니다.\n \n- 변경: 검의 방향 벡터와 검의 휘두름(속도) 벡터를 외적(Cross Product) 연산하여 절단면의 법선(Normal)을 구합니다. 이를 통해 사용자가 휘두르는 궤적 그대로 물체가 잘리는 물리적 사실감을 구현했습니다.\n\n### 4) 절단 후 파편 처리 및 물리 부여 (SetupHull)\n- 기존: 별도의 파편 처리가 없었습니다.\n\n- 변경: EzySlice를 통해 생성된 두 개의 파편(UpperHull, LowerHull)에 실시간으로 MeshCollider와 Rigidbody를 추가합니다. 잘린 단면에 폭발적인 힘(AddExplosionForce)을 가해 조각들이 사방으로 튕겨 나가는 효과를 추가했습니다.\n---\n\n## 5. 향후 작업 계획\n- 음악의 제작 씬을 별도로 만들어서 음악에 맞는 비트를 찍을수 있도록 구성.",
"created_at": "2026-04-30",
"updated_at": "2026-04-30"
},
{
"id": 6,
"title": "[AR] 동물캐릭터를 그림위로 뽑아보기",
"category_id": 11,
"tags": [
"VR",
"Unity"
],
"content": "##스마트폰을 사용하여 그림 위에 캐릭터를 불러와보기!!\n### 1. 준비과정\n- 유니티의 새 프로젝트 중 \"{color:#54a0ff}AR Mobile{/color}\"을 사용.\n- 에셋스토어에서 {color:#54a0ff}\"Teddy Head Kids 2\"{/color} 다운 및 임포트.\n- 동물이 소환될 이미지 다운.\n\n\n---\n\n### 2. 만들기\n1. 준비된 에셋 중 {color:#ff6b9d}Prefab에서 Bear랑 Hippo를 씬에 올려{/color}준다.\n2. 동물들의 크기를 조정하고 거기에 {color:#ff6b9d}Add Componet를 눌러 Sphere Collider를 추가{/color}한후 사이즈를 조정해준다.\n3. 완료된 오브젝트를 프로젝트로 내려 {color:#ff6b9d}하나의 Prefab으로 만들어{/color}준다.\n4. 프로젝트창에서 Create -> XR -> Reference Image Libary 생성 후 'add Image'를 눌러 2개 생성 => 이미지 추가.\n5. Layer에 Animal을 추가하고 각 동물의 {color:#ff6b9d}Prefab에 Layer 적용{/color}.\n6. 스크립트 2개 생성\n- Animal\n 터치 혹은 생성되었을 때 동물에게 입력된 울음 소리가 나올수 있도록 구성.\n```javascript\nusing UnityEngine;\nusing UnityEngine.InputSystem;\n\n\npublic class Animal : MonoBehaviour\n{\n\n public LayerMask animalLayer;\n\n public AudioSource audioSource;\n\n public AudioClip crySound;\n\n void Update()\n {\n if (Touchscreen.current != null)\n {\n var touch = Touchscreen.current.primaryTouch;\n if (touch.press.wasPressedThisFrame)\n\n {\n Vector2 touchPos = touch.position.ReadValue();\n Ray ray = Camera.main.ScreenPointToRay(touchPos);\n if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, animalLayer))\n\n {\n if (hit.transform.IsChildOf(transform))\n {\n if (crySound != null && !audioSource.isPlaying)\n {\n audioSource.PlayOneShot(crySound);\n }\n }\n }\n }\n }\n }\n}\n\n```\n\n- imageTracker\n 기기(스마트폰 등)에서 이미지를 보여주면 생성, 숨기는 기능을 구성.\n```javascript\nusing System.Collections.Generic;\nusing UnityEngine;\nusing UnityEngine.XR.ARFoundation;\nusing UnityEngine.XR.ARSubsystems;\nusing UnityEngine.InputSystem;\n\n\npublic class ImageTracker : MonoBehaviour\n{\n ARTrackedImageManager manager;\n [SerializeField] List<string> listName;\n [SerializeField] List<GameObject> listAnimal;\n\n Dictionary<string, GameObject> dictPrefab = new();\n Dictionary<string, GameObject> dictSpawn = new();\n\n void Awake()\n {\n manager = FindFirstObjectByType<ARTrackedImageManager>();\n for (int i = 0; i < listName.Count; i++)\n { dictPrefab[listName[i]] = listAnimal[i]; }\n }\n\n void SpawnCharacter(ARTrackedImage img)\n\n {\n string name = img.referenceImage.name;\n\n if (!dictPrefab.ContainsKey(name)) return;\n\n var go = Instantiate(dictPrefab[name], img.transform);\n go.transform.localPosition = Vector3.zero;\n\n dictSpawn[name] = go;\n }\n\n void UpdateCharacter(ARTrackedImage img)\n {\n string name = img.referenceImage.name;\n\n if (!dictSpawn.ContainsKey(name)) return;\n\n bool active = img.trackingState == TrackingState.Tracking;\n\n dictSpawn[name].SetActive(active);\n }\n\n void HideCharacter(ARTrackedImage img)\n {\n string name = img.referenceImage.name;\n\n if (dictSpawn.ContainsKey(name))\n\n { dictSpawn[name].SetActive(false); }\n }\n\n void OnChanged(ARTrackablesChangedEventArgs<ARTrackedImage> args)\n {\n foreach (var img in args.added)\n\n SpawnCharacter(img);\n\n foreach (var img in args.updated)\n\n UpdateCharacter(img);\n\n foreach (var img in args.removed)\n\n HideCharacter(img.Value);\n }\n\n void OnEnable()\n { manager.trackablesChanged.AddListener(OnChanged); }\n\n void OnDisable()\n { manager.trackablesChanged.RemoveListener(OnChanged); }\n}\n```\n\n6. ImageTracker의 경우 씬에 빈 오브젝트를 만들어(ArBookManager) 거기안에 추가하기 그리고 난후 {color:#ff6b9d}List Name과 List Animal에 Bear, Hippo를 입력 및 Prefab을 넣어{/color}준다.\n\n7. 각 동물 프리펩에 Audio Source를 추가하고 'Audio Generator'에 준비된 울음소리를 넣어준다. 그다음 {color:#ff6b9d}Animal 스크립트를 붙여주고 거기에 울음소리와 Audio Source를 넣어{/color}준다.\n\n---\n\n> 실제 작동영상\n@video[w=200px,center](uploads/learning/vid_69faa7131137b6.34828927.mp4)",
"created_at": "2026-05-02",
"updated_at": "2026-05-06"
},
{
"id": 7,
"title": "5/6",
"category_id": 19,
"tags": [],
"content": "1. mcp for Unity 링크\nhttps://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity#main\n2. 항목 2\n3. 항목 3",
"created_at": "2026-05-06",
"updated_at": "2026-05-06"
},
{
"id": 8,
"title": "[계획서] 모니터링",
"category_id": 21,
"tags": [
"미래내일일경험"
],
"content": "## 모니터링 프로그램\n1. 서버의 cpu, ram의 온도 등을 받아와 api 혹은 soket으로 json 전송.\n2-1. make를 활용하여 해방하는 파트 혹은 구상하여 온도를 텍스트를 실시간으로 전송.\n2-2. 유니티 혹은 code에서 json을 csv 혹은 그래프에 대한 이미지 생성.\n3. make에서 해당파트 클릭스 그래프로 보이게.",
"created_at": "2026-05-06",
"updated_at": "2026-05-06"
},
{
"id": 9,
"title": "동물사전 만들어보기 -1",
"category_id": 12,
"tags": [
"Unity",
"AR"
],
"content": "## 공부한 것을 통해 간단한 동물사전 만들기\n\n### 변경할점\n1. 기존에 배경 이미지에서 불러오던것을 이번에는 qr코드로 대체\n\n### 세팅\n\n1. Unity에서 새로운 프로젝트를 만든다.(AR Mobile Core) -> 이름은 \"Animals Book\"\n2. 세팅하기 필요없는 씬위에 오브젝트들을 날려주자.\n - {color:#ff4757}UI{/color} 삭제\n - XR Origin 내 Camera Offset -> {color:#ff4757}Object Spawner, Screen Space Ray Interactor{/color} 삭제\n - {color:#ff4757}EventSystem{/color} 삭제\n> [!NOTE]\n> ![](uploads/learning/img_69fbebaa899a91.08494186.png)\n---\n\n### 설치 파일\n1. qr코드를 사용하기 위해서 ZXing이 필요하여 검색하여 유니티에 설치.\n2. Animal 스크립트 작성.\n```csharp\nusing UnityEngine;\nusing UnityEngine.InputSystem;\n\n\npublic class Animal : MonoBehaviour\n{\n public LayerMask animalLayer;\n \n public AudioSource audioSource;\n \n public AudioClip crySound;\n\n void Update()\n {\n if(Touchscreen.current != null)\n {\n var touch = Touchscreen.current.primaryTouch;\n\n if(touch.press.wasPressedThisFrame)\n {\n Vector2 touchPos = touch.position.ReadValue();\n\n Ray ray = Camera.main.ScreenPointToRay(touchPos);\n\n if(Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, animalLayer))\n {\n if (hit.transform.IsChildOf(transform))\n {\n if(crySound != null && !audioSource.isPlaying)\n {\n audioSource.PlayOneShot(crySound);\n }\n }\n }\n }\n }\n }\n}\n```\n\n3. ImageTracker 스크립트 작성\n```csharp\nusing System.Collections;\nusing System.Collections.Generic;\nusing UnityEngine;\nusing UnityEngine.XR.ARFoundation;\nusing ZXing;\nusing Unity.XR.CoreUtils;\nusing UnityEngine.XR.ARSubsystems;\n\n\npublic class ImageTracker : MonoBehaviour\n{\n\n ARCameraManager cameraManager;\n\n [SerializeField] List listName;\n\n [SerializeField] List listAnimal;\n\n\n Dictionary dictPrefab = new();\n\n Dictionary dictSpawn = new();\n\n string lastDetected = \"\";\n\n IBarcodeReader reader = new BarcodeReader();\n\n void Awake()\n {\n cameraManager = FindFirstObjectByType();\n\n for (int i = 0; i < listName.Count; i++)\n {\n dictPrefab[listName[i]] = listAnimal[i];\n }\n }\n\n void OnEnable()\n {\n StartCoroutine(ScanQR());\n }\n\n void OnDisable()\n {\n StopAllCoroutines();\n }\n\n IEnumerator ScanQR()\n {\n while (true)\n {\n yield return new WaitForSeconds(0.5f);\n\n if(!cameraManager.TryAcquireLatestCpuImage(out var cpuImage)) continue;\n\n using (cpuImage)\n {\n var tex = new Texture2D(cpuImage.width, cpuImage.height, TextureFormat.RGBA32, false);\n var conversionParams = new XRCpuImage.ConversionParams(cpuImage, TextureFormat.RGBA32);\n var buffer = tex.GetRawTextureData();\n cpuImage.Convert(conversionParams, buffer);\n tex.Apply();\n\n var result = reader.Decode(tex.GetPixels32(), tex.width, tex.height);\n Destroy(tex);\n\n if (result == null) continue;\n\n string detected = result.Text;\n\n if (detected == lastDetected) continue;\n lastDetected = detected;\n\n SpawnCharacter(detected);\n }\n }\n }\n\n void SpawnCharacter(string name)\n {\n if (!dictPrefab.ContainsKey(name)) return;\n\n if (dictSpawn.ContainsKey(name))\n {\n dictSpawn[name].SetActive(true);\n return;\n }\n\n var spawnPos = Camera.main.transform.position + Camera.main.transform.forward * 1.5f;\n var go = Instantiate(dictPrefab[name], spawnPos, Quaternion.identity);\n dictSpawn[name] = go;\n }\n\n void HideAll()\n {\n foreach (var go in dictSpawn.Values)\n go.SetActive(false);\n }\n}\n```\n\n4. qr 생성기 만들기\n - Assets 폴더 안에 Editor 폴더를 만들어서 넣고 유니티를 재시작하면 상단 메뉴에 Tool이 생긴다.\n```csharp\nusing UnityEngine;\nusing UnityEditor;\nusing ZXing;\nusing ZXing.QrCode;\nusing System.IO;\n\npublic class QRCodeGenerator : EditorWindow\n{\n string animalName = \"\";\n int qrSize = 256;\n string savePath = \"Assets/QRCodes\";\n\n Texture2D previewTex;\n\n [MenuItem(\"Tools/QR Code Generator\")]\n public static void ShowWindow()\n {\n GetWindow(\"QR Code Generator\");\n }\n\n void OnGUI()\n {\n GUILayout.Label(\"QR Code Generator\", EditorStyles.boldLabel);\n GUILayout.Space(10);\n\n animalName = EditorGUILayout.TextField(\"Animal Name\", animalName);\n qrSize = EditorGUILayout.IntField(\"QR Size\", qrSize);\n savePath = EditorGUILayout.TextField(\"Save Path\", savePath);\n\n GUILayout.Space(10);\n\n if (GUILayout.Button(\"Preview\"))\n {\n if (!string.IsNullOrEmpty(animalName))\n previewTex = GenerateQR(animalName, qrSize);\n }\n\n if (GUILayout.Button(\"Generate & Save\"))\n {\n if (!string.IsNullOrEmpty(animalName))\n SaveQR(animalName);\n }\n\n GUILayout.Space(10);\n\n if (previewTex != null)\n {\n GUILayout.Label(\"Preview:\");\n GUILayout.Label(previewTex, GUILayout.Width(256), GUILayout.Height(256));\n }\n }\n\n Texture2D GenerateQR(string text, int size)\n {\n var writer = new BarcodeWriter\n {\n Format = BarcodeFormat.QR_CODE,\n Options = new QrCodeEncodingOptions\n {\n Width = size,\n Height = size,\n Margin = 1\n }\n };\n\n var pixels = writer.Write(text);\n var tex = new Texture2D(size, size);\n tex.SetPixels32(pixels);\n tex.Apply();\n return tex;\n }\n\n void SaveQR(string text)\n {\n if (!Directory.Exists(savePath))\n Directory.CreateDirectory(savePath);\n\n var tex = GenerateQR(text, qrSize);\n var bytes = tex.EncodeToPNG();\n var path = $\"{savePath}/{text}_QR.png\";\n\n File.WriteAllBytes(path, bytes);\n AssetDatabase.Refresh();\n\n Debug.Log($\"QR 저장 완료: {path}\");\n previewTex = tex;\n }\n}\n```\n\n5. 세팅\n 1. 각 {color:#54a0ff}동물 프리팹에 'Audio Source'와 'Animal' 스크립트를 추가{/color}해준다.\n 2. Animal 스크립트 내부에 적절하게 배치해준다\n 3. 각 동물 프리팹에 {color:#ff4757}layer를 넣어{/color}준다.\n![|w=250px,center](uploads/learning/img_69fc1ee3125881.56665647.png)\n\n6. 씬에 {color:#54a0ff}빈오브텍트(ARBookManager)를 만들고 그안에 \"Image Tracker\" 스크립트를 넣어{/color}준다.\n - Name, Animal 리스트에 추가하여 이름과 프리팹 넣어주기\n\n7. QR 코드 만들기 {color:#54a0ff}상단 메뉴 Tool에서 생성기를 누르고 이름을 넣고 만든다{/color}.\n -> 앞으로 이것을 통해 소환된다.\n\n---\n\n### {color:#ee5a6f}문제점{/color}\n1. qr인식을 통해 소환되지만 크기가 제각각이다.\n2. 소환된 동물이 중력의 영향을 받아 떨어져서 없어진다\n3. 방향이 카메라 보는 방향을 보아서 다양한 각도에서 보기힘들다.",
"created_at": "2026-05-07",
"updated_at": "2026-05-07"
},
{
"id": 10,
"title": "동물사전 만들어보기 -2",
"category_id": 12,
"tags": [
"Unity",
"AR"
],
"content": "## 문제점 고치기\n\n### 1. QR 인식을 통해 소환되지만 크기가 제각각이다\n\n소환 위치가 AR 평면 감지 여부에 따라 달라지다 보니\n카메라와의 거리가 매번 달라져 크기가 제각각으로 보이는 문제가 있었다.\n\n`ImageTracker.cs`에 `spawnScale` 값을 추가해 소환 시 크기를 강제로 고정시켜 해결했다.\n\n```javascript\n// 소환 시 크기 고정\ngo.transform.localScale = Vector3.one * spawnScale;\n```\nInspector에서 `Spawn Scale` 값을 조절해 원하는 크기로 맞출 수 있다.\n\n---\n\n### 2. 소환된 동물이 중력의 영향을 받아 떨어져서 없어진다\n\n아주 간단하면서도 어이없는 이유였다.\n\n프리팹을 열어보니 원래 조종이 가능하게 설정되어 스크립트들과\n**Character Controller**가 컴포넌트로 들어가 있었다.\n\nCharacter Controller는 Rigidbody처럼 중력에 영향을 주기 때문에\n해당 컴포넌트를 제거하고 **Sphere Collider**만 추가해주니 해결되었다.\n\n![|w=250px,center](uploads/learning/img_69fc0fc99e4878.90583225.png)\n\n---\n\n### 3. 방향이 카메라 보는 방향을 보아서 다양한 각도에서 보기 힘들다\n\n소환된 동물이 항상 카메라를 정면으로 바라보도록 고정되어 있어\n측면이나 후면을 볼 수 없는 문제가 있었다.\n\n`Animal.cs`에 터치 드래그로 Y축 회전하는 로직을 추가해 해결했다.\n\n```javascript\n// 드래그로 Y축 360도 회전\nfloat deltaX = currentPos.x - lastTouchPos.x;\ntransform.Rotate(Vector3.up, -deltaX * rotateSpeed, Space.World);\n```\n\n터치한 채로 좌우로 드래그하면 동물이 Y축 기준으로 360도 회전해\n다양한 각도에서 확인할 수 있다.",
"created_at": "2026-05-08",
"updated_at": "2026-05-07"
},
{
"id": 11,
"title": "기획서 및 예상 방향",
"category_id": 21,
"tags": [
"미래내일일경험"
],
"content": "![](uploads/learning/img_69fc4a449dbd03.59040666.png)\n\n1. 간단한 테스트 결과 소켓으로 정보 전송이 가능하다 판별하여 진행하기로 합의.\n2. 모니터링의 구성방향에 대하여 소통결과 일단 2가지 방향으로 잡고 실행할 예정.\n3. 추후 담당자와 연결되면 어디까지 기술적으로 가능하고 안되는지 판별 후 추가 및 소거 예정.",
"created_at": "2026-05-07",
"updated_at": "2026-05-07"
},
{
"id": 12,
"title": "바이브코딩으로 주식 자동매매 프로그램 만들어보기 -1",
"category_id": 23,
"tags": [
"Claude",
"자동매매",
"바이브 코딩"
],
"content": "🚀 {color:#00f2ff}Claude Pro를 활용한 AI 주식 자동매매 프로그램 제작기{/color}\n최근 핫한 Claude Pro와 개인용 서버인 Synology NAS를 활용하여, AI 기반의 주식 자동매매 프로그램을 구축하는 과정을 기록합니다. 코드 자체보다는 기획, 피드백, 그리고 시스템의 작동 방식에 집중하여 프로젝트를 설계했습니다.\n\n---\n\n🛠️ {color:#00f2ff}개발 환경 구성{/color}\nAI 모델: Claude Pro (Anthropic)\n\n실행 환경: Synology NAS (Docker 또는 직접 실행)\n\n언어: Python\n\n---\n\n📋 1단계: 프로젝트 생성 및 지침(Custom Instructions) 설정\nClaude 내에 전용 프로젝트를 생성하고, 효율적인 협업을 위해 아래와 같은 엄격한 지침을 설정했습니다. 불필요한 설명을 줄이고 코드와 핵심 로직에만 집중하기 위함입니다.\n\n[Claude 프로젝트 지침]\n\n1. 코드만 출력, 설명은 주석으로 처리할 것\n\n2. 수정 시에는 변경된 함수나 diff 단위로만 제공할 것\n\n3. 모든 답변 마지막은 반드시 1줄 요약으로 끝낼 것\n\n4. 상세 설명은 \"설명해줘\"라고 별도 요청 시에만 작성할 것\n---\n\n📝 2단계: 프로젝트 기획서 초안 작성\n첫 번째 메시지로 \"주식 자동 매매 프로그램을 만들어볼까 하는데 너는 어떻게 하면 좋을지 기획서를 만들어봐\"라고 요청했습니다. Claude는 즉시 시스템 아키텍처와 주요 기능을 포함한 종합기획서_단타자동매매.md 파일을 생성했습니다.\n\n[📎 종합기획서_단타자동매매.md (18.4 KB)](uploads/learning/종합기획서_단타자동매매_6a041d60084271.93225085.txt)\n---\n\n🤖 3단계: AI 전략 고도화 (v2 업데이트)\n단순히 조건에 맞춰 매매하는 기능을 넘어, AI의 판단 능력을 어떻게 활용할지 고민했습니다. 토큰 비용과 효율성을 고려하여 다음과 같은 AI 시장 분석 로직을 추가했습니다.\n\nAI의 역할 정의\n - 장 시작 전 분석: 하루 한 번, 시장 데이터를 분석하여 \"오늘 거래를 진행할지\" 여부를 결정.\n\n - 리스크 관리: 당일 피해야 할 섹터나 종목을 미리 선별하여 필터링.\n\n - 실행: 결정된 가이드라인에 따라 프로그램이 실시간 단타 매매 수행.\n\n이러한 피드백을 반영하여 더욱 정교해진 종합기획서 v2가 완성되었습니다.\n\n[📎 종합기획서_단타자동매매_v2.md (25.8 KB)](uploads/learning/종합기획서_단타자동매매_v2_6a041d600dc6a8.01129668.txt)\n\n💡 주요 시스템 작동 방식 (Summary)\n 1. 시장 분석 (Pre-market): AI가 뉴스 및 지표를 분석해 당일 매매 전략 수립.\n\n 2. 데이터 수집 (Real-time): API를 통해 실시간 주가 및 체결 데이터 수신.\n\n 3. 전략 실행 (Execution): AI의 가이드라인 내에서 기술적 지표에 따라 자동 매수/매도.\n\n 4. 로깅 및 저장: 모든 거래 내역은 Synology NAS에 저장되어 사후 분석에 활용.\n\n![](uploads/learning/img_6a03e141ed08a3.31548852.png)\n\n---\n\n🔨다음에 해볼것!\n 1. 백테스트를 돌려보기 완료하기!(가상의 데이터로도 토큰을 많이 잡아먹기에 투자증권 api를 미리 발급받기)\n 2. 코드적으로 문제없는지 확인하고 만들기",
"created_at": "2026-05-13",
"updated_at": "2026-05-13"
},
{
"id": 13,
"title": "바이브코딩으로 주식 자동매매 프로그램 만들어보기 -2",
"category_id": 23,
"tags": [
"Claude",
"자동매매",
"바이브 코딩"
],
"content": "# 🚀 Claude Pro를 활용한 AI 주식 자동매매 프로그램 제작기\n\n## 2일차: 방향성 확정 및 시스템 구조 완성\n\n백테스트를 여러 차례 시도했지만 실질적인 결과를 얻기 어렵다는 판단을 내렸습니다. 합성 데이터는 현실의 갭하락, VI 발동, 거래정지 같은 변수를 반영하지 못하고, KRX 실제 데이터는 회원제로 전환되어 접근이 번거로워졌습니다.\n\n결론적으로 **백테스트보다 모의투자 직접 검증**이 더 현실적이라는 방향을 확정했습니다.\n\n---\n\n### 📋 시스템 방향성 확정\n\nClaude에게 원하는 구조를 명확하게 전달했습니다.\n\n1. 장 시작 30분 전, Claude Code가 뉴스·수급·지수를 분석하여 오늘 전략 판단\n2. 09:00 장 시작 시 프로그램이 자동 매매 시작\n3. 장 마감 후 오늘 결과를 Claude Code에 전송하여 피드백 및 코드 자동 수정\n4. 모든 과정을 Discord로 실시간 전송\n\n---\n\n### 🤖 핵심 기술 선택\n\n단순히 Claude API를 호출하는 방식이 아니라, **Claude Code headless** 모드를 Docker 컨테이너로 패키징하여 NAS Container Manager에서 자동 스케줄 실행하는 구조를 채택했습니다.\n\n이렇게 하면 별도의 API 비용 없이 **Claude Code 구독 하나**로 장 전 분석과 장 후 피드백을 모두 처리할 수 있습니다.\n\n---\n\n### ⚙️ 최종 시스템 구성\n\n```\n08:30 claude_morning 컨테이너 → 뉴스/수급 분석 → daily_context.json 생성\n → Discord 분석 결과 전송\n09:00 stockbot-main 컨테이너 → 변동성 돌파 전략 자동 매매 시작\n14:50 강제 전량 청산 (하드코딩, 예외 없음)\n15:10 일일 결산 저장 → Discord 결산 전송\n15:30 claude_evening 컨테이너 → 결과 분석 + 코드 자동 수정\n → reports/daily/ 리포트 저장\n → Discord 수정 내용 전송\n → 실전 전환 조건 충족 시 🚀 알림\n```\n\n---\n\n### ✅ 개발 완료 항목\n\nClaude와 함께 전체 폴더 구조와 코드를 완성하고 Gitea에 Push까지 마쳤습니다.\n\n- KIS Open API 연결 테스트 통과 (토큰 발급, 현재가, 잔고, 거래량 순위)\n- Discord Webhook 연결 테스트 통과\n- 모의투자 모드 (`KIS_MOCK=true`) + `DRY_RUN=true` 정상 구동 확인\n- 모의투자 예수금 10,000,000원 확인\n\n---\n\n### 🔭 다음 단계\n\n내일 장 시작(09:00)부터 `DRY_RUN=true` 상태로 실제 신호가 얼마나 발생하는지 Discord로 모니터링합니다. 며칠간 신호 패턴이 정상이면 `DRY_RUN=false`로 전환하여 모의투자 실주문을 시작합니다.\n\n30거래일 검증 후 아래 5가지 실전 전환 조건을 모두 충족하면 Claude Code가 자동으로 실거래 전환을 권고합니다.\n\n| 조건 | 기준 |\n|------|------|\n| 누적 운영 | 30거래일 이상 |\n| 승률 | 최근 30일 > 48% |\n| MDD | 최근 30일 < -10% |\n| 샤프지수 | 최근 30일 > 1.0 |\n| L3 발동 | 월 2회 이하 |",
"created_at": "2026-05-14",
"updated_at": "2026-05-18"
},
{
"id": 14,
"title": "문자",
"category_id": 8,
"tags": [
"C언어"
],
"content": "### **1. ==fgets==: 안전한 문자열 입력의 시작**\n\n==scanf==는 공백을 구분자로 인식하여 데이터가 누락될 위험이 있고, 무엇보다 **버퍼 오버플로우(Buffer Overflow)** 방어 메커니즘이 없습니다. 반면, ==fgets==는 안전한 입력의 표준입니다.\n\n- 필요 헤더: {color:#ff6b9d}<stdio.h>{/color}\n\n- 문법: {color:#ff6b9d}fgets(char *str, int n, FILE *stream);{/color}\n\n- 핵심 원리:\n\n - ==n==에 지정된 크기만큼만 데이터를 읽어 들여, 할당된 {color:#54a0ff}배열의 크기를 넘어서는 데이터가 입력되는 것을 원천 차단{/color}합니다.\n\n - 사용자가 입력한 {color:#ff4757}개행 문자(\\n)까지 버퍼에 포함시키는 특성{/color}이 있습니다.\n\n```c\nchar st[100];\nfgets(st, sizeof(st), stdin);\n```\n---\n### **2. ==strcspn==: 문자열의 '불순물' 제거**\n==fgets==로 입력을 받을 때 마지막에 포함되는 ==\\n==은 문자열 연산이나 비교 시 의도치 않은 결과를 낳을 수 있습니다. 이를 제거하는 것은 정제된 데이터를 다루기 위한 필수 과정입니다.\n\n- 필요 헤더: {color:#ff6b9d}<string.h>{/color}\n\n- 사용 문법: {color:#ff6b9d}st[strcspn(st, \"\\n\")] = '\\0';{/color}\n\n- 동작 원리:\n\n - ==strcspn==은 문자열 내에서 특정 문자(여기서는 ==\\n==)가 처음 등장하는 인덱스를 반환합니다.\n\n - 이 위치에 널 문자(==\\0==)를 강제로 삽입함으로써, 문자열을 ==\\n== 바로 앞에서 종료시킵니다.\n\n```c\n// 예시: 입력받은 문자열 끝의 \\n 제거\nchar st[100];\nfgets(st, sizeof(st), stdin);\nst[strcspn(st, \"\\n\")] = '\\0';\n```\n---\n\n### **3. ==isalnum==과 ==unsigned char== 형변환: 견고한 데이터 필터링**\n데이터를 검증할 때 단순히 문자를 체크하는 것을 넘어, '안전한 형변환'을 고려해야 합니다.\n\n- 필요 헤더: {color:#ff6b9d}<ctype.h>{/color}\n\n- 핵심 원리:\n\n - ==isalnum==은 {color:#54a0ff}매개변수로 정수를 전달{/color}받습니다.\n\n - C언어의 {color:#06ffa5}char는 시스템에 따라 음수를 가질 수 있습니다{/color}. 만약 한글이나 특수 문자가 입력되어 음수 값이 전달되면, 함수 내부적으로 배열의 음수 인덱스(Negative Index)를 참조하게 되어 프로그램이 즉시 종료(Crash)될 수 있습니다.\n\n - 이를 방지하기 위해 반드시 ==(unsigned char)==로 형변환을 하여 {color:#54a0ff}0~255 사이의 양수 인덱스만 전달{/color}되도록 해야 합니다.\n\n```c\n#include <ctype.h>\n\n// 필터링 예제\nchar st[100];\nfgets(st, sizeof(st), stdin);\nif (isalnum((unsigned char)st[i])) {\n // 영어이거나 숫자일 때만 수행할 작업\n}\n```\n> [!TIP]\n> ### 왜 unsigned char를 쓰나요?\n>char 타입은 시스템에 따라 음수를 가질 수 있습니다. 특수 기호나 한글 등이 입력되어 음수 값이 isalnum에 들어가면, 함수 내부 배열에서 잘못된 인덱스(음수 인덱스)를 참조하여 프로그램이 멈추거나 오류가 발생합니다. unsigned char로 변환하면 항상 양수(0~255)로 전달되므로 훨씬 안전합니다.\n\n---\n### **🚀 통합 예제: \"영어+숫자만 남기기\"**\n위에서 배운 함수들을 모두 조합하여, 입력받은 문자열에서 영어와 숫자만 남기고 소문자로 변환하는 코드입니다.\n```c\n#include <stdio.h>\n#include <string.h>\n#include <ctype.h>\n\nint main() {\n char st[101];\n char result[101];\n int j = 0;\n\n printf(\"문자열 입력: \");\n if (fgets(st, sizeof(st), stdin) != NULL) {\n \n // 1. 순회하며 알파벳/숫자만 골라내기\n for (int i = 0; st[i] != '\\0'; i++) {\n if (isalnum((unsigned char)st[i])) {\n result[j++] = tolower((unsigned char)st[i]);\n }\n }\n result[j] = '\\0'; // 문자열 끝 마무리\n \n printf(\"결과: %s\\n\", result);\n }\n return 0;\n}\n```",
"created_at": "2026-05-14",
"updated_at": "2026-05-14"
},
{
"id": 15,
"title": "프로젝트 수행 및 운영 계획",
"category_id": 21,
"tags": [
"미래내일일경험"
],
"content": "### 📂 프로젝트 수행 및 예산 운용 계획\n\n프로젝트의 본격적인 시작에 앞서, 팀원들과 함께 향후 수행 계획 및 지원금 활용 방안에 대해 심도 있는 논의를 진행했습니다.\n\n---\n\n### 💰 지원금 운용 계획: AI 도구 활용 극대화\n\n단순히 소모품을 구매하기보다, {color:#ffd166}팀 전체의 기술적 역량 강화와 결과물의 퀄리티 향상을 최우선 목표로 설정{/color}했습니다. 이에 따라 지원금의 대부분을 {color:#ffd166}최신 AI 솔루션 구독에 투자{/color}하기로 결정했습니다.\n\n - 선택 도구: ==Claude MAX==\n\n - 선정 이유: 최근 개발 및 코딩 분야에서 가장 뛰어난 퍼포먼스를 보여주는 모델로 판단했으며, 이를 통해 개발 효율성을 극대화하고자 합니다.\n\n---\n\n### 👥 팀 내부 역할 분담 (R&R)\n\n효율적인 {color:#3d6b50}프로젝트 관리를 위해 팀원들의 의사를 존중하여 역할을 분배{/color}했습니다. 팀장으로서의 업무 과부하를 방지하고 각 파트의 전문성을 높이는 데 집중했습니다.\n\n| 역할 | 담당 업무 | 비고 |\n| --- | --- | --- |\n| 팀장 | 프로젝트 총괄 및 방향성 설정, 최종 의사결정 | 전체적인 리딩 및 파트별 소통 |\n| 총무 | 지원금 집행 관리, 예산 정산 및 증빙 | 투명한 예산 운영 |\n| 서기 | 회의록 작성, 정기 보고서 및 문서화 | 프로젝트 히스토리 관리 |\n| 운영 보 | 전체적인 일정 관리 및 파트별 업무 서포트 | 유동적인 리소스 지원 |",
"created_at": "2026-05-14",
"updated_at": "2026-05-15"
},
{
"id": 16,
"title": "바이브코딩으로 주식 자동매매 프로그램 만들어보기 -3",
"category_id": 23,
"tags": [
"Claude",
"자동매매",
"바이브 코딩"
],
"content": "# 🚀 Claude Pro를 활용한 AI 주식 자동매매 프로그램 제작기\n\n## 3일차: 첫 실전 가동 — 버그와의 전쟁\n\n드디어 장 중에 처음으로 프로그램을 돌려봤습니다. 결론부터 말하면, 생각보다 많은 버그가 숨어 있었습니다.\n\n---\n\n### 🔴 첫 번째 문제 — KIS Rate Limit\n\n08:30 유니버스 갱신 시 30종목 전일 데이터를 한꺼번에 요청하면서 KIS API 초당 1건 제한을 초과했습니다. 에러 로그가 쏟아졌고, 실패한 종목을 계속 재시도하면서 무한 루프에 빠졌습니다.\n\n원인은 두 가지였습니다.\n\n- 전일 날짜가 아닌 **당일 날짜**로 OHLCV를 요청해서 데이터가 없었음\n- 이미 받은 종목도 재요청하는 로직\n\n`has_prev_data()` 메서드를 추가해서 캐시된 종목은 skip하고, sleep을 1.1초로 늘려서 해결했습니다.\n\n---\n\n### 🔴 두 번째 문제 — 타이밍 미스\n\n08:50에 재시작하는 바람에 08:30 유니버스 갱신 타이밍을 놓쳤습니다. 전일 데이터 없이 목표가 계산이 안 된 상태로 매매 루프가 시작됐습니다.\n\n오늘은 장 중 재시작을 감지해서 즉시 유니버스 + 목표가를 계산하는 임시 코드를 추가해서 진행했습니다. 다음 가동부터는 삭제할 코드입니다.\n\n---\n\n### 🔴 세 번째 문제 — 매도 실패\n\n매수는 되는데 매도가 계속 실패했습니다. 에러 메시지는 `near \"ORDER\": syntax error`.\n\nSQLite는 `UPDATE ... ORDER BY LIMIT` 문법을 지원하지 않습니다. `order_executor.py` 안에 직접 작성된 SQL이 문제였고, 서브쿼리 방식으로 수정해서 해결했습니다.\n\n```sql\n-- 수정 전 (SQLite 미지원)\nUPDATE trades SET ...\nWHERE ticker=? AND exit_time IS NULL\nORDER BY id DESC LIMIT 1\n\n-- 수정 후 (서브쿼리)\nUPDATE trades SET ...\nWHERE id = (\n SELECT id FROM trades\n WHERE ticker=? AND exit_time IS NULL\n ORDER BY id DESC LIMIT 1\n)\n```\n\n---\n\n### ✅ 오늘의 성과\n\n버그투성이였지만 결국 DRY_RUN 상태에서 아래 흐름이 전부 정상 동작하는 것을 확인했습니다.\n\n- 매수 신호 감지 ✅\n- 매도 실행 ✅\n- L3 3연속 손절 발동 → 당일 매매 중단 ✅\n\n오늘 데이터는 `price=0`으로 매수된 종목도 있어서 전략 판단 자료로는 쓸 수 없지만, 시스템이 설계대로 움직인다는 건 확인했습니다.\n\n---\n\n### 🤖 Claude Code 연동 완료\n\n매번 채팅창에서 코드를 주고받는 방식의 한계를 느꼈습니다. 토큰 소모도 많고, 수정된 코드를 파일에 적용하려면 복붙을 반복해야 했습니다.\n\n그래서 **Claude Code**를 로컬에 설치하고 Gitea와 연동했습니다. 이제 터미널에서 명령하면 Claude Code가 직접 파일을 읽고 수정하고 git push까지 합니다.\n\n```\n터미널 1: python app/main.py ← 매매 프로그램 실행\n터미널 2: claude ← 코드 수정/디버깅\n```\n\n`CLAUDE.md`와 `.claude/settings.json`도 세팅해서 매번 컨텍스트 설명 없이도 프로젝트 구조와 규칙을 인식하도록 했습니다.\n\n```json\n{\n \"dangerouslySkipPermissions\": true,\n \"instructions\": \"코드만 출력, 설명은 주석으로. 수정은 변경된 함수/diff 단위만. 수정 후 반드시 git commit/push.\"\n}\n```\n\n---\n\n### 📋 다음 단계\n\n- [ ] `check_entries()` / `check_exits()` sleep 1.1초 적용 (rate limit 근본 해결)\n- [ ] 월요일 08:30 전 정상 가동 확인\n- [ ] 로컬 계정 전환 + 작업 스케줄러 등록 (자동 시작)\n\n---\n\n## 🔭 앞으로 할 것\n\n### 1. 모의투자 정상 가동\n\n현재까지는 타이밍 미스와 버그 수정으로 정상적인 하루 흐름을 한 번도 완주하지 못했습니다. 08:30 전 시작 → 유니버스 갱신 → 목표가 계산 → 09:00 매매 루프 → 14:50 강제 청산 → 15:10 결산까지 전체 흐름이 한 번도 끊기지 않고 돌아가는 것을 먼저 확인합니다. 이후 며칠간 신호 패턴이 정상이면 `DRY_RUN=false`로 전환해서 모의투자 실주문을 시작합니다.\n\n---\n\n### 2. AI 장 전 분석 구현 (claude_morning)\n\n지금까지 `daily_context.json`이 없어서 AI 필터가 fallback 기본값으로만 동작했습니다. 원래 기획대로 장 시작 30분 전에 뉴스와 수급 데이터를 수집하고, 오늘 시장 분위기와 주목할 섹터를 판단해서 매매 프로그램에 전달하는 구조를 완성합니다. 이게 완성되면 기획서에 설계한 전체 흐름이 처음으로 완성됩니다.\n\n```\n08:30 claude_morning → 뉴스/수급 분석 → daily_context.json → Discord 전송\n09:00 매매 프로그램 → AI 필터 적용 → 자동 매매\n15:30 claude_evening → 결과 분석 + config.py 조정 → Discord 전송\n```\n\n---\n\n### 3. NAS 이전 및 완전 자동화\n\n로컬 PC에서 검증이 끝나면 Synology NAS Docker로 이전합니다. 작업 스케줄러 대신 Container Manager가 스케줄을 관리하고, PC 없이 NAS만으로 24시간 자동 운영되는 구조를 완성합니다. 30거래일 검증 후 5가지 실전 전환 조건을 모두 충족하면 Claude Code가 자동으로 실거래 전환을 권고합니다.",
"created_at": "2026-05-18",
"updated_at": "2026-05-18"
},
{
"id": 17,
"title": "바이브코딩으로 주식 자동매매 프로그램 만들어보기 -4",
"category_id": 23,
"tags": [
"Claude",
"자동매매",
"바이브 코딩"
],
"content": "# 4일차: 두 번째 실전 가동 — 구조적 버그 전면 수정\n\n- 첫날(05-18) 봇을 실제로 돌려보며 발견한 구조적 문제들을 오늘 전면 개선했습니다. \n- 버그 수정 4건 + 새 기능 1건.\n\n---\n\n## 🌅 오늘 아침 — 봇이 또 여러 번 켜졌다\n\n![center](uploads/learning/img_6a0c199a30c9b5.01614885.webp)\n\n07:55 스케줄러 태스크와 `run_morning.ps1` 마지막의 `/start-bot` 호출이 둘 다 살아있었습니다. \n봇이 두 번 시작되고 충돌하면서 재시작을 반복하는 상황.\n\n해결: `StockBot_Bot(07:55)` 태스크 비활성화. 모닝 스크립트가 분석 완료 후 봇을 직접 시작하는 구조로 정리.\n\n정리된 스케줄:\n\n| 시간 | 태스크 | 역할 |\n|-------|------------------|-----------------------|\n| 08:15 | StockBot_Morning | 장 전 분석 → 봇 시작 |\n| 11:20 | StockBot_Midday | 장중 분석 → 점심 세션 |\n| 15:30 | StockBot_Evening | 장 후 분석 → 리포트 |\n\n---\n\n## 📊 오늘 디스코드 알림 흐름\n\n![center](uploads/learning/img_6a0c1a0f63adc3.95162510.webp)\n\n08:16에 장 전 분석이 정상 완료됐습니다.\n\n```text\n[장전분석] 2026-05-19 08:15:10\n시장: 중립(52점) | 리스크: 보통 | ✅ 거래허용\n주목 섹터: 반도체, 지주사\n회피 섹터: 원유/에너지, 기술주(해외)\n관심 종목: 000660, 034730, 005930\n📝 美금리 쇼크+마이크론 급락 부담, 지주사 외국인 수급·반도체 모멘텀 혼재\n```\n\n09:00부터 매매가 시작되면서 알림이 쏟아졌습니다. \n전체 거래가 09:00~10:31에 집중됐는데, 변동성 돌파 전략이 장 초반에 가장 활발하게 작동하는 특성이 다시 한번 확인됐습니다.\n\n---\n\n## 🔴 버그 1 — KIS API TR_ID 오류\n\n장 전 분석에서 수급 데이터 수집 시 KIS API가 \"없는 서비스 코드\" 오류를 반환하는 문제.\n\n| 함수 | 기존 (오류) | 수정 |\n|------|------------|------|\n| `get_foreign_institution_rank()` | `FHKST04430000` | `FHPTJ04400000` + 파라미터 추가 |\n| `get_sector_trend()` | `FHKST03010100` | `FHPUP02100000` × 15개 섹터 개별 호출 |\n\nKIS Open API 공식 문서와 실제 동작하는 TR_ID가 달라서 직접 테스트로 하나씩 확인해야 했습니다.\n\n---\n\n## 🔴 버그 2 — L3 발동 시 SL 모니터링 중단\n\n==[red]:가장 치명적인 버그였습니다.== 10:31에 실제로 발생했습니다.\n\n```text\n10:31 [경고-L3] L3: 3연속 손절 발생\n → can_trade() = False\n → check_exits()도 스킵됨 ← 버그\n → 선도전기(007610) SL 감시 없이 방치\n```\n\n기존 L3는 3연속 손절 시 `can_trade() = False`로 전체 매매를 중단하는 방식이었는데, 청산 로직까지 함께 멈춰버렸습니다.\n\nB안으로 전환 — 전면 중단 대신 포지션 크기를 단계적으로 축소:\n\n| 연속 손절 | 포지션 크기 |\n|-----------|--------------|\n| 0회 | 1.0× (정상) |\n| 1회 | 0.7× |\n| 2회 | 0.5× |\n| 3회+ | 0.3× (최소) |\n| 익절 1회 | 한 단계 회복 |\n\n전면 중단이 없어지니 SL 모니터링도 항상 유지됩니다.\n\n---\n\n## 🔴 버그 3 — 14:30 이후 재시작 시 강제청산 미실행\n\n```python\n# 수정 전\nif \"09:00\" <= now <= \"14:30\": # 14:42 재시작 → 조건 밖 → trading_loop 미진입\n\n# 수정 후\nif \"09:00\" <= now < \"15:00\": # 강제청산(14:50) 전까지 포함\n```\n\n14:42에 봇을 재시작했는데 14:50 강제청산이 실행되지 않았고, 선도전기 포지션이 장 마감 후까지 열려있었습니다. 종가 기준 수동 처리했습니다.\n\n---\n\n## 🔴 버그 4 — 14:00~14:50 SL 모니터링 중단\n\n```python\n# 수정 전\nif now_str > \"14:00\":\n await asyncio.sleep(1)\n continue # check_exits() 스킵 → SL 감시 없음\n\n# 수정 후\nif now_str > \"14:00\":\n await self.check_exits() # 청산은 계속\n await asyncio.sleep(1)\n continue\n```\n\n신규 진입만 막고 청산은 계속 모니터링하도록 수정했습니다.\n\n---\n\n## ✨ 새 기능 — 점심 세션 이벤트 기반으로 전환\n\n![center](uploads/learning/img_6a0c1a0f93c817.82156909.webp)\n\n기존 방식은 12:00에 점심 세션이 고정으로 시작됐습니다. \n분석 결과와 무관하게 시간만 되면 시작하는 구조라 비효율적이었습니다.\n\n변경된 흐름:\n\n```\n11:20 /midday 실행\n → 오전 거래 결과 + 현재 시장 스냅샷 분석\n → midday_context.json 저장\n → 봇이 파일 감지 즉시 점심 세션 시작 (이벤트 기반)\n```\n\n봇이 파일 수정 시각(`mtime`)을 비교해서 새로운 분석이 도착했을 때만 반응합니다:\n\n```python\ndef _check_midday_context(self):\n path = Path(\"data/midday_context.json\")\n mtime = path.stat().st_mtime\n if mtime <= self._midday_ctx_mtime:\n return # 변경 없으면 무시\n ctx = json.loads(path.read_text(encoding=\"utf-8\"))\n # 점심 진입 허용 여부, 포지션 배율, 섹터 업데이트 적용\n self._midday_ctx_mtime = mtime\n```\n\n분석이 끝나는 시점에 바로 세션이 시작되므로, 12:00까지 기다리며 시간을 낭비하지 않습니다.\n\n---\n\n## 📈 오늘 매매 결과\n\n| 항목 | 수치 |\n|----------|----------------------------------|\n| 총 거래 | 9건 |\n| 승/패 | 6승 3패 (66.7%) |\n| 순손익 | ==[green]:+90,429원== |\n| TP1 합계 | +199,110원 |\n| SL 합계 | -108,681원 |\n| 강제청산 | +103,343원 (선도전기, 수동 처리) |\n\n버그가 있었음에도 수익이 났습니다. 시스템이 설계대로 작동하고 있다는 건 확인됐습니다.\n\n---\n\n## 📋 누적 현황\n\n- 운영 2거래일\n- 실전 전환 조건까지 28거래일 남음\n- 모드: 모의투자 (KIS_MOCK=true, DRY_RUN=true)\n\n---\n\n## 🔭 다음 단계\n\n- [ ] `DRY_RUN=false` 전환 → 모의투자 실주문 시작\n- [ ] `midday_context.json` 이벤트 감지 안정성 검증\n- [ ] NAS 이전 준비 (로컬 PC 검증 완료 후)",
"created_at": "2026-05-19",
"updated_at": "2026-05-19"
}
]