Gönderi

Django ile Kripto Portfolio Takip Uygulaması Geliştirme

Django ile tam özellikli kripto portfolio takip uygulaması. REST API, Celery ile asenkron işlemler, Redis cache ve gerçek zamanlı fiyat takibi.

Django ile Kripto Portfolio Takip Uygulaması Geliştirme

Kripto para yatırımlarınızı takip etmek, kar/zarar hesaplamalarını yapmak ve portföyünüzü yönetmek için profesyonel bir web uygulaması geliştirmek istiyorsanız Django tam size göre. Bu yazıda, gerçek zamanlı fiyat güncellemeleri, kullanıcı kimlik doğrulama ve detaylı portfolio analitiği içeren kapsamlı bir kripto takip uygulaması oluşturacağız.

Django Neden Portfolio Uygulamaları İçin İdeal?

Django, Python tabanlı güçlü bir web framework’üdür ve finansal uygulamalar için birçok avantaj sunar:

  • ORM (Object-Relational Mapping): Veritabanı işlemlerini Python koduna dönüştürür
  • Built-in Authentication: Kullanıcı yönetimi hazır gelir
  • Admin Panel: Veritabanı yönetimi için otomatik arayüz
  • Security Features: CSRF, XSS, SQL injection koruması
  • REST Framework: API geliştirmek için güçlü araçlar
  • Scalability: Büyük ölçekli uygulamalar için uygun mimari

Django MVT Architecture Django’nun Model-View-Template mimarisi, temiz ve bakımı kolay kod yazmayı sağlar

Proje Yapısı ve Kurulum

Django Projesini Oluşturma

Öncelikle gerekli paketleri kuralım:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Sanal ortam oluşturma
python -m venv venv
source venv/bin/activate  # Windows: venv\Scripts\activate

# Django ve gerekli paketleri kurma
pip install django djangorestframework
pip install python-decouple requests
pip install django-cors-headers celery redis
pip install psycopg2-binary  # PostgreSQL için

# Proje oluşturma
django-admin startproject crypto_portfolio
cd crypto_portfolio

# Uygulama oluşturma
python manage.py startapp portfolio
python manage.py startapp accounts

Proje Ayarları (settings.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# crypto_portfolio/settings.py
from decouple import config
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent

# Güvenlik ayarları
SECRET_KEY = config('SECRET_KEY', default='django-insecure-key-for-development')
DEBUG = config('DEBUG', default=True, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='localhost,127.0.0.1').split(',')

# Uygulama kayıtları
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    
    # Third party apps
    'rest_framework',
    'corsheaders',
    
    # Local apps
    'accounts',
    'portfolio',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'corsheaders.middleware.CorsMiddleware',  # CORS için
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# Database - PostgreSQL kullanımı (production için önerilen)
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': config('DB_NAME', default='crypto_portfolio'),
        'USER': config('DB_USER', default='postgres'),
        'PASSWORD': config('DB_PASSWORD', default='password'),
        'HOST': config('DB_HOST', default='localhost'),
        'PORT': config('DB_PORT', default='5432'),
    }
}

# REST Framework ayarları
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 20,
}

# Celery ayarları (arka plan görevleri için)
CELERY_BROKER_URL = config('REDIS_URL', default='redis://localhost:6379/0')
CELERY_RESULT_BACKEND = CELERY_BROKER_URL
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'

Database Modelleri

Portfolio uygulaması için ihtiyacımız olan ana modeller:

Django Database Models Database modelleri arasındaki ilişkiler ve foreign key bağlantıları

Portfolio Modelleri (portfolio/models.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
from django.db import models
from django.contrib.auth.models import User
from django.core.validators import MinValueValidator
from decimal import Decimal

class Cryptocurrency(models.Model):
    """
    Kripto para bilgilerini tutan model.
    CoinGecko veya CoinMarketCap API'den çekilecek.
    """
    symbol = models.CharField(max_length=10, unique=True)  # BTC, ETH, vb.
    name = models.CharField(max_length=100)  # Bitcoin, Ethereum
    coingecko_id = models.CharField(max_length=50, unique=True)
    logo_url = models.URLField(blank=True, null=True)
    
    # Fiyat bilgileri (cache için)
    current_price = models.DecimalField(
        max_digits=20, 
        decimal_places=8, 
        default=0
    )
    price_change_24h = models.DecimalField(
        max_digits=10, 
        decimal_places=2, 
        default=0
    )
    market_cap = models.BigIntegerField(default=0)
    last_updated = models.DateTimeField(auto_now=True)
    
    class Meta:
        ordering = ['-market_cap']
        verbose_name_plural = 'Cryptocurrencies'
    
    def __str__(self):
        return f"{self.symbol} - {self.name}"


class Portfolio(models.Model):
    """
    Kullanıcının portfolio'sunu temsil eder.
    Her kullanıcı birden fazla portfolio oluşturabilir.
    """
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='portfolios')
    name = models.CharField(max_length=100)
    description = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    is_active = models.BooleanField(default=True)
    
    class Meta:
        ordering = ['-created_at']
        unique_together = ['user', 'name']
    
    def __str__(self):
        return f"{self.user.username} - {self.name}"
    
    @property
    def total_value(self):
        """Portfolio'nun toplam güncel değerini hesaplar"""
        total = Decimal('0')
        for holding in self.holdings.all():
            total += holding.current_value
        return total
    
    @property
    def total_profit_loss(self):
        """Toplam kar/zarar hesapla"""
        total_current = self.total_value
        total_cost = sum(h.total_cost for h in self.holdings.all())
        return total_current - total_cost
    
    @property
    def profit_loss_percentage(self):
        """Kar/zarar yüzdesi"""
        total_cost = sum(h.total_cost for h in self.holdings.all())
        if total_cost == 0:
            return 0
        return ((self.total_value - total_cost) / total_cost) * 100


