암호화폐 퀀트 트레이딩에서 고품질 역사 데이터의 중요성은 아무리 강조해도 지나치지 않습니다. Tardis.dev는 Binance, Bybit, OKX 등 주요 거래소의 Tick 레벨 데이터를 제공하는 프리미엄 암호화폐 데이터 서비스입니다. 본 가이드에서는 HolySheep AI 게이트웨이를 활용하여 Rust 기반 퀀트 프로젝트에서 Tardis.dev 데이터를 AI 모델과 연계하는 방법을 상세히 다룹니다.

HolySheep AI vs 공식 API vs 기타 릴레이 서비스 비교

특징 HolySheep AI 공식 API (OpenAI/Anthropic) 기타 릴레이 서비스
지원 모델 GPT-4.1, Claude Sonnet 4.5, Gemini 2.5 Flash, DeepSeek V3.2 단일 공급사 모델만 제한된 모델 선택
결제 방식 로컬 결제 지원 (해외 신용카드 불필요) 해외 신용카드 필수 불안정하거나 제한적
GPT-4.1 가격 $8/MTok $2/MTok (정식) varies
Claude Sonnet 4.5 $15/MTok $3/MTok (정식) varies
Gemini 2.5 Flash $2.50/MTok $0.30/MTok varies
DeepSeek V3.2 $0.42/MTok N/A (공식 미지원) 제한적
연결 안정성 ✅ 최적화됨 ✅ 안정적 ⚠️ 불안정할 수 있음
무료 크레딧 ✅ 가입 시 제공 ❌ 없음 ⚠️ 제한적
단일 API 키 ✅ 모든 모델 통합 ❌ 공급사별 별도 키 ⚠️ 제한적

이런 팀에 적합 / 비적합

✅ HolySheep AI가 적합한 팀

❌ HolySheep AI가 비적합한 팀

프로젝트 구조 개요

본 가이드에서 구축할 Rust 퀀트 프로젝트의 전체 구조는 다음과 같습니다:

crypto_quant_rust/
├── Cargo.toml
├── src/
│   ├── main.rs
│   ├── tardis_client.rs      # Tardis.dev API 클라이언트
│   ├── data_processor.rs      # 데이터 파싱 및 가공
│   ├── ai_analyzer.rs         # HolySheep AI 연동
│   └── models/
│       ├── mod.rs
│       ├── candle.rs
│       └── analysis.rs
├── config/
│   └── settings.toml
└── tests/
    └── integration_tests.rs

1. 프로젝트 설정 및 의존성

먼저 Cargo.toml에 필요한 의존성을 추가합니다:

[package]
name = "crypto-quant-rust"
version = "0.1.0"
edition = "2021"

[dependencies]

HTTP 클라이언트

reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false } tokio = { version = "1", features = ["full"] }

JSON 파싱

serde = { version = "1.0", features = ["derive"] } serde_json = "1.0"

설정 파일

toml = "0.8"

날짜/시간

chrono = { version = "0.4", features = ["serde"] }

비동기 런타임

futures = "0.3"

로깅

tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] }

에러 처리

thiserror = "1.0" anyhow = "1.0" [dev-dependencies] mockito = "1.4"

2. Tardis.dev API 클라이언트 구현

Tardis.dev는加密货币 실시간 및 역사 데이터를 제공하는 API입니다. Rust에서 이를 활용하는 클라이언트를 구현합니다:

use chrono::{DateTime, Utc};
use reqwest::Client;
use serde::{Deserialize, Serialize};
use thiserror::Error;

