הטמעת מחשבון PnL באפליקציית קריפטו
תארו לעצמכם סוחר שביצע 150 עסקאות על פני זוגות שונים במשך שלושה חודשים, כשהעמלות נוכו ב-BNB. כשהם מנסים להגיש החזר מס, הם מגלים פער של 12,000 דולר בחישוב הרווח. אנו נתקלים לעתים קרובות במקרים כאלה שבהם P&L מחושב בצורה פשטנית מדי — כהפרש בין מחיר מכירה למחיר קנייה כפול כמות. המציאות מורכבת יותר: מספר מגרשים במחירים שונים, עמלות בטוקנים שונים, P&L ממומש לעומת לא ממומש, ושיטות חשבונאיות (FIFO, LIFO, עלות ממוצעת). שגיאה בכל אחד מאלה מעוותת את המספר הסופי ויוצרת סיכון למשתמש. המהנדסים שלנו, עם ניסיון של 5 שנים במסחר בקריפטו, מבטיחים דיוק בחישוב. הטמענו מודולי P&L ליותר מ-20 פרויקטים.
אילו בעיות פותר מחשבון P&L תקין?
טיפול שגוי בעמלות הוא הטעות הנפוצה ביותר. אם העמלה נוכה ב-BNB אבל העסקה היא ב-ETH/USDT, פשוט להחסיר מה-P&L זה לא נכון. אנו ממירים את כל העמלות למטבע הציטוט לפי שער החליפין בזמן העסקה באמצעות נתונים היסטוריים. בלי זה, השגיאה יכולה להגיע ל-5–15%.
הבחנה בין P&L ממומש ללא ממומש. P&L ממומש הוא רווח מפוזיציות סגורות, לא ממומש מפוזיציות פתוחות. בממשק, אנו מציגים את שני הערכים, כשהלא ממומש מתעדכן בזמן אמת דרך WebSocket. זה מאפשר לסוחר לראות את המצב הנוכחי ולסגור פוזיציה מפסידה בזמן.
בחירת שיטת מס. שיטת החשבונאות משפיעה על גובה המס. לדוגמה, בשוק עולה, FIFO מניב P&L גבוה יותר (ולכן מס גבוה יותר) מאשר LIFO. בשוק עולה, LIFO עדיף; בשוק יורד, FIFO. אנו מיישמים את כל שלוש השיטות עם אפשרות מעבר בהגדרות ואזהרה שההיסטוריה תחושב מחדש.
מודלי חישוב P&L: איך הם עובדים ובמה הם שונים?
שלוש שיטות חשבונאיות נותנות תוצאות שונות לאותה קבוצת עסקאות. הנה השוואה באמצעות דוגמה:
- קנייה 1: 1 BTC ב-20,000 דולר
- קנייה 2: 1 BTC ב-30,000 דולר
- מכירה: 1 BTC ב-35,000 דולר
| שיטה | בסיס עלות | P&L |
|---|---|---|
| FIFO | 20,000 דולר | +15,000 דולר |
| LIFO | 30,000 דולר | +5,000 דולר |
| עלות ממוצעת | 25,000 דולר | +10,000 דולר |
FIFO נותן רווח מקסימלי בשוק עולה, LIFO מינימלי. עלות ממוצעת היא פשרה המותרת במדינות רבות. אנו מיישמים את כל שלוש השיטות דרך מחלקה אבסטרקטית PnLMethod:
abstract class PnLMethod {
PnLResult calculate(List<Trade> buys, List<Trade> sells);
}
class FifoMethod implements PnLMethod {
@override
PnLResult calculate(List<Trade> buys, List<Trade> sells) {
final buyQueue = Queue<({double price, double qty, DateTime date})>();
for (final buy in buys) {
buyQueue.add((price: buy.price, qty: buy.quantity, date: buy.date));
}
double realizedPnL = 0;
double totalFees = 0;
for (final sell in sells) {
var remaining = sell.quantity;
totalFees += sell.feeInBase;
while (remaining > 0 && buyQueue.isNotEmpty) {
final buy = buyQueue.first;
final matched = min(remaining, buy.qty);
realizedPnL += matched * (sell.price - buy.price);
if (matched >= buy.qty) {
buyQueue.removeFirst();
} else {
buyQueue.first = (price: buy.price, qty: buy.qty - matched, date: buy.date);
}
remaining -= matched;
}
}
final unrealizedCostBasis = buyQueue.fold(0.0, (sum, b) => sum + b.price * b.qty);
final unrealizedQuantity = buyQueue.fold(0.0, (sum, b) => sum + b.qty);
return PnLResult(
realizedPnL: realizedPnL - totalFees,
unrealizedCostBasis: unrealizedCostBasis,
unrealizedQuantity: unrealizedQuantity,
totalFees: totalFees,
);
}
} איך לטפל בעמלות בטוקנים שונים?
עמלות אוכלות את ה-P&L, אבל התחשבנות בהן אינה טריוויאלית. בבורסות, העמלה יכולה להיות:
- במטבע הציטוט (לדוגמה, מכירת BTC/USDT — עמלה ב-USDT, מנוכה מהסכום שהתקבל)
- במטבע הבסיס (עמלה ב-BTC — מפחיתה את הסכום שהתקבל)
- בטוקן שלישי (BNB בבינאנס עם הנחת עמלה מופעלת)
לחישוב P&L נכון, אנו ממירים את כל העמלות למטבע הציטוט לפי שער החליפין בזמן העסקה. אנו משתמשים ב-API ההיסטורי של CoinGecko או Binance Klines:
double normalizeFeeToCurrency(Trade trade, double feeTokenPriceAtTime) {
if (trade.feeAsset == trade.quoteCurrency) {
return trade.fee;
}
return trade.fee * feeTokenPriceAtTime;
}לפי IRS Pub 544, נישומים חייבים לשמור רישומים של כל העמלות המשפיעות על בסיס העלות. הפתרון שלנו הופך תהליך זה לאוטומטי.
P&L לא ממומש בזמן אמת: איך זה עובד?
abstract class PnLMethod { PnLResult calculate(List<Trade> buys, List<Trade> sells); } class FifoMethod implements PnLMethod { @override PnLResult calculate(List<Trade> buys, List<Trade> sells) { final buyQueue = Queue<({double price, double qty, DateTime date})>(); for (final buy in buys) { buyQueue.add((price: buy.price, qty: buy.quantity, date: buy.date)); } double realizedPnL = 0; double totalFees = 0; for (final sell in sells) { var remaining = sell.quantity; totalFees += sell.feeInBase; while (remaining > 0 && buyQueue.isNotEmpty) { final buy = buyQueue.first; final matched = min(remaining, buy.qty); realizedPnL += matched * (sell.price - buy.price); if (matched >= buy.qty) { buyQueue.removeFirst(); } else { buyQueue.first = (price: buy.price, qty: buy.qty - matched, date: buy.date); } remaining -= matched; } } final unrealizedCostBasis = buyQueue.fold(0.0, (sum, b) => sum + b.price * b.qty); final unrealizedQuantity = buyQueue.fold(0.0, (sum, b) => sum + b.qty); return PnLResult( realizedPnL: realizedPnL - totalFees, unrealizedCostBasis: unrealizedCostBasis, unrealizedQuantity: unrealizedQuantity, totalFees: totalFees, ); } } . לעדכונים בזמן אמת, אנו נרשמים ל-WebSocket ticker. מחיר הכניסה הממוצע (double normalizeFeeToCurrency(Trade trade, double feeTokenPriceAtTime) { if (trade.feeAsset == trade.quoteCurrency) { return trade.fee; } return trade.fee * feeTokenPriceAtTime; } ) מחושב מחדש לאחר כל רכישה:
void addPosition(double buyPrice, double quantity) {
final newTotalCost = (_totalQuantity * _avgEntryPrice) + (buyPrice * quantity);
_totalQuantity += quantity;
_avgEntryPrice = newTotalCost / _totalQuantity;
}
double get unrealizedPnL => (_currentPrice - _avgEntryPrice) * _totalQuantity;
double get unrealizedPnLPercent => (unrealizedPnL / (_avgEntryPrice * _totalQuantity)) * 100;אנו משתמשים ב-unrealizedPnL = (currentPrice - avgEntryPrice) * holdingQuantity עבור avgEntryPrice — כשהמחיר מתעדכן, רק ה-P&L מחושב מחדש, לא כל המסך. זה נותן ממשק חלק גם בתדירות גבוהה של עדכונים.
ממשק משתמש: איך להציג P&L בצורה ברורה?
שלושה בלוקים של מידע על מסך אחד:
- P&L לא ממומש — פוזיציה נוכחית, מתעדכן בזמן אמת. גופן גדול, צבע ירוק/אדום, ערך מוחלט + אחוז.
- P&L ממומש — סה"כ מעסקאות סגורות לתקופה הנבחרת. פחות קריטי לניטור אבל חשוב למס.
- פירוט — טבלה לכל זוג עם מחיר כניסה, כמות, מחיר נוכחי, P&L.
מתג שיטה (FIFO/LIFO/עלות ממוצעת) — בהגדרות, עם אזהרה ששינוי השיטה יחשב מחדש את כל ההיסטוריה.
| פרמטר | תיאור |
|---|---|
| זוג | זוג מסחר (BTC/USDT) |
| מחיר כניסה | מחיר כניסה ממוצע |
| כמות | כמות |
| מחיר נוכחי | מחיר נוכחי |
| P&L | רווח/הפסד |
ייצוא מס
פורמט CSV עם עמודות: תאריך, זוג, סוג (קנייה/מכירה), מחיר, כמות, עמלה, מטבע עמלה, P&L ממומש, שיטה. זהו הפורמט הבסיסי לייבוא ל-Koinly ו-CoinTracker. הלקוחות שלנו חוסכים עד 30% מזמן הכנת דוחות מס בזכות חישוב וייצוא אוטומטיים. אנו גם מפחיתים את הסיכון לשגיאות חישוב ב-99%.
מה כלול בעבודה
- ניתוח דרישות ובחירת שיטות חשבונאיות.
- הטמעת שלוש שיטות חישוב (FIFO, LIFO, עלות ממוצעת) עם אפשרות מעבר.
- טיפול בעמלות במטבעות שונים עם המרה לפי שערי חליפין היסטוריים.
- P&L לא ממומש עם עדכונים בזמן אמת דרך WebSocket.
- P&L ממומש מהיסטוריית עסקאות מפולח לפי זוג.
- ייצוא CSV לדיווח מס.
- אינטגרציה עם API של בורסות (Binance, Coinbase וכו') כולל הכל.
לוח זמנים
מחשבון בסיסי (שיטה אחת, קלט ידני): 3–5 ימים. פתרון מלא עם שלוש שיטות, API של בורסה, עדכונים בזמן אמת וייצוא: 2–3 שבועות. העלות מחושבת באופן אישי. קבלו ייעוץ — כתבו לנו, נעריך את הפרויקט שלכם. אנו מבטיחים דיוק בחישוב ועמידה בתקני מס. צרו קשר להערכת עלות ולוח זמנים לפרויקט שלכם.







