103 lines
4.4 KiB
C#
103 lines
4.4 KiB
C#
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();
|
|
}
|
|
}
|