Files
PineSript/Buyer/Program.cs
2026-04-26 19:00:54 +09:00

149 lines
8.6 KiB
C#

using Buyer;
using Serilog;
// ════════════════════════════════════════════════════════════════
// 로거 초기화
// ════════════════════════════════════════════════════════════════
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information() // 임시 — JSON 바디 확인 후 Information으로 되돌리세요
.WriteTo.Async(a => a.Console(
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"))
.WriteTo.Async(a => a.File(
path: "logs/buyer-.log",
rollingInterval: RollingInterval.Day,
outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"))
.CreateLogger();
// ════════════════════════════════════════════════════════════════
// CLI 인자 파싱
// -test : 테스트 모드 (지정 신호만 실행 후 종료)
// --buy : BUY 신호 테스트
// --sell : SELL 신호 테스트
// --short : SHORT 신호 테스트
// --cover : COVER 신호 테스트
// (신호 미지정 시) : BUY→SELL→SHORT→COVER 전체 실행
//
// 사용 예)
// Buyer.exe -test --short
// Buyer.exe -test --buy --sell
// Buyer.exe -test
// ════════════════════════════════════════════════════════════════
bool cliTestMode = args.Any(a => a.Equals("-test", StringComparison.OrdinalIgnoreCase));
var cliSignals = new List<string>();
if (cliTestMode)
{
if (args.Any(a => a.Equals("--buy", StringComparison.OrdinalIgnoreCase))) cliSignals.Add("BUY");
if (args.Any(a => a.Equals("--sell", StringComparison.OrdinalIgnoreCase))) cliSignals.Add("SELL");
if (args.Any(a => a.Equals("--short", StringComparison.OrdinalIgnoreCase))) cliSignals.Add("SHORT");
if (args.Any(a => a.Equals("--cover", StringComparison.OrdinalIgnoreCase))) cliSignals.Add("COVER");
}
// ════════════════════════════════════════════════════════════════
// 설정 — 변수를 여기서 관리
// ════════════════════════════════════════════════════════════════
var config = new BitgetConfig
{
// ── 종목 ──────────────────────────────────────────────────────
Symbol = "XRPUSDT",
ProductType = "USDT-FUTURES", // USDT-FUTURES | COIN-FUTURES
Granularity = BitgetGranularity.Min30, // BitgetGranularity 상수 사용
// ── WarmUp ────────────────────────────────────────────────────
// Pine Script 지표 최대 윈도우(sma72 등)보다 충분히 크게 설정
WarmUpBars = 500,
// ── REST API ──────────────────────────────────────────────────
RestBaseUrl = "https://api.bitget.com",
RestTimeoutSec = 30,
// ── 폴링 ──────────────────────────────────────────────────────
// REST API 호출 간격 (밀리초). 1000 = 1초마다 호출.
// 봉 완성 시점을 시간 계산 없이 서버 응답 기준으로 감지합니다.
PollIntervalMs = 1000,
// 폴링 1회에 조회할 봉 수 (3이면 충분, 누락 방지)
PollCandleCount = 3,
// ── Pine Script 경로 ──────────────────────────────────────────
// 실행 환경에 맞게 절대 경로 또는 상대 경로로 수정하세요.
PineScriptPath = @"example.pine",
// ── API 키 파일 경로 ──────────────────────────────────────────
// 비워두면 포지션 동기화 및 실제 주문이 비활성화됩니다.
ApiKeyFilePath = @"apikey.json",
// ── 주문 설정 ─────────────────────────────────────────────────
// OrderUsdt > 0 : USDT 금액 기준 자동 계산 (권장)
// 실거래: Floor(OrderUsdt / 현재가) — 예산 초과 방지
// 테스트: Ceiling(Max(OrderUsdt, 5) / 현재가) — 최소 5 USDT 보장
// OrderUsdt = 0 : OrderSize(계약 수 고정) 사용
OrderUsdt = 50m, // 1회 주문 금액 (USDT)
OrderSize = 1m, // OrderUsdt=0 일 때만 사용되는 계약 수 고정값
MarginMode = "crossed", // crossed(교차) | isolated(격리)
Leverage = 10, // 레버리지 배수
// ── 실행 모드 ─────────────────────────────────────────────────
// DryRun = true : 신호 로그만 출력, 실제 주문 미전송 (기본 — 안전)
// DryRun = false : 실제 Bitget 주문 전송
DryRun = false,
// TestOnStart = true : 시작 직후 BUY→SELL→SHORT→COVER 파이프라인 테스트
TestOnStart = false
};
// CLI -test 옵션 반영
if (cliTestMode)
{
config.TestOnStart = true;
config.ExitAfterTest = true;
if (cliSignals.Count > 0)
config.TestSignals = cliSignals;
// 신호 미지정 시 TestSignals는 빈 리스트 → RunTestCycleAsync에서 전체 실행
}
// ════════════════════════════════════════════════════════════════
// Ctrl+C 핸들러
// ════════════════════════════════════════════════════════════════
var cts = new CancellationTokenSource();
Console.CancelKeyPress += (_, e) =>
{
e.Cancel = true;
Log.Information("종료 요청 (Ctrl+C)...");
cts.Cancel();
};
// ════════════════════════════════════════════════════════════════
// 실행
// ════════════════════════════════════════════════════════════════
Log.Information("══════════════════════════════════════════");
Log.Information(" Buyer 실시간 트레이더 시작 (REST 폴링)");
Log.Information(" 심볼: {Symbol}", config.Symbol);
Log.Information(" 타임프레임: {Gran}", config.Granularity);
Log.Information(" 상품: {Product}", config.ProductType);
Log.Information(" WarmUp 봉수: {N}", config.WarmUpBars);
Log.Information(" 폴링 간격: {D}ms", config.PollIntervalMs);
Log.Information(" 스크립트: {Path}", config.PineScriptPath);
Log.Information(" 주문 방식: {Mode}", config.OrderUsdt > 0
? $"USDT 기준 {config.OrderUsdt} USDT 자동계산"
: $"계약 수 고정 {config.OrderSize}계약");
Log.Information(" 마진 모드: {Mode}", config.MarginMode);
Log.Information(" 레버리지: {Lev}x", config.Leverage);
Log.Information(" DryRun: {DryRun}", config.DryRun);
Log.Information(" API 키 파일: {Key}", string.IsNullOrWhiteSpace(config.ApiKeyFilePath) ? "(미설정)" : config.ApiKeyFilePath);
Log.Information("══════════════════════════════════════════");
try
{
using var trader = new RealtimeTrader(config);
await trader.RunAsync(cts.Token);
}
catch (Exception ex)
{
Log.Fatal(ex, "치명적 오류 발생");
}
finally
{
Log.Information("프로그램 종료");
await Log.CloseAndFlushAsync();
}