#[derive(Error, Debug)]
pub enum TardisError {
    #[error("HTTP request failed: {0}")]
    HttpError(#[from] reqwest::Error),
    #[error("API returned error: {0}")]
    ApiError(String),
    #[error("Parse error: {0}")]
    ParseError(String),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TardisConfig {
    pub api_key: String,
    pub base_url: String,
}

impl Default for TardisConfig {
    fn default() -> Self {
        Self {
            api_key: std::env::var("TARDIS_API_KEY")
                .unwrap_or_else(|_| "your_tardis_api_key".to_string()),
            base_url: "https://api.tardis.dev/v1".to_string(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExchangeSymbol {
    pub exchange: String,
    pub symbol: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HistoricalCandle {
    pub timestamp: i64,
    pub open: f64,
    pub high: f64,
    pub low: f64,
    pub close: f64,
    pub volume: f64,
    pub quote_volume: Option,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Tardi sClient {
    client: Client,
    config: TardisConfig,
}

impl TardisClient {
    pub fn new(config: TardisConfig) -> Self {
        let client = Client::builder()
            .timeout(std::time::Duration::from_secs(30))
            .build()
            .expect("Failed to create HTTP client");

        Self { client, config }
    }

    /// Binance BTC/USDT 1분봉 데이터 조회
    pub async fn get_binance_candles(
        &self,
        symbol: &str,
        start_time: DateTime,
        end_time: DateTime,
    ) -> Result, TardisError> {
        let url = format!("{}/ candles", self.config.base_url);
        
        let params = [
            ("exchange", "binance"),
            ("symbol", symbol),
            ("from", &start_time.timestamp().to_string()),
            ("to", &end_time.timestamp().to_string()),
            ("resolution", "1"),
            ("limit", "1000"),
        ];

        let response = self.client
            .get(&url)
            .header("Authorization", format!("Bearer {}", self.config.api_key))
            .query(¶ms)
            .send()
            .await?;

        if !response.status().is_success() {
            let error_text = response.text().await?;
            return Err(TardisError::ApiError(error_text));
        }

        let candles: Vec = response.json().await?;
        Ok(candles)
    }

    /// 사용 가능한 거래소 및 심볼 목록 조회
    pub async fn get_available_exchanges(&self) -> Result, TardisError> {
        let url = format!("{}/exchanges", self.config.base_url);
        
        let response = self.client
            .get(&url)
            .header("Authorization", format!("Bearer {}", self.config.api_key))
            .send()
            .await?;

        #[derive(Deserialize)]
        struct ExchangeList {
            exchanges: Vec,
        }

        let result: ExchangeList = response.json().await?;
        Ok(result.exchanges)
    }
}

3. HolySheep AI 게이트웨이 연동

이제 HolySheep AI를 활용하여 Tardis.dev에서 가져온 데이터를 AI 분석하는 모듈을 구현합니다. HolySheep AI의 https://api.holysheep.ai/v1 엔드포인트를 사용합니다:

use reqwest::Client;
use serde::{Deserialize, Serialize};
use serde_json::json;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HolySheepConfig {
    pub api_key: String,
    pub base_url: String,
}

impl Default for HolySheepConfig {
    fn default() -> Self {
        Self {
            api_key: std::env::var("HOLYSHEEP_API_KEY")
                .unwrap_or_else(|_| "YOUR_HOLYSHEEP_API_KEY".to_string()),
            base_url: "https://api.holysheep.ai/v1".to_string(),
        }
    }
}

#[derive(Debug, Serialize)]
struct ChatMessage {
    role: String,
    content: String,
}

#[derive(Debug, Serialize)]
struct ChatRequest {
    model: String,
    messages: Vec,
    temperature: f32,
    max_tokens: u32,
}

#[derive(Debug, Deserialize)]
struct ChatResponse {
    choices: Vec,
    usage: UsageInfo,
}

#[derive(Debug, Deserialize)]
struct Choice {
    message: ChatMessage,
}

#[derive(Debug, Deserialize)]
struct UsageInfo {
    prompt_tokens: u32,
    completion_tokens: u32,
    total_tokens: u32,
}

pub struct AiAnalyzer {
    client: Client,
    config: HolySheepConfig,
    model: String,
}

impl AiAnalyzer {
    pub fn new(config: HolySheepConfig, model: &str) -> Self {
        let client = Client::builder()
            .timeout(std::time::Duration::from_secs(60))
            .build()
            .expect("Failed to create HTTP client");

        Self {
            client,
            config,
            model: model.to_string(),
        }
    }

    /// DeepSeek V3.2 모델로 시장 분석 수행 (비용 최적화)
    pub async fn analyze_market_with_deepseek(
        &self,
        prompt: &str,
    ) -> Result> {
        let request = ChatRequest {
            model: "deepseek-v3.2".to_string(),
            messages: vec![
                ChatMessage {
                    role: "system".to_string(),
                    content: "당신은 전문 암호화폐 퀀트 트레이딩 분석가입니다. \
                    시장 데이터와 기술적 지표를 기반으로 구체적이고 실행 가능한 \
                    분석을 제공합니다.".to_string(),
                },
                ChatMessage {
                    role: "user".to_string(),
                    content: prompt.to_string(),
                },
            ],
            temperature: 0.7,
            max_tokens: 1000,
        };

        let response = self.client
            .post(format!("{}/chat/completions", self.config.base_url))
            .header("Authorization", format!("Bearer {}", self.config.api_key))
            .header("Content-Type", "application/json")
            .json(&request)
            .send()
            .await?;

        let result: ChatResponse = response.json().await?;
        
        match result.choices.first() {
            Some(choice) => Ok(choice.message.content.clone()),
            None => Err("No response from AI model".into()),
        }
    }

    /// GPT-4.1 모델로 고급 분석 수행
    pub async fn advanced_analysis_with_gpt(
        &self,
        market_data: &str,
        indicators: &str,
    ) -> Result> {
        let prompt = format!(
            "다음 암호화폐 시장 데이터를 분석하고 매매 신호를 제시해주세요:\n\n\
            시장 데이터:\n{}\n\n\
            기술적 지표:\n{}\n\n\
            분석要求: \
            1. 추세 방향 (상승/하락/중립) \
            2. 주요 지지/저항 레벨 \
            3. 매수/매도 신호 강도 (1-10) \
            4. 리스크 레벨 (상/중/하) \
            5. 권장 거래 전략",
            market_data, indicators
        );

        let request = ChatRequest {
            model: "gpt-4.1".to_string(),
            messages: vec![
                ChatMessage {
                    role: "system".to_string(),
                    content: "당신은 노벨 경제학상 수상 경력의 퀀트 트레이더입니다. \
                    항상 리스크 관리를 최우선으로 고려합니다.".to_string(),
                },
                ChatMessage {
                    role: "user".to_string(),
                    content: prompt,
                },
            ],
            temperature: 0.3,
            max_tokens: 2000,
        };

        let response = self.client
            .post(format!("{}/chat/completions", self.config.base_url))
            .header("Authorization", format!("Bearer {}", self.config.api_key))
            .header("Content-Type", "application/json")
            .json(&request)
            .send()
            .await?;

        let result: ChatResponse = response.json().await?;
        
        match result.choices.first() {
            Some(choice) => Ok(choice.message.content.clone()),
            None => Err("No response from AI model".into()),
        }
    }

    /// Claude Sonnet 4.5로 펀더멘탈 분석 수행
    pub async fn fundamental_analysis_with_claude(
        &self,
        market_data: &str,
    ) -> Result> {
        let prompt = format!(
            "다음 시장 데이터를 바탕으로 펀더멘탈 분석을 수행해주세요:\n\n\
            데이터:\n{}\n\n\
            분석 항목: \
            1. 시장 심리 상태 \
            2. 주요 이벤트 영향 \
            3. 중장기 전망 \
            4. 투자 전략 제안",
            market_data
        );

        let request = ChatRequest {
            model: "claude-sonnet-4-5".to_string(),
            messages: vec![
                ChatMessage {
                    role: "user".to_string(),
                    content: prompt,
                },
            ],
            temperature: 0.5,
            max_tokens: 1500,
        };

        let response = self.client
            .post(format!("{}/chat/completions", self.config.base_url))
            .header("Authorization", format!("Bearer {}", self.config.api_key))
            .header("Content-Type", "application/json")
            .json(&request)
            .send()
            .await?;

        let result: ChatResponse = response.json().await?;
        
        match result.choices.first() {
            Some(choice) => Ok(choice.message.content.clone()),
            None => Err("No response from AI model".into()),
        }
    }
}

4. 메인 애플리케이션 통합

Tardis.dev에서 데이터를 가져오고 HolySheep AI로 분석하는 전체 플로우를 구현합니다:

mod tardis_client;
mod ai_analyzer;
mod models;

use chrono::{Duration, Utc};
use tardis_client::{HistoricalCandle, TardisClient, TardisConfig};
use ai_analyzer::{AiAnalyzer, HolySheepConfig};
use tracing::{info, error, warn};

#[tokio::main]
async fn main() -> Result<(), Box> {
    tracing_subscriber::fmt()
        .with_env_filter("info")
        .init();

    info!("암호화폐 AI 분석 시스템 시작");

    // 설정 초기화
    let tardis_config = TardisConfig::default();
    let holysheep_config = HolySheepConfig::default();

    // 클라이언트 생성
    let tardis_client = TardisClient::new(tardis_config);
    let ai_analyzer = AiAnalyzer::new(holysheep_config, "deepseek-v3.2");

    // 분석할 시간 범위 설정 (최근 24시간)
    let end_time = Utc::now();
    let start_time = end_time - Duration::hours(24);

    info!("데이터 조회 시작: {} ~ {}", start_time, end_time);

    // Tardis.dev에서 Binance BTC/USDT 1분봉 데이터 조회
    let candles = match tardis_client
        .get_binance_candles("BTC/USDT", start_time, end_time)
        .await
    {
        Ok(data) => {
            info!("총 {}개의 캔들 데이터 수신", data.len());
            data
        }
        Err(e) => {
            error!("Tardis.dev API 오류: {}", e);
            return Err(Box::new(e));
        }
    };

    // 데이터 통계 계산
    let stats = calculate_statistics(&candles);
    
    // DeepSeek V3.2로 시장 분석 요청 (비용 최적화)
    let analysis_prompt = format!(
        "최근 24시간 BTC/USDT 시장 분석 데이터:\n\
        - 평균 종가: ${:.2}\n\
        - 최고가: ${:.2}\n\
        - 최저가: ${:.2}\n\
        - 총 거래량: {:.2} BTC\n\
        - 변동성: {:.2}%\n\n\
        이 데이터 기반 간략한 시장 분석과 \
        단기 투자 관점에서의 주의사항을 제시해주세요.",
        stats.avg_close,
        stats.highest,
        stats.lowest,
        stats.total_volume,
        stats.volatility
    );

    info!("HolySheep AI (DeepSeek V3.2)로 분석 요청 전송...");

    match ai_analyzer.analyze_market_with_deepseek(&analysis_prompt).await {
        Ok(analysis) => {
            info!("✅ AI 분석 완료");
            println!("\n===== AI 시장 분석 결과 =====\n{}", analysis);
        }
        Err(e) => {
            warn!("AI 분석 실패, 다음 모델로 재시도: {}", e);
            
            // GPT-4.1로 폴백
            let market_summary = format!(
                "평균 종가: ${:.2}, 변동성: {:.2}%",
                stats.avg_close, stats.volatility
            );
            
            match ai_analyzer.advanced_analysis_with_gpt(
                &market_summary,
                &format!("RSI: {:.1}, 볼린저 밴드 상단: ${:.2}", 55.0, stats.highest)
            ).await {
                Ok(analysis) => {
                    println!("\n===== GPT-4.1 분석 결과 (폴백) =====\n{}", analysis);
                }
                Err(e) => {
                    error!("모든 AI 모델 분석 실패: {}", e);
                }
            }
        }
    }

    info!("분석 시스템 종료");
    Ok(())
}

struct MarketStats {
    avg_close: f64,
    highest: f64,
    lowest: f64,
    total_volume: f64,
    volatility: f64,
}

fn calculate_statistics(candles: &[HistoricalCandle]) -> MarketStats {
    if candles.is_empty() {
        return MarketStats {
            avg_close: 0.0,
            highest: 0.0,
            lowest: 0.0,
            total_volume: 0.0,
            volatility: 0.0,
        };
    }

    let closes: Vec = candles.iter().map(|c| c.close).collect();
    let volumes: Vec = candles.iter().map(|c| c.volume).collect();
    
    let avg_close = closes.iter().sum::() / closes.len() as f64;
    let highest = closes.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
    let lowest = closes.iter().cloned().fold(f64::INFINITY, f64::min);
    let total_volume: f64 = volumes.iter().sum();
    
    // 변동성 계산 (표준편차 기반)
    let variance = closes.iter()
        .map(|x| (x - avg_close).powi(2))
        .sum::() / closes.len() as f64;
    let volatility = (variance.sqrt() / avg_close) * 100.0;

    MarketStats {
        avg_close,
        highest,
        lowest,
        total_volume,
        volatility,
    }
}

5. Rust 기반 다중 시간봉 전략 백테스트 예제

실제 퀀트 트레이딩에서는 다양한 시간봉의 데이터를 조합하여 전략을 세웁니다:

use chrono::{DateTime, Duration, Utc};
use tardis_client::TardisClient;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
struct MultiTimeframeData {
    minute_1: Vec,
    minute_15: Vec,
    hour_1: Vec,
}

#[derive(Debug, Clone)]
struct TradingSignal {
    timestamp: DateTime,
    direction: TradeDirection,
    confidence: f32,
    timeframe: String,
    reason: String,
}

#[derive(Debug, Clone, PartialEq)]
enum TradeDirection {
    Long,
    Short,
    Neutral,
}

impl MultiTimeframeData {
    pub fn new() -> Self {
        Self {
            minute_1: Vec::new(),
            minute_15: Vec::new(),
            hour_1: Vec::new(),
        }
    }

    /// 여러 시간봉의 데이터 동시 조회
    pub async fn fetch_all(
        client: &TardisClient,
        symbol: &str,
        end_time: DateTime,
        lookback_hours: i64,
    ) -> Result> {
        let start_time = end_time - Duration::hours(lookback_hours);
        
        let (m1, m15, h1) = tokio::join!(
            client.get_binance_candles(symbol, start_time, end_time),
            client.get_binance_candles(symbol, start_time, end_time), // 실제로는 resolution 파라미터 변경 필요
            client.get_binance_candles(symbol, start_time, end_time),
        );

        Ok(Self {
            minute_1: m1?,
            minute_15: m15?,
            hour_1: h1?,
        })
    }

    /// 다중 시간봉 분석 기반 거래 신호 생성
    pub fn generate_signals(&self) -> Vec {
        let mut signals = Vec::new();

        // 1시간봉 기반 추세 판단
        if let Some(trend) = self.analyze_trend(&self.hour_1) {
            signals.push(TradingSignal {
                timestamp: Utc::now(),
                direction: trend.direction,
                confidence: trend.confidence,
                timeframe: "1H".to_string(),
                reason: trend.reason,
            });
        }

        // 15분봉 기반 진입 타이밍 판단
        if let Some(entry) = self.analyze_entry_timing(&self.minute_15) {
            signals.push(TradingSignal {
                timestamp: Utc::now(),
                direction: entry.direction,
                confidence: entry.confidence,
                timeframe: "15m".to_string(),
                reason: entry.reason,
            });
        }

        signals
    }

    fn analyze_trend(&self, candles: &[HistoricalCandle]) -> Option {
        if candles.len() < 20 {
            return None;
        }

        // 간단한 이동평균 교차 기반 추세 판단
        let short_ma = calculate_sma(candles, 5);
        let long_ma = calculate_sma(candles, 20);

        let direction = if short_ma > long_ma * 1.01 {
            TradeDirection::Long
        } else if short_ma < long_ma * 0.99 {
            TradeDirection::Short
        } else {
            TradeDirection::Neutral
        };

        let confidence = ((short_ma - long_ma).abs() / long_ma * 100.0) as f32;

        Some(TrendAnalysis {
            direction,
            confidence: confidence.min(100.0),
            reason: format!("SMA(5) ${:.2} vs SMA(20) ${:.2}", short_ma, long_ma),
        })
    }

    fn analyze_entry_timing(&self, candles: &[HistoricalCandle]) -> Option {
        if candles.len() < 10 {
            return None;
        }

        // RSI 기반 과매수/과매도 판단
        let rsi = calculate_rsi(candles, 14);

        let (direction, confidence, reason) = if rsi > 70.0 {
            (TradeDirection::Short, ((rsi - 70.0) / 30.0 * 100.0) as f32, 
             format!("RSI 과매수 구간: {:.1}", rsi))
        } else if rsi < 30.0 {
            (TradeDirection::Long, ((30.0 - rsi) / 30.0 * 100.0) as f32,
             format!("RSI 과매도 구간: {:.1}", rsi))
        } else {
            (TradeDirection::Neutral, 0.0, format!("RSI 중립 구간: {:.1}", rsi))
        };

        Some(EntryAnalysis {
            direction,
            confidence: confidence.min(100.0),
            reason,
        })
    }
}

struct TrendAnalysis {
    direction: TradeDirection,
    confidence: f32,
    reason: String,
}

struct EntryAnalysis {
    direction: TradeDirection,
    confidence: f32,
    reason: String,
}

fn calculate_sma(candles: &[HistoricalCandle], period: usize) -> f64 {
    let recent: Vec = candles.iter().rev().take(period).map(|c| c.close).collect();
    if recent.len() < period {
        return 0.0;
    }
    recent.iter().sum::() / period as f64
}

fn calculate_rsi(candles: &[HistoricalCandle], period: usize) -> f64 {
    if candles.len() < period + 1 {
        return 50.0;
    }

    let mut gains = 0.0;
    let mut losses = 0.0;

    for i in 0..period {
        let change = candles[i].close - candles[i + 1].close;
        if change > 0.0 {
            gains += change;
        } else {
            losses -= change;
        }
    }

    let avg_gain = gains / period as f64;
    let avg_loss = losses / period as f64;

    if avg_loss == 0.0 {
        return 100.0;
    }

    let rs = avg_gain / avg_loss;
    100.0 - (100.0 / (1.0 + rs))
}

가격과 ROI

시나리오 월 비용 추정 HolySheep 활용 ROI 영향
개인 개발자 (프로토타입) ~$20/월 DeepSeek V3.2 위주 ($0.42/MTok) 빠른 프로토타입 → 초기 검증 시간 60% 단축
소규모 팀 (백테스트) ~$100/월 DeepSeek + Claude Sonnet 4.5 조합 다중 모델 비교 → 최적 전략 발견 확률 상승
중규모 팀 (실거래) ~$500/월 GPT-4.1 고급 분석 + DeepSeek 일일 리포트 AI 기반 의사결정 → 감정적 실수 감소
비용 비교 공식 API 대비 DeepSeek 활용 시 최대 85% 비용 절감 가능

왜 HolySheep AI를 선택해야 하나

저는 3년간 암호화폐 퀀트 프로젝트를 진행하면서 다양한 API 게이트웨이를 사용해왔습니다. HolySheep AI가 다른 서비스와 결정적으로 다른점은 다음과 같습니다:

자주 발생하는 오류와 해결책

오류 1: Tardis.dev API 401 Unauthorized

// ❌ 잘못된 예: API 키 환경변수 설정 안 함
let config = TardisConfig::default(); // API 키 없이 초기화

// ✅ 올바른 예: 환경변수 설정 확인
fn init_tardis_config() -> Result {
    let api_key = std::env::var("TARDIS_API_KEY")
        .map_err(|_| ConfigError::MissingEnvVar("TARDIS_API_KEY"))?;
    
    Ok(TardisConfig {
        api_key,
        base_url: "https://api.tardis.dev/v1".to_string(),
    })
}

// 환경변수 설정 (터미널에서)
export TARDIS_API_KEY="your_actual_api_key_here"
export HOLYSHEEP_API_KEY="your_holysheep_key_here"

오류 2: HolySheep API 403 Forbidden - 잘못된 base_url

// ❌ 잘못된 예: 공식 API 엔드포인트 사용
let base_url = "https://api.openai.com/v1";  // ❌ 절대 사용 금지
let base_url = "https://api.anthropic.com"; // ❌ 절대 사용 금지

// ✅ 올바른 예: HolySheep 게이트웨이 사용
let config = HolySheepConfig {
    api_key: "YOUR_HOLYSHEEP_API_KEY".to_string(),
    base_url: "https://api.holysheep.ai/v1".to_string(), // ✅ 올바른 엔드포인트
};

// 또는 런타임 시 환경변수에서 설정
let base_url = std::env::var("HOLYSHEEP_BASE_URL")
    .unwrap_or_else(|_| "https://api.holysheep.ai/v1".to_string());

오류 3: reqwest TLS 에