Compare commits
6 Commits
660c540828
...
master
Author | SHA1 | Date | |
---|---|---|---|
d0aedfc21f | |||
711e24d244 | |||
84879b89d1 | |||
df31c1aa49 | |||
b9dc545ebb | |||
ee10df4cd0 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -4,7 +4,9 @@
|
|||||||
/.idea/
|
/.idea/
|
||||||
# Distribution / packaging:
|
# Distribution / packaging:
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
|
/dist/
|
||||||
# Python cache:
|
# Python cache:
|
||||||
__pycache__/
|
__pycache__/
|
||||||
# Tests:
|
# Tests:
|
||||||
.coverage
|
.coverage
|
||||||
|
*.sqlite3
|
0
src/django_project/__init__.py
Normal file
0
src/django_project/__init__.py
Normal file
12
src/django_project/asgi.py
Normal file
12
src/django_project/asgi.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
"""
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_project.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
124
src/django_project/settings.py
Normal file
124
src/django_project/settings.py
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
"""
|
||||||
|
Django settings for django_project project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 4.0.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/4.0/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/4.0/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = 'django-insecure->!a2bxb17qhep345g&i2_fcp8-r%8h_df$cy#ccd3*zci309'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django_stockfin_db',
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'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',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'django_project.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'django_project.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': BASE_DIR / 'db.sqlite3',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/4.0/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
|
||||||
|
# https://docs.djangoproject.com/en/4.0/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/4.0/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = 'static/'
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
21
src/django_project/urls.py
Normal file
21
src/django_project/urls.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
"""django_project URL Configuration
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/4.0/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
]
|
12
src/django_project/wsgi.py
Normal file
12
src/django_project/wsgi.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
"""
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_project.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
0
src/django_stockfin_db/__init__.py
Normal file
0
src/django_stockfin_db/__init__.py
Normal file
32
src/django_stockfin_db/admin.py
Normal file
32
src/django_stockfin_db/admin.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from django_stockfin_db.models import FinancialPosition, ReportingPeriod, Company, CompanyName, Figure
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(FinancialPosition)
|
||||||
|
class FinancialPositionAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('name', 'financial_statement', 'parent_name')
|
||||||
|
|
||||||
|
@admin.display(description='Parent Position Name')
|
||||||
|
def parent_name(self, obj: FinancialPosition) -> str:
|
||||||
|
return obj.parent.name if obj.parent else '-'
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(ReportingPeriod)
|
||||||
|
class ReportingPeriodAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('__str__', 'date_end', 'date_start')
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Company)
|
||||||
|
class CompanyAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('__str__', 'symbol')
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(CompanyName)
|
||||||
|
class CompanyNameAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Figure)
|
||||||
|
class FigureAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('position', 'period', 'company', 'value')
|
6
src/django_stockfin_db/apps.py
Normal file
6
src/django_stockfin_db/apps.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class DjangoStockfinDbConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'django_stockfin_db'
|
0
src/django_stockfin_db/migrations/__init__.py
Normal file
0
src/django_stockfin_db/migrations/__init__.py
Normal file
163
src/django_stockfin_db/models.py
Normal file
163
src/django_stockfin_db/models.py
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
from datetime import date
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from django.db.models import Model
|
||||||
|
from django.db.models.fields import CharField, FloatField, BooleanField, DateField, DateTimeField
|
||||||
|
from django.db.models.fields.related import ForeignKey
|
||||||
|
from django.db.models.deletion import PROTECT, CASCADE
|
||||||
|
from django.db.models.enums import TextChoices
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractBaseModel(Model):
|
||||||
|
|
||||||
|
date_created = DateTimeField(
|
||||||
|
auto_now_add=True,
|
||||||
|
verbose_name=_("Time of creation")
|
||||||
|
)
|
||||||
|
date_modified = DateTimeField(
|
||||||
|
auto_now=True,
|
||||||
|
verbose_name=_("Time of last modification")
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class FinancialPosition(AbstractBaseModel):
|
||||||
|
|
||||||
|
class FinStmt(TextChoices):
|
||||||
|
BS = 'BS', _("Balance Sheet")
|
||||||
|
IS = 'IS', _("Income Statement")
|
||||||
|
CF = 'CF', _("Cash Flow Statement")
|
||||||
|
|
||||||
|
financial_statement = CharField(
|
||||||
|
max_length=2,
|
||||||
|
db_index=True,
|
||||||
|
choices=FinStmt.choices,
|
||||||
|
verbose_name=_("Financial statement")
|
||||||
|
)
|
||||||
|
name = CharField(
|
||||||
|
max_length=255,
|
||||||
|
db_index=True,
|
||||||
|
verbose_name=_("Position name")
|
||||||
|
)
|
||||||
|
parent = ForeignKey(
|
||||||
|
to='self',
|
||||||
|
on_delete=PROTECT,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name=_("Parent position")
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"{self.financial_statement} - {self.name}"
|
||||||
|
|
||||||
|
|
||||||
|
class ReportingPeriod(AbstractBaseModel):
|
||||||
|
|
||||||
|
date_end = DateField(
|
||||||
|
db_index=True,
|
||||||
|
verbose_name=_("Period end date")
|
||||||
|
)
|
||||||
|
date_start = DateField(
|
||||||
|
db_index=True,
|
||||||
|
verbose_name=_("Period start date")
|
||||||
|
)
|
||||||
|
verified = BooleanField(
|
||||||
|
default=False,
|
||||||
|
verbose_name=_("Exact dates verified")
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
date_fmt = '%b %Y'
|
||||||
|
assert isinstance(self.date_end, date) and isinstance(self.date_start, date)
|
||||||
|
return f"{'Quarter' if self.is_quarter else 'Year'}: " \
|
||||||
|
f"{self.date_start.strftime(date_fmt)} - {self.date_end.strftime(date_fmt)}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_quarter(self) -> bool:
|
||||||
|
assert isinstance(self.date_end, date) and isinstance(self.date_start, date)
|
||||||
|
delta = self.date_end - self.date_start
|
||||||
|
return delta.days < 100
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['-date_end', 'date_start']
|
||||||
|
|
||||||
|
|
||||||
|
class Company(AbstractBaseModel):
|
||||||
|
|
||||||
|
symbol = CharField(
|
||||||
|
max_length=16,
|
||||||
|
unique=True,
|
||||||
|
verbose_name=_("Stock ticker symbol")
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"{self.name if self.name else '[~~~ NO NAME ~~~]'} ({self.symbol})"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> Optional[str]:
|
||||||
|
if self.current_name_obj is not None:
|
||||||
|
return self.current_name_obj.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_name_obj(self) -> Optional['CompanyName']:
|
||||||
|
try:
|
||||||
|
return self.company_names.latest()
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
return None
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = _("Companies")
|
||||||
|
|
||||||
|
|
||||||
|
class CompanyName(AbstractBaseModel):
|
||||||
|
|
||||||
|
company = ForeignKey(
|
||||||
|
to=Company,
|
||||||
|
on_delete=CASCADE,
|
||||||
|
related_name='company_names',
|
||||||
|
related_query_name='company_name',
|
||||||
|
)
|
||||||
|
name = CharField(
|
||||||
|
max_length=1024,
|
||||||
|
db_index=True,
|
||||||
|
verbose_name=_("Name")
|
||||||
|
)
|
||||||
|
name_since = DateField(
|
||||||
|
db_index=True,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name=_("Has this name since")
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
get_latest_by = ['name_since', 'date_modified']
|
||||||
|
|
||||||
|
|
||||||
|
class Figure(AbstractBaseModel):
|
||||||
|
|
||||||
|
position = ForeignKey(
|
||||||
|
to=FinancialPosition,
|
||||||
|
on_delete=PROTECT
|
||||||
|
)
|
||||||
|
period = ForeignKey(
|
||||||
|
to=ReportingPeriod,
|
||||||
|
on_delete=PROTECT
|
||||||
|
)
|
||||||
|
company = ForeignKey(
|
||||||
|
to=Company,
|
||||||
|
on_delete=PROTECT
|
||||||
|
)
|
||||||
|
value = FloatField(
|
||||||
|
verbose_name=_("Figure on financial statement")
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['company', 'period', 'position']
|
3
src/django_stockfin_db/tests.py
Normal file
3
src/django_stockfin_db/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
22
src/manage.py
Executable file
22
src/manage.py
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_project.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
0
src/mw_stockfin/__init__.py
Normal file
0
src/mw_stockfin/__init__.py
Normal file
6
src/mw_stockfin/apps.py
Normal file
6
src/mw_stockfin/apps.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class MwStockfinConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'mw_stockfin'
|
33
src/mw_stockfin/functions.py
Normal file
33
src/mw_stockfin/functions.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
from django_stockfin_db.models import FinancialPosition, ReportingPeriod, Company, Figure
|
||||||
|
|
||||||
|
from mwfin import get_balance_sheet
|
||||||
|
from mwfin.functions import ResultDict, RowData
|
||||||
|
|
||||||
|
|
||||||
|
def end_date_to_reporting_period(date_str: str) -> ReportingPeriod:
|
||||||
|
"""Convert an end date string into parameters for a ReportingPeriod, `get_or_create` the object and return it."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_or_create_position(name: str, parent_position: FinancialPosition = None) -> FinancialPosition:
|
||||||
|
return FinancialPosition.objects.get_or_create(name=name, parent=parent_position)
|
||||||
|
|
||||||
|
|
||||||
|
def data_row_to_figures(company: Company, position: FinancialPosition, data: RowData):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def save_single_result_dict(fin_stmt: str, symbol: str, result_dict: ResultDict):
|
||||||
|
company = Company.objects.get_or_create(symbol=symbol)
|
||||||
|
|
||||||
|
|
||||||
|
def result_dicts_to_db(fin_stmt: str, **data: ResultDict):
|
||||||
|
for symbol, result_dict in data.items():
|
||||||
|
save_single_result_dict(fin_stmt, symbol, result_dict)
|
||||||
|
|
||||||
|
|
||||||
|
async def main(*symbols: str):
|
||||||
|
data = await get_balance_sheet(*symbols)
|
||||||
|
if len(symbols) == 1:
|
||||||
|
save_single_result_dict('BS', symbols[0], data)
|
||||||
|
result_dicts_to_db('BS', **data)
|
Reference in New Issue
Block a user