Files
tradeing/frontend/app/automation/page.tsx
T
chpark c4e6aab7b2 React + FastAPI 풀 마이그레이션 — Streamlit 제거
- backend/ — FastAPI + JWT + 모든 REST 엔드포인트
- frontend/ — Next.js 14 + Tailwind + 7페이지 (대시보드/트레이드/거래소/자동매매/설정/내정보/로그인)
- core_logic.py — 신호계산/알림 로직 분리 (기존 app_streamlit.py 에서 추출)
- users_db.py + bcrypt 인증, exchange_keys.py + Fernet 암호화
- trades_db.py — 진입/청산 lifecycle 추적, signal_events raw 로그
- settings_db.py — 모든 운영 파라미터 DB 영속 저장 (RSI/거래량/펀딩비 임계값 포함)
- docker-compose: frontend / backend / postgres + Traefik 라우팅
- assets/logo.svg — JUNGGOMOA 그라디언트 로고

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 17:27:11 +09:00

85 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useEffect, useState } from 'react';
import { api } from '@/lib/api';
import { Card, PageHeader, Input, Select, Button, Toggle, Banner } from '@/components/ui';
import { Bot, FlaskConical } from 'lucide-react';
export default function AutomationPage() {
const [cfg, setCfg] = useState<any>({});
const [creds, setCreds] = useState<any[]>([]);
const [msg, setMsg] = useState<string | null>(null);
async function load() {
const [a, c] = await Promise.all([api.get('/api/automation'), api.get('/api/exchange/credentials')]);
setCfg(a); setCreds(c.filter((x: any) => x.enabled));
}
useEffect(() => { load(); }, []);
async function save() {
await api.put('/api/automation', { values: cfg });
setMsg('✅ 저장 완료');
}
async function testBalance() {
try {
const r = await api.post('/api/automation/test/balance', {});
setMsg(r.ok ? `🧪 DryRun balance=${r.balance} USDT (${r.exchange})` : `${r.error}`);
} catch (e: any) { setMsg('❌ ' + e.message); }
}
return (
<div>
<PageHeader title="🤖 자동매매 설정" subtitle="현재 어댑터 — DRY-RUN 더미 (실 주문 X). 인터페이스/설정만 갖춰진 상태." />
<Banner level="warning"> SDK . stdout .</Banner>
<Card className="mt-5">
<div className="flex items-center gap-2 mb-4 text-blue-600">
<Bot size={16} /> <span className="font-bold text-slate-800 text-sm"> </span>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
<Toggle checked={cfg.enabled === '1'} onChange={(v: boolean) => setCfg({ ...cfg, enabled: v ? '1' : '0' })} label="자동매매 ON (글로벌 킬스위치)" />
<Toggle checked={cfg.dry_run === '1'} onChange={(v: boolean) => setCfg({ ...cfg, dry_run: v ? '1' : '0' })} label="DRY-RUN (실 주문 X)" />
<Select label="허용 방향" value={cfg.allowed_directions || 'long,short'} onChange={(e: any) => setCfg({ ...cfg, allowed_directions: e.target.value })}>
<option value="long,short">long + short</option>
<option value="long">long only</option>
<option value="short">short only</option>
</Select>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 mb-4">
<Select label="활성 거래소 키" value={cfg.active_credential || ''} onChange={(e: any) => setCfg({ ...cfg, active_credential: e.target.value })}>
<option value="">()</option>
{creds.map((c: any) => (
<option key={c.id} value={c.id}>#{c.id} {c.exchange.toUpperCase()} [{c.label || '-'}] {c.testnet ? '🧪' : '🟢'}</option>
))}
</Select>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-4">
<Input label="레버리지" type="number" min="1" max="125" value={cfg.leverage || '10'} onChange={(e: any) => setCfg({ ...cfg, leverage: e.target.value })} />
<Input label="포지션(잔고%)" type="number" step="0.1" value={cfg.position_size_pct || '1.0'} onChange={(e: any) => setCfg({ ...cfg, position_size_pct: e.target.value })} />
<Input label="동시 진입 최대" type="number" value={cfg.max_open_trades || '3'} onChange={(e: any) => setCfg({ ...cfg, max_open_trades: e.target.value })} />
<Input label="최소 신호 score" type="number" value={cfg.min_signal_score || '1'} onChange={(e: any) => setCfg({ ...cfg, min_signal_score: e.target.value })} />
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-4">
<Input label="Take Profit (%, 0=OFF)" type="number" step="0.1" value={cfg.tp_pct || '0.0'} onChange={(e: any) => setCfg({ ...cfg, tp_pct: e.target.value })} />
</div>
<div className="flex gap-2 pt-3 border-t border-slate-200">
<Button onClick={save}>💾 </Button>
<Button variant="secondary" onClick={testBalance}><FlaskConical size={14} className="inline mr-1" /> DryRun balance </Button>
</div>
{msg && <div className="mt-3 text-sm text-slate-600">{msg}</div>}
</Card>
<Card className="mt-4">
<div className="text-sm font-bold text-slate-800 mb-2"> (raw)</div>
<pre className="text-xs bg-slate-50 p-3 rounded overflow-x-auto">{JSON.stringify(cfg, null, 2)}</pre>
</Card>
</div>
);
}