Using Kraken as Market provider

This commit is contained in:
Flatlogic Bot 2025-12-29 13:31:35 +00:00
parent 2d3a86ad45
commit 2750ec443e
27 changed files with 171 additions and 333 deletions

View File

@ -49,11 +49,7 @@ CSRF_COOKIE_SAMESITE = "None"
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'core', 'core',
'signals', 'signals',
@ -64,8 +60,6 @@ MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
# Disable X-Frame-Options middleware to allow Flatlogic preview iframes. # Disable X-Frame-Options middleware to allow Flatlogic preview iframes.
# 'django.middleware.clickjacking.XFrameOptionsMiddleware', # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
] ]
@ -82,8 +76,6 @@ TEMPLATES = [
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [
'django.template.context_processors.request', 'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
# IMPORTANT: do not remove injects PROJECT_DESCRIPTION/PROJECT_IMAGE_URL and cache-busting timestamp # IMPORTANT: do not remove injects PROJECT_DESCRIPTION/PROJECT_IMAGE_URL and cache-busting timestamp
'core.context_processors.project_context', 'core.context_processors.project_context',
], ],
@ -99,36 +91,15 @@ WSGI_APPLICATION = 'config.wsgi.application'
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.mysql', 'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.getenv('DB_NAME', ''), 'NAME': BASE_DIR / 'db.sqlite3',
'USER': os.getenv('DB_USER', ''), }
'PASSWORD': os.getenv('DB_PASS', ''),
'HOST': os.getenv('DB_HOST', '127.0.0.1'),
'PORT': os.getenv('DB_PORT', '3306'),
'OPTIONS': {
'charset': 'utf8mb4',
},
},
} }
# Password validation # Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization # Internationalization

View File

@ -14,13 +14,11 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path 1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
from django.contrib import admin
from django.urls import include, path from django.urls import include, path
from django.conf import settings from django.conf import settings
from django.conf.urls.static import static from django.conf.urls.static import static
urlpatterns = [ urlpatterns = [
path("admin/", admin.site.urls),
path("", include("core.urls")), path("", include("core.urls")),
path("api/", include("signals.urls")), path("api/", include("signals.urls")),
] ]

View File

