Модель данных
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 TM: vivazzi.pro/viva-tm/
Если есть вопросы, то сначала посмотрите FAQ. Если не нашли ответ, то пишите в комментариях, используйте контакты или можете:
Удачного вам финансового процветания!
P. S. Обращаюсь к участникам доверительного управления Viva TM: добавляя свой комментарий на моём сайте, пожалуйста, не размещайте свои реферальные ссылки для привлечения партнёров. Для построения вашей партнёрской сети используйте рекомендации на странице Партнёрская программа. Рекомендации по развитию структуры.
P. S. S. Все статьи и предлагаемые способы заработка пройдены через мой личный опыт и размещено для вас в ознакомительных целях, поэтому ответственность за свои действия несёте только вы. Пожалуйста, помните об этом. Если вы начинающий инвестор, прочтите Правила инвестора.
Представляю вашему вниманию книгу, написанную моим близким другом Максимом Макуриным: Секреты эффективного управления ассортиментом.
Книга предназначается для широкого круга читателей и, по мнению автора, будет полезна специалистам отдела закупок и логистики, категорийным и финансовым менеджерам, менеджерам по продажам, аналитикам, руководителям и директорам, в компетенции которых принятие решений по управлению ассортиментом.
Комментарии: 0