Rewrote OHLC into daily balances.

This commit is contained in:
Alexis Lahouze 2017-05-25 22:04:12 +02:00
parent 5d498adf32
commit 69d5f57b90
2 changed files with 79 additions and 63 deletions

View File

@ -166,8 +166,9 @@ class Operation(db.Model):
return query return query
@classmethod @classmethod
def get_ohlc_per_day_for_range(cls, account, begin=None, end=None): def query_daily_balances(cls, account, begin=None, end=None):
"""Get Opening, High, Low, Closing per day for a specific range""" """Get expenses, revenues, income and balance per day for a specific
range and a specific account."""
if isinstance(account, (int, str)): if isinstance(account, (int, str)):
account_id = account account_id = account
else: else:
@ -175,53 +176,65 @@ class Operation(db.Model):
end = end if end else arrow.now().ceil('month').date() end = end if end else arrow.now().ceil('month').date()
sold = db.func.sum(cls.value).over(
order_by=[cls.operation_date, db.desc(cls.value), cls.label]
)
previous = sold - cls.value
subquery = db.session.query(
cls.operation_date,
sold.label("sold"),
previous.label("previous")
).filter(
cls.account_id == account_id,
cls.canceled == db.false()
).subquery()
query = db.session.query( query = db.session.query(
subquery.c.operation_date, cls.operation_date,
db.func.first_value(subquery.c.previous).over( db.func.coalesce(
partition_by=subquery.c.operation_date db.func.sum(
).label("open"), cls.value
db.func.max( ).filter(
db.func.greatest( db.func.sign(cls.value) == -1
subquery.c.previous, subquery.c.sold ).over(
partition_by=[cls.account_id, cls.operation_date],
),
0
).label("expenses"),
db.func.coalesce(
db.func.sum(
cls.value
).filter(
db.func.sign(cls.value) == 1
).over(
partition_by=[cls.account_id, cls.operation_date],
),
0
).label("revenues"),
db.func.coalesce(
db.func.sum(
cls.value
).over(
partition_by=[cls.account_id, cls.operation_date],
) )
).over( ).label("income"),
partition_by=subquery.c.operation_date db.func.coalesce(
).label('high'), db.func.sum(
db.func.min( cls.value
db.func.least( ).over(
subquery.c.previous, subquery.c.sold partition_by=cls.account_id,
order_by=cls.operation_date
) )
).over( ).label("balance")
partition_by=subquery.c.operation_date ).distinct(
).label('low'), ).order_by(
db.func.last_value(subquery.c.sold).over( cls.operation_date
partition_by=subquery.c.operation_date ).filter(
).label('close') cls.account_id == account_id
).distinct() )
if begin: if begin:
query = query.filter(subquery.c.operation_date >= str(begin)) base_query = query.subquery()
if end: query = db.session.query(
query = query.filter(subquery.c.operation_date <= str(end)) base_query
).filter(
base_query.c.operation_date >= str(begin)
).order_by(
base_query.c.operation_date
)
query = query.order_by( if end:
subquery.c.operation_date query = query.filter(query.c.operation_date <= str(end))
)
elif end:
query = query.filter(cls.operation_date <= str(end))
return query return query

View File

@ -7,7 +7,7 @@ import dateutil.parser
from flask_jwt_extended import jwt_required from flask_jwt_extended import jwt_required
from flask_restplus import Namespace, Resource, fields from flask_restplus import Namespace, Resource, fields
from ..models import db, row_as_dict from ..models import db, row_as_dict, result_as_dicts
from ..models.accounts import Account from ..models.accounts import Account
from ..models.operations import Operation from ..models.operations import Operation
@ -71,33 +71,33 @@ category_model = ns.model('Category', {
description='Total income for the category') description='Total income for the category')
}) })
# OHLC model. # Daily balance model.
ohlc_model = ns.model('OHLC', { daily_balance_model = ns.model('Daily balance', {
'operation_date': fields.DateTime( 'operation_date': fields.Date(
dt_format='iso8601', dt_format='iso8601',
readonly=True, readonly=True,
required=True, required=True,
description='Date of the OHLC object' description='Date'
), ),
'open': fields.Float( 'expenses': fields.Float(
readonly=True, readonly=True,
required=True, required=True,
description='Open value' description='Expenses'
), ),
'high': fields.Float( 'revenues': fields.Float(
readonly=True, readonly=True,
required=True, required=True,
description='High value' description='Revenues'
), ),
'low': fields.Float( 'income': fields.Float(
readonly=True, readonly=True,
required=True, required=True,
description='Low value' description='Income'
), ),
'close': fields.Float( 'balance': fields.Float(
readonly=True, readonly=True,
required=True, required=True,
description='Close value' description='Balance'
) )
}) })
@ -330,26 +330,29 @@ class CategoryResource(Resource):
return Operation.get_categories_for_range(account_id, **data).all() return Operation.get_categories_for_range(account_id, **data).all()
@ns.route('/<int:account_id>/ohlc') @ns.route('/<int:account_id>/daily_balances')
@ns.doc( @ns.doc(
security='apikey', security='apikey',
params={ params={
'account_id': 'Id of the account to manage' 'account_id': 'Id of the account to manage'
}, },
responses={ responses={
200: ('OK', [ohlc_model]), 200: ('OK', [daily_balance_model]),
401: 'Unauthorized', 401: 'Unauthorized',
404: 'Account not found' 404: 'Account not found'
}) })
class OHLCResource(Resource): class DailyBalancesResource(Resource):
"""Resource to expose OHLC.""" """Resource to expose account daily balances."""
@ns.expect(range_parser) @ns.expect(range_parser)
@ns.marshal_list_with(ohlc_model) @ns.marshal_list_with(daily_balance_model)
@jwt_required @jwt_required
def get(self, account_id): def get(self, account_id):
"""Get OHLC data for a specific date range and account.""" """Get account daily balance data for a specific date range and
account."""
data = range_parser.parse_args() data = range_parser.parse_args()
return Operation.get_ohlc_per_day_for_range(account_id, **data).all() return list(result_as_dicts(
Operation.query_daily_balances(account_id, **data)
)), 200