Модель данных

8 января 2020 г. 20:20

Различные функции ДУ работают с единой моделью данных, которая представлена на данной странице.

brokers.py

accounts.py

deposit.py

operations.py

# -*- coding: utf-8 -*-
from __future__ import unicode_literals, division

import json

from django.db import models
from django.contrib.postgres.fields import JSONField
from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible
from django.utils.html import escape
from django.utils.translation import ugettext_lazy as _, pgettext_lazy
from sb_core.mixins import AdminUrlsMixin
from sb_core.templatetags.sb_tags import with_domain

from viva_tm_wsc.exceptions import BrokerUpdateRateError
from viva_tm_wsc.settings import TRADING_CURRENCIES_CHOICES, TRADING_CURRENCIES



@python_2_unicode_compatible
class Broker(AdminUrlsMixin, models.Model):
    title = models.CharField(_('Title'), max_length=100)
    name = models.CharField(_('Unique identifier'), max_length=100)

    rate_content = models.TextField(
        _('Rate content'), blank=True,
        help_text=_('html, json and etc.<br/>If request package can not load html using cookie')
    )
    cookies = models.TextField(_('Cookies'), blank=True)

    extra = JSONField(_('Extra'), null=True, blank=True, default={}, editable=False)

    def broker_update_rate_error(self, value, mes):
        return BrokerUpdateRateError(
            'Broker "{}": {}'.format(self.title, value), '"{}": Error with update_rates'.format(self.title),
            '<p>Broker "{0}". Try update cookie <a href="{1}">{1}</a>. '
            'Error with handling of data:</p><pre>{2}</pre>'.format(
                self.title, with_domain(self.get_admin_absolute_url()), escape(mes)
            )
        )

    def update_rates(self, cookies='', currency=None):
        available_brokers = ('forex4you', 'roboforex')

        if self.name not in available_brokers:
            raise Exception('{} is not available. Use broker: {}'.format(self.name, available_brokers))

        now = timezone.now()
        headers = {'cookie': cookies or self.cookies}
        rates = getattr(self, '{}_rates'.format(self.name))(headers, self.rate_content)

        # save rates
        for base_currency, quote_currencies in rates.items():
            for data in quote_currencies:
                rate, created = Rate.objects.update_or_create(
                    broker=self, base_currency=base_currency, quote_currency=data['quote_currency'],
                    defaults={'ask': data['ask'], 'bid': data['bid'], 'update_date': now})

                if created:
                    rate.extra['stat'] = {}

                rate.extra['stat'].setdefault('items', []).append((now.strftime('%d.%m.%Y %H:%M'),
                                                                   data['ask'], data['bid']))

                rate.save(update_fields=('extra',))

                self.extra.setdefault('currencies', {}).update(
                    {'{}/{}'.format(base_currency, data['quote_currency']): {
                        'ask': float(data['ask']), 'bid': float(data['bid']),
                        'spread': abs(float(data['ask']) - float(data['bid']))
                    }}
                )

        self.save(update_fields=('extra',))

    def forex4you_rates(self, headers, content=''):
        rates = {}
        # parsing rates from Forex4you

        return rates

    def roboforex_rates(self, headers, content=''):
        rates = {}
        # parsing rates from Roboforex

        return rates

    def get_currency_choice(self):
        return [(TRADING_CURRENCIES.get(cur)[1], TRADING_CURRENCIES.get(cur)[2]) for cur in self.availablecurrencyinbroker_set.values_list('currency', flat=True) if cur in TRADING_CURRENCIES]

    @staticmethod
    def get_all_broker_choice():
        return Broker.objects.values_list('id', 'name', 'title')

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = _('Broker')
        verbose_name_plural = _('Brokers')


@python_2_unicode_compatible
class AvailableCurrencyInBroker(AdminUrlsMixin, models.Model):
    broker = models.ForeignKey(Broker, verbose_name=_('Broker'))
    currency = models.CharField(_('Currency'), max_length=8, choices=TRADING_CURRENCIES_CHOICES)

    def __str__(self):
        return _('{} is in {}').format(self.currency, self.broker)

    class Meta:
        verbose_name = _('Available rate in broker')
        verbose_name_plural = _('Available rates in broker')


