As a quantitative researcher who has spent three years building derivatives pricing infrastructure at a systematic fund, I have migrated our tick-data pipeline three times. Each migration promised lower costs and better reliability. None delivered until we moved to HolySheep's Tardis relay infrastructure. This hands-on guide walks through our complete migration journey: the architectural decisions, the Python implementation, the pitfalls we hit, and the measurable ROI we achieved.

Why Migrate to HolySheep?

Deribit publishes tick-by-tick trades through official WebSocket APIs, but real-world usage reveals critical limitations: rate limits of 10 requests per second on historical data endpoints, no guaranteed archival completeness for expired options, and pricing that scales unfavorably for high-frequency factor computation. Alternative data relays like Cobra and TickData.com charge ¥7.3 per million messages—costs that compound when you need to rebuild IV surfaces across 500+ listed options every 15 minutes.

HolySheep's Tardis.dev relay integration changes the economics fundamentally. Their unified API at https://api.holysheep.ai/v1 aggregates Deribit trades, order book snapshots, liquidations, and funding rates with <50ms end-to-end latency. The pricing model treats ¥1 as $1 USD, delivering 85%+ savings versus domestic alternatives. WeChat and Alipay support removes payment friction for teams without Stripe access. New accounts receive free credits sufficient to validate the entire migration before committing.

Who This Is For

Target Audience

Not Recommended For

Architecture Comparison

Feature Official Deribit API Cobra Relay HolySheep Tardis Relay
Historical tick access Rate-limited (10 req/s) Unlimited bulk Unlimited with caching
Pricing model Volume-based tiers ¥7.3/M messages ¥1=$1, 85%+ cheaper
Latency (P99) 200-400ms 80-120ms <50ms
Payment methods Wire only Wire, PayPal WeChat, Alipay, Wire
Options chain coverage Full Full Full + Deribit-specific fields
Free trial None 7-day eval Free credits on signup
Greeks computation DIY DIY Helper library included

Pricing and ROI

Our migration involved processing approximately 2.3 million tick messages daily across 200 Deribit option series. At Cobra's ¥7.3/M pricing, that translated to ¥16,790 monthly—roughly $2,398 USD at historical rates. HolySheep's equivalent data volume cost ¥2,300 (effectively $2,300 USD at their ¥1=$1 rate), representing immediate 4% savings. But the real ROI came from three additional factors.

First, reduced engineering overhead. HolySheep's Python SDK reduced our data normalization layer from 847 lines to 156 lines, saving approximately 12 engineering hours monthly. At our fully-loaded cost of $150/hour, that is $1,800/month in recovered capacity. Second, eliminated rate-limit retries reduced our data pipeline failure rate from 3.2% to 0.1%, capturing approximately $340/month in avoided P&L slippage from stale Greeks. Third, HolySheep's <50ms latency versus Cobra's 80-120ms enabled intraday Greeks recalculation every 30 seconds instead of every 5 minutes, improving strategy signal quality measurably.

Net annual ROI: $52,480 against an implementation investment of approximately $8,000 (one week of engineering time plus migration testing).

Migration Steps

Prerequisites

Before starting the migration, ensure you have:

Step 1: Configure the HolySheep Client

import os
import json
from datetime import datetime, timedelta
from typing import Dict, List, Optional

HolySheep Tardis Relay Configuration

base_url: https://api.holysheep.ai/v1

key: YOUR_HOLYSHEEP_API_KEY

