Files
PineSript/Buyer/RealtimeTrader.cs

103 lines
4.4 KiB
C#
Raw Normal View History

2026-04-25 17:32:49 +09:00
using PineScriptV6;
using Serilog;
namespace Buyer
{
/// <summary>
/// 실시간 트레이더.
/// 1) REST API로 과거 봉을 받아 WarmUp (지표 초기화)
/// 2) CandlePoller로 봉 완성 시각마다 REST 폴링
/// 3) 새 완성 봉 → ProcessCandle → 신호 발생 시 로그 출력
/// </summary>
public class RealtimeTrader : IDisposable
{
private readonly BitgetConfig _cfg;
private readonly PineScriptExecutor _executor;
private readonly BitgetRestClient _rest;
private readonly CandlePoller _poller;
public RealtimeTrader(BitgetConfig cfg)
{
_cfg = cfg;
_executor = new PineScriptExecutor(cfg.PineScriptPath);
_rest = new BitgetRestClient(cfg);
_poller = new CandlePoller(cfg, _rest);
}
public async Task RunAsync(CancellationToken ct = default)
{
// ── Step 1: 과거 봉으로 WarmUp ───────────────────────────
Log.Information("[Trader] ═══ WarmUp 시작 ═══");
Log.Information("[Trader] 심볼={Symbol} 타임프레임={Gran} 봉수={N}",
_cfg.Symbol, _cfg.Granularity, _cfg.WarmUpBars);
var warmupCandles = await _rest.GetCandlesAsync(_cfg.WarmUpBars);
if (warmupCandles.Count == 0)
{
Log.Error("[Trader] WarmUp 데이터 없음 — 종료");
return;
}
_executor.WarmUp(warmupCandles);
// WarmUp 마지막 봉 시각을 폴러에 전달 → 중복 처리 방지
var lastWarmupTime = warmupCandles[^1].Time;
_poller.SetLastProcessedTime(lastWarmupTime);
Log.Information("[Trader] ═══ WarmUp 완료 ═══");
Log.Information("[Trader] 마지막 WarmUp 봉: {Time}",
lastWarmupTime.ToLocalTime().ToString("yyyy-MM-dd HH:mm"));
Log.Information("[Trader] 실시간 폴링 모드 진입 (Ctrl+C로 종료)");
// ── Step 2: 신호 델리게이트 등록 ────────────────────────
TradingSignal.OnBuy = price => LogOrder("BUY 롱 진입", price);
TradingSignal.OnSell = price => LogOrder("SELL 롱 청산", price);
TradingSignal.OnShort = price => LogOrder("SHORT 숏 진입", price);
TradingSignal.OnCover = price => LogOrder("COVER 숏 청산", price);
// ── Step 3: 폴러 콜백 등록 ──────────────────────────────
_poller.OnCandle += candle =>
{
try
{
var signals = _executor.ProcessCandle(candle);
Log.Information("[Trader] ProcessCandle 완료: {Time} 신호={N}개",
candle.Time.ToLocalTime().ToString("yyyy-MM-dd HH:mm"),
signals.Count);
// 신호는 TradingSignal 델리게이트를 통해 자동으로 LogOrder 호출됨
}
catch (Exception ex)
{
Log.Error(ex, "[Trader] ProcessCandle 오류: {Time}",
candle.Time.ToString("yyyy-MM-dd HH:mm"));
}
};
// ── Step 4: 폴링 루프 실행 (블로킹) ─────────────────────
await _poller.RunAsync(ct);
Log.Information("[Trader] 종료");
}
// ── 주문 로그 ────────────────────────────────────────────────
private void LogOrder(string action, decimal price)
{
string bar = new string('═', 42);
Log.Information(bar);
Log.Information(" ▶ {Action} | {Symbol} | {Price:F5}",
action, _cfg.Symbol, price);
Log.Information(" 시각: {Time}",
DateTimeOffset.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Log.Information(bar);
// TODO: 실제 주문 코드는 여기에 추가
// await _orderClient.PlaceMarketOrderAsync(_cfg.Symbol, side, qty);
}
public void Dispose() => _rest.Dispose();
}
}