@python_2_unicode_compatible
class Rate(AdminUrlsMixin, models.Model):
    broker = models.ForeignKey(Broker, verbose_name=_('Broker'))

    base_currency = models.CharField(_('Base currency'), max_length=8, choices=TRADING_CURRENCIES_CHOICES)
    quote_currency = models.CharField(_('Quote currency'), max_length=8, choices=TRADING_CURRENCIES_CHOICES)

    ask = models.DecimalField(_('Current ask price'), decimal_places=8, max_digits=21,
                              help_text=_('Purchase price'))
    bid = models.DecimalField(_('Current bid price'), decimal_places=8, max_digits=21, null=True, blank=True,
                              help_text=_('May be for withdrawal or transfer between in broker'))

    update_date = models.DateTimeField(_('Update date'))

    extra = JSONField(_('Extra'), null=True, blank=True, default={})

    def pair(self):
        return '{}/{}'.format(self.base_currency, self.quote_currency)
    pair.short_description = _('Currency pair')

    def ask_bid(self):
        return '{}/{}'.format(self.ask, self.bid)
    ask_bid.short_description = _('Ask/Bid')

    @property
    def spread(self):
        return abs(self.ask - self.bid)
    ask_bid.short_description = _('Spread')

    def __str__(self):
        return '{}:{} ({})'.format(self.pair(), self.ask_bid(), self.broker)

    class Meta:
        verbose_name = pgettext_lazy('trading', 'Rate')
        verbose_name_plural = pgettext_lazy('trading', 'Rates')

# -*- coding: utf-8 -*-
from __future__ import unicode_literals, division

from decimal import Decimal

from django.conf import settings
from django.db import models
from django.contrib.postgres.fields import JSONField
from django.db.models import QuerySet
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from sb_core.mixins import AdminUrlsMixin

from money.currencies import CURRENCY_CHOICES, RUB, USD, USD_CENT, EUR, CURRENCIES


class TMAccountQS(QuerySet):
    def private_accounts(self):
        return self.filter(is_tm=False)

    def tm_accounts(self):
        return self.filter(is_tm=True)

    def active(self):
        return self.tm_accounts().exclude(account_type=TMAccount.ARCHIVE).order_by('order')

    def safety_accounts(self):
        return self.tm_accounts().filter(account_type=TMAccount.SAFETY).order_by('order')

    def profitable_accounts(self):
        return self.tm_accounts().filter(account_type=TMAccount.PROFITABLE).order_by('order')

    def unlimited_accounts(self):
        return self.filter(amount_limit=Decimal(0))

    def archival(self):
        return self.tm_accounts().filter(account_type=TMAccount.ARCHIVE).order_by('order')


