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