"텔레그램_연동_기능_구현_및_설정_스키마_수정"
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -31,7 +31,7 @@ class HoldingSchema(BaseModel):
|
|||||||
class Config:
|
class Config:
|
||||||
from_attributes = True
|
from_attributes = True
|
||||||
|
|
||||||
@router.get("/summary", response_model=AccountStatusSchema)
|
@router.get("/balance", response_model=AccountStatusSchema)
|
||||||
async def get_account_summary(db: AsyncSession = Depends(get_db)):
|
async def get_account_summary(db: AsyncSession = Depends(get_db)):
|
||||||
stmt = select(AccountStatus).where(AccountStatus.id == 1)
|
stmt = select(AccountStatus).where(AccountStatus.id == 1)
|
||||||
result = await db.execute(stmt)
|
result = await db.execute(stmt)
|
||||||
|
|||||||
@@ -16,7 +16,19 @@ class SettingsSchema(BaseModel):
|
|||||||
appKey: str | None = None
|
appKey: str | None = None
|
||||||
appSecret: str | None = None
|
appSecret: str | None = None
|
||||||
accountNumber: str | None = None
|
accountNumber: str | None = None
|
||||||
|
|
||||||
|
# Integrations
|
||||||
|
useTelegram: bool | None = None
|
||||||
|
telegramToken: str | None = None
|
||||||
|
telegramChatId: str | None = None
|
||||||
|
|
||||||
|
useNaverNews: bool | None = None
|
||||||
|
naverClientId: str | None = None
|
||||||
|
naverClientSecret: str | None = None
|
||||||
|
|
||||||
|
# Configs
|
||||||
kisApiDelayMs: int | None = None
|
kisApiDelayMs: int | None = None
|
||||||
|
newsScrapIntervalMin: int | None = None
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
from_attributes = True
|
from_attributes = True
|
||||||
@@ -69,7 +81,18 @@ async def update_settings(payload: SettingsSchema, db: AsyncSession = Depends(ge
|
|||||||
if payload.accountNumber is not None:
|
if payload.accountNumber is not None:
|
||||||
settings.accountNumber = encrypt_str(payload.accountNumber)
|
settings.accountNumber = encrypt_str(payload.accountNumber)
|
||||||
|
|
||||||
|
# Integrations
|
||||||
|
if payload.useTelegram is not None: settings.useTelegram = payload.useTelegram
|
||||||
|
if payload.telegramToken is not None: settings.telegramToken = payload.telegramToken
|
||||||
|
if payload.telegramChatId is not None: settings.telegramChatId = payload.telegramChatId
|
||||||
|
|
||||||
|
if payload.useNaverNews is not None: settings.useNaverNews = payload.useNaverNews
|
||||||
|
if payload.naverClientId is not None: settings.naverClientId = payload.naverClientId
|
||||||
|
if payload.naverClientSecret is not None: settings.naverClientSecret = payload.naverClientSecret
|
||||||
|
|
||||||
|
# Configs
|
||||||
if payload.kisApiDelayMs is not None: settings.kisApiDelayMs = payload.kisApiDelayMs
|
if payload.kisApiDelayMs is not None: settings.kisApiDelayMs = payload.kisApiDelayMs
|
||||||
|
if payload.newsScrapIntervalMin is not None: settings.newsScrapIntervalMin = payload.newsScrapIntervalMin
|
||||||
|
|
||||||
await db.commit()
|
await db.commit()
|
||||||
await db.refresh(settings)
|
await db.refresh(settings)
|
||||||
|
|||||||
@@ -57,13 +57,13 @@ async def get_trade_history(limit: int = 100, db: AsyncSession = Depends(get_db)
|
|||||||
result = await db.execute(stmt)
|
result = await db.execute(stmt)
|
||||||
return result.scalars().all()
|
return result.scalars().all()
|
||||||
|
|
||||||
@router.get("/reserved-orders", response_model=List[ReservedOrderSchema])
|
@router.get("/", response_model=List[ReservedOrderSchema])
|
||||||
async def get_reserved_orders(db: AsyncSession = Depends(get_db)):
|
async def get_reserved_orders(db: AsyncSession = Depends(get_db)):
|
||||||
stmt = select(ReservedOrder)
|
stmt = select(ReservedOrder)
|
||||||
result = await db.execute(stmt)
|
result = await db.execute(stmt)
|
||||||
return result.scalars().all()
|
return result.scalars().all()
|
||||||
|
|
||||||
@router.post("/reserved-orders")
|
@router.post("/")
|
||||||
async def create_reserved_order(req: CreateReservedOrderRequest, db: AsyncSession = Depends(get_db)):
|
async def create_reserved_order(req: CreateReservedOrderRequest, db: AsyncSession = Depends(get_db)):
|
||||||
import uuid
|
import uuid
|
||||||
new_id = str(uuid.uuid4())
|
new_id = str(uuid.uuid4())
|
||||||
@@ -92,7 +92,7 @@ async def create_reserved_order(req: CreateReservedOrderRequest, db: AsyncSessio
|
|||||||
await db.commit()
|
await db.commit()
|
||||||
return {"id": new_id, "status": "MONITORING"}
|
return {"id": new_id, "status": "MONITORING"}
|
||||||
|
|
||||||
@router.delete("/reserved-orders/{order_id}")
|
@router.delete("/{order_id}")
|
||||||
async def delete_reserved_order(order_id: str, db: AsyncSession = Depends(get_db)):
|
async def delete_reserved_order(order_id: str, db: AsyncSession = Depends(get_db)):
|
||||||
from sqlalchemy import delete
|
from sqlalchemy import delete
|
||||||
await db.execute(delete(ReservedOrder).where(ReservedOrder.id == order_id))
|
await db.execute(delete(ReservedOrder).where(ReservedOrder.id == order_id))
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class UpdateGroupRequest(BaseModel):
|
|||||||
|
|
||||||
# --- Endpoints ---
|
# --- Endpoints ---
|
||||||
|
|
||||||
@router.get("/", response_model=List[WatchlistGroupSchema])
|
@router.get("/groups", response_model=List[WatchlistGroupSchema])
|
||||||
async def get_watchlists(db: AsyncSession = Depends(get_db)):
|
async def get_watchlists(db: AsyncSession = Depends(get_db)):
|
||||||
# Load groups with items (Need relationship setup?
|
# Load groups with items (Need relationship setup?
|
||||||
# Current models.py WatchlistGroup doesn't have `items` relationship defined explicitly in snippet provided?
|
# Current models.py WatchlistGroup doesn't have `items` relationship defined explicitly in snippet provided?
|
||||||
|
|||||||
BIN
backend/app/core/__pycache__/startup.cpython-312.pyc
Normal file
BIN
backend/app/core/__pycache__/startup.cpython-312.pyc
Normal file
Binary file not shown.
@@ -6,6 +6,7 @@ from app.db.models import ApiSettings
|
|||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.services.kis_auth import kis_auth
|
from app.services.kis_auth import kis_auth
|
||||||
from app.services.sync_service import sync_service
|
from app.services.sync_service import sync_service
|
||||||
|
from app.services.telegram_service import telegram_service
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -48,12 +49,17 @@ async def run_startup_sequence():
|
|||||||
logger.error(f" [FAILED] Authentication Failed: {e}")
|
logger.error(f" [FAILED] Authentication Failed: {e}")
|
||||||
logger.error(" Please check your AppKey/Secret and ensure KIS API Server is reachable.")
|
logger.error(" Please check your AppKey/Secret and ensure KIS API Server is reachable.")
|
||||||
|
|
||||||
# Phase 2.5: Telegram (Placeholder)
|
# Phase 2.5: Telegram Integration
|
||||||
if settings_obj.useTelegram and settings_obj.telegramToken:
|
if settings_obj.useTelegram and settings_obj.telegramToken and settings_obj.telegramChatId:
|
||||||
logger.info(">> [Phase 2] Telegram Token Found. Sending Startup Message...")
|
logger.info(">> [Phase 2.5] Telegram Integration Enabled. Sending Startup Notification...")
|
||||||
# TODO: Implement Telegram Sender
|
msg = "🚀 <b>BatchuKis 배취키스</b> 시스템이 시작되었습니다.\n자동매매 엔진이 가동 중입니다."
|
||||||
|
await telegram_service.send_message(
|
||||||
|
settings_obj.telegramToken,
|
||||||
|
settings_obj.telegramChatId,
|
||||||
|
msg
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logger.info(">> [Phase 2] Telegram Disabled or Token missing.")
|
logger.info(">> [Phase 2.5] Telegram Disabled or Token/ChatID missing.")
|
||||||
|
|
||||||
# Phase 3: Data Sync (Master Stocks & Account)
|
# Phase 3: Data Sync (Master Stocks & Account)
|
||||||
logger.info(">> [Phase 3-1] Syncing Account Data...")
|
logger.info(">> [Phase 3-1] Syncing Account Data...")
|
||||||
|
|||||||
BIN
backend/app/services/__pycache__/sync_service.cpython-312.pyc
Normal file
BIN
backend/app/services/__pycache__/sync_service.cpython-312.pyc
Normal file
Binary file not shown.
33
backend/app/services/telegram_service.py
Normal file
33
backend/app/services/telegram_service.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import httpx
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class TelegramService:
|
||||||
|
def __init__(self):
|
||||||
|
self.base_url = "https://api.telegram.org/bot"
|
||||||
|
|
||||||
|
async def send_message(self, token: str, chat_id: str, text: str) -> bool:
|
||||||
|
if not token or not chat_id:
|
||||||
|
logger.warning("Telegram: Token or Chat ID is missing.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
url = f"{self.base_url}{token}/sendMessage"
|
||||||
|
payload = {
|
||||||
|
"chat_id": chat_id,
|
||||||
|
"text": text,
|
||||||
|
"parse_mode": "HTML"
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
resp = await client.post(url, json=payload, timeout=10.0)
|
||||||
|
resp.raise_for_status()
|
||||||
|
logger.info(f"Telegram: Message sent successfully to {chat_id}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Telegram: Failed to send message: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
telegram_service = TelegramService()
|
||||||
Binary file not shown.
1828
backend/tmp_master/kosdaq_code.mst
Normal file
1828
backend/tmp_master/kosdaq_code.mst
Normal file
File diff suppressed because it is too large
Load Diff
BIN
backend/tmp_master/kosdaq_code.mst.zip
Normal file
BIN
backend/tmp_master/kosdaq_code.mst.zip
Normal file
Binary file not shown.
2486
backend/tmp_master/kospi_code.mst
Normal file
2486
backend/tmp_master/kospi_code.mst
Normal file
File diff suppressed because it is too large
Load Diff
BIN
backend/tmp_master/kospi_code.mst.zip
Normal file
BIN
backend/tmp_master/kospi_code.mst.zip
Normal file
Binary file not shown.
Reference in New Issue
Block a user