ניתוח נתוני קריפטו הוא רק הצעד הראשון. כשהנתונים מגיעים מחמש בורסות, שלוש רשתות בלוקצ'יין ושתי פלטפורמות חברתיות—כל מקור שולח אותם בפורמט משלו. Binance מחזירה חותמות זמן במילישניות, OKX בשניות, Telegram בפורמט datetime של UTC, נתוני on-chain בשניות Unix מהבלוק. הכמויות משתנות: wei, Gwei, מחרוזת עם נקודה עשרונית. אנו בונים שכבת נורמליזציה שהופכת את הכאוס הזה לפורמט יחיד וצפוי. העריכו את הפרויקט שלכם ביום אחד—פשוט צרו קשר.
כיצד נורמליזציית נתונים משפיעה על אמינות מערכות DeFi
שגיאה בטיקר אחד או אובדן דיוק במקום העשרוני השישי עלולים להוביל לאובדן כספים או למדדים שגויים. הניסיון שלנו—10+ שנים בפיתוח בלוקצ'יין—מראה ש-80% מתקריות הנתונים נובעות מנורמליזציה לא תקינה. בלעדיה, שום גידור מתגלגל או ארביטראז' לא עובד. צינור נתונים מנורמל מעבד נתונים פי 3 מהר יותר מתסריטים אד-הוק, והסבירות לשגיאות יורדת בסדר גודל. עם למעלה מ-50 פרויקטים מוצלחים, הצוות שלנו מבטיח פתרונות חזקים. העלות האופיינית לפרויקט שלנו מתחילה ב-$5,000 ויכולה לחסוך ללקוחות למעלה מ-$20,000 בשנה בהפחתת טיפול בשגיאות.
בעיות של נתונים הטרוגניים
נפרט פערים ספציפיים שנתקלים בהם בפרויקטים אמיתיים:
- חותמות זמן: Unix במילישניות (Binance, רוב ה-CEX), Unix בשניות (בלוקים של Ethereum, Chainlink), מחרוזות ISO 8601 (חלק מ-REST APIs), יחסי ("לפני שעתיים") — בגרידת נתונים חברתיים, datetimes עם אזור זמן לעומת ללא אזור זמן.
- כמויות ומחירים: Wei (10^-18 ETH) — Ethereum on-chain, Lamports (10^-9 SOL) — Solana on-chain, מחרוזת עם עשרונים ("1234.567890") — Binance REST, מספר שלם עם עשרונים קבועים (100000000 = 1 BTC בחלק מהבורסות), Float64 — אובדן דיוק במספרים גדולים.
- מזהי נכסים:
BTCUSDT(Binance),BTC-USDT(OKX),BTC/USDT(תקן ccxt),tBTCUST(Bitfinex), כתובת ERC-20 (0x2260fac...) לעומת טיקר (WBTC), מזהה CoinGecko ("bitcoin") לעומת מזהה CMC (1). - פורמטים מספריים:
nullלעומת"0"לעומת0לעומת שדה חסר — עבור נפחי אפס;-0.0— ערך תקף ב-Python/JS float, התנהגות בלתי צפויה בהשוואות; NaN — לפעמים נמצא ב-JSON מ-API של צד שלישי.
כיצד לבנות שכבת נורמליזציה
המערכת מורכבת משלוש שכבות:
Raw Data (from scrapers) ↓ [Validation Layer] — отбрасываем невалидные записи, логируем ошибки ↓ [Transformation Layer] — приводим к единому формату ↓ [Enrichment Layer] — добавляем derived поля (USD-стоимость, нормализованный тикер) ↓ Normalized Storage שכבת אימות
לפני הטרנספורמציה, אימות מפורש של נתוני הקלט. השתמשו ב-Pydantic v2 עבור Python. לפי תיעוד Pydantic, אימות קפדני מונע השחתת נתונים.
from pydantic import BaseModel, field_validator, model_validator from decimal import Decimal from datetime import datetime from typing import Optional class RawTradeEvent(BaseModel): """Схема для сырых trade событий от любой биржи""" exchange: str raw_symbol: str raw_price: str | float | int raw_quantity: str | float | int raw_timestamp: int | str | float side: str # 'buy'/'sell' или 'BUY'/'SELL' или 1/2 raw_trade_id: str | int @field_validator('raw_price', 'raw_quantity', mode='before') @classmethod def coerce_to_string(cls, v): if isinstance(v, float): return f"{v:.10f}" return str(v) @field_validator('side', mode='before') @classmethod def normalize_side(cls, v): s = str(v).lower() if s in ('buy', 'b', '1', 'true'): return 'buy' if s in ('sell', 's', '2', 'false'): return 'sell' raise ValueError(f"Unknown side value: {v}") רשומות לא תקינות לא שוברות את כל הצינור—הן מתועדות בטבלת Raw Data (from scrapers) ↓ [Validation Layer] — отбрасываем невалидные записи, логируем ошибки ↓ [Transformation Layer] — приводим к единому формату ↓ [Enrichment Layer] — добавляем derived поля (USD-стоимость, нормализованный тикер) ↓ Normalized Storage נפרדת עם הקשר גולמי וסיבת השגיאה.
שכבת טרנספורמציה
המרה לפורמט קנוני:
from dataclasses import dataclass from decimal import Decimal, ROUND_DOWN from datetime import datetime, timezone @dataclass class NormalizedTrade: exchange: str symbol: str # canonical: "BTC/USDT" price: Decimal # всегда Decimal, никаких float quantity: Decimal quote_quantity: Decimal # price * quantity side: str # 'buy' или 'sell' timestamp: datetime # UTC timezone-aware trade_id: str # строка, уникальна в рамках биржи def normalize_trade(raw: RawTradeEvent) -> NormalizedTrade: return NormalizedTrade( exchange=raw.exchange, symbol=normalize_symbol(raw.raw_symbol, raw.exchange), price=parse_decimal(raw.raw_price), quantity=parse_decimal(raw.raw_quantity), quote_quantity=parse_decimal(raw.raw_price) * parse_decimal(raw.raw_quantity), side=raw.side, timestamp=normalize_timestamp(raw.raw_timestamp), trade_id=str(raw.raw_trade_id), ) def normalize_timestamp(raw: int | str | float) -> datetime: """Приводит любой timestamp к UTC datetime""" if isinstance(raw, str): dt = datetime.fromisoformat(raw.replace('Z', '+00:00')) return dt.astimezone(timezone.utc) ts = float(raw) if ts > 1e12: ts = ts / 1000 return datetime.fromtimestamp(ts, tz=timezone.utc) def parse_decimal(value: str) -> Decimal: """Безопасная конвертация в Decimal""" try: d = Decimal(str(value)) if d.is_nan() or d.is_infinite(): raise ValueError(f"Non-finite decimal: {value}") return d except Exception as e: raise ValueError(f"Cannot parse decimal from '{value}': {e}") ב-Python, Decimal מבטיח אחסון מדויק של מספרי נקודה צפה.
נורמליזציית סמלים
מיפוי טיקרים בין בורסות הוא משימה נפרדת. אנו משתמשים בפורמט from pydantic import BaseModel, field_validator, model_validator from decimal import Decimal from datetime import datetime from typing import Optional class RawTradeEvent(BaseModel): """Схема для сырых trade событий от любой биржи""" exchange: str raw_symbol: str raw_price: str | float | int raw_quantity: str | float | int raw_timestamp: int | str | float side: str # 'buy'/'sell' или 'BUY'/'SELL' или 1/2 raw_trade_id: str | int @field_validator('raw_price', 'raw_quantity', mode='before') @classmethod def coerce_to_string(cls, v): if isinstance(v, float): return f"{v:.10f}" return str(v) @field_validator('side', mode='before') @classmethod def normalize_side(cls, v): s = str(v).lower() if s in ('buy', 'b', '1', 'true'): return 'buy' if s in ('sell', 's', '2', 'false'): return 'sell' raise ValueError(f"Unknown side value: {v}") תואם-ccxt:
SYMBOL_MAPPINGS = { "binance": { "BTCUSDT": "BTC/USDT", "ETHUSDT": "ETH/USDT", }, "okx": { "BTC-USDT": "BTC/USDT", "BTC-USDT-SWAP": "BTC/USDT:USDT", # perpetual }, "bybit": { "BTCUSDT": "BTC/USDT", "BTCPERP": "BTC/USDT:USDT", }, } def normalize_symbol(raw_symbol: str, exchange: str) -> str: exchange_map = SYMBOL_MAPPINGS.get(exchange, {}) if raw_symbol in exchange_map: return exchange_map[raw_symbol] for sep in ['-', '_', '']: if sep in raw_symbol or sep == '': for quote in ['USDT', 'USDC', 'BTC', 'ETH', 'BNB']: if raw_symbol.endswith(quote): base = raw_symbol[:-len(quote)] return f"{base}/{quote}" raise ValueError(f"Cannot normalize symbol '{raw_symbol}' for exchange '{exchange}'") למה רישום סכמות חשוב
מקורות נתונים משתנים. Binance עדכנה את ה-API שלה—הוסיפה שדה, שינתה פורמט חותמת זמן. ללא גרסת סכמה, כל הנורמליזציה נשברת. רישום סכמות (בדומה ל-Confluent Schema Registry עבור Kafka) פותר זאת: כל רשומה מכילה את גרסת הסכמה של המקור, נתונים ישנים לא נשברים, וניתן להריץ נורמליזציה מחדש כשהלוגיקה מתוקנת ללא גרידה מחדש.
SCHEMA_VERSIONS = { "binance_trade": { "v1": BinanceTradeV1Schema, # предыдущая версия API "v2": BinanceTradeV2Schema, # после обновления: добавлен quoteQty } } def get_schema(source: str, version: str): return SCHEMA_VERSIONS[source][version] ניטור איכות נתונים
נורמליזציה ללא ניטור היא אשליה של איכות. מדדים מרכזיים:
SELECT source, COUNT(*) FILTER (WHERE status = 'error') AS errors, COUNT(*) AS total, ROUND(100.0 * COUNT(*) FILTER (WHERE status = 'error') / COUNT(*), 2) AS error_rate_pct FROM normalization_log WHERE created_at > NOW() - INTERVAL '1 hour' GROUP BY source ORDER BY error_rate_pct DESC; התראה כאשר error_rate > 5% עבור כל מקור—משמעות הדבר שפורמט הנתונים השתנה ויש לעדכן את הסכמה. בדיקת עקביות בין מקורות: מחיר BTC זהה באותו זמן לא אמור לסטות בין בורסות ביותר מ-0.5%. כך מושגת אחריות לדיוק נתונים של 97.5%.
מדדי איכות נורמליזציה:
| מדד | תיאור | סף התראה |
|---|---|---|
| שיעור שגיאות | חלק הרשומות הלא תקינות | >5% |
| פער בין מקורות | סטיית מחיר BTC בין בורסות | >0.5% |
| אחזור | עיכוב מגרידה לנורמליזציה | >10 שניות |
מחסנית טכנולוגית
| רכיב | בחירה |
|---|---|
| אימות סכמות | Pydantic v2 (Python) או Zod (TypeScript) |
| עיבוד מספרי | Python validation_errors, PostgreSQL from dataclasses import dataclass from decimal import Decimal, ROUND_DOWN from datetime import datetime, timezone @dataclass class NormalizedTrade: exchange: str symbol: str # canonical: "BTC/USDT" price: Decimal # всегда Decimal, никаких float quantity: Decimal quote_quantity: Decimal # price * quantity side: str # 'buy' или 'sell' timestamp: datetime # UTC timezone-aware trade_id: str # строка, уникальна в рамках биржи def normalize_trade(raw: RawTradeEvent) -> NormalizedTrade: return NormalizedTrade( exchange=raw.exchange, symbol=normalize_symbol(raw.raw_symbol, raw.exchange), price=parse_decimal(raw.raw_price), quantity=parse_decimal(raw.raw_quantity), quote_quantity=parse_decimal(raw.raw_price) * parse_decimal(raw.raw_quantity), side=raw.side, timestamp=normalize_timestamp(raw.raw_timestamp), trade_id=str(raw.raw_trade_id), ) def normalize_timestamp(raw: int | str | float) -> datetime: """Приводит любой timestamp к UTC datetime""" if isinstance(raw, str): dt = datetime.fromisoformat(raw.replace('Z', '+00:00')) return dt.astimezone(timezone.utc) ts = float(raw) if ts > 1e12: ts = ts / 1000 return datetime.fromtimestamp(ts, tz=timezone.utc) def parse_decimal(value: str) -> Decimal: """Безопасная конвертация в Decimal""" try: d = Decimal(str(value)) if d.is_nan() or d.is_infinite(): raise ValueError(f"Non-finite decimal: {value}") return d except Exception as e: raise ValueError(f"Cannot parse decimal from '{value}': {e}") |
| תור | Redis Streams או Kafka |
| אחסון | PostgreSQL (מנורמל) + גיבוי גולמי ב-S3 |
| רישום סכמות | מותאם אישית או Confluent Schema Registry |
| ניטור איכות | בדיקות dbt + מדדי Prometheus |
נתונים גולמיים נשמרים תמיד ב-S3 לפני הנורמליזציה. אם מתגלה שגיאה בלוגיקת הנורמליזציה, ניתן להריץ אותה מחדש מהנתונים המקוריים ללא גרידה מחדש.
כיצד ליישם שכבת נורמליזציה: תהליך שלב-אחר-שלב
- ניתוח מקורות: זיהוי כל מקורות הנתונים (בורסות, בלוקצ'יינים, APIs), איסוף דוגמאות פורמט.
- עיצוב סכמות: יצירת סכמות Pydantic/Zod לכל מקור עם גרסאות.
- פיתוח טרנספורמציות: כתיבת פונקציות נורמליזציה לכל שדה (חותמת זמן, כמויות, סמלים).
- בדיקות וניטור: הרצה על נתונים היסטוריים, הגדרת התראות.
מה כלול בעבודה
חבילת שכבת הנורמליזציה שלנו כוללת תוצרים מוחשיים:
- שכבת נורמליזציה מוכנה למקורות שלכם (עד 7 בגרסה הבסיסית)
- דיאגרמת ארכיטקטורה מפורטת
- תיעוד סכמות ו-API
- גישה למאגר הקוד עם בדיקות
- מדדי ביצועים
- הכשרת הצוות שלכם לעבודה עם המערכת (עד 2 מפגשים)
- תמיכה למשך חודש לאחר ההשקה
אנו מספקים גם דוח ביצועים המציג שיפורי אחזור והפחתת שגיאות. צרו קשר כדי לדון בפרויקט שלכם. אנו מבטיחים תהליך שקוף וגישה אישית.