@python_2_unicode_compatible
class TMAccount(AdminUrlsMixin, models.Model):
    broker = models.ForeignKey('viva_tm_wsc.Broker', verbose_name=_('Broker'))

    number = models.IntegerField(_('Account number'))
    title = models.CharField(_('Title'), max_length=100)
    creation_date = models.DateField(_('Creation date'), null=True, blank=True)

    SAFETY = 'safety'
    PROFITABLE = 'profitable'
    ARCHIVE = 'archive'
    PRIVATE = 'private'
    ACCOUNT_TYPES = ((SAFETY, _('Safety')), (PROFITABLE, _('Profitable')),
                     (ARCHIVE, _('Archive')), (PRIVATE, _('Private')))
    account_type = models.CharField(_('Type'), max_length=10, choices=ACCOUNT_TYPES, default=SAFETY,
                                    help_text=_('Archive account will not be parsed from myfxbook'))

    is_copied = models.BooleanField(_('Is copied?'), default=False)
    is_tm = models.BooleanField(_('Is TM?'), default=False)

    user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('User'), null=True, blank=True,
                             related_name='tm_accounts')

    currency = models.CharField(_('Currency'), max_length=8, choices=CURRENCY_CHOICES, default=USD_CENT)
    initial_currency = models.CharField(_('Base currency'), max_length=8, choices=CURRENCY_CHOICES, default=USD)
    ratio = models.DecimalField(_('Ratio'), decimal_places=8, max_digits=21, default=Decimal(1))

    preferred_currency = models.CharField(_('Preferred currency for display'), max_length=8, choices=CURRENCY_CHOICES,
                                          default=RUB)

    extra = JSONField(_('Extra'), null=True, blank=True, default={})
    order = models.PositiveSmallIntegerField(' ', default=0, blank=False, null=False)

    amount = models.DecimalField(_('Amount'), decimal_places=8, max_digits=21, default=0)
    amount_limit = models.DecimalField(_('Limit amount'), decimal_places=8, max_digits=21, default=0)

    profit = models.DecimalField(_('Profit'), decimal_places=8, max_digits=21, default=0, editable=False)

    objects = TMAccountQS.as_manager()

    def __str__(self):
        return '{} ({}: {})'.format(self.title, self.broker, self.number)

    class Meta:
        ordering = ('order', )
        verbose_name = _('Trading account')
        verbose_name_plural = _('Trading accounts')
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, division

import json
from django.conf import settings
from django.contrib.postgres.fields import JSONField
from django.db import models
from django.db.models import QuerySet
from viva_tm_wsc.models import TMAccount


class DepositQS(QuerySet):
    def active(self):
        return self.exclude(tm_account__account_type=TMAccount.ARCHIVE).order_by('tm_account__order')

    def safety_deposits(self):
        return self.filter(tm_account__account_type=TMAccount.SAFETY).order_by('tm_account__order')

    def profitable_deposits(self):
        return self.filter(tm_account__account_type=TMAccount.PROFITABLE).order_by('tm_account__order')

    def archival(self):
        return self.filter(tm_account__account_type=TMAccount.ARCHIVE).order_by('tm_account__order')

    def without_excluded_users(self):
        site_info = SiteInfo.objects.get(name='viva_tm')
        params = json.loads(site_info.value)

        if params['balancing']['excluded_users']:
            return self.exclude(user_id__in=params['balancing']['excluded_users'])

        return self


@python_2_unicode_compatible
class Deposit(AdminUrlsMixin, models.Model):
    tm_account = models.ForeignKey(TMAccount, verbose_name=_('TM account'))

    user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('User'), related_name='viva_tm_wsc_deposits')

    amount = models.DecimalField(_('Amount'), decimal_places=8, max_digits=21, default=0, editable=False)
    amount_new = models.DecimalField(_('Amount'), decimal_places=8, max_digits=21, default=0, editable=False)

    start_date = models.DateTimeField(_('Start date'), null=True, blank=True)
    end_date = models.DateTimeField(_('End date'), null=True, blank=True)

    available_withdrawal_amount = models.DecimalField(_('Available withdrawal amount'), decimal_places=8, max_digits=21,
                                                      default=0, editable=False)
    available_withdrawal_amount_new = models.DecimalField(_('Available withdrawal amount'), decimal_places=8,
                                                          max_digits=21, default=0, editable=False)

    preferred_currency = models.CharField(_('Preferred currency for display'), max_length=8, choices=CURRENCY_CHOICES,
                                          default=RUB, help_text=_('Chosen currency by user'))

    extra = JSONField(_('Extra'), null=True, blank=True, default={})

    objects = DepositQS.as_manager()

    def __str__(self):
        return '{}: Thread №{} (TM_acc: {}, {})'.format(
            self.user, self.id, self.tm_account.number, self.tm_account.title
        )

    class Meta:
        unique_together = ('tm_account', 'user')
        verbose_name = _('Thread')
        verbose_name_plural = _('Threads')

# -*- coding: utf-8 -*-
from __future__ import unicode_literals, division

from datetime import datetime

from django.conf import settings
from django.contrib.postgres.fields import JSONField
from django.db import models
from django.db.models import QuerySet
from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from django_fsm import FSMField, transition
from sb_core.mixins import AdminUrlsMixin

