Splitting Telegram Alerts Into a User Channel and a Developer Channel

🌐 Korean Wiring Telegram notifications into a bot takes about thirty minutes. Get a bot token, call sendMessage , done. The trouble starts afterward: a single notification channel stops being usable very quickly. Buy filled, sell filled, rotation complete — all fine. But then API token expiry warnings, order rejection payloads, exception stacks and trigger failures land in the same room. Buried under messages they cannot interpret, users miss the fill notifications that actually matter. Then they turn notifications off. This post is about splitting alerts into a user channel and a developer channel. Structurally it is two functions, but where you draw that line ended up shaping how the whole thing is operated. The line between them One test: "is there anything the user can do with this message?" User channel — buy and sell fills, rotations, liquidations, bot start/stop. Things that happened in the account. A person can look and decide Developer channel ...

텔레그램 알림 이중화 — 사용자용과 개발자용을 갈라 놓기

🌐 English 봇에 텔레그램 알림을 붙이는 건 30분이면 끝납니다. 봇 토큰 받고, sendMessage 부르고, 끝. 문제는 그 다음입니다. 알림이 하나뿐이면 금방 못 쓰게 됩니다. 매수 체결, 매도 체결, 갈아타기 완료 — 여기까지는 좋습니다. 그런데 API 토큰 만료 경고, 주문 거부 응답, 예외 스택, 트리거 실패도 같은 방으로 들어옵니다. 사용자는 자기가 이해할 수 없는 메시지에 묻혀 정작 중요한 체결 알림을 놓칩니다. 그러다 알림을 꺼 버립니다. 이 글은 알림을 사용자 채널과 개발자 채널로 나눈 기록입니다. 함수 두 개로 갈라지는 단순한 구조지만, 그 경계를 어디에 긋느냐가 실제로는 운영 전체를 좌우했습니다. 둘로 나눈 기준 기준은 "이 메시지를 받고 사용자가 할 수 있는 일이 있는가" 하나입니다. 사용자 채널 — 매수·매도 체결, 갈아타기, 청산, 봇 시작/정지. 계좌에 실제로 일어난 일. 사용자가 보고 판단할 수 있습니다 개발자 채널 — API 오류, 예외, 파라미터 이상, 재시도 실패. 코드가 고쳐야 하는 일. 사용자가 받아 봐야 불안하기만 합니다 애매한 것들이 있습니다. "주문이 거부됐다"는 어느 쪽일까요. 둘 다입니다. 사용자에게는 "매수가 안 됐습니다"로, 개발자에게는 거부 코드와 응답 원문으로 갑니다. 같은 사건을 다른 언어로 두 번 보내는 것 이지, 한쪽으로 몰아 버리면 안 됩니다. 토큰과 chat_id를 어느 스코프에 둘 것인가 이 설계에서 가장 중요한 결정입니다. 두 채널은 봇 토큰부터 다릅니다. // 사용자 채널 — 봇 토큰은 공용, chat_id 는 사용자마다 다르다 function SendBOTUser(body) { if (getchatIdTel() == null || getchatIdTel() == "0") { return; ...

Turning a Monthly-Dividend ETF Rotation Strategy Into Code

이미지
🌐 Korean Up front: this post is about how a strategy gets translated into code . It is not investment advice and it guarantees nothing. The structure below can lose money, and it does. No specific product is recommended, so tickers are referred to only as "mid-month ETF" and "month-end ETF." Which instruments go in those slots is entirely the operator's decision, and so is the outcome. The hard part of automated trading is usually not coming up with a strategy — it is translating the rule already in your head into code. In your head, "rotate around the fifteenth, wait if the gap is wide, otherwise leave it alone" is one sentence. Unfold it into conditionals and it becomes twenty branches. This post covers implementing a rotation between two monthly-dividend ETFs in Apps Script. The short version: collapsing those twenty branches into a single array was the best decision in the whole implementation. Two slots, and why only two To col...

