Files
myProfile/data/learning.json
T

170 lines
48 KiB
JSON
Raw Normal View History

2026-05-31 21:05:59 +09:00
[
{
"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
"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.
"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
"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 결산까지 전체 흐름이 한 번도 끊기지 않고 돌아가는 것을 먼
"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() # 청산은
"created_at": "2026-05-19",
"updated_at": "2026-05-19"
}
]