from money.currencies import RUB
from viva_tm_wsc.settings import TRANSFER_CURRENCY_CHOICES
from viva_tm_wsc.date_utils import get_next_day_utc


class OperationQS(QuerySet):
    def blocked(self):
        now = timezone.now()
        return self.filter(block_date_start__lte=now, block_date_end__gte=now)

    def withdrawals(self):
        return self.filter(status__in=[Operation.WITHDRAW_IN_PROCESS, Operation.WITHDRAW_IS_TRANSFERRED_TO_USER],
                           date__gt=timezone.now())


@python_2_unicode_compatible
class Operation(AdminUrlsMixin, models.Model):
    NEW = 'new'

    # refill transitions
    REFILL_REQUIRES_CONFIRMATION = 'refill_requires_confirmation'
    REFILL_CONFIRMED = 'refill_confirmed'
    REFILL_NOT_CONFIRMED = 'refill_not_confirmed'
    REFILL_IS_TRANSFERRED_TO_TM = 'refill_is_transferred_to_tm'

    # withdraw transitions
    WITHDRAW_IN_PROCESS = 'withdraw_in_process'
    WITHDRAW_IS_TRANSFERRED_TO_USER = 'withdraw_is_transferred_to_user'
    WITHDRAW_REJECTED = 'withdraw_rejected'

    # transfer transactions
    TRANSFER_TO_DEPOSIT = 'transfer_to_deposit'
    TRANSFER_FROM_DEPOSIT = 'transfer_from_deposit'

    # balancing
    BALANCING = 'balancing'

    TRANSITION_TARGETS = {
        NEW: _('New'),

        REFILL_REQUIRES_CONFIRMATION: _('Refill requires confirmation'),
        REFILL_CONFIRMED: _('Refill confirmed'),
        REFILL_NOT_CONFIRMED: _('Refill not confirmed'),
        REFILL_IS_TRANSFERRED_TO_TM: _('Refill is transferred to TM'),

        WITHDRAW_IN_PROCESS: _('Withdrawal in process'),
        WITHDRAW_IS_TRANSFERRED_TO_USER: _('Withdrawal is transferred to investor'),
        WITHDRAW_REJECTED: _('Withdrawal rejected'),

        TRANSFER_TO_DEPOSIT: _('Transfer to deposit'),
        TRANSFER_FROM_DEPOSIT: _('Transfer from deposit'),

        BALANCING: _('Balancing'),
    }

    deposit = models.ForeignKey('viva_tm_wsc.Deposit', verbose_name=_('Deposit'))
    bind_deposit = models.ForeignKey('viva_tm_wsc.Deposit', verbose_name=_('Transfer from / to deposit'),
                                     related_name='transferred_operations', null=True, blank=True)
    bind_operation = models.OneToOneField('self', on_delete=models.CASCADE, verbose_name=_('Bind operation'),
                                          related_name='other_bind_operation', null=True, editable=False)

    status = FSMField(default=NEW)

    transferred_amount = models.DecimalField(
        _('Transferred amount'), decimal_places=8, max_digits=21, null=True, blank=True,
        help_text=_('If the field is not filled in,<br/>'
                    'the transfer amount with the corresponding currency will be entered.')
    )
    transferred_amount_currency = models.CharField(_('Currency'), max_length=8, choices=TRANSFER_CURRENCY_CHOICES,
                                                   default=RUB)

    bid_price = models.DecimalField(_('Bid price (at the time of transfer)'), decimal_places=8, max_digits=21,
                                    null=True, blank=True)
    ask_price = models.DecimalField(_('Ask price (at the time of transfer)'), decimal_places=8, max_digits=21,
                                    null=True, blank=True)

    amount = models.DecimalField(_('Amount'), decimal_places=8, max_digits=21, null=True, blank=True)

    creation_date = models.DateTimeField(_('Creation date'), auto_now_add=True)
    confirmation_date = models.DateTimeField(_('Confirmation date'), null=True, blank=True)
    fact_date = models.DateTimeField(_('Date of refill or withdrawal'), null=True, blank=True)
    date = models.DateTimeField(_('Date'), null=True, blank=True,
                                help_text='{}<br/>{}'.format(_('Date of completion'), _('it is used in du_calc')))


    comment = models.CharField(_('Comment'), max_length=255, blank=True,
                               help_text='Operation details at the moment of creation.')

    extra = JSONField(_('Extra'), null=True, blank=True, default={})

    objects = OperationQS.as_manager()

    # --- debug transitions ---
    @transition(field=status, source='*', target=NEW,
                conditions=[lambda self: settings.DEBUG],
                custom={'button_name': _('Set status to "New"')})
    def set_status_to_new(self):
        pass

    # --- refill transitions ---
    @transition(field=status, source=NEW, target=REFILL_REQUIRES_CONFIRMATION,
                conditions=(lambda self: not self.bind_deposit and (self.amount > 0 or self.transferred_amount > 0),),
                custom={'button_name': TRANSITION_TARGETS[REFILL_REQUIRES_CONFIRMATION]})
    def to_refill_requires_confirmation(self):
        pass

    @transition(field=status, source=REFILL_REQUIRES_CONFIRMATION, target=REFILL_CONFIRMED,
                custom={'button_name': TRANSITION_TARGETS[REFILL_CONFIRMED]})
    def refill_confirmed(self):
        pass

    @transition(field=status, source=REFILL_REQUIRES_CONFIRMATION, target=REFILL_NOT_CONFIRMED,
                custom={'button_name': TRANSITION_TARGETS[REFILL_NOT_CONFIRMED]})
    def refill_not_confirmed(self):
        pass

    def can_refill_is_transferred_to_tm(self):
        return self.transferred_amount

    @transition(field=status, source=REFILL_CONFIRMED, target=REFILL_IS_TRANSFERRED_TO_TM,
                conditions=(can_refill_is_transferred_to_tm, ),
                custom={'button_name': TRANSITION_TARGETS[REFILL_IS_TRANSFERRED_TO_TM]})
    def refill_is_transferred_to_tm(self):
        if not self.date:
            self.date = get_next_day_utc(datetime.now())

        self.fact_date = timezone.now()
        self.deposit.user.send_email(_('Refill is transferred to account of Viva TM'),
                                     render_to_string('viva_tm_wsc/email/refill_is_transferred_to_tm.html',
                                                      {'operation': self, 'email': self.deposit.user.email}))

    # --- withdraw transitions ---
    @transition(field=status, source=NEW, target=WITHDRAW_IN_PROCESS,
                conditions=(lambda self: not self.bind_deposit and ((self.amount or False) < 0 or
                                                                    (self.transferred_amount or False) < 0),),
                custom={'button_name': _('Set "Withdrawal in process"')})
    def to_withdraw_in_process(self):
        pass

    @transition(field=status, source=WITHDRAW_IN_PROCESS, target=WITHDRAW_IS_TRANSFERRED_TO_USER,
                custom={'button_name': TRANSITION_TARGETS[WITHDRAW_IS_TRANSFERRED_TO_USER]})
    def withdraw_is_transferred_to_user(self):
        self.fact_date = timezone.now()

        self.deposit.user.send_email(_('Your withdrawal is transferred'),
                                     render_to_string('viva_tm_wsc/email/withdraw_is_transferred_to_user.html',
                                                      {'operation': self, 'email': self.deposit.user.email}))

    @transition(field=status, source=WITHDRAW_IN_PROCESS, target=WITHDRAW_REJECTED,
                custom={'button_name': TRANSITION_TARGETS[WITHDRAW_REJECTED]})
    def withdraw_rejected(self):
        pass

    # --- transitions to other deposits ---
    def create_bind_operation(self):
        if not self.bind_operation:
            if not self.date:
                self.date = get_next_day_utc(datetime.now())

            bind_operation = Operation(deposit=self.bind_deposit, bind_deposit=self.deposit,
                                       bind_operation=self, amount=-self.amount, date=self.date)
            bind_operation.save()
            if bind_operation.amount < 0:
                bind_operation.transfer_to_deposit()
            else:
                bind_operation.transfer_from_deposit()

            bind_operation.save()
            self.bind_operation = bind_operation

    @transition(field=status, source=NEW, target=TRANSFER_TO_DEPOSIT,
                conditions=(lambda self: self.bind_deposit and self.amount < 0, ),
                custom={'button_name': _('Send transfer')})
    def transfer_to_deposit(self):
        self.create_bind_operation()

    @transition(field=status, source=NEW, target=TRANSFER_FROM_DEPOSIT,
                conditions=(lambda self: self.bind_deposit and self.amount > 0,),
                custom={'button_name': _('Get transfer')})
    def transfer_from_deposit(self):
        self.create_bind_operation()

    @transition(field=status, source=NEW, target=BALANCING,
                custom={'button_name': _('Balancing')})
    def balancing(self):
        pass

    # --- END transactions ---

    @classmethod
    def get_transition_name(cls, target):
        return cls.TRANSITION_TARGETS.get(target, target)

    def status_name(self):
        return self.TRANSITION_TARGETS.get(self.status, self.status)

    status_name.short_description = _('Status')


    def __str__(self):
        return str(self.amount)

    class Meta:
        ordering = ('-date', '-creation_date')
        verbose_name = _('Operation')
        verbose_name_plural = _('Operations')