class Holding(models.Model):
    """
    Portfolio içindeki bir kripto para pozisyonunu temsil eder.
    """
    portfolio = models.ForeignKey(
        Portfolio, 
        on_delete=models.CASCADE, 
        related_name='holdings'
    )
    cryptocurrency = models.ForeignKey(
        Cryptocurrency, 
        on_delete=models.CASCADE
    )
    quantity = models.DecimalField(
        max_digits=20, 
        decimal_places=8,
        validators=[MinValueValidator(Decimal('0'))]
    )
    average_buy_price = models.DecimalField(
        max_digits=20, 
        decimal_places=8,
        validators=[MinValueValidator(Decimal('0'))]
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        unique_together = ['portfolio', 'cryptocurrency']
        ordering = ['-created_at']
    
    def __str__(self):
        return f"{self.portfolio.name} - {self.cryptocurrency.symbol}"
    
    @property
    def total_cost(self):
        """Toplam maliyet hesapla"""
        return self.quantity * self.average_buy_price
    
    @property
    def current_value(self):
        """Güncel değer hesapla"""
        return self.quantity * self.cryptocurrency.current_price
    
    @property
    def profit_loss(self):
        """Kar/zarar hesapla"""
        return self.current_value - self.total_cost
    
    @property
    def profit_loss_percentage(self):
        """Kar/zarar yüzdesi"""
        if self.total_cost == 0:
            return 0
        return ((self.current_value - self.total_cost) / self.total_cost) * 100


class Transaction(models.Model):
    """
    Alım/satım işlemlerini kaydeder.
    """
    TRANSACTION_TYPES = (
        ('BUY', 'Alım'),
        ('SELL', 'Satım'),
    )
    
    portfolio = models.ForeignKey(
        Portfolio, 
        on_delete=models.CASCADE, 
        related_name='transactions'
    )
    cryptocurrency = models.ForeignKey(Cryptocurrency, on_delete=models.CASCADE)
    transaction_type = models.CharField(max_length=4, choices=TRANSACTION_TYPES)
    quantity = models.DecimalField(
        max_digits=20, 
        decimal_places=8,
        validators=[MinValueValidator(Decimal('0'))]
    )
    price_per_unit = models.DecimalField(
        max_digits=20, 
        decimal_places=8,
        validators=[MinValueValidator(Decimal('0'))]
    )
    fee = models.DecimalField(
        max_digits=20, 
        decimal_places=8, 
        default=0
    )
    notes = models.TextField(blank=True)
    transaction_date = models.DateTimeField()
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        ordering = ['-transaction_date']
    
    def __str__(self):
        return f"{self.transaction_type} - {self.cryptocurrency.symbol}"
    
    @property
    def total_amount(self):
        """İşlem tutarı + komisyon"""
        return (self.quantity * self.price_per_unit) + self.fee
    
    def save(self, *args, **kwargs):
        """
        Transaction kaydedildiğinde holding'i güncelle.
        """
        super().save(*args, **kwargs)
        self.update_holding()
    
    def update_holding(self):
        """
        Bu transaction'a göre holding'i güncelle.
        Alım ise holding'e ekle, satım ise çıkar.
        """
        holding, created = Holding.objects.get_or_create(
            portfolio=self.portfolio,
            cryptocurrency=self.cryptocurrency,
            defaults={'quantity': 0, 'average_buy_price': 0}
        )
        
        if self.transaction_type == 'BUY':
            # Ortalama alış fiyatını güncelle
            total_cost = holding.total_cost + (self.quantity * self.price_per_unit)
            total_quantity = holding.quantity + self.quantity
            holding.average_buy_price = total_cost / total_quantity if total_quantity > 0 else 0
            holding.quantity = total_quantity
        
        elif self.transaction_type == 'SELL':
            # Satış işlemi - miktar azalt
            holding.quantity -= self.quantity
            if holding.quantity < 0:
                holding.quantity = 0
        
        holding.save()


class PriceAlert(models.Model):
    """
    Kullanıcıların fiyat alarmları için model.
    """
    ALERT_TYPES = (
        ('ABOVE', 'Üzerine Çıkınca'),
        ('BELOW', 'Altına Düşünce'),
    )
    
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='price_alerts')
    cryptocurrency = models.ForeignKey(Cryptocurrency, on_delete=models.CASCADE)
    alert_type = models.CharField(max_length=5, choices=ALERT_TYPES)
    target_price = models.DecimalField(
        max_digits=20, 
        decimal_places=8,
        validators=[MinValueValidator(Decimal('0'))]
    )
    is_active = models.BooleanField(default=True)
    triggered = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    triggered_at = models.DateTimeField(blank=True, null=True)
    
    class Meta:
        ordering = ['-created_at']
    
    def __str__(self):
        return f"{self.cryptocurrency.symbol} - {self.alert_type} - ${self.target_price}"

