Compare commits

...

9 Commits

22 changed files with 685 additions and 333 deletions

1
.gitignore vendored
View File

@@ -159,3 +159,4 @@ cython_debug/
core/static/admin core/static/admin
core/static/debug_toolbar core/static/debug_toolbar
Makefile Makefile
static/

View File

@@ -1,26 +1,26 @@
run: run:
docker-compose -f docker-compose.dev.yml --env-file=stack.env up -d docker-compose --env-file=stack.env up -d
build: build:
docker-compose -f docker-compose.dev.yml --env-file=stack.env build docker-compose --env-file=stack.env build
stop: stop:
docker-compose -f docker-compose.dev.yml --env-file=stack.env down docker-compose --env-file=stack.env down
log: log:
docker-compose -f docker-compose.dev.yml --env-file=stack.env logs -f docker-compose --env-file=stack.env logs -f
test: test:
docker-compose -f docker-compose.dev.yml --env-file=stack.env run -e LIVE=$(LIVE) --rm app_dev sh -c ". /venv/bin/activate && python manage.py test $(MODULES) -v 2" docker-compose --env-file=stack.env run -e LIVE=$(LIVE) --rm app_dev sh -c ". /venv/bin/activate && python manage.py test $(MODULES) -v 2"
migrate: migrate:
docker-compose -f docker-compose.dev.yml --env-file=stack.env run --rm app_dev sh -c ". /venv/bin/activate && python manage.py migrate" docker-compose --env-file=stack.env run --rm app_dev sh -c ". /venv/bin/activate && python manage.py migrate"
makemigrations: makemigrations:
docker-compose -f docker-compose.dev.yml --env-file=stack.env run --rm app_dev sh -c ". /venv/bin/activate && python manage.py makemigrations" docker-compose --env-file=stack.env run --rm app_dev sh -c ". /venv/bin/activate && python manage.py makemigrations"
auth: auth:
docker-compose -f docker-compose.dev.yml --env-file=stack.env run --rm app_dev sh -c ". /venv/bin/activate && python manage.py createsuperuser" docker-compose --env-file=stack.env run --rm app_dev sh -c ". /venv/bin/activate && python manage.py createsuperuser"
token: token:
docker-compose -f docker-compose.dev.yml --env-file=stack.env run --rm app_dev sh -c ". /venv/bin/activate && python manage.py addstatictoken m" docker-compose --env-file=stack.env run --rm app_dev sh -c ". /venv/bin/activate && python manage.py addstatictoken m"

View File

@@ -1,26 +1,26 @@
run: run:
docker-compose --env-file=stack.env up -d docker-compose -f docker-compose.prod.yml --env-file=stack.env up -d
build: build:
docker-compose --env-file=stack.env build docker-compose -f docker-compose.prod.yml --env-file=stack.env build
stop: stop:
docker-compose --env-file=stack.env down docker-compose -f docker-compose.prod.yml --env-file=stack.env down
log: log:
docker-compose --env-file=stack.env logs -f docker-compose -f docker-compose.prod.yml --env-file=stack.env logs -f
test: test:
docker-compose --env-file=stack.env run -e LIVE=$(LIVE) --rm app sh -c ". /venv/bin/activate && python manage.py test $(MODULES) -v 2" docker-compose -f docker-compose.prod.yml --env-file=stack.env run -e LIVE=$(LIVE) --rm app sh -c ". /venv/bin/activate && python manage.py test $(MODULES) -v 2"
migrate: migrate:
docker-compose --env-file=stack.env run --rm app sh -c ". /venv/bin/activate && python manage.py migrate" docker-compose -f docker-compose.prod.yml --env-file=stack.env run --rm app sh -c ". /venv/bin/activate && python manage.py migrate"
makemigrations: makemigrations:
docker-compose --env-file=stack.env run --rm app sh -c ". /venv/bin/activate && python manage.py makemigrations" docker-compose -f docker-compose.prod.yml --env-file=stack.env run --rm app sh -c ". /venv/bin/activate && python manage.py makemigrations"
auth: auth:
docker-compose --env-file=stack.env run --rm app sh -c ". /venv/bin/activate && python manage.py createsuperuser" docker-compose -f docker-compose.prod.yml --env-file=stack.env run --rm app sh -c ". /venv/bin/activate && python manage.py createsuperuser"
token: token:
docker-compose --env-file=stack.env run --rm app sh -c ". /venv/bin/activate && python manage.py addstatictoken m" docker-compose -f docker-compose.prod.yml --env-file=stack.env run --rm app sh -c ". /venv/bin/activate && python manage.py addstatictoken m"

View File

@@ -48,13 +48,18 @@ LAGO_URL = getenv("LAGO_URL", "")
DEBUG = getenv("DEBUG", "false").lower() in trues DEBUG = getenv("DEBUG", "false").lower() in trues
PROFILER = getenv("PROFILER", "false").lower() in trues PROFILER = getenv("PROFILER", "false").lower() in trues
REDIS_HOST = getenv("REDIS_HOST", "redis_fisk_dev")
REDIS_PASSWORD = getenv("REDIS_PASSWORD", "changeme")
REDIS_DB = int(getenv("REDIS_DB", "10"))
REDIS_PORT = int(getenv("REDIS_PORT", "6379"))
if DEBUG: if DEBUG:
import socket # only if you haven't already imported this import socket # only if you haven't already imported this
hostname, _, ips = socket.gethostbyname_ex(socket.gethostname()) hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
INTERNAL_IPS = [ip[: ip.rfind(".")] + ".1" for ip in ips] + [ INTERNAL_IPS = [ip[: ip.rfind(".")] + ".1" for ip in ips] + [
"127.0.0.1", "127.0.0.1",
"10.0.2.2", # "10.0.2.2",
] ]
SETTINGS_EXPORT = ["BILLING_ENABLED", "URL", "HOOK_PATH", "ASSET_PATH"] SETTINGS_EXPORT = ["BILLING_ENABLED", "URL", "HOOK_PATH", "ASSET_PATH"]

View File