Страницы раздела "Дизайнерские решения и программный код ДУ"

Содержание

Присоединяйтесь к нашим чатам

Наши чаты разделены на информационные и флуд-чаты. В информационных чатах мы обсуждаем проекты, связанные с Viva Invest, а во флуд-чатах мы общаемся не только по проектам, но и на разные темы: что-то весёлое, интересное, познавательное и т. д.

Чаты и группы Viva Invest


Сайт доверительного управления Viva TM: vivazzi.pro/viva-tm/

Если есть вопросы, то сначала посмотрите FAQ. Если не нашли ответ, то пишите в комментариях, используйте контакты или можете:

Удачного вам финансового процветания!

P. S. Обращаюсь к участникам доверительного управления Viva TM: добавляя свой комментарий на моём сайте, пожалуйста, не размещайте свои реферальные ссылки для привлечения партнёров. Для построения вашей партнёрской сети используйте рекомендации на странице Партнёрская программа. Рекомендации по развитию структуры.

P. S. S. Все статьи и предлагаемые способы заработка пройдены через мой личный опыт и размещено для вас в ознакомительных целях, поэтому ответственность за свои действия несёте только вы. Пожалуйста, помните об этом. Если вы начинающий инвестор, прочтите Правила инвестора.

Оцените статью

0 из 5 (всего 0 оценок)

Поля, отмеченные звёздочкой ( * ) , являются обязательными.

Спасибо за ваш отзыв!

После нажатия кнопки "Отправить" ваше сообщение будет доставлено мне на почту.

Автор статьи

Права на использование материала, расположенного на этой странице https://vivazzi.pro/viva-tm/code/model/:

Разрешается копировать материал с указанием её автора и ссылки на оригинал без использования параметра rel="nofollow" в теге <a>. Использование:

Автор статьи: Мальцев Артём
Ссылка на статью: <a href="https://vivazzi.pro/viva-tm/code/model/">https://vivazzi.pro/viva-tm/code/model/</a>

Подробнее: Правила использования сайта

Комментариев: 0

Вы можете оставить комментарий как незарегистрированный пользователь.

Но зарегистрировавшись, вы сможете:

  • получать оповещения об ответах
  • просматривать свои комментарии
  • иметь возможность использовать все функции разработанных сервисов

Для комментрирования от своего имени, войдите или зарегистрируйтесь обычным способом или через социальные сети:

Отправить

На данный момент нет специального поиска, поэтому я предлагаю воспользоваться обычной поисковой системой, например, Google, добавив "vivazzi" после своего запроса.

Попробуйте