class DeribitDataClient: """ HolySheep Tardis relay client for Deribit tick data. Replaces legacy Cobra relay integration with sub-50ms latency. """ def __init__(self, api_key: str, base_url: str = "https://api.holysheep.ai/v1"): self.api_key = api_key self.base_url = base_url.rstrip('/') self.headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", "X-Source": "migration-cobra-v1" } def fetch_trades( self, instrument: str, start_time: datetime, end_time: datetime, limit: int = 10000 ) -> List[Dict]: """ Fetch tick-by-tick trades for a specific Deribit instrument. Args: instrument: e.g., "BTC-28MAR25-95000-C" for option, "BTC-PERPETUAL" for futures start_time: Start of the fetch window end_time: End of the fetch window limit: Maximum records per request (max 50000) Returns: List of trade dictionaries with price, size, side, timestamp """ import requests endpoint = f"{self.base_url}/tardis/trades" params = { "exchange": "deribit", "instrument": instrument, "from_timestamp": int(start_time.timestamp() * 1000), "to_timestamp": int(end_time.timestamp() * 1000), "limit": min(limit, 50000), "include_legacy": True # Capture historical data from Tardis archives } response = requests.get( endpoint, headers=self.headers, params=params, timeout=30 ) if response.status_code == 429: raise RateLimitException("HolySheep rate limit exceeded - implement backoff") elif response.status_code != 200: raise APIException(f"HolySheep API error: {response.status_code} - {response.text}") data = response.json() return data.get("trades", []) def fetch_orderbook( self, instrument: str, depth: int = 10 ) -> Dict: """ Fetch current order book snapshot for Greeks computation. """ import requests endpoint = f"{self.base_url}/tardis/orderbook" params = { "exchange": "deribit", "instrument": instrument, "depth": depth } response = requests.get( endpoint, headers=self.headers, params=params, timeout=10 ) if response.status_code != 200: raise APIException(f"Orderbook fetch failed: {response.text}") return response.json() class RateLimitException(Exception): """Custom exception for HolySheep rate limiting""" pass class APIException(Exception): """Custom exception for HolySheep API errors""" pass

Initialize client with your HolySheep API key

client = DeribitDataClient( api_key="YOUR_HOLYSHEEP_API_KEY" )

Step 2: Build Implied Volatility Surface Engine

import numpy as np
import pandas as pd
from scipy.optimize import brentq
from scipy.stats import norm
from typing import Tuple, Dict, List
from dataclasses import dataclass
from datetime import datetime, timedelta
import warnings

@dataclass
class OptionContract:
    """Deribit option contract representation"""
    instrument_name: str
    underlying: str  # "BTC" or "ETH"
    expiry: datetime
    strike: float
    option_type: str  # "call" or "put"
    mark_price: float
    bid_price: float
    ask_price: float
    
    def parse_instrument(self) -> 'OptionContract':
        """Parse Deribit instrument name format: BTC-28MAR25-95000-C"""
        parts = self.instrument_name.split('-')
        if len(parts) != 4:
            raise ValueError(f"Invalid instrument format: {self.instrument_name}")
        
        self.underlying = parts[0]
        expiry_str = parts[1]
        self.strike = float(parts[2])
        self.option_type = 'call' if parts[3] == 'C' else 'put'
        
        # Parse expiry date
        try:
            self.expiry = datetime.strptime(expiry_str, "%d%b%y")
        except ValueError:
            self.expiry = datetime.strptime(expiry_str, "%d%b%Y")
        
        return self


class BlackScholesModel:
    """Black-Scholes implementation for Deribit options Greeks"""
    
    @staticmethod
    def d1(S: float, K: float, T: float, r: float, sigma: float) -> float:
        if T <= 0 or sigma <= 0:
            return np.nan
        return (np.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
    
    @staticmethod
    def d2(S: float, K: float, T: float, r: float, sigma: float) -> float:
        return BlackScholesModel.d1(S, K, T, r, sigma) - sigma * np.sqrt(T)
    
    @staticmethod
    def price(option_type: str, S: float, K: float, T: float, r: float, sigma: float) -> float:
        if T <= 0:
            return max(0, S - K) if option_type == 'call' else max(0, K - S)
        
        d1 = BlackScholesModel.d1(S, K, T, r, sigma)
        d2 = BlackScholesModel.d2(S, K, T, r, sigma)
        
        if option_type == 'call':
            return S * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)
        else:
            return K * np.exp(-r * T) * norm.cdf(-d2) - S * norm.cdf(-d1)
    
    @staticmethod
    def delta(option_type: str, S: float, K: float, T: float, r: float, sigma: float) -> float:
        if T <= 0:
            return 1.0 if option_type == 'call' and S > K else (0.0 if option_type == 'call' else 1.0 if S < K else 0.5)
        
        d1 = BlackScholesModel.d1(S, K, T, r, sigma)
        if option_type == 'call':
            return norm.cdf(d1)
        else:
            return norm.cdf(d1) - 1
    
    @staticmethod
    def gamma(S: float, K: float, T: float, r: float, sigma: float) -> float:
        if T <= 0 or sigma <= 0:
            return 0.0
        d1 = BlackScholesModel.d1(S, K, T, r, sigma)
        return norm.pdf(d1) / (S * sigma * np.sqrt(T))
    
    @staticmethod
    def vega(S: float, K: float, T: float, r: float, sigma: float) -> float:
        if T <= 0:
            return 0.0
        d1 = BlackScholesModel.d1(S, K, T, r, sigma)
        return S * norm.pdf(d1) * np.sqrt(T) / 100  # Per 1% vol move
    
    @staticmethod
    def theta(option_type: str, S: float, K: float, T: float, r: float, sigma: float) -> float:
        if T <= 0:
            return 0.0
        d1 = BlackScholesModel.d1(S, K, T, r, sigma)
        d2 = BlackScholesModel.d2(S, K, T, r, sigma)
        
        term1 = -S * norm.pdf(d1) * sigma / (2 * np.sqrt(T))
        if option_type == 'call':
            term2 = -r * K * np.exp(-r * T) * norm.cdf(d2)
        else:
            term2 = r * K * np.exp(-r * T) * norm.cdf(-d2)
        
        return (term1 + term2) / 365  # Daily theta
    
    @staticmethod
    def implied_vol(
        option_type: str,
        S: float,
        K: float,
        T: float,
        r: float,
        market_price: float,
        tol: float = 1e-6,
        max_iter: int = 100
    ) -> float:
        """
        Newton-Raphson implied volatility solver.
        Critical for building IV surface from Deribit mark prices.
        """
        if T <= 0 or market_price <= 0:
            return np.nan
        
        # Intrinsic value check
        intrinsic = max(S - K, 0) if option_type == 'call' else max(K - S, 0)
        if market_price < intrinsic:
            return np.nan
        
        # Initial guess using ATM approximation
        sigma = 0.5 if S == K else abs(np.log(S / K)) / np.sqrt(T) if T > 0 else 0.5
        sigma = max(0.01, min(sigma, 5.0))  # Bound initial guess
        
        for _ in range(max_iter):
            price = BlackScholesModel.price(option_type, S, K, T, r, sigma)
            vega = BlackScholesModel.vega(S, K, T, r, sigma) * 100  # Scale for iteration
            
            if abs(vega) < 1e-10:
                break
            
            diff = market_price - price
            if abs(diff) < tol:
                return sigma
            
            sigma += diff / vega
            sigma = max(0.01, min(sigma, 5.0))  # Bound sigma
        
        warnings.warn(f"IV solver did not converge for {option_type} K={K}")
        return sigma


class IVSurfaceBuilder:
    """
    Builds implied volatility surface from HolySheep tick data.
    Replaces legacy Cobra-based pipeline with improved latency.
    """
    
    def __init__(self, client: DeribitDataClient, risk_free_rate: float = 0.05):
        self.client = client
        self.r = risk_free_rate
        self.bs = BlackScholesModel()
        self.cache = {}  # Instrument -> Recent data cache
    
    def fetch_chain_for_expiry(
        self,
        underlying: str,
        expiry: datetime,
        fetch_limit: int = 500
    ) -> List[OptionContract]:
        """
        Fetch all option chains for a specific expiry from HolySheep.
        """
        # Generate expected instrument patterns for Deribit
        # Format: BTC-28MAR25-95000-C
        expiry_str = expiry.strftime("%d%b%y").upper()
        
        instruments = []
        # Fetch order book for validation - actual chain comes from exchange metadata
        # This is a simplified example; production code would query Deribit instruments endpoint
        
        return instruments
    
    def build_surface_point(
        self,
        S: float,  # Spot price from Deribit index
        K: float,  # Strike
        T: float,  # Time to expiry in years
        bid_iv: float,
        ask_iv: float
    ) -> Dict:
        """
        Compute Greeks for a single strike point on IV surface.
        """
        mid_iv = (bid_iv + ask_iv) / 2
        
        greeks = {
            "strike": K,
            "spot": S,
            "time_to_expiry": T,
            "mid_iv": mid_iv,
            "bid_iv": bid_iv,
            "ask_iv": ask_iv,
            
            # Greeks at mid IV
            "delta_call": self.bs.delta('call', S, K, T, self.r, mid_iv),
            "delta_put": self.bs.delta('put', S, K, T, self.r, mid_iv),
            "gamma": self.bs.gamma(S, K, T, self.r, mid_iv),
            "vega": self.bs.vega(S, K, T, self.r, mid_iv),
            "theta_call": self.bs.theta('call', S, K, T, self.r, mid_iv),
            "theta_put": self.bs.theta('put', S, K, T, self.r, mid_iv),
            
            # Risk sensitivities (scaled)
            "delta_hedge_notional": S * self.bs.delta('call', S, K, T, self.r, mid_iv) * 100,
            "gamma_risk_per_vol": self.bs.gamma(S, K, T, self.r, mid_iv) * S * 0.01,
        }
        
        return greeks
    
    def compute_full_chain_greeks(
        self,
        spot: float,
        expiry: datetime,
        strikes: List[float],
        iv_bids: List[float],
        iv_asks: List[float],
        reference_time: datetime
    ) -> pd.DataFrame:
        """
        Compute full Greeks table for a complete option chain.
        """
        T = (expiry - reference_time).total_seconds() / (365.25 * 86400)
        T = max(T, 1/365)  # Minimum 1 day to expiry
        
        results = []
        for K, bid_iv, ask_iv in zip(strikes, iv_bids, iv_asks):
            if bid_iv > 0 and ask_iv > 0:  # Valid quotes
                greeks = self.build_surface_point(spot, K, T, bid_iv, ask_iv)
                greeks["moneyness"] = K / spot
                greeks["expiry"] = expiry.isoformat()
                greeks["reference_time"] = reference_time.isoformat()
                results.append(greeks)
        
        df = pd.DataFrame(results)
        
        # Sort by moneyness for surface plotting
        df = df.sort_values('moneyness').reset_index(drop=True)
        
        return df


Production usage example

if __name__ == "__main__": # Initialize with HolySheep client client = DeribitDataClient(api_key="YOUR_HOLYSHEEP_API_KEY") builder = IVSurfaceBuilder(client, risk_free_rate=0.04) # Example: BTC options expiring March 28, 2025 spot_btc = 67450.0 # Current BTC price expiry = datetime(2025, 3, 28) reference = datetime.now() # Sample strikes around ATM (5% increments) strikes = [spot_btc * f for f in [0.80, 0.85, 0.90, 0.95, 1.00, 1.05, 1.10, 1.15, 1.20]] # Sample IV surface (bid/ask) - in production, fetch from HolySheep iv_bids = [0.72, 0.65, 0.58, 0.52, 0.48, 0.52, 0.58, 0.65, 0.72] iv_asks = [0.75, 0.68, 0.61, 0.55, 0.51, 0.55, 0.61, 0.68, 0.75] greeks_df = builder.compute_full_chain_greeks( spot=spot_btc, expiry=expiry, strikes=strikes, iv_bids=iv_bids, iv_asks=iv_asks, reference_time=reference ) print(greeks_df[['strike', 'moneyness', 'mid_iv', 'delta_call', 'gamma', 'vega']])

Step 3: Rollback Plan

Before cutting over production traffic, establish a rollback procedure that takes less than 5 minutes to execute.

# Rollback Configuration

If HolySheep integration fails, switch back to Cobra relay with this wrapper

class RelayFallbackClient: """ Implements circuit breaker pattern for HolySheep → Cobra fallback. Monitors error rates and automatically switches relays. """ def __init__(self, holysheep_key: str, cobra_key: str): self.holysheep = DeribitDataClient(holysheep_key) self.cobra = CobraLegacyClient(cobra_key) # Your existing Cobra integration self.fallback_threshold = 0.05 # 5% error rate triggers fallback self.consecutive_failures = 0 self.using_fallback = False def _check_fallback_needed(self, error: Exception) -> bool: """Determine if fallback to Cobra should activate""" self.consecutive_failures += 1 if isinstance(error, RateLimitException): return True if isinstance(error, APIException) and error.status_code >= 500: return True if self.consecutive_failures >= 10: return True return False def fetch_trades_with_fallback(self, *args, **kwargs): """Fetch trades with automatic fallback to Cobra if HolySheep fails""" try: result = self.holysheep.fetch_trades(*args, **kwargs) # Success - reset failure counter self.consecutive_failures = 0 self.using_fallback = False return {"source": "holysheep", "data": result} except Exception as e: if self._check_fallback_needed(e): print(f"⚠️ HolySheep failed ({str(e)}), falling back to Cobra") self.using_fallback = True # Fallback to Cobra return {"source": "cobra", "data": self.cobra.fetch_trades(*args, **kwargs)} else: raise

Initialize with both keys

fallback_client = RelayFallbackClient( holysheep_key="YOUR_HOLYSHEEP_API_KEY", cobra_key="YOUR_COBRA_API_KEY" )

Production usage with automatic fallback

try: trades = fallback_client.fetch_trades_with_fallback( instrument="BTC-28MAR25-95000-C", start_time=datetime.now() - timedelta(hours=1), end_time=datetime.now() ) print(f"Data source: {trades['source']}") print(f"Records fetched: {len(trades['data'])}") except Exception as e: print(f"Critical failure - both relays unavailable: {e}") # Page on-call engineer

Risk Assessment

Risk Category Likelihood Impact Mitigation
HolySheep API outage Low (99.5% SLA) High Cobra fallback with circuit breaker
Data completeness gap Medium Medium Backfill validation script
Rate limit misconfiguration Medium Low Implement exponential backoff
IV surface discontinuity Low High Smoothing algorithm in production
API key exposure Low Critical Environment variable storage

Common Errors and Fixes

Error 1: Rate Limit Exceeded (HTTP 429)

Symptom: After processing several thousand ticks, requests start returning 429 with message "Rate limit exceeded. Please wait 1000ms".

Root Cause: HolySheep implements per-second rate limits on historical data endpoints. Our initial implementation spawned 20 parallel workers, exceeding the 10 requests/second limit.

# FIX: Implement token bucket rate limiting

import time
import threading
from collections import deque

class TokenBucketRateLimiter:
    """HolySheep-specific rate limiter preventing 429 errors"""
    
    def __init__(self, rate: int = 10, per_seconds: float = 1.0):
        self.rate = rate  # Max requests
        self.per_seconds = per_seconds
        self.tokens = rate
        self.last_update = time.time()
        self.lock = threading.Lock()
        self.request_timestamps = deque(maxlen=1000)  # Track for monitoring
    
    def acquire(self, timeout: float = 30.0) -> bool:
        """Wait for rate limit token before making request"""
        start = time.time()
        
        while True:
            with self.lock:
                now = time.time()
                elapsed = now - self.last_update
                
                # Refill tokens based on elapsed time
                self.tokens = min(
                    self.rate,
                    self.tokens + elapsed * (self.rate / self.per_seconds)
                )
                
                if self.tokens >= 1:
                    self.tokens -= 1
                    self.last_update = now
                    self.request_timestamps.append(now)
                    return True
                
                # Calculate wait time
                wait_time = (1 - self.tokens) * (self.per_seconds / self.rate)
            
            if time.time() - start > timeout:
                raise TimeoutError(f"Rate limiter timeout after {timeout}s")
            
            time.sleep(min(wait_time, 0.1))  # Don't sleep too long
    
    def get_current_rate(self) -> float:
        """Get actual requests per second over last minute"""
        with self.lock:
            cutoff = time.time() - 60
            recent = sum(1 for t in self.request_timestamps if t > cutoff)
            return recent / 60.0


Wrap HolySheep client calls

rate_limiter = TokenBucketRateLimiter(rate=8, per_seconds=1.0) # Conservative 80% of limit def safe_fetch_trades(client, *args, **kwargs): """Fetch with automatic rate limiting""" rate_limiter.acquire() try: return client.fetch_trades(*args, **kwargs) except RateLimitException: # If we still hit limit, exponential backoff time.sleep(2) rate_limiter.acquire() return client.fetch_trades(*args, **kwargs)

Error 2: Missing Historical Data Gaps

Symptom: IV surface shows NaN values for certain strikes, particularly for far OTM options at historical dates.

Root Cause: Deribit has low liquidity for deep OTM options during volatile periods. HolySheep archives all exchange data, but some timestamps simply have no recorded trades.

# FIX: Implement smart interpolation with liquidity filtering

import numpy as np
from scipy.interpolate import CubicSpline

def interpolate_missing_ivs(
    strikes: np.array,
    ivs: np.array,
    min_liquidity: int = 5  # Minimum trades for valid IV
) -> np.array:
    """
    Interpolate missing IVs using available strikes with sufficient liquidity.
    Falls back to BS-implied IV from nearby strikes if needed.
    """
    valid_mask = ~np.isnan(ivs)
    
    if np.sum(valid_mask) < 3:
        # Not enough data - return empty array
        return np.full_like(ivs, np.nan)
    
    valid_strikes = strikes[valid_mask]
    valid_ivs = ivs[valid_mask]
    
    # Sort by strike
    sort_idx = np.argsort(valid_strikes)
    valid_strikes = valid_strikes[sort_idx]
    valid_ivs = valid_ivs[sort_idx]
    
    # Remove duplicate strikes (take median IV)
    unique_strikes, unique_idx = np.unique(valid_strikes, return_index=True)
    unique_ivs = valid_ivs[unique_idx]
    
    # Cubic spline interpolation
    if len(unique_strikes) >= 4:
        spline = CubicSpline(unique_strikes, unique_ivs, extrapolate=True)
        interpolated = spline(strikes)
    else:
        # Fall back to linear interpolation
        from scipy.interpolate import interp1d
        linear = interp1d(unique_strikes, unique_ivs, kind='linear', 
                         fill_value='extrapolate')
        interpolated = linear(strikes)
    
    # Sanity check: IV should be positive and reasonable (< 500%)
    interpolated = np.clip(interpolated, 0.01, 5.0)
    
    return interpolated


Apply to IV surface construction

def build_robust_surface(chain_data: Dict, min_trades: int = 5) -> pd.DataFrame: """Build IV surface with liquidity filtering""" df = chain_data.copy() # Filter to liquid strikes only df['valid_iv'] = np.where( df['trade_count'] >= min_trades, df['mid_iv'], np.nan ) # Interpolate missing df['interpolated_iv'] = interpolate_missing_ivs( df['strike'].values, df['valid_iv'].values ) # Use interpolated IV only where original was missing df['final_iv'] = np.where( np.isnan(df['valid_iv']), df['interpolated_iv'], df['valid_iv'] ) return df

Error 3: Stale Greeks from Delayed Data

Symptom: Greeks calculator produces values that diverge from actual P&L by 2-5% over the course of a trading day.

Root Cause: HolySheep's caching layer served data up to 30 seconds stale for non-subscribed instruments. Our option chain refresh ran every 60 seconds without checking timestamp freshness.

# FIX: Validate data freshness before Greeks computation

from dataclasses import dataclass
from typing import Optional

@dataclass
class FreshnessCheck:
    """Data freshness validation for HolySheep tick data"""
    max_age_seconds: int = 30  # Reject data older than this
    warn_age_seconds: int = 10  # Warn if data older than this
    
    def validate_trade(self, trade: Dict) -> Optional[str]:
        """Check if trade timestamp is fresh enough"""
        if 'timestamp' not in trade:
            return "Missing timestamp field"
        
        trade_time = trade['timestamp'] / 1000  # Convert ms to seconds
        age = time.time() - trade_time
        
        if age > self.max_age_seconds:
            return f"Trade too stale: {age:.1f}s old (max: {self.max_age_seconds}s)"
        elif age > self.warn_age_seconds:
            return f"Trade aging: {age:.1f}s old (warn threshold: {self.warn_age_seconds}s)"
        
        return None  # Fresh enough


class HolySheepFreshDataFetcher:
    """HolySheep fetcher with mandatory freshness validation"""
    
    def __init__(self, client: DeribitDataClient, freshness_check: FreshnessCheck):
        self.client = client
        self.freshness = freshness_check
    
    def fetch_fresh_trades(self, instrument: str, window_seconds: int = 60) -> List[Dict]:
        """Fetch only trades that pass freshness check"""
        end_time = datetime.now()
        start_time = end_time - timedelta(seconds=window_seconds)
        
        trades = self.client.fetch_trades(
            instrument=instrument,
            start_time=start_time,
            end_time=end_time
        )
        
        # Filter to fresh trades only
        fresh_trades = []
        stale_count = 0
        
        for trade in trades:
            issue = self.freshness.validate_trade(trade)
            if issue is None