OKX · ניהול סיכונים
ניהול סיכונים במסחר אוטומטי דרך OKX API: פוזיציה, הזמנות וטיפול בשגיאות
הרבה אנשים חושבים שהסיכון באלגו בא מהשוק: טעית בכיוון — הפסדת. אבל מי שהריץ סקריפטים תקופה יגיד לך שמה שמאפס חשבון ברגע הוא בדרך כלל לא השוק, אלא הקוד שלך עצמך — כיוון הפוך, לולאה שלא נעצרת, הזמנה כפולה אחרי ניתוק רשת. במסחר ידני, לפני הלחיצה על אישור יש עוד מבט אחרון; לסקריפט אין את המבט הזה, הוא יבצע את הטעות בנאמנות וברציפות.
במאמר הזה אנחנו (צוות המערכת של MeowQuant) עוסקים במיוחד בניהול סיכונים באלגו, ומגיעים אליו מ"איך מגינים ברמת הקוד", לא בקריאות כלליות של "שלוט בפוזיציה". נפרק את כמה סוגי הסיכון הייחודיים למסחר API, ניתן קוד של סטופ-לוס, פיצול פוזיציות ו-retry שאפשר להעתיק, ואז נבהיר את הקו האדום של אבטחת החשבון ואת סדר העלייה לבמה של "התחלה קרה בסכום קטן". בסוף הקריאה אתה אמור להיות מסוגל להתקין לסקריפט שלך כמה בלמים: תקרה להזמנה בודדת (לסך הכול אנחנו נותנים רק שלד — הבדיקות שחסרות בו מפורטות בגוף הכתבה, וההשלמה עליך), סטופ-לוס שלא תלוי בזה שאתה יושב מול המסך, בדיקת סטטוס לפני כל שליחה חוזרת, והרשאות מצומצמות למינימום. בלמים מלאים לא אומרים שלא תפסיד — הם אומרים שכשמשהו משתבש, אתה יודע איפה הגבול של ההפסד.
הסיכונים הייחודיים של מסחר API
למסחר ידני ולמסחר אוטומטי דרך API יש סוגי סיכון שונים. האויב העיקרי של מסחר ידני הוא הרגש והשיפוט; במסחר API, מעל לזה, מתווספת קטגוריה שלמה של סיכונים "ייחודיים למכונה". מי שמזהה אותם יודע איפה צריך להתקין את הבלם.
תוכנה שמשתוללת
זה הטיפוסי ביותר. קטע לוגיקה נכתב שגוי — כיוון קנייה/מכירה הפוך, תנאי סטופ-לוס שנכתב הפוך, לולאה שחסר בה תנאי יציאה — והתוכנה לא נעצרת לחשוב "אולי משהו לא בסדר" כמו אדם, אלא מבצעת את ההוראות השגויות שוב ושוב. ידנית אולי היית מבחין כבר אחרי הזמנה שגויה אחת, אבל הסקריפט יכול להזין עשרות הזמנות שגויות בשניות. עוצמת ההרס של ההשתוללות נעוצה ב"נאמנות" שלה.
הזמנות כפולות
קורה הכי הרבה בקפיצות רשת או ב-timeout של בקשה. שלחת בקשת הזמנה, לא קיבלת תגובה, הסקריפט חשב שהיא נכשלה ושלח שוב — אבל בעצם ההזמנה הראשונה כבר התבצעה, וכך הזמנת פעמיים. בחוזים, זה אומר פוזיציה כפולה, סיכון כפול. הזמנה כפולה היא לרוב לא שגיאת לוגיקה, אלא חוסר טיפול ב"מצב הביניים הלא ודאי".
ניתוק רשת ונפילת חיבור
הסקריפט רץ על המחשב או השרת שלך, וברגע שהרשת מתנתקת הוא מאבד קשר עם הבורסה. המצב הגרוע: הפוזיציה שלך עדיין חשופה בשוק, ולוגיקת הסטופ-לוס שהייתה אמורה להגן עליה התנתקה יחד עם הסקריפט. עד שתתחבר מחדש, ייתכן שהכל כבר השתנה לבלי הכר. מהות סיכון הניתוק היא לא לשים את כל ההגנה על ההנחה ש"הסקריפט תמיד מקוון".
ניהול פוזיציות ופיצול
החומה הראשונה של ניהול סיכונים היא לא לתת לאף טעות בודדת לפגוע בכל ההון שלך. זה מושג דרך כללי פוזיציה, והכללים האלה צריכים להיות כתובים קשיח בקוד, לא להסתמך על איפוק שלך ברגע אמת.
כמה עקרונות שכדאי תמיד להקפיד עליהם:
- הגדר תקרה לפוזיציה בודדת. אף הזמנה לא תעלה על אחוז קבוע מסך ההון (למשל 1%–2%). כך גם אם ההזמנה הזו טועה בכיוון לחלוטין, להפסד יש תקרה.
- הגדר תקרה לסך הפוזיציות. סך הפוזיציות הפתוחות לא יעלה על סכום כולל, ואל תיתן לתוכנה הזדמנות לזרוק את כל ההון בבת אחת.
- פצל פוזיציות, אל תיכנס הכל בבת אחת. חלק את ההון לכמה מנות שנכנסות בהדרגה, כדי להפחית את הזעזוע מטעות שיפוט בנקודת כניסה אחת.
- השאר מזומן לסקריפט. שמור תמיד בחשבון חלק מההון שלא משתתף במסחר, גם כחיץ וגם כמרחב לפעולה ידנית כשמשהו משתבש.
כתוב את אלה כאילוצים קשיחים בקוד, בדוק לפני הזמנה, ואם לא מתקיים — דחה את ההזמנה. הקטע הבא הוא שלד, ואי אפשר להריץ אותו כמו שהוא על כסף אמיתי: הוא ממש רק את "תקרת ההזמנה הבודדת", ואת מה שחסר נמנה אחד-אחד מיד אחרי הקוד:
MAX_PER_ORDER_RATIO = 0.02 # הזמנה בודדת לא תעלה על 2% מבסיס ההון
MAX_TOTAL_RATIO = 0.30 # סך הפוזיציות לא יעלה על 30% מהבסיס (לא ממומש בשלד הזה)
def check_position_limit(okx, symbol, order_value_usdt):
if not (order_value_usdt > 0):
raise ValueError('סכום ההזמנה חייב להיות חיובי')
bal = okx.fetch_balance()
usdt_total = bal['USDT']['total'] # שים לב: זו רק יתרת ה-USDT, לא ההון הכולל של החשבון
# תקרת הזמנה בודדת: לקחת את יתרת ה-USDT כבסיס נכון בקירוב
# רק במקרה הפשוט של "חשבון ספוט שכמעט כולו USDT"
if order_value_usdt > usdt_total * MAX_PER_ORDER_RATIO:
raise ValueError('חורג מתקרת הפוזיציה הבודדת, ההזמנה נדחית')
# TODO: תקרת סך הפוזיציות (MAX_TOTAL_RATIO) עדיין לא מומשה —
# במצב הזה אין בכלל אילוץ על החשיפה הכוללת
return True
הסיבה שזה נשאר שלד היא שחסרים כאן לפחות ארבעה דברים, וכל אחד מהם יכול להפוך את ה"תקרה" לאות מתה:
- איך מחשבים את בסיס ההון.
bal['USDT']['total']היא היתרה של מטבע אחד, USDT. אם אתה מחזיק גם BTC או ETH, או מריץ חשבון חוזים, הבסיס האמיתי צריך להיות ההון של החשבון מתורגם לפי שיטת הערכה אחידה, ולא המספר הבודד הזה. - החשיפה של פוזיציות והזמנות קיימות. כדי שתקרת סך הפוזיציות תהיה בעלת משמעות, צריך לספור גם את הפוזיציות שכבר פתוחות וגם את ההזמנות שעדיין תלויות בספר ויכולות להתבצע בכל רגע. אם תסתכל רק על "כמה גדולה ההזמנה הזו", עשר הזמנות ברצף יחרגו בכל זאת.
- הזמנות במקביל. אם כמה אסטרטגיות קוראות לפונקציה הזו בו-זמנית, כל אחת עשויה לקרוא את אותה יתרה, כל אחת תחליט "לא חרגתי", וכולן ישלחו הזמנות יחד. או שתסדר את מסלול ההזמנה בנעילה שתריץ אותו בטור, או שתנהל מקומית רישום של "המכסה שכבר תפוסה".
- תקינות הקלט עצמו. הסכום חייב להיות חיובי — בדיוק בשביל זה נוספה שורת הבדיקה הראשונה. מספר שלילי או אפס מרוקנים את ההשוואה מתוכן.
שלד הוא שלד, אבל הכיוון נכון: הוא הופך את הרצון "אני צריך לשלוט בפוזיציה" לשער שהסקריפט חייב לעבור לפני כל הזמנה. הרצון מתנדנד, הקוד לא. מה שנשאר לך הוא להשלים את ארבעת הסעיפים האלה, ורק אז לשחרר אותו לכסף אמיתי.
סטופ-לוס חייב להיות תכנותי (קוד)
אם תזכור רק משפט אחד מהמאמר הזה, תזכור את זה: סטופ-לוס חייב להיות תכנותי. הסקריפט רץ אוטומטית, ואי אפשר להשגיח על השוק 24 שעות; כשהשוק מתהפך מהר, עד שתראה ותסגור ידנית בדרך כלל כבר עברת כמה רמות מחיר.
לסטופ-לוס תכנותי יש שתי גישות, ומומלץ לשלב ביניהן:
גישה ראשונה: הזמנה מותנית / סטופ-לוס בצד הבורסה. במקביל להזמנת ההזמנה הראשית, תלה ב-OKX הזמנת סטופ-לוס, ותן לבורסה לסגור את הפוזיציה אוטומטית במחיר ההפעלה. היתרון הכי גדול שלה: גם אם הסקריפט שלך מתנתק, הסטופ-לוס הזה עדיין פעיל בצד הבורסה. זה הצעד האמין ביותר נגד "פוזיציה חשופה בעת ניתוק".
גישה שנייה: ניטור מקומי בסקריפט + סגירה בהפעלה. הסקריפט קורא בלולאה ברציפות את ההפסד הלא ממומש של הפוזיציה, וברגע שהוא חורג מהסף שהגדרת הוא שולח ביוזמתו הזמנת סגירה. היא גמישה יותר (מאפשרת trailing stop, סטופ לפי זמן ועוד), אבל בתנאי שהסקריפט מקוון.
להלן השלד המינימלי של סטופ-לוס בניטור מקומי, להדגמת הלוגיקה (לפני חשבון אמיתי חובה לאמת בדמו):
import ccxt, time
# 🔴 כשזה נדרך, זו מכירה במחיר שוק — פעולה אמיתית שמתבצעת מיד
# הרץ קודם מספיק עם מפתח הדמו (Demo Trading), ודא שתנאי ההדק והכמות
# נכונים, ורק אז שקול להחליף לאישורים של החשבון האמיתי
# את מפתח הדמו יוצרים בנפרד בתוך סביבת המסחר המדומה, והוא אינו אותה
# ערכת אישורים של החשבון האמיתי
okx = ccxt.okx({
'apiKey': 'ה_apiKey_של_הדמו',
'secret': 'ה_secret_של_הדמו',
'password': 'ה_Passphrase_של_הדמו',
'enableRateLimit': True,
})
# בלי השורה הזו, הסטופ-לוס שלמטה סוגר פוזיציה אמיתית במחיר שוק בחשבון האמיתי
okx.set_sandbox_mode(True)
# בדיקה עצמית: חייב להדפיס 1, ואם הודפס None אל תמשיך
print(okx.headers.get('x-simulated-trading'))
symbol = 'BTC/USDT'
entry_price = 60000 # מחיר הכניסה הממוצע שלך (דוגמה)
amount = 0.001 # כמות הפוזיציה
stop_ratio = 0.05 # סטופ-לוס בירידה של 5%
while True:
ticker = okx.fetch_ticker(symbol)
last = ticker['last']
drawdown = (entry_price - last) / entry_price
if drawdown >= stop_ratio:
okx.create_order(symbol, 'market', 'sell', amount)
print(f'הופעל סטופ-לוס, נסגרה פוזיציה ב-market @ {last}')
break
time.sleep(10) # השאר מספיק מרווח, אל תפציץ את הממשק
שים לב לשני דברים: ראשית, חובה sleep בלולאה, אחרת תיתקל במגבלת קצב; שנית, סטופ-לוס מקומי הוא רק תוספת, וסטופ-לוס בצד הבורסה הוא קו ההגנה האחרון בעת ניתוק — עשה את שניהם וזה יציב.
מניעת הזמנות כפולות וניתוק רשת
שורש ההזמנה הכפולה הוא "אני לא בטוח אם ההזמנה הקודמת התבצעה או לא". הליבה של ההגנה: אל תשלח מחדש באופן עיוור, קודם וודא סטטוס.
שתי שיטות שעובדות יחד:
השתמש במזהה לקוח ייחודי (בתיעוד של OKX הוא נקרא clOrdId). בעת ההזמנה צרף ID שאתה מייצר בעצמך. הבורסה אכן בודקת אותו מול כפילויות, אבל היקף הבדיקה הוא רק ההזמנות בחשבון שלך שעדיין לא הגיעו למצב סופי — אם אותו ID מתנגש עם הזמנה שעוד חיה (פתוחה או מבוצעת חלקית), הבקשה תידחה עם 51016 "מזהה הזמנת הלקוח כבר קיים".
וכאן בדיוק הגבול, וכדאי לראות אותו בבירור: לפי התיעוד של OKX, ברגע שהזמנה מגיעה למצב סופי (בוצעה במלואה, בוטלה), אפשר להשתמש באותו clOrdId שוב. כלומר הוא חוסם "שלחת פעמיים באותו רגע והראשונה עוד תלויה בספר", אבל הוא לא חוסם "ההזמנה הקודמת בעצם בוצעה, לא קיבלת תשובה ושלחת שוב עם אותו ID" — את השנייה הזו הבורסה תקבל כהזמנה חדשה לגמרי. לכן אל תראה ב-clOrdId ערובה לאידמפוטנטיות; הוא רק ידית אחיזה.
יש גם דרישת פורמט נוקשה: אותיות וספרות בלבד, עד 32 תווים. כלומר str(uuid.uuid4()) — מחרוזת של 36 תווים עם מקפים — לא עומדת בדרישה, וצריך לקחת .hex.
מה שבאמת מונע כפילויות הוא התהליך שלמטה, ו-clOrdId הוא רק הידית שהתהליך הזה אוחז בה:
- לפני שליחת ההזמנה, רשום מקומית "כוונת הפעולה ← clOrdId ← סטטוס" ושמור לדיסק, לא רק בזיכרון — התהליך קורס והזיכרון נעלם.
- כשהבקשה נכנסת ל-timeout או פשוט לא חוזרת תשובה, אל תשלח מחדש, אלא קודם בדוק את ה-clOrdId מול הבורסה.
- "לא נמצא בהזמנות הפתוחות" אינו "לא בוצע". ייתכן שההזמנה בוצעה ועברה להיסטוריה, ולכן צריך שאילתה שמכסה גם מצבים סופיים: קח את ה-clOrdId אל ממשק פרטי ההזמנה (ממשק רשימת ההזמנות הפתוחות בכלל לא מכיר את הסינון הזה), ובמידת הצורך עבור גם על רישומי הביצוע. להסיק מההזמנות הפתוחות בלבד — זו הטעות הנפוצה ביותר בשלב הזה.
- רק אחרי שווידאת שה-clOrdId הזה באמת לא קיים, מותר לשלוח מחדש.
- אחרי הפעלה מחדש של התהליך, הדבר הראשון הוא לשלוף מהרישום המקומי את הכוונות שהסטטוס שלהן עדיין פתוח, להשוות לכל אחת את הסטטוס, ורק אז לתת לאסטרטגיה להמשיך.
import ccxt, uuid
# clOrdId must be alphanumeric only, 32 characters at most.
# uuid4() as a string carries hyphens and runs 36 chars - not valid; .hex gives 32 alphanumerics.
def new_cid():
return 'meow' + uuid.uuid4().hex[:16] # 4 + 16 = 20 chars, all alphanumeric
def save_intent(cid, **fields):
"""Persist 'intent -> clOrdId -> state' to disk. SQLite, a flat file,
it does not matter; what matters is that you can read it back after a
restart. Sketch only - implement it yourself."""
...
def find_order_by_cid(okx, symbol, cid):
"""Look an order up by clOrdId. This goes through the order-details
endpoint GET /api/v5/trade/order, which reaches both live orders and
ones that have already reached a terminal state.
Note: the request parameters of the pending-order and history lists
(orders-pending / orders-history) have no clOrdId field at all, so
passing it in params is silently ignored - the endpoint still returns
a whole page of orders, you filter locally, and when paging fails to
turn it up you conclude the order was never placed. So do not use
those two list endpoints to find an order by cid."""
try:
# Pass None for ordId and ccxt queries by the clOrdId in params instead
return okx.fetch_order(None, symbol, params={'clOrdId': cid})
except ccxt.OrderNotFound:
return None
symbol = 'BTC/USDT'
cid = new_cid()
# 1) Persist first, send afterwards
save_intent(cid, symbol=symbol, side='buy', amount=0.001, price=50000, state='sending')
try:
order = okx.create_order(
symbol, 'limit', 'buy', 0.001, 50000,
params={'clientOrderId': cid}, # ccxt maps this onto OKX's clOrdId
)
save_intent(cid, state='placed')
except Exception as e:
# 2) Timeout or no response: whether the order went in is unknown - do not resend
print('תוצאת ההזמנה לא ידועה, קודם בודקים:', e)
found = find_order_by_cid(okx, symbol, cid)
if found is None:
# 3) Order details checked, confirmed absent - only now may you resend
save_intent(cid, state='not_found_retry')
else:
# It went in; record the real state and do not send a second one
save_intent(cid, state=found['status'])
אחרי timeout / ניתוק — קודם בדוק, אז השלם. ענף ה-except שלמעלה הוא בדיוק הצורה הקודית של הכלל הזה: timeout של בקשה לא אומר שההזמנה נכשלה, אלא רק שאינך יודע את התוצאה. אחרי חיבור מחדש אותו דבר — קודם fetch_open_orders, fetch_positions, ובמידת הצורך גם מעבר על היסטוריית ההזמנות והביצועים, כדי לראות בבירור את המצב האמיתי הנוכחי של החשבון, ורק אז תן לאסטרטגיה להמשיך. הפיכת "קודם לבדוק סטטוס, אז לפעול" לצעד הקבוע הראשון אחרי חיבור מחדש חוסמת את רוב הפעולות הכפולות שנגרמות מאובדן קשר.
מגבלת קצב, זמן קצוב ו-retry
ניהול סיכונים הוא לא רק מניעת הפסד כספי, אלא גם מניעה של הסקריפט "ממוטט את עצמו". טיפול גרוע במגבלת קצב ובזמן קצוב יגרום לאסטרטגיה שלך להיכשל ברגע הקריטי.
מגבלת קצב: הדלק באתחול 'enableRateLimit': True, ו-ccxt תשתול אוטומטית מרווחים בין הבקשות. בלולאות תדירות גבוהה הוסף נסיגה מעריכית (נתקלת פעם — חכה שנייה; שוב — 2 ואז 4 שניות). על החלק הזה הרחבנו במאמר על שגיאות API נפוצות, וכאן רק נדגיש: בכל לולאה שפוגעת בממשק, מגביל קצב הוא ציוד תקני.
זמן קצוב (timeout): הגדר לבקשה זמן קצוב ברור, אל תיתן לסקריפט להיתקע ולקפוא כולו בגלל בקשה אחת. ב-ccxt אפשר להגדיר 'timeout' (במילישניות):
okx = ccxt.okx({
'apiKey': 'ה_apiKey_שלך',
'secret': 'ה_secret_שלך',
'password': 'ה_Passphrase_שלך',
'enableRateLimit': True,
'timeout': 10000, # זמן קצוב של 10 שניות לבקשה בודדת, למניעת תקיעה
})
ל-retry יש תקרה. בקפיצת רשת אפשר לנסות שוב, אבל הגדר מספר ניסיונות מקסימלי, ואחרי שמיצית — התרע / עצור, ולא נסיונות אינסופיים. נסיונות אינסופיים הם בעצמם סוג של "תוכנה משתוללת" — הם יבזבזו בקשות ברציפות, ואף יבצעו פעולות כפולות, בלי שתשגיח.
הרשאות ורשימת IP: הקו האדום של האבטחה
השכבה התחתונה ביותר של ניהול הסיכונים היא אבטחת החשבון. הסקריפט כתוב יציב ככל שיהיה, אם האישורים דלפו הכל מתאפס. כאן יש שני קווים אדומים כמעט בלי יוצא מן הכלל:
סמן רק 'קריאה' ו'מסחר', לעולם אל תסמן 'משיכה'. הרצת אלגו בכלל לא משתמשת בפעולת המשיכה. כל עוד אין למפתח הרשאת משיכה, גם אם שלושת האישורים — apiKey, secret, Passphrase — ידלפו יום אחד, מי שישיג אותם לא יוכל להוציא את המטבעות שלך לכתובת בבלוקצ'יין; וברגע שתסמן משיכה, דליפה שווה למסירת המפתח של הכספת. רק אל תראה בזה ביטוח, וגם מה שהוא חוסם צר יותר ממה שרובם מניחים: לפי ההגדרה בתיעוד של OKX, הרשאת המסחר עצמה כוללת כבר העברת כספים (חשבון מימון ↔ חשבון מסחר, חשבון ראשי ↔ תת-חשבונות) וחלק מהגדרות החשבון, כך שהתוקף עדיין יכול להזיז כסף בין החשבונות שלך. ולגבי הפסדים: גם מפתח עם הרשאת מסחר בלבד יכול לרוקן אותך — מי שמחזיק בו יכול לדחוף פקודות בצמד לא נזיל עם ספר פקודות דליל ולשמש בעצמו את הצד הנגדי, והכסף שלך יוצא בתור 'הפסדי מסחר' בזמן שבדוח כל שורה נראית כמו עסקה רגילה. אילוצי הפוזיציה והסטופ-לוס שלמעלה מקטינים את הנזק, אבל קו ההגנה השני האמיתי הוא רשימת IP מורשים — צריך לעשות אותה יחד עם 'בלי הרשאת משיכה', לא במקומה.
הגדר רשימת IP מורשים. אם הסקריפט שלך רץ על מכונה עם IP קבוע (שרת ענן, חיבור ביתי עם IP ציבורי קבוע), הוסף את ה-IP הזה לרשימה של המפתח, שתקבל בקשות רק ממנו, והאבטחה עולה עוד מדרגה. ב-OKX אפשר לקשור לכל מפתח עד 20 כתובות IP, כולל IPv4/IPv6 וטווחי רשת, ולכן "כתובת היציאה מתחלפת מדי פעם" אינה בהכרח סיבה לוותר על הרשימה. כשכתובת היציאה באמת לא יציבה, הכיוון הוא לקבע את היציאה (להעביר את האסטרטגיה למכונה עם IP קבוע) ולא להשאיר את המפתח בלי קישור — ויש עוד כלל שקל לפספס: מפתח בלי IP מקושר, שיש לו הרשאת מסחר או משיכה, פג תוקף אחרי 14 ימים ללא פעילות. קשור או לא, אל תכתוב את האישורים קשיח בקוד שעולה לרשת; עבור למשתני סביבה או לקובץ תצורה שלא מסונכרן.
שני הקווים האלה, בתוספת הפוזיציה והסטופ-לוס מקודם, מרכיבים את ההגנה ש"גם אם יקרה המצב הגרוע ביותר, להפסד יש גבול". הצעדים המדויקים ליצירת מפתח נמצאים במבוא המעשי ל-API שלנו, ואפשר גם להשתמש ברשימת בדיקת הרשאות API כדי לעבור על הכל לפני פתיחת מפתח.
התחלה קרה בסכום קטן: רשימה לפני הכסף האמיתי
כל ניהול הסיכונים ברמת הקוד מותקן, ולפני עלייה לחשבון אמיתי נשאר צעד אחרון: אל תיכנס מיד בפוזיציה כבדה. אסטרטגיה חדשה שעולה לכסף אמיתי — אנחנו תמיד הולכים ב"התחלה קרה בסכום קטן", בסדר הזה:
- קודם הרצה רציפה בדמו. כיסוי כמה סבבי עליות וירידות, אישור שהזמנה, ביטול, סטופ-לוס ו-callbacks כולם כמצופה. הצעד הזה ללא עלות, ודילוג עליו שווה לשימוש בכסף אמיתי כסביבת בדיקה.
- אחר כך התחלה קרה עם מעט כסף אמיתי. סכום קטן עד שגם אם תפסיד הכל לא יכאב לך, התייחס אליו כשכר לימוד. ה-slippage והעומק שהדמו לא מסוגל לשחזר — רק עכשיו ייחשפו.
- עקוב אחריו בימים הראשונים. אל תרפה באמת, ודא שההתנהגות בחשבון האמיתי תואמת לדמו, שהסטופ-לוס באמת מופעל, שאין הזמנות כפולות, ורק אז הגדל בהדרגה.
- חוזים ומינוף בסוף. לפני שהספוט רץ יציב, אל תיגע במינוף. מינוף מגביר פי כמה כל אחת מקטגוריות הסיכון הנ"ל, ובשלב המתחיל אנחנו ממליצים בחום לעשות רק ספוט.
חבר את הרשימה הזו לאילוצי הקוד מקודם, ומה שאתה מתקין לסקריפט הוא לא בלם אחד אלא כמה: לפוזיציה יש תקרה, הסטופ-לוס מופעל אוטומטית, לפני כל שליחה חוזרת נבדק הסטטוס, גם אם האישורים דלפו אי אפשר להוציא מטבעות לכתובת בבלוקצ'יין (אבל מי שהשיג אותם עדיין יכול להעביר כספים בין החשבונות שלך, וגם לרוקן את היתרה במסחר מול עצמו, ולכן רשימת IP מורשים חייבת לבוא יחד), והעלייה לבמה היא קודם בכסף קטן. אף אחד מהם לבדו לא מספיק, ורק יחד הם משאירים בין "טעות" ל"איפוס" מספיק חיץ.
מהמורה נפוצה
print רץ על המסך, לעצור ב-Ctrl+C, לתקן את הכיוון, להוסיף תנאי יציאה ו-sleep(10), ולצרף את קטע בדיקת תקרת הפוזיציה הבודדת. אבל אילו זה היה קורה בחשבון אמיתי, ועוד עם מינוף, שרשרת ההזמנות השגויות הזו הייתה כסף אמיתי שזורם החוצה ברציפות. בדיוק תרחיש כזה מסביר את הכללים: לוגיקת סטופ-לוס חייבת לעבור אימות בדמו קודם, לולאה חייבת מרווח ותנאי יציאה, ולפני הזמנה חובה לעבור בדיקת תקרת פוזיציה — שלושת אלה, אף אחד מהם לא חסר.
שאלות נפוצות
מהו הסיכון שהכי מפיל אנשים במסחר אוטומטי דרך API?
לא השוק עצמו, אלא ה'נאמנות' של התוכנה — סקריפט לא מהסס כמו אדם. כיוון קנייה/מכירה הפוך, לולאה שחסר בה תנאי יציאה, הזמנה שנשלחת שוב אחרי ניתוק רשת — בפעולה ידנית אולי היית מבחין בזה לפני האישור, אבל הסקריפט יבצע עשרות פעמים ברצף. בתוספת מינוף בחוזים, טעות כזו יכולה לאפס את הקרן בזמן קצרצר. הליבה של ההגנה היא לכתוב גם את ניהול הסיכונים לקוד, ולא לסמוך על כך שתשגיח בעצמך.
חייבים לכתוב סטופ-לוס בקוד? לא מספיק להשגיח ידנית?
מומלץ בחום לעשות זאת תכנותית. הסקריפט רץ אוטומטית, ואי אפשר להשגיח 24 שעות; ברגע שהשוק מתהפך מהר, עד שתראה ותעצור ידנית בדרך כלל כבר מאוחר. כתוב את לוגיקת הסטופ-לוס לתוך האסטרטגיה, או השתמש בהזמנות מותנות / סטופ-לוס של OKX, כדי שהתוכנה תסגור פוזיציה אוטומטית בקו ההפעלה — רק כך עומדים בקצב של מסחר אוטומטי. השגחה ידנית יכולה רק להשלים, לא להיות קו ההגנה היחיד.
איך מונעים מהסקריפט להזמין הזמנות כפולות?
מזהה לקוח ייחודי (בתיעוד של OKX הוא clOrdId) עוזר, אבל אל תראה בו ערובה לאידמפוטנטיות: לפי ההסבר הרשמי, היקף בדיקת הכפילות הוא רק ההזמנות בחשבון שלך שעדיין לא הגיעו למצב סופי — התנגשות עם הזמנה שעוד פתוחה או מבוצעת חלקית תידחה (51016), ואילו ברגע שהזמנה בוצעה במלואה או בוטלה אפשר להשתמש באותו מזהה שוב, ולכן הוא לא חוסם 'ההזמנה הקודמת כבר בוצעה ואתה שלחת שוב'. מה שבאמת מונע כפילויות הוא התהליך: לפני ההזמנה שמור לדיסק 'כוונת הפעולה ← clOrdId ← סטטוס'; כשהבקשה נכנסת ל-timeout או לא חוזרת תשובה, קודם בדוק עם המזהה הזה את ההזמנות ואת רישומי הביצוע, וזכור שהיעדר מההזמנות הפתוחות אינו אומר שלא בוצע — צריך לעבור גם על היסטוריית ההזמנות והביצועים; שלח מחדש רק אחרי שווידאת שההזמנה באמת לא קיימת; ואחרי הפעלה מחדש של התהליך שחזר קודם מהרישום המקומי את הכוונות הפתוחות. מבחינת פורמט, clOrdId חייב להיות אותיות וספרות בלבד ועד 32 תווים.
איך מגדירים הרשאות של API Key בצורה בטוחה?
למפתח שמריץ אלגו סמן רק 'קריאה' ו'מסחר', ולעולם אל תסמן 'משיכה'. כך גם אם שלושת האישורים ידלפו, מי שישיג אותם לא יוכל להוציא את המטבעות שלך לכתובת בבלוקצ'יין. אבל אל תראה בזה ביטוח, וגם מה שהוא חוסם צר יותר ממה שרובם מניחים: לפי התיעוד של OKX, הרשאת המסחר עצמה כוללת כבר העברת כספים וחלק מהגדרות החשבון, כך שהתוקף עדיין יכול להזיז כסף בין חשבון המימון, חשבון המסחר ותת-החשבונות שלך; ומעבר לזה, גם מפתח עם הרשאת מסחר בלבד יכול לרוקן אותך — מי שמחזיק בו יכול לדחוף פקודות בצמד לא נזיל עם ספר פקודות דליל ולשמש בעצמו את הצד הנגדי, והכסף שלך יוצא בתור 'הפסדי מסחר'. לכן הסעיף הזה עובד רק יחד עם השלב הבא, ולבדו הוא לא מספיק. בנוסף קשור למפתח רשימת IP מורשים (אם אתה רץ על מכונה עם IP קבוע), שתקבל בקשות רק מ-IP מוגדר, והאבטחה עולה עוד מדרגה. מסחר אוטומטי בכלל לא משתמש בפעולת המשיכה.
כמה להשקיע בפעם הראשונה של אסטרטגיה חדשה בחשבון אמיתי?
סכום שגם אם תפסיד את כולו לא יכאב לך, התייחס אליו כשכר לימוד ולא כקרן. קודם סדר את התהליך בדמו, אחר כך עשה התחלה קרה עם מעט כסף אמיתי, עקוב אחריה בימים הראשונים, ודא שההתנהגות בחשבון האמיתי תואמת לדמו ושה-slippage והביצוע בתחום הצפוי, ורק אז הגדל בהדרגה. להיכנס מיד בפוזיציה כבדה שווה להמר את כל ההון על קוד שלא אומת.
מה יקרה לסקריפט שרץ אם תהיה ניתוק רשת?
תלוי איך כתבת אותו. כתיבה גרועה היא שבניתוק הבקשה נכנסת ל-timeout והסקריפט קורס, או שהוא מנסה שוב באופן עיוור וגורם להזמנות כפולות, בזמן שהפוזיציה שלך חשופה בשוק בלי סטופ-לוס. הדרך הבטוחה: הגדר לבקשות timeout ותקרת ניסיונות, אחרי חיבור מחדש קודם בדוק את הפוזיציות וההזמנות הפתוחות לפני שתחליט מה לעשות, והישען על סטופ-לוס בצד הבורסה (ולא רק על שיפוט מקומי בסקריפט), כך שגם אם הסקריפט מתנתק, הגנת הסגירה ממשיכה לפעול בצד הבורסה.
ניהול סיכונים הוא לא דבר שנגמר ברגע שסיימת לכתוב קוד — הוא צריך לגדול יחד עם האסטרטגיה שלך. מומלץ לשמור את הרשימה מהמאמר הזה, ולעבור עליה בכל פעם שעולה אסטרטגיה חדשה. הלאה אפשר ללכת לשגיאות API נפוצות כדי להשלים את יציבות הסקריפט, וקוד חדש תמיד קודם לדמו לאימות. זכור את המשפט הישן: קודם לשרוד, אחר כך לדבר על רווחים.