@ -1,6 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}{{ project_name }}{% endblock %} {% block title %}Trading Signal: {{ active_symbol }}{% endblock %}
{% block head %} {% block head %}
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
@ -8,11 +8,15 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
<style> <style>
:root { :root {
--bg-color-start: #6a11cb; --bg-color: #121212;
--bg-color-end: #2575fc; --text-color: #e0e0e0;
--text-color: #ffffff; --card-bg-color: #1e1e1e;
--card-bg-color: rgba(255, 255, 255, 0.01); --card-border-color: #2c2c2c;
--card-border-color: rgba(255, 255, 255, 0.1); --primary-color: #4A90E2; /* A modern blue */
--buy-color: #4CAF50;
--sell-color: #F44336;
--wait-color: #FFC107;
--font-family: 'Inter', sans-serif;
} }
* { * {
@ -21,125 +25,132 @@
body { body {
margin: 0; margin: 0;
font-family: 'Inter', sans-serif; font-family: var(--font-family);
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end)); background-color: var(--bg-color);
color: var(--text-color); color: var(--text-color);
display: flex; display: flex;
justify-content: center; flex-direction: column;
align-items: center; align-items: center;
min-height: 100vh; min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
inset: 0;
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'><path d='M-10 10L110 10M10 -10L10 110' stroke-width='1' stroke='rgba(255,255,255,0.05)'/></svg>");
animation: bg-pan 20s linear infinite;
z-index: -1;
}
@keyframes bg-pan {
0% {
background-position: 0% 0%;
}
100% {
background-position: 100% 100%;
}
}
main {
padding: 2rem; padding: 2rem;
} }
.card { header {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 16px;
padding: 2.5rem 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.25);
}
h1 {
font-size: clamp(2.2rem, 3vw + 1.2rem, 3.2rem);
font-weight: 700;
margin: 0 0 1.2rem;
letter-spacing: -0.02em;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
opacity: 0.92;
}
.loader {
margin: 1.5rem auto;
width: 56px;
height: 56px;
border: 4px solid rgba(255, 255, 255, 0.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.runtime code {
background: rgba(0, 0, 0, 0.25);
padding: 0.15rem 0.45rem;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
footer {
position: absolute;
bottom: 1rem;
width: 100%; width: 100%;
text-align: center; max-width: 600px;
font-size: 0.85rem; display: flex;
opacity: 0.75; justify-content: center;
align-items: center;
margin-bottom: 2rem;
} }
.symbol-selector-form {
display: flex;
gap: 1rem;
align-items: center;
}
.symbol-selector-form label {
font-size: 1rem;
font-weight: 700;
}
.symbol-selector {
padding: 0.75rem 1rem;
font-size: 1rem;
background-color: var(--card-bg-color);
color: var(--text-color);
border: 1px solid var(--card-border-color);
border-radius: 8px;
cursor: pointer;
}
.submit-btn {
padding: 0.75rem 1.5rem;
font-size: 1rem;
font-weight: 700;
background-color: var(--primary-color);
color: #ffffff;
border: none;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.submit-btn:hover {
background-color: #357ABD;
}
main {
width: 100%;
max-width: 600px;
display: flex;
flex-direction: column;
align-items: center;
}
.signal-card {
width: 100%;
background-color: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 12px;
padding: 2.5rem;
display: flex;
flex-direction: column;
align-items: center;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
}
.signal-card h1 {
margin: 0;
font-size: 1.5rem;
color: var(--primary-color);
font-weight: 700;
margin-bottom: 0.5rem;
}
.signal-card p {
margin: 0;
font-size: 4rem;
font-weight: 700;
}
.signal-BUY {
color: var(--buy-color);
}
.signal-SELL {
color: var(--sell-color);
}
.signal-WAIT {
color: var(--wait-color);
}
</style> </style>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<header>
<!-- 2.A. MANUAL SYMBOL SELECTOR -->
<form action="{% url 'home' %}" method="get" class="symbol-selector-form">
<label for="symbol-select">Active Symbol:</label>
<select name="symbol" id="symbol-select" class="symbol-selector">
{% for sym in available_symbols %}
<option value="{{ sym }}" {% if sym == active_symbol %}selected{% endif %}>
{{ sym }}
</option>
{% endfor %}
</select>
<button type="submit" class="submit-btn">Analyze</button>
</form>
</header>
<main> <main>
<div class="card"> <!-- 4. FRONTEND BEHAVIOR: Display data for the active symbol -->
<h1>Analyzing your requirements and generating your app…</h1> <div class="signal-card">
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes"> <h1>{{ active_symbol }}</h1>
<span class="sr-only">Loading…</span> <p class="signal-{{ signal }}">{{ signal }}</p>
</div>
<p class="hint">AppWizzy AI is collecting your requirements and applying the first changes.</p>
<p class="hint">This page will refresh automatically as the plan is implemented.</p>
<p class="runtime">
Runtime: Django <code>{{ django_version }}</code> · Python <code>{{ python_version }}</code>
— UTC <code>{{ current_time|date:"Y-m-d H:i:s" }}</code>
</p>
</div> </div>
</main> </main>
<footer>
Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC)
</footer>
{% endblock %} {% endblock %}

View File

@ -1,25 +1,25 @@
import os
import platform
from django import get_version as django_version
from django.shortcuts import render from django.shortcuts import render
from django.utils import timezone from signals.engine import generate_signal
def home(request): def home(request):
"""Render the landing screen with loader and environment details.""" """
host_name = request.get_host().lower() Renders the home page with a trading signal for the active symbol.
agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic" """
now = timezone.now() # 1. ACTIVE SYMBOL STATE: Get symbol from URL parameter, default to BTC/USDT
symbol = request.GET.get('symbol', 'BTC/USDT').upper()
try:
# 3. BACKEND INTEGRATION: Generate signal for the active symbol
signal = generate_signal(symbol)
except Exception as e:
signal = f'Error: {e}'
# A list of symbols for the dropdown selector
available_symbols = ['BTC/USDT', 'ETH/USDT', 'XRP/USDT', 'LTC/USDT', 'ADA/USDT', 'SOL/USDT', 'DOGE/USDT']
context = { context = {
"project_name": "New Style", 'active_symbol': symbol,
"agent_brand": agent_brand, 'signal': signal,
"django_version": django_version(), 'available_symbols': available_symbols,
"python_version": platform.python_version(),
"current_time": now,
"host_name": host_name,
"project_description": os.getenv("PROJECT_DESCRIPTION", ""),
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
} }
return render(request, "core/index.html", context) return render(request, "core/index.html", context)

BIN
db.sqlite3 Normal file

Binary file not shown.

View File

@ -1,4 +1,4 @@
Django==5.2.7 Django==5.2.7
mysqlclient==2.2.7
python-dotenv==1.1.1 python-dotenv==1.1.1
ccxt ccxt
pandas

View File

@ -3,8 +3,32 @@ import ccxt
import pandas as pd import pandas as pd
def get_ohlcv(symbol, timeframe): def get_ohlcv(symbol, timeframe):
exchange = ccxt.binance() exchange = ccxt.kraken()
ohlcv = exchange.fetch_ohlcv(symbol, timeframe) # Symbol mapping for Kraken
symbol_map = {
'BTC/USDT': 'BTC/USD',
'ETH/USDT': 'ETH/USD',
'XRP/USDT': 'XRP/USD',
'LTC/USDT': 'LTC/USD',
'ADA/USDT': 'ADA/USD',
'SOL/USDT': 'SOL/USD',
'DOGE/USDT': 'DOGE/USD',
}
try:
# First, try the original symbol
ohlcv = exchange.fetch_ohlcv(symbol, timeframe)
except ccxt.BadSymbol:
# If the symbol is not found, try the mapped symbol
mapped_symbol = symbol_map.get(symbol)
if mapped_symbol:
try:
ohlcv = exchange.fetch_ohlcv(mapped_symbol, timeframe)
except ccxt.ExchangeError as e:
raise Exception(f"Could not fetch data for {symbol} or {mapped_symbol} from Kraken: {e})")
else:
raise Exception(f"Symbol {symbol} not found on Kraken and no mapping available.")
df = pd.DataFrame(ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']) df = pd.DataFrame(ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms') df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
return df return df

View File

View File

@ -1,26 +0,0 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
from .models import UserProfile, Signal, Trade, Backtest
class UserProfileInline(admin.StackedInline):
model = UserProfile
can_delete = False
verbose_name_plural = 'Profile'
fk_name = 'user'
class CustomUserAdmin(UserAdmin):
inlines = (UserProfileInline, )
def get_inline_instances(self, request, obj=None):
if not obj:
return list()
return super(CustomUserAdmin, self).get_inline_instances(request, obj)
admin.site.unregister(User)
admin.site.register(User, CustomUserAdmin)
admin.site.register(Signal)
admin.site.register(Trade)
admin.site.register(Backtest)

View File

@ -1,9 +0,0 @@
from django.apps import AppConfig
class TradingConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'trading'
def ready(self):
import trading.signals

View File

@ -1,59 +0,0 @@
# Generated by Django 5.2.7 on 2025-12-28 13:55
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Signal',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('symbol', models.CharField(max_length=20)),
('signal', models.CharField(choices=[('BUY', 'Buy'), ('SELL', 'Sell'), ('WAIT', 'Wait')], max_length=4)),
('confidence', models.FloatField()),
('timestamp', models.DateTimeField(auto_now_add=True)),
],
),
migrations.CreateModel(
name='Backtest',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('start_date', models.DateTimeField()),
('end_date', models.DateTimeField()),
('results', models.JSONField()),
('timestamp', models.DateTimeField(auto_now_add=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Trade',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('symbol', models.CharField(max_length=20)),
('entry_price', models.FloatField()),
('exit_price', models.FloatField(blank=True, null=True)),
('entry_timestamp', models.DateTimeField()),
('exit_timestamp', models.DateTimeField(blank=True, null=True)),
('signal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='trading.signal')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='UserProfile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('role', models.CharField(choices=[('Free', 'Free'), ('Pro', 'Pro'), ('Admin', 'Admin')], default='Free', max_length=10)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -1,50 +0,0 @@
from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
USER_ROLE_CHOICES = (
('Free', 'Free'),
('Pro', 'Pro'),
('Admin', 'Admin'),
)
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
role = models.CharField(max_length=10, choices=USER_ROLE_CHOICES, default='Free')
def __str__(self):
return f'{self.user.username} - {self.role}'
class Signal(models.Model):
SIGNAL_CHOICES = (
('BUY', 'Buy'),
('SELL', 'Sell'),
('WAIT', 'Wait'),
)
symbol = models.CharField(max_length=20)
signal = models.CharField(max_length=4, choices=SIGNAL_CHOICES)
confidence = models.FloatField()
timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f'{self.timestamp} - {self.symbol} - {self.signal}'
class Trade(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
symbol = models.CharField(max_length=20)
entry_price = models.FloatField()
exit_price = models.FloatField(null=True, blank=True)
entry_timestamp = models.DateTimeField()
exit_timestamp = models.DateTimeField(null=True, blank=True)
signal = models.ForeignKey(Signal, on_delete=models.CASCADE)
def __str__(self):
return f'{self.user.username} - {self.symbol}'
class Backtest(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
start_date = models.DateTimeField()
end_date = models.DateTimeField()
results = models.JSONField()
timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f'{self.user.username} - {self.timestamp}'

View File

@ -1,16 +0,0 @@
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User
from .models import UserProfile
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
try:
instance.profile.save()
except UserProfile.DoesNotExist:
UserProfile.objects.create(user=instance)

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,3 +0,0 @@
from django.shortcuts import render
# Create your views here.