@@ -56,23 +56,6 @@ INSTALLED_APPS = [
"cachalot", "cachalot",
] ]
# Performance optimisations
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": "unix:///var/run/socks/redis.sock",
"OPTIONS": {
"db": "10",
"parser_class": "redis.connection.HiredisParser",
"pool_class": "redis.BlockingConnectionPool",
},
}
}
# CACHE_MIDDLEWARE_ALIAS = 'default'
# CACHE_MIDDLEWARE_SECONDS = '600'
# CACHE_MIDDLEWARE_KEY_PREFIX = ''
CRISPY_TEMPLATE_PACK = "bulma" CRISPY_TEMPLATE_PACK = "bulma"
CRISPY_ALLOWED_TEMPLATE_PACKS = ("bulma",) CRISPY_ALLOWED_TEMPLATE_PACKS = ("bulma",)
DJANGO_TABLES2_TEMPLATE = "django-tables2/bulma.html" DJANGO_TABLES2_TEMPLATE = "django-tables2/bulma.html"
@@ -184,7 +167,7 @@ REST_FRAMEWORK = {
INTERNAL_IPS = [ INTERNAL_IPS = [
"127.0.0.1", "127.0.0.1",
"10.1.10.11", # "10.1.10.11",
] ]
DEBUG_TOOLBAR_PANELS = [ DEBUG_TOOLBAR_PANELS = [
@@ -208,6 +191,24 @@ DEBUG_TOOLBAR_PANELS = [
from app.local_settings import * # noqa from app.local_settings import * # noqa
# Performance optimisations
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
# "LOCATION": "unix:///var/run/socks/redis.sock",
"LOCATION": "unix:///var/run/redis.sock",
"OPTIONS": {
"db": REDIS_DB,
# "parser_class": "django_redis.cache.RedisCache",
# "PASSWORD": REDIS_PASSWORD,
"pool_class": "redis.BlockingConnectionPool",
},
}
}
# CACHE_MIDDLEWARE_ALIAS = 'default'
# CACHE_MIDDLEWARE_SECONDS = '600'
# CACHE_MIDDLEWARE_KEY_PREFIX = ''
if PROFILER: # noqa - trust me its there if PROFILER: # noqa - trust me its there
import pyroscope import pyroscope

82
core/exchanges/mexc.py Normal file

File diff suppressed because one or more lines are too long

View File

@@ -23,15 +23,16 @@ def initialise_elasticsearch():
def store_msg(index, msg): def store_msg(index, msg):
global client return
if not client: # global client
client = initialise_elasticsearch() # if not client:
if "ts" not in msg: # client = initialise_elasticsearch()
msg["ts"] = datetime.utcnow().isoformat() # if "ts" not in msg:
try: # msg["ts"] = datetime.utcnow().isoformat()
result = client.index(index=index, body=msg) # try:
except ConnectionError as e: # result = client.index(index=index, body=msg)
log.error(f"Error indexing '{msg}': {e}") # except ConnectionError as e:
return # log.error(f"Error indexing '{msg}': {e}")
if not result["result"] == "created": # return
log.error(f"Indexing of '{msg}' failed: {result}") # if not result["result"] == "created":
# log.error(f"Indexing of '{msg}' failed: {result}")

View File

@@ -1 +1 @@
from core.lib.schemas import alpaca_s, drakdoo_s, oanda_s # noqa from core.lib.schemas import alpaca_s, drakdoo_s, oanda_s, mexc_s # noqa

View File

@@ -0,0 +1 @@
from pydantic import BaseModel, Field

View File

@@ -1,29 +1,30 @@
from decimal import Decimal as D from decimal import Decimal as D
from typing import Optional
from pydantic import BaseModel from pydantic import BaseModel
class PositionLong(BaseModel): class PositionLong(BaseModel):
units: str units: str
averagePrice: str | None averagePrice: Optional[str] = None
pl: str pl: str
resettablePL: str resettablePL: str
financing: str financing: str
dividendAdjustment: str dividendAdjustment: str
guaranteedExecutionFees: str guaranteedExecutionFees: str
tradeIDs: list[str] | None tradeIDs: Optional[list[str]] = []
unrealizedPL: str unrealizedPL: str
class PositionShort(BaseModel): class PositionShort(BaseModel):
units: str units: str
averagePrice: str | None averagePrice: Optional[str] = None
pl: str pl: str
resettablePL: str resettablePL: str
financing: str financing: str
dividendAdjustment: str dividendAdjustment: str
guaranteedExecutionFees: str guaranteedExecutionFees: str
tradeIDs: list[str] | None tradeIDs: Optional[list[str]] = []
unrealizedPL: str unrealizedPL: str
@@ -306,7 +307,7 @@ class PositionDetailsNested(BaseModel):
dividendAdjustment: str dividendAdjustment: str
guaranteedExecutionFees: str guaranteedExecutionFees: str
unrealizedPL: str unrealizedPL: str
marginUsed: str | None marginUsed: Optional[str] = None
class PositionDetails(BaseModel): class PositionDetails(BaseModel):
@@ -373,7 +374,9 @@ class Instrument(BaseModel):
guaranteedStopLossOrderMode: str guaranteedStopLossOrderMode: str
tags: list[InstrumentTag] tags: list[InstrumentTag]
financing: InstrumentFinancing financing: InstrumentFinancing
guaranteedStopLossOrderLevelRestriction: InstrumentGuaranteedRestriction | None guaranteedStopLossOrderLevelRestriction: Optional[
InstrumentGuaranteedRestriction
] = None
class AccountInstruments(BaseModel): class AccountInstruments(BaseModel):
@@ -474,33 +477,33 @@ class Trade(BaseModel):
quoteGuaranteedExecutionFee: str quoteGuaranteedExecutionFee: str
halfSpreadCost: str halfSpreadCost: str
# takeProfitOrder: TakeProfitOrder | None # takeProfitOrder: TakeProfitOrder | None
takeProfitOrder: dict | None takeProfitOrder: Optional[dict] = None
stopLossOrder: dict | None stopLossOrder: Optional[dict] = None
trailingStopLossOrder: dict | None trailingStopLossOrder: Optional[dict] = None
class SideCarOrder(BaseModel): class SideCarOrder(BaseModel):
id: str id: str
createTime: str createTime: str
state: str state: str
price: str | None price: Optional[str] = None
timeInForce: str timeInForce: str
gtdTime: str | None gtdTime: Optional[str] = None
clientExtensions: dict | None clientExtensions: Optional[dict] = None
tradeID: str tradeID: str
clientTradeID: str | None clientTradeID: Optional[str] = None
type: str type: str
time: str | None time: Optional[str] = None
priceBound: str | None priceBound: Optional[str] = None
positionFill: str | None positionFill: Optional[str] = None
reason: str | None reason: Optional[str] = None
orderFillTransactionID: str | None orderFillTransactionID: Optional[str] = None
tradeOpenedID: str | None tradeOpenedID: Optional[str] = None
tradeReducedID: str | None tradeReducedID: Optional[str] = None
tradeClosedIDs: list[str] | None tradeClosedIDs: Optional[list[str]] = []
cancellingTransactionID: str | None cancellingTransactionID: Optional[str] = None
replacesOrderID: str | None replacesOrderID: Optional[str] = None
replacedByOrderID: str | None replacedByOrderID: Optional[str] = None
class OpenTradesTrade(BaseModel): class OpenTradesTrade(BaseModel):
@@ -517,10 +520,10 @@ class OpenTradesTrade(BaseModel):
dividendAdjustment: str dividendAdjustment: str
unrealizedPL: str unrealizedPL: str
marginUsed: str marginUsed: str
takeProfitOrder: SideCarOrder | None takeProfitOrder: Optional[SideCarOrder] = None
stopLossOrder: SideCarOrder | None stopLossOrder: Optional[SideCarOrder] = None
trailingStopLossOrder: SideCarOrder | None trailingStopLossOrder: Optional[SideCarOrder] = None
trailingStopValue: dict | None trailingStopValue: Optional[dict] = None
class OpenTrades(BaseModel): class OpenTrades(BaseModel):
@@ -578,13 +581,13 @@ class OrderTransaction(BaseModel):
requestID: str requestID: str
time: str time: str
type: str type: str
instrument: str | None instrument: Optional[str] = None
units: str | None units: Optional[str] = None
timeInForce: str | None timeInForce: Optional[str] = None
positionFill: str | None positionFill: Optional[str] = None
reason: str reason: str
longPositionCloseout: LongPositionCloseout | None longPositionCloseout: LongPositionCloseout | None
longOrderFillTransaction: dict | None longOrderFillTransaction: Optional[dict] = None
class OrderCreate(BaseModel): class OrderCreate(BaseModel):
@@ -677,12 +680,12 @@ class TradeDetailsTrade(BaseModel):
state: str state: str
currentUnits: str currentUnits: str
realizedPL: str realizedPL: str
closingTransactionIDs: list[str] | None closingTransactionIDs: Optional[list[str]] = []
financing: str financing: str
dividendAdjustment: str dividendAdjustment: str
closeTime: str | None closeTime: Optional[str] = None
averageClosePrice: str | None averageClosePrice: Optional[str] = None
clientExtensions: ClientExtensions | None clientExtensions: Optional[ClientExtensions] = None
class TradeDetails(BaseModel): class TradeDetails(BaseModel):
@@ -733,10 +736,10 @@ TradeCloseSchema = {
class TradeCRCDO(BaseModel): class TradeCRCDO(BaseModel):
takeProfitOrderCancelTransaction: OrderTransaction takeProfitOrderCancelTransaction: Optional[OrderTransaction]
takeProfitOrderTransaction: OrderTransaction takeProfitOrderTransaction: Optional[OrderTransaction]
stopLossOrderCancelTransaction: OrderTransaction stopLossOrderCancelTransaction: Optional[OrderTransaction]
stopLossOrderTransaction: OrderTransaction stopLossOrderTransaction: Optional[OrderTransaction]
relatedTransactionIDs: list[str] relatedTransactionIDs: list[str]
lastTransactionID: str lastTransactionID: str

View File

@@ -38,11 +38,15 @@ class Command(BaseCommand):
log.debug(f"Scheduling checking process job every {INTERVAL} seconds") log.debug(f"Scheduling checking process job every {INTERVAL} seconds")
scheduler.add_job(job, "interval", seconds=INTERVAL) scheduler.add_job(job, "interval", seconds=INTERVAL)
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
scheduler._eventloop = loop
scheduler.start() scheduler.start()
loop = asyncio.get_event_loop()
try: try:
loop.run_forever() loop.run_forever()
except (KeyboardInterrupt, SystemExit): except (KeyboardInterrupt, SystemExit):
log.info("Process terminating") log.info("Process terminating")
finally: finally:
scheduler.shutdown(wait=False)
loop.close() loop.close()

View File

@@ -8,6 +8,7 @@ from django.db import models
from core.exchanges.alpaca import AlpacaExchange from core.exchanges.alpaca import AlpacaExchange
from core.exchanges.fake import FakeExchange from core.exchanges.fake import FakeExchange
from core.exchanges.mexc import MEXCExchange
from core.exchanges.oanda import OANDAExchange from core.exchanges.oanda import OANDAExchange
# from core.lib.customers import get_or_create, update_customer_fields # from core.lib.customers import get_or_create, update_customer_fields
@@ -15,7 +16,12 @@ from core.lib import billing
from core.util import logs from core.util import logs
log = logs.get_logger(__name__) log = logs.get_logger(__name__)
EXCHANGE_MAP = {"alpaca": AlpacaExchange, "oanda": OANDAExchange, "fake": FakeExchange} EXCHANGE_MAP = {
"alpaca": AlpacaExchange,
"oanda": OANDAExchange,
"mexc": MEXCExchange,
"fake": FakeExchange,
}
TYPE_CHOICES = ( TYPE_CHOICES = (
("market", "Market"), ("market", "Market"),
("limit", "Limit"), ("limit", "Limit"),
@@ -141,7 +147,12 @@ class User(AbstractUser):
class Account(models.Model): class Account(models.Model):
EXCHANGE_CHOICES = (("alpaca", "Alpaca"), ("oanda", "OANDA"), ("fake", "Fake")) EXCHANGE_CHOICES = (
("alpaca", "Alpaca"),
("oanda", "OANDA"),
("mexc", "MEXC"),
("fake", "Fake"),
)
user = models.ForeignKey(User, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
exchange = models.CharField(choices=EXCHANGE_CHOICES, max_length=255) exchange = models.CharField(choices=EXCHANGE_CHOICES, max_length=255)
@@ -165,11 +176,13 @@ class Account(models.Model):
if client: if client:
response = client.get_instruments() response = client.get_instruments()
supported_symbols = client.get_supported_assets(response) supported_symbols = client.get_supported_assets(response)
currency = client.get_account()["currency"] acct_info = client.get_account()
log.debug(f"Supported symbols for {self.name}: {supported_symbols}") log.debug(f"Supported symbols for {self.name}: {supported_symbols}")
self.supported_symbols = supported_symbols self.supported_symbols = supported_symbols
self.instruments = response self.instruments = response
self.currency = currency if "currency" in acct_info.keys():
currency = acct_info["currency"]
self.currency = currency
if save: if save:
self.save() self.save()

View File

@@ -311,7 +311,10 @@
{% endif %} {% endif %}
{% if user.is_authenticated %} {% if user.is_authenticated %}
<a class="button" href="{% url 'logout' %}">Logout</a> <form method="POST" action="{% url 'logout' %}" style="display:inline;">
{% csrf_token %}
<button type="submit" class="button">Logout</button>
</form>
{% endif %} {% endif %}
</div> </div>

View File

@@ -30,7 +30,7 @@ class AccountInfo(LoginRequiredMixin, OTPRequiredMixin, ObjectRead):
"sandbox", "sandbox",
"supported_symbols", "supported_symbols",
"initial_balance", "initial_balance",
"instruments", #"instruments",
] ]
def get_object(self, **kwargs): def get_object(self, **kwargs):

View File

@@ -1,174 +0,0 @@
version: "3.8"
name: fisk_dev
services:
app_dev:
image: xf/fisk:dev
container_name: fisk_dev
build:
context: .
args:
OPERATION: ${OPERATION}
volumes:
- ${PORTAINER_GIT_DIR}:/code
- ${PORTAINER_GIT_DIR}/docker/uwsgi.ini:/conf/uwsgi.ini
- ${APP_DATABASE_FILE}:/conf/db.sqlite3
- fisk_static_dev:${STATIC_ROOT}
#ports:
# - "8000:8000" # uwsgi socket
env_file:
- stack.env
volumes_from:
- tmp_dev
depends_on:
# redis:
# condition: service_healthy
migration_dev:
condition: service_started
collectstatic_dev:
condition: service_started
networks:
- default
- xf
- elastic
scheduling_dev:
image: xf/fisk:dev
container_name: scheduling_fisk_dev
build:
context: .
args:
OPERATION: ${OPERATION}
command: sh -c '. /venv/bin/activate && python manage.py scheduling'
volumes:
- ${PORTAINER_GIT_DIR}:/code
- ${PORTAINER_GIT_DIR}/docker/uwsgi.ini:/conf/uwsgi.ini
- ${APP_DATABASE_FILE}:/conf/db.sqlite3
- fisk_static_dev:${STATIC_ROOT}
env_file:
- stack.env
volumes_from:
- tmp_dev
depends_on:
redis_dev:
condition: service_healthy
migration_dev:
condition: service_started
collectstatic_dev:
condition: service_started
networks:
- default
- xf
- elastic
migration_dev:
image: xf/fisk:dev
container_name: migration_fisk_dev
build:
context: .
args:
OPERATION: ${OPERATION}
command: sh -c '. /venv/bin/activate && python manage.py migrate --noinput'
volumes:
- ${PORTAINER_GIT_DIR}:/code
- ${APP_DATABASE_FILE}:/conf/db.sqlite3
- fisk_static_dev:${STATIC_ROOT}
volumes_from:
- tmp_dev
env_file:
- stack.env
collectstatic_dev:
image: xf/fisk:dev
container_name: collectstatic_fisk_dev
build:
context: .
args:
OPERATION: ${OPERATION}
command: sh -c '. /venv/bin/activate && python manage.py collectstatic --noinput'
volumes:
- ${PORTAINER_GIT_DIR}:/code
- ${APP_DATABASE_FILE}:/conf/db.sqlite3
- fisk_static_dev:${STATIC_ROOT}
volumes_from:
- tmp_dev
env_file:
- stack.env
nginx_dev:
image: nginx:latest
container_name: nginx_fisk_dev
ports:
- ${APP_PORT}:9999
ulimits:
nproc: 65535
nofile:
soft: 65535
hard: 65535
volumes:
- ${PORTAINER_GIT_DIR}:/code
- ${PORTAINER_GIT_DIR}/docker/nginx/conf.d/${OPERATION}.conf:/etc/nginx/conf.d/default.conf
- fisk_static_dev:${STATIC_ROOT}
volumes_from:
- tmp_dev
networks:
- default
- xf
depends_on:
app_dev:
condition: service_started
# volumes_from:
# - tmp
# depends_on:
# redis:
# condition: service_healthy
tmp_dev:
image: busybox
container_name: tmp_fisk_dev
command: chmod -R 777 /var/run/socks
volumes:
- /var/run/socks
# For caching
redis_dev:
image: redis
container_name: redis_fisk_dev
command: redis-server /etc/redis.conf
ulimits:
nproc: 65535
nofile:
soft: 65535
hard: 65535
volumes:
- ${PORTAINER_GIT_DIR}/docker/redis.conf:/etc/redis.conf
- fisk_redis_data_dev:/data
volumes_from:
- tmp_dev
healthcheck:
test: "redis-cli -s /var/run/socks/redis.sock ping"
interval: 2s
timeout: 2s
retries: 15
# pyroscope:
# image: "pyroscope/pyroscope:latest"
# ports:
# - "4040:4040"
# command:
# - "server"
networks:
default:
driver: bridge
xf:
external: true
elastic:
external: true
volumes:
fisk_static_dev: {}
fisk_redis_data_dev: {}

391
docker-compose.prod.yml Normal file
View File

@@ -0,0 +1,391 @@
version: "2.2"
services:
app:
image: xf/fisk:prod
container_name: fisk
build:
context: .
args:
OPERATION: ${OPERATION}
volumes:
- ${PORTAINER_GIT_DIR}:/code
- ${PORTAINER_GIT_DIR}/docker/uwsgi.ini:/conf/uwsgi.ini
- ${APP_DATABASE_FILE}:/conf/db.sqlite3
# - /code/xf/static:${STATIC_ROOT}
- type: bind
source: /code/run
target: /var/run
#ports:
# - "8000:8000" # uwsgi socket
# Dirty hack for Podman
environment:
APP_PORT: "${APP_PORT}"
PORTAINER_GIT_DIR: "${PORTAINER_GIT_DIR}"
APP_LOCAL_SETTINGS: "${APP_LOCAL_SETTINGS}"
APP_DATABASE_FILE: "${APP_DATABASE_FILE}"
DOMAIN: "${DOMAIN}"
URL: "${URL}"
ALLOWED_HOSTS: "${ALLOWED_HOSTS}"
NOTIFY_TOPIC: "${NOTIFY_TOPIC}"
CSRF_TRUSTED_ORIGINS: "${CSRF_TRUSTED_ORIGINS}"
DEBUG: "${DEBUG}"
SECRET_KEY: "${SECRET_KEY}"
STATIC_ROOT: "${STATIC_ROOT}"
REGISTRATION_OPEN: "${REGISTRATION_OPEN}"
OPERATION: "${OPERATION}"
ELASTICSEARCH_USERNAME: "${ELASTICSEARCH_USERNAME}"
ELASTICSEARCH_PASSWORD: "${ELASTICSEARCH_PASSWORD}"
ELASTICSEARCH_HOST: "${ELASTICSEARCH_HOST}"
ELASTICSEARCH_TLS: "${ELASTICSEARCH_TLS}"
TEST_ACCOUNT_NAME: "${TEST_ACCOUNT_NAME}"
TEST_ACCOUNT_EXCHANGE: "${TEST_ACCOUNT_EXCHANGE}"
TEST_ACCOUNT_API_KEY: "${TEST_ACCOUNT_API_KEY}"
TEST_ACCOUNT_API_SECRET: "${TEST_ACCOUNT_API_SECRET}"
PROFILER: "${PROFILER}"
BILLING_ENABLED: "${BILLING_ENABLED}"
LAGO_API_KEY: "${LAGO_API_KEY}"
LAGO_ORG_ID: "${LAGO_ORG_ID}"
LAGO_URL: "${LAGO_URL}"
STRIPE_TEST: "${STRIPE_TEST}"
STRIPE_API_KEY_TEST: "${STRIPE_API_KEY_TEST}"
STRIPE_PUBLIC_API_KEY_TEST: "${STRIPE_PUBLIC_API_KEY_TEST}"
STRIPE_API_KEY_PROD: "${STRIPE_API_KEY_PROD}"
STRIPE_PUBLIC_API_KEY_PROD: "${STRIPE_PUBLIC_API_KEY_PROD}"
STRIPE_ENDPOINT_SECRET: "${STRIPE_ENDPOINT_SECRET}"
REDIS_HOST: "${REDIS_HOST}"
# env_file:
# - stack.env
# volumes_from:
# - tmp
depends_on:
# redis:
# condition: service_healthy
migration:
condition: service_started
collectstatic:
condition: service_started
# networks:
# - default
# - xf
# - elastic
#network_mode: "pasta:-T,6380:6379"
deploy:
resources:
limits:
cpus: '0.1'
memory: 0.25G
network_mode: host
scheduling:
image: xf/fisk:prod
container_name: scheduling_fisk
build:
context: .
args:
OPERATION: ${OPERATION}
command: sh -c '. /venv/bin/activate && python manage.py scheduling'
volumes:
- ${PORTAINER_GIT_DIR}:/code
- ${PORTAINER_GIT_DIR}/docker/uwsgi.ini:/conf/uwsgi.ini
- ${APP_DATABASE_FILE}:/conf/db.sqlite3
# - /code/xf/static:${STATIC_ROOT}
- type: bind
source: /code/run
target: /var/run
# Dirty hack for Podman
environment:
APP_PORT: "${APP_PORT}"
PORTAINER_GIT_DIR: "${PORTAINER_GIT_DIR}"
APP_LOCAL_SETTINGS: "${APP_LOCAL_SETTINGS}"
APP_DATABASE_FILE: "${APP_DATABASE_FILE}"
DOMAIN: "${DOMAIN}"
URL: "${URL}"
ALLOWED_HOSTS: "${ALLOWED_HOSTS}"
NOTIFY_TOPIC: "${NOTIFY_TOPIC}"
CSRF_TRUSTED_ORIGINS: "${CSRF_TRUSTED_ORIGINS}"
DEBUG: "${DEBUG}"
SECRET_KEY: "${SECRET_KEY}"
STATIC_ROOT: "${STATIC_ROOT}"
REGISTRATION_OPEN: "${REGISTRATION_OPEN}"
OPERATION: "${OPERATION}"
ELASTICSEARCH_USERNAME: "${ELASTICSEARCH_USERNAME}"
ELASTICSEARCH_PASSWORD: "${ELASTICSEARCH_PASSWORD}"
ELASTICSEARCH_HOST: "${ELASTICSEARCH_HOST}"
ELASTICSEARCH_TLS: "${ELASTICSEARCH_TLS}"
TEST_ACCOUNT_NAME: "${TEST_ACCOUNT_NAME}"
TEST_ACCOUNT_EXCHANGE: "${TEST_ACCOUNT_EXCHANGE}"
TEST_ACCOUNT_API_KEY: "${TEST_ACCOUNT_API_KEY}"
TEST_ACCOUNT_API_SECRET: "${TEST_ACCOUNT_API_SECRET}"
PROFILER: "${PROFILER}"
BILLING_ENABLED: "${BILLING_ENABLED}"
LAGO_API_KEY: "${LAGO_API_KEY}"
LAGO_ORG_ID: "${LAGO_ORG_ID}"
LAGO_URL: "${LAGO_URL}"
STRIPE_TEST: "${STRIPE_TEST}"
STRIPE_API_KEY_TEST: "${STRIPE_API_KEY_TEST}"
STRIPE_PUBLIC_API_KEY_TEST: "${STRIPE_PUBLIC_API_KEY_TEST}"
STRIPE_API_KEY_PROD: "${STRIPE_API_KEY_PROD}"
STRIPE_PUBLIC_API_KEY_PROD: "${STRIPE_PUBLIC_API_KEY_PROD}"
STRIPE_ENDPOINT_SECRET: "${STRIPE_ENDPOINT_SECRET}"
REDIS_HOST: "${REDIS_HOST}"
# env_file:
# - stack.env
# volumes_from:
# - tmp
depends_on:
redis:
condition: service_healthy
migration:
condition: service_started
collectstatic:
condition: service_started
# networks:
# - default
# - xf
# - db
#network_mode: "pasta:-T,6380:6379"
deploy:
resources:
limits:
cpus: '0.25'
memory: 0.25G
network_mode: host
migration:
image: xf/fisk:prod
container_name: migration_fisk
build:
context: .
args:
OPERATION: ${OPERATION}
command: sh -c '. /venv/bin/activate && python manage.py migrate --noinput'
volumes:
- ${PORTAINER_GIT_DIR}:/code
- ${APP_DATABASE_FILE}:/conf/db.sqlite3
# - /code/xf/static:${STATIC_ROOT}
- type: bind
source: /code/run
target: /var/run
# volumes_from:
# - tmp
# Dirty hack for Podman
environment:
APP_PORT: "${APP_PORT}"
PORTAINER_GIT_DIR: "${PORTAINER_GIT_DIR}"
APP_LOCAL_SETTINGS: "${APP_LOCAL_SETTINGS}"
APP_DATABASE_FILE: "${APP_DATABASE_FILE}"
DOMAIN: "${DOMAIN}"
URL: "${URL}"
ALLOWED_HOSTS: "${ALLOWED_HOSTS}"
NOTIFY_TOPIC: "${NOTIFY_TOPIC}"
CSRF_TRUSTED_ORIGINS: "${CSRF_TRUSTED_ORIGINS}"
DEBUG: "${DEBUG}"
SECRET_KEY: "${SECRET_KEY}"
STATIC_ROOT: "${STATIC_ROOT}"
REGISTRATION_OPEN: "${REGISTRATION_OPEN}"
OPERATION: "${OPERATION}"
ELASTICSEARCH_USERNAME: "${ELASTICSEARCH_USERNAME}"
ELASTICSEARCH_PASSWORD: "${ELASTICSEARCH_PASSWORD}"
ELASTICSEARCH_HOST: "${ELASTICSEARCH_HOST}"
ELASTICSEARCH_TLS: "${ELASTICSEARCH_TLS}"
TEST_ACCOUNT_NAME: "${TEST_ACCOUNT_NAME}"
TEST_ACCOUNT_EXCHANGE: "${TEST_ACCOUNT_EXCHANGE}"
TEST_ACCOUNT_API_KEY: "${TEST_ACCOUNT_API_KEY}"
TEST_ACCOUNT_API_SECRET: "${TEST_ACCOUNT_API_SECRET}"
PROFILER: "${PROFILER}"
BILLING_ENABLED: "${BILLING_ENABLED}"
LAGO_API_KEY: "${LAGO_API_KEY}"
LAGO_ORG_ID: "${LAGO_ORG_ID}"
LAGO_URL: "${LAGO_URL}"
STRIPE_TEST: "${STRIPE_TEST}"
STRIPE_API_KEY_TEST: "${STRIPE_API_KEY_TEST}"
STRIPE_PUBLIC_API_KEY_TEST: "${STRIPE_PUBLIC_API_KEY_TEST}"
STRIPE_API_KEY_PROD: "${STRIPE_API_KEY_PROD}"
STRIPE_PUBLIC_API_KEY_PROD: "${STRIPE_PUBLIC_API_KEY_PROD}"
STRIPE_ENDPOINT_SECRET: "${STRIPE_ENDPOINT_SECRET}"
REDIS_HOST: "${REDIS_HOST}"
# env_file:
# - stack.env
# networks:
# - default
# - xf
# - db
#network_mode: "pasta:-T,6380:6379"
deploy:
resources:
limits:
cpus: '0.25'
memory: 0.25G
network_mode: host
collectstatic:
image: xf/fisk:prod
container_name: collectstatic_fisk
build:
context: .
args:
OPERATION: ${OPERATION}
command: sh -c '. /venv/bin/activate && python manage.py collectstatic --noinput'
volumes:
- ${PORTAINER_GIT_DIR}:/code
- ${APP_DATABASE_FILE}:/conf/db.sqlite3
# - /code/xf/static:${STATIC_ROOT}
- type: bind
source: /code/run
target: /var/run
# volumes_from:
# - tmp
# Dirty hack for Podman
environment:
APP_PORT: "${APP_PORT}"
PORTAINER_GIT_DIR: "${PORTAINER_GIT_DIR}"
APP_LOCAL_SETTINGS: "${APP_LOCAL_SETTINGS}"
APP_DATABASE_FILE: "${APP_DATABASE_FILE}"
DOMAIN: "${DOMAIN}"
URL: "${URL}"
ALLOWED_HOSTS: "${ALLOWED_HOSTS}"
NOTIFY_TOPIC: "${NOTIFY_TOPIC}"
CSRF_TRUSTED_ORIGINS: "${CSRF_TRUSTED_ORIGINS}"
DEBUG: "${DEBUG}"
SECRET_KEY: "${SECRET_KEY}"
STATIC_ROOT: "${STATIC_ROOT}"
REGISTRATION_OPEN: "${REGISTRATION_OPEN}"
OPERATION: "${OPERATION}"
ELASTICSEARCH_USERNAME: "${ELASTICSEARCH_USERNAME}"
ELASTICSEARCH_PASSWORD: "${ELASTICSEARCH_PASSWORD}"
ELASTICSEARCH_HOST: "${ELASTICSEARCH_HOST}"
ELASTICSEARCH_TLS: "${ELASTICSEARCH_TLS}"
TEST_ACCOUNT_NAME: "${TEST_ACCOUNT_NAME}"
TEST_ACCOUNT_EXCHANGE: "${TEST_ACCOUNT_EXCHANGE}"
TEST_ACCOUNT_API_KEY: "${TEST_ACCOUNT_API_KEY}"
TEST_ACCOUNT_API_SECRET: "${TEST_ACCOUNT_API_SECRET}"
PROFILER: "${PROFILER}"
BILLING_ENABLED: "${BILLING_ENABLED}"
LAGO_API_KEY: "${LAGO_API_KEY}"
LAGO_ORG_ID: "${LAGO_ORG_ID}"
LAGO_URL: "${LAGO_URL}"
STRIPE_TEST: "${STRIPE_TEST}"
STRIPE_API_KEY_TEST: "${STRIPE_API_KEY_TEST}"
STRIPE_PUBLIC_API_KEY_TEST: "${STRIPE_PUBLIC_API_KEY_TEST}"
STRIPE_API_KEY_PROD: "${STRIPE_API_KEY_PROD}"
STRIPE_PUBLIC_API_KEY_PROD: "${STRIPE_PUBLIC_API_KEY_PROD}"
STRIPE_ENDPOINT_SECRET: "${STRIPE_ENDPOINT_SECRET}"
REDIS_HOST: "${REDIS_HOST}"
# env_file:
# - stack.env
# networks:
# - default
# - xf
# - db
#network_mode: "pasta:-T,6380:6379"
deploy:
resources:
limits:
cpus: '0.25'
memory: 0.25G
network_mode: host
# nginx:
# image: nginx:latest
# container_name: nginx_fisk
# expose:
# - ${APP_PORT}
# ports:
# - ${APP_PORT}:9999
# # ulimits:
# # nproc: 65535
# # nofile:
# # soft: 65535
# # hard: 65535
# volumes:
# - ${PORTAINER_GIT_DIR}:/code
# - ${PORTAINER_GIT_DIR}/docker/nginx/conf.d/${OPERATION}.conf:/etc/nginx/conf.d/default.conf
# - fisk_static:${STATIC_ROOT}
# - type: bind
# source: /code/run
# target: /var/run
# # volumes_from:
# # - tmp
# # networks:
# # - default
# # - xf
# depends_on:
# app:
# condition: service_started
# # forward the redis port from the host to the container as 6380
# # network_mode: "pasta:-t,9999"
# deploy:
# resources:
# limits:
# cpus: '0.25'
# memory: 0.25G
# network_mode: host
# volumes_from:
# - tmp
# depends_on:
# redis:
# condition: service_healthy
# tmp:
# image: busybox
# container_name: tmp_fisk
# command: chmod -R 777 /var/run/socks
# volumes:
# - /var/run/socks
# For caching
redis:
image: redis
container_name: redis_fisk
command: redis-server /etc/redis.conf
# ulimits:
# nproc: 65535
# nofile:
# soft: 65535
# hard: 65535
volumes:
- ${PORTAINER_GIT_DIR}/docker/redis.conf:/etc/redis.conf
- fisk_redis_data:/data
- type: bind
source: /code/run
target: /var/run
# networks:
# - default
# - xf
# - db
# volumes_from:
# - tmp
healthcheck:
test: "redis-cli ping"
interval: 2s
timeout: 2s
retries: 15
# network_mode: "pasta:-T,6380:6379"
deploy:
resources:
limits:
cpus: '0.25'
memory: 0.25G
network_mode: host
# pyroscope:
# image: "pyroscope/pyroscope:latest"
# ports:
# - "4040:4040"
# command:
# - "server"
# networks:
# default:
# driver: bridge
# xf:
# external: true
# db:
# external: true
volumes:
# fisk_static: {}
fisk_redis_data: {}

View File

@@ -1,9 +1,11 @@
version: "2.2" version: "2.2"
name: fisk_dev
services: services:
app: app_dev:
image: xf/fisk:prod image: xf/fisk:dev
container_name: fisk container_name: fisk_dev
build: build:
context: . context: .
args: args:
@@ -12,28 +14,28 @@ services:
- ${PORTAINER_GIT_DIR}:/code - ${PORTAINER_GIT_DIR}:/code
- ${PORTAINER_GIT_DIR}/docker/uwsgi.ini:/conf/uwsgi.ini - ${PORTAINER_GIT_DIR}/docker/uwsgi.ini:/conf/uwsgi.ini
- ${APP_DATABASE_FILE}:/conf/db.sqlite3 - ${APP_DATABASE_FILE}:/conf/db.sqlite3
- fisk_static:${STATIC_ROOT} - fisk_static_dev:${STATIC_ROOT}
#ports: #ports:
# - "8000:8000" # uwsgi socket # - "8000:8000" # uwsgi socket
env_file: env_file:
- stack.env - stack.env
volumes_from: # volumes_from:
- tmp # - tmp_dev
depends_on: depends_on:
# redis: # redis:
# condition: service_healthy # condition: service_healthy
migration: migration_dev:
condition: service_started condition: service_started
collectstatic: collectstatic_dev:
condition: service_started condition: service_started
networks: networks:
- default - default
- xf - xf
- elastic # - db
scheduling: scheduling_dev:
image: xf/fisk:prod image: xf/fisk:dev
container_name: scheduling_fisk container_name: scheduling_fisk_dev
build: build:
context: . context: .
args: args:
@@ -43,26 +45,26 @@ services:
- ${PORTAINER_GIT_DIR}:/code - ${PORTAINER_GIT_DIR}:/code
- ${PORTAINER_GIT_DIR}/docker/uwsgi.ini:/conf/uwsgi.ini - ${PORTAINER_GIT_DIR}/docker/uwsgi.ini:/conf/uwsgi.ini
- ${APP_DATABASE_FILE}:/conf/db.sqlite3 - ${APP_DATABASE_FILE}:/conf/db.sqlite3
- fisk_static:${STATIC_ROOT} - fisk_static_dev:${STATIC_ROOT}
env_file: env_file:
- stack.env - stack.env
volumes_from: # volumes_from:
- tmp # - tmp_dev
depends_on: depends_on:
redis: redis_dev:
condition: service_healthy condition: service_healthy
migration: migration_dev:
condition: service_started condition: service_started
collectstatic: collectstatic_dev:
condition: service_started condition: service_started
networks: networks:
- default - default
- xf - xf
- elastic # - db
migration: migration_dev:
image: xf/fisk:prod image: xf/fisk:dev
container_name: migration_fisk container_name: migration_fisk_dev
build: build:
context: . context: .
args: args:
@@ -71,15 +73,15 @@ services:
volumes: volumes:
- ${PORTAINER_GIT_DIR}:/code - ${PORTAINER_GIT_DIR}:/code
- ${APP_DATABASE_FILE}:/conf/db.sqlite3 - ${APP_DATABASE_FILE}:/conf/db.sqlite3
- fisk_static:${STATIC_ROOT} - fisk_static_dev:${STATIC_ROOT}
volumes_from: # volumes_from:
- tmp # - tmp_dev
env_file: env_file:
- stack.env - stack.env
collectstatic: collectstatic_dev:
image: xf/fisk:prod image: xf/fisk:dev
container_name: collectstatic_fisk container_name: collectstatic_fisk_dev
build: build:
context: . context: .
args: args:
@@ -88,15 +90,15 @@ services:
volumes: volumes:
- ${PORTAINER_GIT_DIR}:/code - ${PORTAINER_GIT_DIR}:/code
- ${APP_DATABASE_FILE}:/conf/db.sqlite3 - ${APP_DATABASE_FILE}:/conf/db.sqlite3
- fisk_static:${STATIC_ROOT} - fisk_static_dev:${STATIC_ROOT}
volumes_from: # volumes_from:
- tmp # - tmp_dev
env_file: env_file:
- stack.env - stack.env
nginx: nginx_dev:
image: nginx:latest image: nginx:latest
container_name: nginx_fisk container_name: nginx_fisk_dev
ports: ports:
- ${APP_PORT}:9999 - ${APP_PORT}:9999
ulimits: ulimits:
@@ -107,14 +109,14 @@ services:
volumes: volumes:
- ${PORTAINER_GIT_DIR}:/code - ${PORTAINER_GIT_DIR}:/code
- ${PORTAINER_GIT_DIR}/docker/nginx/conf.d/${OPERATION}.conf:/etc/nginx/conf.d/default.conf - ${PORTAINER_GIT_DIR}/docker/nginx/conf.d/${OPERATION}.conf:/etc/nginx/conf.d/default.conf
- fisk_static:${STATIC_ROOT} - fisk_static_dev:${STATIC_ROOT}
volumes_from: # volumes_from:
- tmp # - tmp_dev
networks: networks:
- default - default
- xf - xf
depends_on: depends_on:
app: app_dev:
condition: service_started condition: service_started
@@ -124,17 +126,17 @@ services:
# redis: # redis:
# condition: service_healthy # condition: service_healthy
tmp: # tmp_dev:
image: busybox # image: busybox
container_name: tmp_fisk # container_name: tmp_fisk_dev
command: chmod -R 777 /var/run/socks # command: chmod -R 777 /var/run/socks
volumes: # volumes:
- /var/run/socks # - /var/run/socks
# For caching # For caching
redis: redis_dev:
image: redis image: redis
container_name: redis_fisk container_name: redis_fisk_dev
command: redis-server /etc/redis.conf command: redis-server /etc/redis.conf
ulimits: ulimits:
nproc: 65535 nproc: 65535
@@ -143,11 +145,11 @@ services:
hard: 65535 hard: 65535
volumes: volumes:
- ${PORTAINER_GIT_DIR}/docker/redis.conf:/etc/redis.conf - ${PORTAINER_GIT_DIR}/docker/redis.conf:/etc/redis.conf
- fisk_redis_data:/data - fisk_redis_data_dev:/data
volumes_from: # volumes_from:
- tmp # - tmp_dev
healthcheck: healthcheck:
test: "redis-cli -s /var/run/socks/redis.sock ping" test: "redis-cli ping"
interval: 2s interval: 2s
timeout: 2s timeout: 2s
retries: 15 retries: 15
@@ -164,9 +166,9 @@ networks:
driver: bridge driver: bridge
xf: xf:
external: true external: true
elastic: # db:
external: true # external: true
volumes: volumes:
fisk_static: {} fisk_static_dev: {}
fisk_redis_data: {} fisk_redis_data_dev: {}

View File

@@ -1,6 +1,6 @@
upstream django { upstream django {
server app:8000; #server app:8000;
#server unix:///var/run/socks/app.sock; server unix:///var/run/uwsgi-fisk.sock;
} }
server { server {

View File

@@ -1,2 +1,5 @@
unixsocket /var/run/socks/redis.sock unixsocket /var/run/redis.sock
unixsocketperm 777 unixsocketperm 777
port 0
# port 6379
# requirepass changeme

View File

@@ -4,9 +4,19 @@ module=app.wsgi:application
env=DJANGO_SETTINGS_MODULE=app.settings env=DJANGO_SETTINGS_MODULE=app.settings
master=1 master=1
pidfile=/tmp/project-master.pid pidfile=/tmp/project-master.pid
socket=0.0.0.0:8000 #socket=0.0.0.0:8000
socket=/var/run/uwsgi-fisk.sock
# socket 777
chmod-socket=777
harakiri=20 harakiri=20
max-requests=100000 #max-requests=100000
# Set a lower value for max-requests to prevent memory leaks from building up over time
max-requests=1000
# Ensure old worker processes are cleaned up properly
reload-on-as=512
reload-on-rss=256
vacuum=1 vacuum=1
home=/venv home=/venv
processes=12 processes=4
threads=2
log-level=debug

View File

@@ -17,8 +17,12 @@ django-otp-yubikey
phonenumbers phonenumbers
qrcode qrcode
pydantic pydantic
# Alpaca
alpaca-py alpaca-py
# OANDA
oandapyV20 oandapyV20
# MEXC
pymexc
glom glom
elasticsearch elasticsearch
apscheduler apscheduler
@@ -30,4 +34,6 @@ git+https://git.zm.is/XF/django-crud-mixins
redis redis
hiredis hiredis
django-cachalot django-cachalot
django_redis
# Billing
lago-python-client lago-python-client