월배당 ETF 갈아타기 전략을 코드로 옮기기 — 날짜 배열 하나로 끝내기

이미지
🌐 English 먼저 밝힙니다. 이 글은 전략을 코드로 옮기는 방법 에 대한 기록이며, 투자 권유가 아니고 수익을 보장하지 않습니다. 아래 구조는 손실이 날 수 있고 실제로 납니다. 특정 종목을 추천하지 않으므로 상품명 대신 "월중 ETF / 월말 ETF"로만 씁니다. 어떤 종목을 넣을지는 전적으로 사용자 판단이며 그 결과도 본인 책임입니다. 자동매매에서 어려운 건 대체로 전략을 생각해 내는 것 이 아니라 이미 머릿속에 있는 규칙을 코드로 옮기는 것 입니다. 사람 머릿속에서는 "15일쯤에 갈아타고, 괴리 벌어지면 기다리고, 애매하면 그냥 둔다"가 한 문장인데, 이걸 조건문으로 펴면 갑자기 분기가 스무 개로 늘어납니다. 이 글은 월배당 ETF 두 종목을 번갈아 타는 규칙 을 Apps Script로 옮기면서 정리한 것들입니다. 결론부터 말하면, 스무 개의 분기를 배열 하나로 줄인 것 이 이 구현에서 가장 잘한 결정이었습니다. 월중과 월말, 두 칸으로 나눈 이유 월배당 ETF는 배당을 받으려면 특정 날짜까지 보유 하고 있어야 합니다. 이 날짜가 상품마다 다릅니다. 그래서 배당락 주기가 어긋나는 두 종목 을 잡으면, 한쪽의 마감일이 지난 뒤 다른 쪽의 마감일 전까지 옮겨 타는 구간이 생깁니다. 봇은 이 두 자리를 "월중" 칸과 "월말" 칸 으로 부릅니다. 화면에도 종목 카드가 두 장뿐이고, 각 카드에 종목번호·현재가·보유수량·괴리율·배당매수마감일이 붙습니다. 슬롯이 두 개로 고정 이라는 점이 중요합니다 — 종목을 몇 개든 담을 수 있게 만들면 "어느 것과 어느 것을 바꿀까"라는 문제가 새로 생기고, 그건 전혀 다른 전략입니다. 슬롯은 두 개로 고정입니다. 각 카드에 배당매수마감일과 괴리율이 함께 보입니다. 스케줄을 배열 하나로 처음에는 이렇게 썼습니다. if (day < 15) { ... } else if (da...

Publishing a Chrome Extension to the Web Store: Getting Through Review

이미지
🌐 Korean After the extension works, one step remains: getting it on the store. Running it locally in developer mode needs nothing but a folder, but handing it to someone else means mailing a zip and explaining "turn on developer mode." Chrome also greets anyone running an unpacked extension with a warning banner on every browser launch. This post covers actually listing the trading bot control panel from the previous two posts and passing review. Less about the click path, more about where it snags. Two numbers to know first A five dollar developer fee. One time per account, independent of how many extensions you publish. You cannot create a dashboard item until it clears, and activation can lag the payment slightly. Review takes days. Sometimes under a day, often a few. The number that matters is not the average but the variance . Ask for a lot of permissions or describe them vaguely and the submission routes to manual review, which is visibly slower. Largely,...

크롬 웹스토어에 확장 프로그램 배포하기 — 심사 통과까지

이미지
🌐 English 확장을 다 만들고 나면 마지막 단계가 남습니다. 스토어에 올리기. 개발자 모드로 로컬에서 쓸 때는 폴더만 있으면 되지만, 다른 사람에게 주려면 매번 zip을 보내고 "개발자 모드 켜세요"를 설명해야 합니다. 그리고 크롬은 개발자 모드로 로드한 확장에 브라우저를 켤 때마다 경고 배너 를 띄웁니다. 이 글은 앞의 두 글에서 만든 매매 봇 컨트롤 패널을 실제로 크롬 웹스토어에 올리고 심사를 통과한 기록입니다. 절차 자체보다 어디서 걸리는지 에 무게를 뒀습니다. 먼저 알아야 할 두 가지 숫자 개발자 등록비 5달러. 계정당 한 번 내는 일회성 비용이고, 확장 개수와 무관합니다. 이걸 내야 대시보드에 아이템을 만들 수 있습니다. 결제 후 계정 활성화까지 잠깐 걸릴 수 있습니다. 심사 기간은 며칠 단위. 빠르면 하루 안, 보통은 며칠입니다. 여기서 중요한 건 평균이 아니라 편차 입니다. 권한을 많이 요구하거나 설명이 모호하면 수동 검토로 넘어가면서 눈에 띄게 길어집니다. "언제 통과되는지"는 대체로 내가 만든 manifest가 결정합니다. 권한 최소화가 곧 심사 속도다 앞 글에서 manifest 권한을 최소로 유지한 이야기를 했는데, 그 효과가 가장 크게 나타나는 곳이 여기입니다. 이 확장의 권한은 이렇습니다. "permissions": [ "windows" ], "host_permissions": [ "https://script.google.com/*" ] 심사에서 문제가 되는 것은 대체로 다음 셋입니다. <all_urls> 또는 넓은 host_permissions — "모든 사이트의 데이터를 읽고 변경" 경고가 뜨고, 왜 필요한지 소명해야 합니다 content_scripts — 남의 페이지에 코드를 주입한다는 뜻이라 검토 강도가 올라갑니다 ...

Building a Trading Bot Control Panel as a Chrome Extension (Manifest V3)

이미지
🌐 Korean Once the backend lives on Google Apps Script, the next question arrives: what do I drive it with? GAS can serve HTML from doGet , so a web app screen is the obvious answer, and that is where this started. A few weeks in, one friction had piled up: checking the bot meant opening a tab, finding the URL, and waiting for a load. Twenty times a session. So the control surface moved into a Chrome extension popup . This post is that migration, and what Manifest V3 had to say about it. Why an extension instead of a web dashboard One reason: it is always one click away . No tab to open — just a toolbar icon No URL to remember — the deployment URL hides inside the extension It appears over whatever page you are on — glance at a chart, act immediately If the browser is open, it is in the same place it always was What you give up is equally clear: the screen is small. A popup is effectively about 400px wide. That decides the character of the thing — this is a re...

크롬 확장으로 매매 봇 컨트롤 패널 만들기 (Manifest V3)

이미지
🌐 English 백엔드를 Google Apps Script로 올리고 나면 다음 질문이 나옵니다. 이걸 뭘로 조작하지? GAS는 doGet 으로 HTML을 뱉을 수 있으니 웹앱 화면을 만들면 됩니다. 실제로 처음엔 그렇게 했습니다. 그런데 몇 주 쓰다 보니 불편이 하나 쌓였습니다. 봇 상태 한 번 보려고 탭을 새로 열고, URL을 찾고, 로딩을 기다립니다. 장중에 이걸 하루 스무 번 합니다. 그래서 조작 화면을 크롬 확장 팝업 으로 옮겼습니다. 이 글은 그 과정과, Manifest V3에서 걸린 것들의 기록입니다. 왜 웹 대시보드가 아니라 확장인가 이유는 하나입니다. 항상 한 클릭 거리 에 있다는 것. 탭을 열 필요가 없다 — 툴바 아이콘 하나 URL을 기억할 필요가 없다 — 배포 URL은 확장 안에 숨는다 어느 사이트를 보고 있든 그대로 뜬다 — 차트를 보다가 바로 조작한다 브라우저가 켜져 있으면 항상 같은 자리에 있다 반대로 포기하는 것도 분명합니다. 화면이 좁습니다. 팝업은 실질적으로 400px 남짓입니다. 그래서 이 선택은 "대시보드가 아니라 리모컨" 이라는 성격을 확정합니다. 차트와 통계는 넣지 않고, 지금 상태 확인 + 시작/정지 + 파라미터 수정 만 담기로 했습니다. 좁은 화면이 오히려 기능을 골라 줬습니다. 확장 팝업 전체 모습. 폭 400px 안에 상태 pill, 액션 바, 종목 카드, 계좌 설정을 세로로 쌓았습니다. Manifest V3 — 권한은 적을수록 좋다 MV3의 manifest는 생각보다 짧습니다. 중요한 건 무엇을 넣느냐가 아니라 무엇을 안 넣느냐 입니다. { "manifest_version": 3, "name": "Stock Trading Bot", "description": "Apps Script 기반 자동 주식 매매 봇 컨트롤 패널...

Building a Trading Bot Without a Server: Google Apps Script as the Backend

이미지
🌐 Korean The first wall you hit when building a trading bot is not the strategy. It is "where do I run this?" It has to stay awake through the whole session, poll prices every few minutes, and if the process dies at 3 a.m. you lose an entire trading day. Renting a VPS solves it for a few dollars a month, but now you own a server: OS updates, certificates, restart-on-boot, log rotation. This post is about not building that server at all, and using Google Apps Script (GAS) as the backend instead. The short version: it costs nothing and runs around the clock. In exchange you inherit a specific set of constraints, and those constraints end up deciding a surprising amount of the design. Why Apps Script can be a backend at all Apps Script is usually filed under "spreadsheet automation," but it hands you three things for free: An HTTP endpoint — deploy as a web app and doGet / doPost get a public URL A scheduler — time-based triggers can run a function ...

서버 없이 자동매매 봇 만들기 — Google Apps Script를 백엔드로 쓰기

이미지
🌐 English 자동매매 봇을 만들 때 가장 먼저 부딪히는 건 전략이 아니라 "이걸 어디서 돌리지" 입니다. 장중 내내 깨어 있어야 하고, 몇 분에 한 번씩 시세를 확인해야 하고, 새벽에 프로세스가 죽으면 다음 날 매매가 통째로 없습니다. VPS를 하나 빌리면 해결되지만 월 몇 달러가 나가고, 무엇보다 관리할 서버가 하나 늘어납니다 . OS 업데이트, 인증서, 재부팅 후 자동 시작, 로그 로테이션. 이 글은 그 서버를 아예 만들지 않고 Google Apps Script(GAS)를 백엔드로 쓴 기록입니다. 결과부터 말하면 비용 0원에 24시간 돌아갑니다. 대신 GAS 특유의 제약이 있고, 그 제약이 설계를 상당 부분 결정합니다. GAS를 백엔드로 쓴다는 발상 Apps Script는 보통 "스프레드시트 자동화 도구"로 알려져 있지만, 실제로는 세 가지를 공짜로 줍니다. HTTP 엔드포인트 — 웹앱으로 배포하면 doGet / doPost 가 공개 URL을 갖습니다 스케줄러 — 시간 기반 트리거로 1분마다 함수를 실행할 수 있습니다 영속 저장소 — PropertiesService 가 키-값 저장소 역할을 합니다 백엔드에 필요한 것이 사실상 이 셋입니다. API 서버, 크론, DB. 거래 파라미터 수십 개와 종목 두어 개를 다루는 봇이라면 RDB가 필요 없습니다. 그래서 이 조합이 성립합니다. doPost를 REST 엔드포인트로 웹앱으로 배포하면 doPost(e) 하나가 모든 요청을 받습니다. 라우터가 없으니 본문에 action을 실어 보내고 switch로 분기 하는 방식이 가장 단순합니다. function doPost(e) { const body = JSON.parse(e.postData.contents); const action = body.action; const payload = body.payload; switch (action) { ...