Migrations Oluşturma ve Uygulama

1
2
3
4
5
6
7
8
# Migration dosyalarını oluştur
python manage.py makemigrations

# Veritabanına uygula
python manage.py migrate

# Superuser oluştur (admin panel için)
python manage.py createsuperuser

REST API Geliştirme

Django REST Framework kullanarak güçlü bir API oluşturalım:

Django REST API Architecture Django REST Framework ile API geliştirme mimarisi

Serializers (portfolio/serializers.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
from rest_framework import serializers
from django.contrib.auth.models import User
from .models import (
    Cryptocurrency, Portfolio, Holding, 
    Transaction, PriceAlert
)

class CryptocurrencySerializer(serializers.ModelSerializer):
    """Cryptocurrency model için serializer"""
    
    price_change_percentage = serializers.SerializerMethodField()
    
    class Meta:
        model = Cryptocurrency
        fields = [
            'id', 'symbol', 'name', 'coingecko_id', 
            'logo_url', 'current_price', 'price_change_24h',
            'price_change_percentage', 'market_cap', 'last_updated'
        ]
        read_only_fields = ['last_updated']
    
    def get_price_change_percentage(self, obj):
        """24 saatlik değişim yüzdesi"""
        if obj.current_price > 0:
            return float(obj.price_change_24h)
        return 0


class HoldingSerializer(serializers.ModelSerializer):
    """Holding model için serializer"""
    
    cryptocurrency = CryptocurrencySerializer(read_only=True)
    cryptocurrency_id = serializers.PrimaryKeyRelatedField(
        queryset=Cryptocurrency.objects.all(),
        source='cryptocurrency',
        write_only=True
    )
    total_cost = serializers.DecimalField(
        max_digits=20, 
        decimal_places=2, 
        read_only=True
    )
    current_value = serializers.DecimalField(
        max_digits=20, 
        decimal_places=2, 
        read_only=True
    )
    profit_loss = serializers.DecimalField(
        max_digits=20, 
        decimal_places=2, 
        read_only=True
    )
    profit_loss_percentage = serializers.DecimalField(
        max_digits=10, 
        decimal_places=2, 
        read_only=True
    )
    
    class Meta:
        model = Holding
        fields = [
            'id', 'portfolio', 'cryptocurrency', 'cryptocurrency_id',
            'quantity', 'average_buy_price', 'total_cost',
            'current_value', 'profit_loss', 'profit_loss_percentage',
            'created_at', 'updated_at'
        ]
        read_only_fields = ['created_at', 'updated_at']


class PortfolioSerializer(serializers.ModelSerializer):
    """Portfolio model için serializer"""
    
    holdings = HoldingSerializer(many=True, read_only=True)
    total_value = serializers.DecimalField(
        max_digits=20, 
        decimal_places=2, 
        read_only=True
    )
    total_profit_loss = serializers.DecimalField(
        max_digits=20, 
        decimal_places=2, 
        read_only=True
    )
    profit_loss_percentage = serializers.DecimalField(
        max_digits=10, 
        decimal_places=2, 
        read_only=True
    )
    holdings_count = serializers.SerializerMethodField()
    
    class Meta:
        model = Portfolio
        fields = [
            'id', 'user', 'name', 'description',
            'total_value', 'total_profit_loss', 'profit_loss_percentage',
            'holdings', 'holdings_count', 'is_active',
            'created_at', 'updated_at'
        ]
        read_only_fields = ['user', 'created_at', 'updated_at']
    
    def get_holdings_count(self, obj):
        """Portfolio içindeki holding sayısı"""
        return obj.holdings.count()
    
    def create(self, validated_data):
        """Portfolio oluştururken user'ı otomatik ata"""
        validated_data['user'] = self.context['request'].user
        return super().create(validated_data)


class TransactionSerializer(serializers.ModelSerializer):
    """Transaction model için serializer"""
    
    cryptocurrency = CryptocurrencySerializer(read_only=True)
    cryptocurrency_id = serializers.PrimaryKeyRelatedField(
        queryset=Cryptocurrency.objects.all(),
        source='cryptocurrency',
        write_only=True
    )
    total_amount = serializers.DecimalField(
        max_digits=20, 
        decimal_places=2, 
        read_only=True
    )
    
    class Meta:
        model = Transaction
        fields = [
            'id', 'portfolio', 'cryptocurrency', 'cryptocurrency_id',
            'transaction_type', 'quantity', 'price_per_unit',
            'fee', 'total_amount', 'notes', 'transaction_date',
            'created_at'
        ]
        read_only_fields = ['created_at']
    
    def validate(self, data):
        """
        Satış işleminde yeterli miktarın olup olmadığını kontrol et.
        """
        if data.get('transaction_type') == 'SELL':
            portfolio = data.get('portfolio')
            crypto = data.get('cryptocurrency')
            quantity = data.get('quantity')
            
            try:
                holding = Holding.objects.get(
                    portfolio=portfolio,
                    cryptocurrency=crypto
                )
                if holding.quantity < quantity:
                    raise serializers.ValidationError(
                        f"Yetersiz bakiye. Mevcut miktar: {holding.quantity}"
                    )
            except Holding.DoesNotExist:
                raise serializers.ValidationError(
                    "Bu kripto para için holding bulunamadı."
                )
        
        return data


class PriceAlertSerializer(serializers.ModelSerializer):
    """PriceAlert model için serializer"""
    
    cryptocurrency = CryptocurrencySerializer(read_only=True)
    cryptocurrency_id = serializers.PrimaryKeyRelatedField(
        queryset=Cryptocurrency.objects.all(),
        source='cryptocurrency',
        write_only=True
    )
    
    class Meta:
        model = PriceAlert
        fields = [
            'id', 'user', 'cryptocurrency', 'cryptocurrency_id',
            'alert_type', 'target_price', 'is_active',
            'triggered', 'created_at', 'triggered_at'
        ]
        read_only_fields = ['user', 'triggered', 'created_at', 'triggered_at']

Views (portfolio/views.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
from rest_framework import viewsets, status, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from django.db.models import Q, Sum, F
from django_filters.rest_framework import DjangoFilterBackend

from .models import (
    Cryptocurrency, Portfolio, Holding, 
    Transaction, PriceAlert
)
from .serializers import (
    CryptocurrencySerializer, PortfolioSerializer,
    HoldingSerializer, TransactionSerializer,
    PriceAlertSerializer
)

class CryptocurrencyViewSet(viewsets.ReadOnlyModelViewSet):
    """
    Cryptocurrency verileri için ViewSet.
    Sadece okuma izni var (liste ve detay).
    """
    queryset = Cryptocurrency.objects.all()
    serializer_class = CryptocurrencySerializer
    filter_backends = [filters.SearchFilter, filters.OrderingFilter]
    search_fields = ['symbol', 'name']
    ordering_fields = ['market_cap', 'current_price', 'price_change_24h']
    ordering = ['-market_cap']
    
    @action(detail=False, methods=['get'])
    def top_gainers(self, request):
        """24 saatte en çok yükselen coinler"""
        cryptos = self.queryset.order_by('-price_change_24h')[:10]
        serializer = self.get_serializer(cryptos, many=True)
        return Response(serializer.data)
    
    @action(detail=False, methods=['get'])
    def top_losers(self, request):
        """24 saatte en çok düşen coinler"""
        cryptos = self.queryset.order_by('price_change_24h')[:10]
        serializer = self.get_serializer(cryptos, many=True)
        return Response(serializer.data)


class PortfolioViewSet(viewsets.ModelViewSet):
    """
    Portfolio CRUD işlemleri için ViewSet.
    Kullanıcı sadece kendi portfolio'larını görebilir.
    """
    serializer_class = PortfolioSerializer
    permission_classes = [IsAuthenticated]
    filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
    filterset_fields = ['is_active']
    ordering_fields = ['created_at', 'name']
    ordering = ['-created_at']
    
    def get_queryset(self):
        """Kullanıcının kendi portfolio'ları"""
        return Portfolio.objects.filter(
            user=self.request.user
        ).prefetch_related('holdings__cryptocurrency')
    
    @action(detail=True, methods=['get'])
    def summary(self, request, pk=None):
        """
        Portfolio özet bilgileri.
        Toplam değer, kar/zarar, coin dağılımı vb.
        """
        portfolio = self.get_object()
        
        # Coin bazında dağılım
        holdings_distribution = []
        total_value = portfolio.total_value
        
        for holding in portfolio.holdings.all():
            percentage = (holding.current_value / total_value * 100) if total_value > 0 else 0
            holdings_distribution.append({
                'symbol': holding.cryptocurrency.symbol,
                'name': holding.cryptocurrency.name,
                'quantity': float(holding.quantity),
                'current_value': float(holding.current_value),
                'percentage': float(percentage),
                'profit_loss': float(holding.profit_loss),
                'profit_loss_percentage': float(holding.profit_loss_percentage)
            })
        
        # Son işlemler
        recent_transactions = Transaction.objects.filter(
            portfolio=portfolio
        ).order_by('-transaction_date')[:5]
        
        summary_data = {
            'portfolio': PortfolioSerializer(portfolio).data,
            'distribution': holdings_distribution,
            'recent_transactions': TransactionSerializer(
                recent_transactions, 
                many=True
            ).data,
            'total_holdings': portfolio.holdings.count(),
            'total_transactions': portfolio.transactions.count()
        }
        
        return Response(summary_data)
    
    @action(detail=True, methods=['get'])
    def performance(self, request, pk=None):
        """
        Portfolio performans metrikleri.
        """
        portfolio = self.get_object()
        
        # En karlı ve zararlı pozisyonlar
        best_performer = portfolio.holdings.order_by('-profit_loss_percentage').first()
        worst_performer = portfolio.holdings.order_by('profit_loss_percentage').first()
        
        performance_data = {
            'total_value': float(portfolio.total_value),
            'total_profit_loss': float(portfolio.total_profit_loss),
            'profit_loss_percentage': float(portfolio.profit_loss_percentage),
            'best_performer': HoldingSerializer(best_performer).data if best_performer else None,
            'worst_performer': HoldingSerializer(worst_performer).data if worst_performer else None,
        }
        
        return Response(performance_data)


class HoldingViewSet(viewsets.ModelViewSet):
    """
    Holding CRUD işlemleri için ViewSet.
    """
    serializer_class = HoldingSerializer
    permission_classes = [IsAuthenticated]
    
    def get_queryset(self):
        """Kullanıcının portfolio'larındaki holding'ler"""
        return Holding.objects.filter(
            portfolio__user=self.request.user
        ).select_related('cryptocurrency', 'portfolio')


class TransactionViewSet(viewsets.ModelViewSet):
    """
    Transaction CRUD işlemleri için ViewSet.
    """
    serializer_class = TransactionSerializer
    permission_classes = [IsAuthenticated]
    filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
    filterset_fields = ['transaction_type', 'cryptocurrency']
    ordering_fields = ['transaction_date', 'created_at']
    ordering = ['-transaction_date']
    
    def get_queryset(self):
        """Kullanıcının portfolio'larındaki işlemler"""
        return Transaction.objects.filter(
            portfolio__user=self.request.user
        ).select_related('cryptocurrency', 'portfolio')


class PriceAlertViewSet(viewsets.ModelViewSet):
    """
    PriceAlert CRUD işlemleri için ViewSet.
    """
    serializer_class = PriceAlertSerializer
    permission_classes = [IsAuthenticated]
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['is_active', 'triggered', 'alert_type']
    
    def get_queryset(self):
        """Kullanıcının alarm'ları"""
        return PriceAlert.objects.filter(
            user=self.request.user
        ).select_related('cryptocurrency')
    
    def perform_create(self, serializer):
        """Alarm oluştururken user'ı otomatik ata"""
        serializer.save(user=self.request.user)

URL Yapılandırması (portfolio/urls.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import (
    CryptocurrencyViewSet, PortfolioViewSet,
    HoldingViewSet, TransactionViewSet,
    PriceAlertViewSet
)

router = DefaultRouter()
router.register(r'cryptocurrencies', CryptocurrencyViewSet, basename='cryptocurrency')
router.register(r'portfolios', PortfolioViewSet, basename='portfolio')
router.register(r'holdings', HoldingViewSet, basename='holding')
router.register(r'transactions', TransactionViewSet, basename='transaction')
router.register(r'price-alerts', PriceAlertViewSet, basename='price-alert')

urlpatterns = [
    path('', include(router.urls)),
]

Ana URL Yapılandırması (crypto_portfolio/urls.py)

1
2
3
4
5
6
7
8
9
10
from django.contrib import admin
from django.urls import path, include
from rest_framework.authtoken import views as auth_views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('portfolio.urls')),
    path('api/auth/', include('rest_framework.urls')),
    path('api/token-auth/', auth_views.obtain_auth_token),
]

Gerçek Zamanlı Fiyat Güncellemeleri

CoinGecko API kullanarak kripto fiyatlarını güncelleyen servis:

Price Update Service (portfolio/services.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import requests
from decimal import Decimal
from django.utils import timezone
from .models import Cryptocurrency, PriceAlert
import logging

logger = logging.getLogger(__name__)

class CryptoPriceService:
    """
    CoinGecko API'den kripto fiyatlarını çeken servis.
    """
    BASE_URL = "https://api.coingecko.com/api/v3"
    
    @classmethod
    def update_all_prices(cls):
        """
        Tüm kripto paraların fiyatlarını güncelle.
        """
        cryptocurrencies = Cryptocurrency.objects.all()
        coin_ids = ','.join([c.coingecko_id for c in cryptocurrencies])
        
        try:
            url = f"{cls.BASE_URL}/simple/price"
            params = {
                'ids': coin_ids,
                'vs_currencies': 'usd',
                'include_24hr_change': 'true',
                'include_market_cap': 'true'
            }
            
            response = requests.get(url, params=params, timeout=10)
            response.raise_for_status()
            data = response.json()
            
            updated_count = 0
            for crypto in cryptocurrencies:
                if crypto.coingecko_id in data:
                    coin_data = data[crypto.coingecko_id]
                    
                    old_price = crypto.current_price
                    crypto.current_price = Decimal(str(coin_data.get('usd', 0)))
                    crypto.price_change_24h = Decimal(
                        str(coin_data.get('usd_24h_change', 0))
                    )
                    crypto.market_cap = coin_data.get('usd_market_cap', 0)
                    crypto.last_updated = timezone.now()
                    crypto.save()
                    
                    # Fiyat alarmlarını kontrol et
                    cls.check_price_alerts(crypto, old_price)
                    
                    updated_count += 1
            
            logger.info(f"{updated_count} cryptocurrency updated successfully")
            return updated_count
            
        except requests.RequestException as e:
            logger.error(f"Error updating prices: {str(e)}")
            return 0
    
    @classmethod
    def update_single_price(cls, cryptocurrency):
        """
        Tek bir kripto paranın fiyatını güncelle.
        """
        try:
            url = f"{cls.BASE_URL}/simple/price"
            params = {
                'ids': cryptocurrency.coingecko_id,
                'vs_currencies': 'usd',
                'include_24hr_change': 'true',
                'include_market_cap': 'true'
            }
            
            response = requests.get(url, params=params, timeout=10)
            response.raise_for_status()
            data = response.json()
            
            if cryptocurrency.coingecko_id in data:
                coin_data = data[cryptocurrency.coingecko_id]
                
                old_price = cryptocurrency.current_price
                cryptocurrency.current_price = Decimal(str(coin_data.get('usd', 0)))
                cryptocurrency.price_change_24h = Decimal(
                    str(coin_data.get('usd_24h_change', 0))
                )
                cryptocurrency.market_cap = coin_data.get('usd_market_cap', 0)
                cryptocurrency.last_updated = timezone.now()
                cryptocurrency.save()
                
                cls.check_price_alerts(cryptocurrency, old_price)
                
                logger.info(f"Updated {cryptocurrency.symbol}: ${cryptocurrency.current_price}")
                return True
            
        except requests.RequestException as e:
            logger.error(f"Error updating {cryptocurrency.symbol}: {str(e)}")
            return False
    
    @classmethod
    def check_price_alerts(cls, cryptocurrency, old_price):
        """
        Fiyat alarmlarını kontrol et ve tetiklenen alarmları işaretle.
        """
        current_price = cryptocurrency.current_price
        
        # Aktif alarmları getir
        alerts = PriceAlert.objects.filter(
            cryptocurrency=cryptocurrency,
            is_active=True,
            triggered=False
        )
        
        for alert in alerts:
            triggered = False
            
            if alert.alert_type == 'ABOVE' and current_price >= alert.target_price:
                triggered = True
            elif alert.alert_type == 'BELOW' and current_price <= alert.target_price:
                triggered = True
            
            if triggered:
                alert.triggered = True
                alert.triggered_at = timezone.now()
                alert.save()
                
                # Burada email veya push notification gönderilebilir
                logger.info(
                    f"Price alert triggered for {alert.user.username}: "
                    f"{cryptocurrency.symbol} {alert.alert_type} ${alert.target_price}"
                )
    
    @classmethod
    def get_coin_list(cls):
        """
        CoinGecko'dan coin listesini çek ve veritabanına kaydet.
        """
        try:
            url = f"{cls.BASE_URL}/coins/list"
            response = requests.get(url, timeout=10)
            response.raise_for_status()
            coins = response.json()
            
            # İlk 100 coini ekle (örnek için)
            for coin_data in coins[:100]:
                Cryptocurrency.objects.get_or_create(
                    coingecko_id=coin_data['id'],
                    defaults={
                        'symbol': coin_data['symbol'].upper(),
                        'name': coin_data['name']
                    }
                )
            
            logger.info(f"Added/updated {len(coins[:100])} cryptocurrencies")
            return True
            
        except requests.RequestException as e:
            logger.error(f"Error fetching coin list: {str(e)}")
            return False

Celery Tasks (portfolio/tasks.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from celery import shared_task
from .services import CryptoPriceService
import logging

logger = logging.getLogger(__name__)

@shared_task
def update_crypto_prices():
    """
    Tüm kripto fiyatlarını güncelle.
    Periyodik olarak çalışacak (örn: her 5 dakikada).
    """
    logger.info("Starting crypto price update task")
    updated_count = CryptoPriceService.update_all_prices()
    logger.info(f"Crypto price update completed: {updated_count} updated")
    return updated_count

@shared_task
def sync_coingecko_list():
    """
    CoinGecko'dan coin listesini senkronize et.
    Günde bir kez çalışmalı.
    """
    logger.info("Starting CoinGecko list sync")
    result = CryptoPriceService.get_coin_list()
    logger.info(f"CoinGecko list sync completed: {result}")
    return result

Celery Konfigürasyonu (crypto_portfolio/celery.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import os
from celery import Celery
from celery.schedules import crontab

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'crypto_portfolio.settings')

app = Celery('crypto_portfolio')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()

# Periyodik görevler
app.conf.beat_schedule = {
    'update-crypto-prices-every-5-minutes': {
        'task': 'portfolio.tasks.update_crypto_prices',
        'schedule': 300.0,  # 5 dakika
    },
    'sync-coingecko-list-daily': {
        'task': 'portfolio.tasks.sync_coingecko_list',
        'schedule': crontab(hour=0, minute=0),  # Her gece saat 00:00
    },
}

Admin Panel Özelleştirme

Django Admin’i portfolio yönetimi için özelleştirelim:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# portfolio/admin.py
from django.contrib import admin
from django.utils.html import format_html
from .models import (
    Cryptocurrency, Portfolio, Holding,
    Transaction, PriceAlert
)

@admin.register(Cryptocurrency)
class CryptocurrencyAdmin(admin.ModelAdmin):
    list_display = [
        'symbol', 'name', 'current_price_display',
        'price_change_display', 'market_cap_display',
        'last_updated'
    ]
    list_filter = ['last_updated']
    search_fields = ['symbol', 'name', 'coingecko_id']
    readonly_fields = ['last_updated']
    
    def current_price_display(self, obj):
        return f"${obj.current_price:,.2f}"
    current_price_display.short_description = 'Current Price'
    
    def price_change_display(self, obj):
        color = 'green' if obj.price_change_24h >= 0 else 'red'
        return format_html(
            '<span style="color: {};">{:.2f}%</span>',
            color, obj.price_change_24h
        )
    price_change_display.short_description = '24h Change'
    
    def market_cap_display(self, obj):
        return f"${obj.market_cap:,}"
    market_cap_display.short_description = 'Market Cap'


class HoldingInline(admin.TabularInline):
    model = Holding
    extra = 0
    readonly_fields = ['total_cost', 'current_value', 'profit_loss']
    
    def total_cost(self, obj):
        return f"${obj.total_cost:,.2f}"
    
    def current_value(self, obj):
        return f"${obj.current_value:,.2f}"
    
    def profit_loss(self, obj):
        color = 'green' if obj.profit_loss >= 0 else 'red'
        return format_html(
            '<span style="color: {};">${:,.2f}</span>',
            color, obj.profit_loss
        )


@admin.register(Portfolio)
class PortfolioAdmin(admin.ModelAdmin):
    list_display = [
        'name', 'user', 'total_value_display',
        'profit_loss_display', 'holdings_count',
        'is_active', 'created_at'
    ]
    list_filter = ['is_active', 'created_at', 'user']
    search_fields = ['name', 'user__username']
    readonly_fields = ['created_at', 'updated_at']
    inlines = [HoldingInline]
    
    def total_value_display(self, obj):
        return f"${obj.total_value:,.2f}"
    total_value_display.short_description = 'Total Value'
    
    def profit_loss_display(self, obj):
        pl = obj.total_profit_loss
        color = 'green' if pl >= 0 else 'red'
        return format_html(
            '<span style="color: {};">${:,.2f} ({:.2f}%)</span>',
            color, pl, obj.profit_loss_percentage
        )
    profit_loss_display.short_description = 'Profit/Loss'
    
    def holdings_count(self, obj):
        return obj.holdings.count()
    holdings_count.short_description = 'Holdings'


@admin.register(Transaction)
class TransactionAdmin(admin.ModelAdmin):
    list_display = [
        'transaction_date', 'portfolio', 'transaction_type',
        'cryptocurrency', 'quantity', 'price_per_unit',
        'total_amount_display'
    ]
    list_filter = ['transaction_type', 'transaction_date', 'cryptocurrency']
    search_fields = ['portfolio__name', 'cryptocurrency__symbol']
    date_hierarchy = 'transaction_date'
    
    def total_amount_display(self, obj):
        return f"${obj.total_amount:,.2f}"
    total_amount_display.short_description = 'Total Amount'


@admin.register(PriceAlert)
class PriceAlertAdmin(admin.ModelAdmin):
    list_display = [
        'user', 'cryptocurrency', 'alert_type',
        'target_price', 'is_active', 'triggered',
        'created_at'
    ]
    list_filter = ['alert_type', 'is_active', 'triggered', 'created_at']
    search_fields = ['user__username', 'cryptocurrency__symbol']
    readonly_fields = ['triggered_at']

Uygulamayı Çalıştırma

Development Sunucusu

1
2
3
4
5
6
7
8
# Django sunucusunu başlat
python manage.py runserver

# Celery worker başlat (ayrı terminal)
celery -A crypto_portfolio worker --loglevel=info

# Celery beat başlat (periyodik görevler için, ayrı terminal)
celery -A crypto_portfolio beat --loglevel=info

API Kullanım Örnekleri

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Token alma
curl -X POST http://localhost:8000/api/token-auth/ \
  -H "Content-Type: application/json" \
  -d '{"username": "user", "password": "pass"}'

# Portfolio oluşturma
curl -X POST http://localhost:8000/api/portfolios/ \
  -H "Authorization: Token YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Ana Portfolio", "description": "İlk portfoliom"}'

# Transaction ekleme (alım)
curl -X POST http://localhost:8000/api/transactions/ \
  -H "Authorization: Token YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "portfolio": 1,
    "cryptocurrency_id": 1,
    "transaction_type": "BUY",
    "quantity": "0.5",
    "price_per_unit": "50000",
    "transaction_date": "2024-11-15T10:00:00Z"
  }'

# Portfolio özeti görüntüleme
curl http://localhost:8000/api/portfolios/1/summary/ \
  -H "Authorization: Token YOUR_TOKEN"

Production İçin Best Practices

Güvenlik Ayarları

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# production settings
DEBUG = False
ALLOWED_HOSTS = ['yourdomain.com']

# HTTPS zorunlu
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

# CORS ayarları
CORS_ALLOWED_ORIGINS = [
    "https://yourdomain.com",
]

# Rate limiting
REST_FRAMEWORK['DEFAULT_THROTTLE_CLASSES'] = [
    'rest_framework.throttling.AnonRateThrottle',
    'rest_framework.throttling.UserRateThrottle'
]
REST_FRAMEWORK['DEFAULT_THROTTLE_RATES'] = {
    'anon': '100/day',
    'user': '1000/day'
}

Database Optimization

1
2
3
4
5
6
7
8
9
10
11
# portfolio/views.py içinde
class PortfolioViewSet(viewsets.ModelViewSet):
    def get_queryset(self):
        return Portfolio.objects.filter(
            user=self.request.user
        ).select_related(
            'user'
        ).prefetch_related(
            'holdings__cryptocurrency',
            'transactions__cryptocurrency'
        )

Caching Stratejisi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from django.core.cache import cache
from django.views.decorators.cache import cache_page

class CryptocurrencyViewSet(viewsets.ReadOnlyModelViewSet):
    @cache_page(60 * 5)  # 5 dakika cache
    def list(self, request):
        cache_key = 'crypto_list'
        data = cache.get(cache_key)
        
        if not data:
            queryset = self.get_queryset()
            serializer = self.get_serializer(queryset, many=True)
            data = serializer.data
            cache.set(cache_key, data, 300)  # 5 dakika
        
        return Response(data)

Sonuç

Bu yazıda Django kullanarak profesyonel bir kripto portfolio takip uygulaması geliştirdik. Uygulamanın temel özellikleri:

  • User Authentication: Güvenli kullanıcı yönetimi
  • Portfolio Management: Çoklu portfolio desteği
  • Real-time Prices: CoinGecko API entegrasyonu
  • Transaction Tracking: Alım/satım işlemlerini kaydetme
  • Profit/Loss Calculation: Otomatik kar/zarar hesaplama
  • Price Alerts: Kullanıcı tanımlı fiyat alarmları
  • REST API: Mobil ve web frontend için API
  • Admin Panel: Kolay yönetim arayüzü
  • Background Tasks: Celery ile arka plan görevleri

Geliştirme Önerileri

Uygulamayı daha da geliştirebilirsiniz:

  1. Frontend Ekleyin: React, Vue.js veya Angular ile modern UI
  2. Grafik ve Analitik: Chart.js ile portfolio performans grafikleri
  3. Çoklu Para Birimi: USD, EUR, TRY desteği
  4. Exchange API Entegrasyonu: Binance, Coinbase gibi borsalardan otomatik import
  5. Tax Reporting: Vergi raporlama özellikleri
  6. Mobile App: React Native veya Flutter ile mobil uygulama
  7. WebSocket: Gerçek zamanlı fiyat güncellemeleri
  8. Two-Factor Authentication: Ekstra güvenlik katmanı

Django’nun güçlü yapısı sayesinde tüm bu özellikleri kolayca ekleyebilir ve ölçeklenebilir bir uygulama geliştirebilirsiniz.

Kaynaklar

Bu gönderi CC BY 4.0 lisansı altındadır.