Compare commits

...

23 commits

Author SHA1 Message Date
Frederik Jaeckel
aaa143f373 Version 6.0.7 2022-11-28 23:20:07 +01:00
Frederik Jaeckel
99e18094aa diagram: interpolator ergänzt,
asset: berechnung 'nextupdate' korrigiert
2022-11-28 23:15:28 +01:00
Frederik Jaeckel
d1421403b1 asset: sortierung - neueste nach oben, farben - älter als 5 tage = gedimmt 2022-11-28 15:44:07 +01:00
Frederik Jaeckel
475457d202 Etikett ver 6.0.6 zum Änderungssatz 2ffa138f55f2 hinzugefügt 2022-11-26 22:51:13 +01:00
Frederik Jaeckel
c1cc1acb5f Version 6.0.7 2022-11-26 22:51:04 +01:00
Frederik Jaeckel
7bb3e4f929 asset:tabellenzugriff optimiert,
diagram: darstellung in diagramm ergänzt
2022-11-26 22:42:02 +01:00
Frederik Jaeckel
047b6980e4 Etikett ver 6.0.5 zum Änderungssatz 6e85642a2e97 hinzugefügt 2022-11-25 22:50:14 +01:00
Frederik Jaeckel
9d88cf1067 Version 6.0.5 2022-11-25 22:50:03 +01:00
Frederik Jaeckel
620c42fc62 asset: suche in name, sortiert nach name, sortierer für wkn, isin, symbl 2022-11-25 22:46:32 +01:00
Frederik Jaeckel
6311dce3d1 asset: spalten tag/monat/3monate... ok + test 2022-11-25 21:55:43 +01:00
Frederik Jaeckel
64b8383096 abfrage der prozentwerte für tag/1 monat, 3 monate, 6 monate, 12 monate
test muß noch
2022-11-25 15:28:49 +01:00
Frederik Jaeckel
a99d11a4a0 Etikett ver 6.0.4 zum Änderungssatz 8e3f0b006892 hinzugefügt 2022-11-25 11:02:13 +01:00
Frederik Jaeckel
4ae5601c04 Version 6.0.4 2022-11-25 11:01:55 +01:00
Frederik Jaeckel
31c76dfb48 asset: abfrage updatezeitpunkt optimiert, anzeige der vortagsprozente, farbe für zeilen 2022-11-25 11:00:03 +01:00
Frederik Jaeckel
57cb06d60e prozentwerte begonnen 2022-11-24 23:17:44 +01:00
Frederik Jaeckel
5587bfea3a Etikett ver 6.0.3 zum Änderungssatz 0a2f82baca7e hinzugefügt 2022-11-23 22:23:49 +01:00
Frederik Jaeckel
f919d9e290 Version 6.0.3 2022-11-23 22:23:42 +01:00
Frederik Jaeckel
e8614b1242 bug in updtneeded-suche, online-quellen erweitert 2022-11-23 22:22:22 +01:00
Frederik Jaeckel
a71bc0a79a Etikett ver 6.0.2 zum Änderungssatz 76feab691fd9 hinzugefügt 2022-11-23 10:45:38 +01:00
Frederik Jaeckel
f1d9b3b1dd Version 6.0.2 2022-11-23 10:45:28 +01:00
Frederik Jaeckel
4947b495c2 asset: feld 'Datum' + 'Name' neu, rec_name optimiert, Test korrigiert 2022-11-23 10:44:09 +01:00
Frederik Jaeckel
aeb949cc20 Etikett ver 6.0.1 zum Änderungssatz e16a79cd456e hinzugefügt 2022-11-22 22:47:03 +01:00
Frederik Jaeckel
916d73ef12 Version 6.0.1 2022-11-22 22:45:15 +01:00
16 changed files with 1549 additions and 153 deletions

View file

@ -14,6 +14,39 @@ Requires
Changes Changes
======= =======
*6.0.7 - 28.11.2022*
- fix: add diagram-interpolate
- fix: corrected nextupdate
*6.0.6 - 26.11.2022*
- add: diagram-display
*6.0.5 - 25.11.2022*
- add: sorter for name/nsin/isin/symbol
- add: columns percentage by day/month/3month/6month/12month
- fix: online-updater
*6.0.4 - 25.11.2022*
- add: assets - colors for percentual success/loss
- updt: optimize timestamp for next online-update
*6.0.3 - 23.11.2022*
- fix: bug in searcher
- add: online-sources
*6.0.2 - 23.11.2022*
- asset: add field 'date', optimized 'rec_name'
*6.0.1 - 22.11.2022*
- works
*6.0.0 - 09.11.2022* *6.0.0 - 09.11.2022*
- init - init

View file

@ -10,6 +10,7 @@ from .identifier import Identifier
from .cron import Cron from .cron import Cron
from .onlinesource import OnlineSource from .onlinesource import OnlineSource
from .update_wiz import UpdateSoureWizard from .update_wiz import UpdateSoureWizard
from .diagram import GraphDef, ChartPoint
def register(): def register():
@ -20,6 +21,10 @@ def register():
Identifier, Identifier,
Cron, Cron,
module='investment', type_='model') module='investment', type_='model')
Pool.register(
GraphDef,
ChartPoint,
module='investment', type_='model', depends=['diagram'])
Pool.register( Pool.register(
UpdateSoureWizard, UpdateSoureWizard,
module='investment', type_='wizard') module='investment', type_='wizard')

581
asset.py
View file

@ -6,17 +6,29 @@
from trytond.model import ModelView, ModelSQL, fields from trytond.model import ModelView, ModelSQL, fields
from trytond.transaction import Transaction from trytond.transaction import Transaction
from trytond.pool import Pool from trytond.pool import Pool
from trytond.pyson import Eval, Bool, And from trytond.pyson import Eval, Bool, And, If, Date
from trytond.report import Report
from decimal import Decimal from decimal import Decimal
from datetime import time from datetime import time
from sql.functions import CurrentTime from sql.functions import CurrentDate, CurrentTimestamp, Round, Extract
from sql.conditionals import Case from sql.conditionals import Case, Coalesce
from sql import Literal
digits_percent = 2
sel_updtdays = [
('work', 'Mon - Fri'),
('week', 'Mon - Sun'),
]
class Asset(ModelSQL, ModelView): class Asset(ModelSQL, ModelView):
'Asset' 'Asset'
__name__ = 'investment.asset' __name__ = 'investment.asset'
name = fields.Function(fields.Char(string='Name', readonly=True),
'on_change_with_name', searcher='search_rec_name')
company = fields.Many2One(string='Company', model_name='company.company', company = fields.Many2One(string='Company', model_name='company.company',
required=True, ondelete="RESTRICT") required=True, ondelete="RESTRICT")
product = fields.Many2One(string='Product', required=True, product = fields.Many2One(string='Product', required=True,
@ -38,7 +50,11 @@ class Asset(ModelSQL, ModelView):
model_name='investment.rate') model_name='investment.rate')
rate = fields.Function(fields.Numeric(string='Current Rate', rate = fields.Function(fields.Numeric(string='Current Rate',
readonly=True, digits=(16, Eval('currency_digits', 4)), readonly=True, digits=(16, Eval('currency_digits', 4)),
depends=['currency_digits']), 'on_change_with_rate') depends=['currency_digits']),
'get_rate_data', searcher='search_rate')
date = fields.Function(fields.Date(string='Date', readonly=True,
help='Date of current rate'),
'get_rate_data', searcher='search_date')
company_currency = fields.Function(fields.Many2One(readonly=True, company_currency = fields.Function(fields.Many2One(readonly=True,
string='Company Currency', states={'invisible': True}, string='Company Currency', states={'invisible': True},
@ -50,8 +66,7 @@ class Asset(ModelSQL, ModelView):
currency = fields.Many2One(string='Currency', select=True, currency = fields.Many2One(string='Currency', select=True,
required=True, model_name='currency.currency', ondelete='RESTRICT') required=True, model_name='currency.currency', ondelete='RESTRICT')
currency_digits = fields.Integer(string='Currency Digits', currency_digits = fields.Integer(string='Digits', required=True)
required=True)
wkn = fields.Function(fields.Char(string='NSIN', readonly=True, wkn = fields.Function(fields.Char(string='NSIN', readonly=True,
help='National Securities Identifying Number'), help='National Securities Identifying Number'),
@ -66,13 +81,54 @@ class Asset(ModelSQL, ModelView):
updtsource = fields.Many2One(string='Update Source', updtsource = fields.Many2One(string='Update Source',
help='Select a source for the course update.', help='Select a source for the course update.',
ondelete='SET NULL', model_name='investment.source') ondelete='SET NULL', model_name='investment.source')
updtdays = fields.Selection(string='Select days', required=True,
selection=sel_updtdays)
updttime = fields.Time(string='Time', updttime = fields.Time(string='Time',
states={ states={
'readonly': ~Bool(Eval('updtsource')), 'readonly': ~Bool(Eval('updtsource')),
}, depends=['updtsource']) }, depends=['updtsource'])
updtneeded = fields.Function(fields.Boolean(string='Course update needed', nextupdate = fields.Function(fields.DateTime(string='Next Update',
readonly=True), readonly=True),
'on_change_with_updtneeded', searcher='search_updtneeded') 'get_nextupdates', searcher='search_nextupdate')
# percentage change
change_day1 = fields.Function(fields.Numeric(string='Previous Day',
help='percentage change in value compared to the previous day',
readonly=True, digits=(16,digits_percent)),
'get_percentage_change', searcher='search_percentage')
change_month1 = fields.Function(fields.Numeric(string='1 Month',
help='percentage change in value compared to last month',
readonly=True, digits=(16,digits_percent)),
'get_percentage_change', searcher='search_percentage')
change_month3 = fields.Function(fields.Numeric(string='3 Months',
help='percentage change in value during 3 months',
readonly=True, digits=(16,digits_percent)),
'get_percentage_change', searcher='search_percentage')
change_month6 = fields.Function(fields.Numeric(string='6 Months',
help='percentage change in value during 6 months',
readonly=True, digits=(16,digits_percent)),
'get_percentage_change', searcher='search_percentage')
change_month12 = fields.Function(fields.Numeric(string='1 Year',
help='percentage change in value during 1 year',
readonly=True, digits=(16,digits_percent)),
'get_percentage_change', searcher='search_percentage')
@classmethod
def __setup__(cls):
super(Asset, cls).__setup__()
cls._order.insert(0, ('name', 'ASC'))
cls._order.insert(0, ('date', 'DESC'))
@classmethod
def view_attributes(cls):
return super().view_attributes() + [
('/tree', 'visual',
If(Eval('date', Date()) < Date(delta_days=-5), 'muted',
If(Eval('change_day1', 0) < 0, 'warning',
If(Eval('change_day1', 0) > 0, 'success', '')
))
),
]
@classmethod @classmethod
def default_currency(cls): def default_currency(cls):
@ -96,6 +152,18 @@ class Asset(ModelSQL, ModelView):
""" """
return 4 return 4
@classmethod
def default_updttime(cls):
""" 14 o'clock UTC
"""
return time(14, 0)
@classmethod
def default_updtdays(cls):
""" default: mon - fri
"""
return 'work'
@fields.depends('updtsource', 'updttime') @fields.depends('updtsource', 'updttime')
def on_change_updtsource(self): def on_change_updtsource(self):
""" clear time-fields """ clear time-fields
@ -105,23 +173,6 @@ class Asset(ModelSQL, ModelView):
else : else :
self.updttime = time(11, 30) self.updttime = time(11, 30)
@fields.depends('id', 'currency_digits')
def on_change_with_rate(self, name=None):
""" get current rate
"""
pool = Pool()
Rate = pool.get('investment.rate')
IrDate = pool.get('ir.date')
if self.id:
rates = Rate.search([
('date', '<=', IrDate.today()),
('asset.id', '=', self.id),
], order=[('date', 'DESC')], limit=1)
if len(rates) > 0:
exp = Decimal(Decimal(1) / 10 ** (self.currency_digits or 4))
return rates[0].rate.quantize(exp)
@fields.depends('product', 'uom') @fields.depends('product', 'uom')
def on_change_product(self): def on_change_product(self):
""" update unit by product """ update unit by product
@ -138,6 +189,13 @@ class Asset(ModelSQL, ModelView):
if self.currency: if self.currency:
self.currency_digits = self.currency.digits self.currency_digits = self.currency.digits
@fields.depends('product')
def on_change_with_name(self, name=None):
""" get name of product
"""
if self.product:
return self.product.name
@fields.depends('product') @fields.depends('product')
def on_change_with_product_uom(self, name=None): def on_change_with_product_uom(self, name=None):
""" get category of product-uom """ get category of product-uom
@ -164,51 +222,369 @@ class Asset(ModelSQL, ModelView):
if self.company.currency.id != self.currency.id: if self.company.currency.id != self.currency.id:
return self.company.currency.id return self.company.currency.id
@fields.depends('id')
def on_change_with_updtneeded(self, name=None):
""" get state of update
"""
Asset2 = Pool().get('investment.asset')
if self.id:
if Asset2.search_count([
('updtneeded', '=', True),
('id', '=', self.id)
]) == 1:
return True
return False
@classmethod @classmethod
def search_updtneeded(cls, names, clause): def get_rate_data_sql(cls):
""" search for assets to update """ get sql for rate/date
""" """
pool = Pool() pool = Pool()
Asset2 = pool.get('investment.asset') Asset = pool.get('investment.asset')
Rate = pool.get('investment.rate') Rate = pool.get('investment.rate')
IrDate = pool.get('ir.date') tab_asset = Asset.__table__()
tab_asset = Asset2.__table__()
tab_rate = Rate.__table__() tab_rate = Rate.__table__()
Operator = fields.SQL_OPERATORS[clause[1]]
context = Transaction().context
query_date = context.get('qdate', IrDate.today())
query_time = context.get('qtime', CurrentTime())
query = tab_asset.join(tab_rate, query = tab_asset.join(tab_rate,
condition=(tab_asset.id==tab_rate.asset) & \ condition=tab_asset.id==tab_rate.asset
(tab_rate.date == query_date), ).select(
type_ = 'LEFT OUTER', tab_asset.id,
).select(tab_asset.id, Round(tab_rate.rate, tab_asset.currency_digits).as_('rate'),
where=Operator( tab_rate.date,
Case( distinct_on=[tab_asset.id],
((tab_rate.id == None) & \ order_by=[tab_asset.id, tab_rate.date.desc],
(tab_asset.updtsource != None) & \
(tab_asset.updttime <= query_time), True),
default_ = False,
),
clause[2]),
) )
return [('id', '=', query)] return (query, tab_asset)
@classmethod
def get_rate_data(cls, assets, names):
""" get date and rate of asset
"""
cursor = Transaction().connection.cursor()
(query, tab_asset) = cls.get_rate_data_sql()
query.where=tab_asset.id.in_([x.id for x in assets])
cursor.execute(*query)
records = cursor.fetchall()
result = {x:{y.id: None for y in assets} for x in names}
for record in records:
(id1, rate1, date1) = record
asset = Asset(id1)
exp = Decimal(Decimal(1) / 10 ** (asset.currency_digits or 4))
values = {'rate': record[1].quantize(exp), 'date': record[2]}
for name in names:
result[name][record[0]] = values[name]
return result
@classmethod
def search_date(cls, names, clause):
""" search in date
"""
(tab_query, tab_asset) = cls.get_rate_data_sql()
Operator = fields.SQL_OPERATORS[clause[1]]
query = tab_query.select(
tab_query.id,
where=Operator(tab_query.date, clause[2]),
)
return [('id', 'in', query)]
@classmethod
def search_rate(cls, names, clause):
""" search in rate
"""
(tab_query, tab_asset) = cls.get_rate_data_sql()
Operator = fields.SQL_OPERATORS[clause[1]]
query = tab_query.select(
tab_query.id,
where=Operator(tab_query.rate, clause[2]),
)
return [('id', 'in', query)]
@staticmethod
def order_date(tables):
""" order date
"""
(tab_query, tab_asset) = Asset.get_rate_data_sql()
table, _ = tables[None]
query = tab_query.select(
tab_query.date,
where=tab_query.id==table.id,
)
return [query]
@staticmethod
def order_rate(tables):
""" order rate
"""
(tab_query, tab_asset) = Asset.get_rate_data_sql()
table, _ = tables[None]
query = tab_query.select(
tab_query.rate,
where=tab_query.id==table.id,
)
return [query]
@classmethod
def get_percentage_sql(cls, name_lst, select_date=True):
""" get table for percentages and dates,
generate adapted query
select_date: True = select newest date
"""
pool = Pool()
Rate = pool.get('investment.rate')
Asset = pool.get('investment.asset')
tab_asset = Asset.__table__()
tab_rate_today = Rate.__table__()
rate_tab = {
'day1': {'tab': Rate.__table__(), 'day': 0},
'month1': {'tab': Rate.__table__(), 'day': 30},
'month3': {'tab': Rate.__table__(), 'day': 3*30},
'month6': {'tab': Rate.__table__(), 'day': 6*30},
'month12': {'tab': Rate.__table__(), 'day': 12*30},
}
context = Transaction().context
# create tables to query percentual changes for
# 1 day, 30 days, 3 months, 6 months, 1 year
query_date = context.get('qdate', CurrentDate())
query_today = tab_asset.join(tab_rate_today,
condition=tab_asset.id==tab_rate_today.asset,
).select(
tab_asset.id,
tab_rate_today.id.as_('id_rate'),
tab_rate_today.date,
tab_rate_today.rate,
)
# limit to newest date until 'query_date'
if select_date == True:
query_today.distinct_on=[tab_asset.id]
query_today.order_by=[tab_asset.id, tab_rate_today.date.desc]
query_today.where=tab_rate_today.date <= query_date
# create join for requested fields, to minimize database-load
query = query_today
for name in name_lst:
query = query.join(rate_tab[name]['tab'],
# select newest date from <period> until 5 days older
condition=(query_today.id==rate_tab[name]['tab'].asset) & \
(query_today.date > (rate_tab[name]['tab'].date + Literal(rate_tab[name]['day']))) & \
(query_today.date <= (rate_tab[name]['tab'].date + Literal(rate_tab[name]['day'] + 5))),
type_ = 'LEFT OUTER')
# add select for requested fields to join
select_lst = [
query_today.id,
query_today.id_rate,
query_today.date,
]
for name in name_lst:
select_lst.append(
Case(
((rate_tab[name]['tab'].rate != None) & (query_today.rate != None) & \
(rate_tab[name]['tab'].rate != Literal(0.0)),
query_today.rate * Literal(100.0) / rate_tab[name]['tab'].rate - Literal(100.0)),
else_ = None,
).as_(name))
order_by_lst = [query_today.id, query_today.id_rate]
order_by_lst.extend([
rate_tab[name]['tab'].date.desc for name in name_lst
])
query = query.select(
*select_lst,
distinct_on=[query_today.id, query_today.id_rate],
order_by=order_by_lst,
)
return query
@staticmethod
def order_change_day1(tables):
""" order day1
"""
Assert = Pool().get('investment.asset')
tab_asset = Asset.get_percentage_sql(['day1'])
table, _ = tables[None]
query = tab_asset.select(
tab_asset.day1,
where=tab_asset.id==table.id,
)
return [query]
@staticmethod
def order_change_month1(tables):
""" order month1
"""
Assert = Pool().get('investment.asset')
tab_asset = Asset.get_percentage_sql(['month1'])
table, _ = tables[None]
query = tab_asset.select(
tab_asset.month1,
where=tab_asset.id==table.id,
)
return [query]
@staticmethod
def order_change_month3(tables):
""" order month1
"""
Assert = Pool().get('investment.asset')
tab_asset = Asset.get_percentage_sql(['month3'])
table, _ = tables[None]
query = tab_asset.select(
tab_asset.month3,
where=tab_asset.id==table.id,
)
return [query]
@staticmethod
def order_change_month6(tables):
""" order month1
"""
Assert = Pool().get('investment.asset')
tab_asset = Asset.get_percentage_sql(['month6'])
table, _ = tables[None]
query = tab_asset.select(
tab_asset.month6,
where=tab_asset.id==table.id,
)
return [query]
@staticmethod
def order_change_month12(tables):
""" order month1
"""
Assert = Pool().get('investment.asset')
tab_asset = Asset.get_percentage_sql(['month12'])
table, _ = tables[None]
query = tab_asset.select(
tab_asset.month12,
where=tab_asset.id==table.id,
)
return [query]
@classmethod
def search_percentage(cls, names, clause):
""" search for percentages
"""
Operator = fields.SQL_OPERATORS[clause[1]]
field_name = clause[0][len('change_'):]
tab_percent = cls.get_percentage_sql([field_name])
query = tab_percent.select(tab_percent.id,
where=Operator(Round(getattr(tab_percent, field_name), 2),
clause[2]))
return [('id', 'in', query)]
@classmethod
def get_percentage_change(cls, assets, names):
""" get percentage per period
"""
cursor = Transaction().connection.cursor()
name_lst = [x[len('change_'):] for x in names]
tab_percent = cls.get_percentage_sql(name_lst)
query = tab_percent.select(
tab_percent.id,
*[getattr(tab_percent, x) for x in name_lst],
where=tab_percent.id.in_([x.id for x in assets]),
)
cursor.execute(*query)
records = cursor.fetchall()
result = {x:{y.id: None for y in assets} for x in names}
exp = Decimal(Decimal(1) / 10 ** digits_percent)
for record in records:
cnt1 = 1
for x in name_lst:
result['change_%s' % x][record[0]] = record[cnt1].quantize(exp) \
if record[cnt1] is not None else None
cnt1 += 1
return result
@classmethod
def get_next_update_datetime_sql(cls):
""" get sql for datetime of next planned update
"""
pool = Pool()
Asset = pool.get('investment.asset')
Rate = pool.get('investment.rate')
tab_asset = Asset.__table__()
tab_rate = Rate.__table__()
context = Transaction().context
query_date = context.get('qdate', CurrentDate() - Literal(1))
# get last date of rate
tab_date = tab_asset.join(tab_rate,
condition=tab_asset.id == tab_rate.asset,
type_ = 'LEFT OUTER',
).select(
tab_asset.id,
(Coalesce(tab_rate.date, query_date) + Literal(1)).as_('date'),
tab_asset.updtdays,
tab_asset.updttime,
distinct_on = [tab_asset.id],
order_by = [tab_asset.id, tab_rate.date.desc],
where=tab_asset.updtsource != None,
)
query = tab_date.select(
tab_date.id,
(Case(
((tab_date.updtdays == 'work') & \
(Extract('dow', tab_date.date) == 0), tab_date.date + Literal(1)),
((tab_date.updtdays == 'work') & \
(Extract('dow', tab_date.date) == 6), tab_date.date + Literal(2)),
else_ = tab_date.date,
) + tab_date.updttime).as_('updttime'),
)
return query
@classmethod
def get_nextupdates(cls, assets, names):
""" get timestamp of next update
"""
Asset2 = Pool().get('investment.asset')
tab_updt = Asset2.get_next_update_datetime_sql()
cursor = Transaction().connection.cursor()
query = tab_updt.select(
tab_updt.id,
tab_updt.updttime,
where=tab_updt.id.in_([x.id for x in assets]),
)
cursor.execute(*query)
records = cursor.fetchall()
result = {x:{y.id: None for y in assets} for x in names}
for record in records:
(id1, updt) = record
r1 = {'nextupdate': updt}
for n in names:
result[n][id1] = r1[n]
return result
@classmethod
def search_nextupdate(cls, names, clause):
""" search for assets to update
"""
Asset2 = Pool().get('investment.asset')
tab_updt = Asset2.get_next_update_datetime_sql()
Operator = fields.SQL_OPERATORS[clause[1]]
query = tab_updt.select(
tab_updt.id,
where=Operator(tab_updt.updttime, clause[2]),
)
return [('id', 'in', query)]
@classmethod @classmethod
def get_identifier_sql(cls, tab_asset): def get_identifier_sql(cls, tab_asset):
@ -244,6 +620,70 @@ class Asset(ModelSQL, ModelView):
) )
return query return query
@staticmethod
def order_name(tables):
""" order name
"""
pool = Pool()
Templ = pool.get('product.template')
Product = pool.get('product.product')
Asset = pool.get('investment.asset')
table, _ = tables[None]
tab_asset = Asset.__table__()
tab_prod = Product.__table__()
tab_templ = Templ.__table__()
query = tab_asset.join(tab_prod,
condition=tab_asset.product==tab_prod.id
).join(tab_templ,
condition=tab_templ.id==tab_prod.template
).select(tab_templ.name,
where=tab_asset.id==table.id
)
return [query]
@staticmethod
def order_wkn(tables):
""" order wkn
"""
Asset = Pool().get('investment.asset')
tab_ids = Asset.get_identifier_sql(Asset.__table__())
table, _ = tables[None]
query = tab_ids.select(
getattr(tab_ids, 'wkn'),
where=tab_ids.id==table.id,
)
return [query]
@staticmethod
def order_isin(tables):
""" order isin
"""
Asset = Pool().get('investment.asset')
tab_ids = Asset.get_identifier_sql(Asset.__table__())
table, _ = tables[None]
query = tab_ids.select(
getattr(tab_ids, 'isin'),
where=tab_ids.id==table.id,
)
return [query]
@staticmethod
def order_secsymb(tables):
""" order secsymb
"""
Asset = Pool().get('investment.asset')
tab_ids = Asset.get_identifier_sql(Asset.__table__())
table, _ = tables[None]
query = tab_ids.select(
getattr(tab_ids, 'secsymb'),
where=tab_ids.id==table.id,
)
return [query]
@classmethod @classmethod
def search_identifier(cls, names, clause): def search_identifier(cls, names, clause):
""" search in identifier """ search in identifier
@ -294,17 +734,24 @@ class Asset(ModelSQL, ModelView):
def get_rec_name(self, name): def get_rec_name(self, name):
""" record name """ record name
""" """
return '%(prod)s [%(curr)s/%(unit)s]' % { return '%(prod)s - %(rate)s %(curr)s/%(unit)s [%(date)s]' % {
'prod': getattr(self.product, 'rec_name', '-'), 'prod': getattr(self.product, 'rec_name', '-'),
'curr': getattr(self.currency, 'rec_name', '-'), 'curr': getattr(self.currency, 'symbol', '-'),
'unit': getattr(self.uom, 'rec_name', '-'), 'unit': getattr(self.uom, 'rec_name', '-'),
'rate': Report.format_number(self.rate, lang=None,
digits=self.currency_digits or 4) \
if self.rate is not None else '-',
'date': Report.format_date(self.date) if self.date is not None else '-',
} }
@classmethod @classmethod
def search_rec_name(cls, name, clause): def search_rec_name(cls, name, clause):
""" search in rec_name """ search in rec_name
""" """
return [('product.rec_name',) + tuple(clause[1:])] return ['OR',
('product.rec_name',) + tuple(clause[1:]),
('product.identifiers.code',) + tuple(clause[1:]),
]
@classmethod @classmethod
def cron_update(cls): def cron_update(cls):
@ -313,9 +760,11 @@ class Asset(ModelSQL, ModelView):
pool = Pool() pool = Pool()
Asset2 = pool.get('investment.asset') Asset2 = pool.get('investment.asset')
OnlineSource = pool.get('investment.source') OnlineSource = pool.get('investment.source')
context = Transaction().context
query_time = context.get('qdatetime', CurrentTimestamp())
for asset in Asset2.search([ for asset in Asset2.search([
('updtneeded', '=', True), ('nextupdate', '<=', query_time),
]): ]):
OnlineSource.update_rate(asset) OnlineSource.update_rate(asset)

149
diagram.py Normal file
View file

@ -0,0 +1,149 @@
# -*- coding: utf-8 -*-
# This file is part of the investment-module from m-ds for Tryton.
# The COPYRIGHT file at the top level of this repository contains the
# full copyright notices and license terms.
from trytond.model import ModelView, ModelSQL, fields
from trytond.transaction import Transaction
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
from sql.functions import Function
from datetime import timedelta
from decimal import Decimal
class Concat2(Function):
""" concat columns
"""
__slots__ = ()
_function = 'concat'
# end Concat2
class GraphDef(metaclass=PoolMeta):
__name__ = 'diagram.graphdef'
asset = fields.Many2One(string='Asset',
model_name='investment.asset',
states={
'invisible': Eval('dtype', '') != 'investment.asset',
'required': Eval('dtype', '') == 'investment.asset',
}, depends=['dtype'])
@classmethod
def _get_dtypes(cls):
""" return list of types
"""
l1 = super(GraphDef, cls)._get_dtypes()
l1.append('investment.asset')
return l1
def get_recname_value(self):
""" value by dtype
"""
if self.dtype == 'investment.asset':
return getattr(self.asset, 'rec_name', '-')
return super(GraphDef, self).get_recname_value()
def get_field_key(self):
""" get to read value from json
"""
if self.dtype == 'investment.asset':
return 'asset%d' % self.asset.id
return super(GraphDef, self).get_field_key()
def get_scaling_for_investment_asset(self):
""" get scaling for currency
"""
Rate = Pool().get('investment.rate')
if self.scaling == 'fix':
return None
if self.scaling == 'alldata':
query = [('asset.id', '=', self.asset.id)]
elif self.scaling == 'view':
query = [
('asset.id', '=', self.asset.id),
('date', '>=', self.chart.used_start_date()),
('date', '<=', self.chart.used_end_date()),
]
elif self.scaling == 'six':
query = [
('asset.id', '=', self.asset.id),
('date', '>=', self.chart.used_start_date() - timedelta(days=180)),
('date', '<=', self.chart.used_end_date()),
]
min_rec = Rate.search(query, limit=1, order=[('rate', 'ASC')])
max_rec = Rate.search(query, limit=1, order=[('rate', 'DESC')])
min_val = min_rec[0].rate if len(min_rec) > 0 else None
max_val = max_rec[0].rate if len(max_rec) > 0 else None
return self.compute_scaling_factor(min_val, max_val)
# end GraphDef
class ChartPoint(metaclass=PoolMeta):
__name__ = 'diagram.point'
@classmethod
def get_interpolated_val(cls, keyname, query_date):
""" query two neighbour-values to
interpolate missing value
"""
Rate = Pool().get('investment.rate')
if keyname is None:
return None
# check if query is for us
if keyname.startswith('asset'):
asset_id = int(keyname[len('asset'):])
before = Rate.search([
('date', '<', query_date),
('asset.id', '=', asset_id),
], limit=1, order=[('date', 'DESC')])
after = Rate.search([
('date', '>', query_date),
('asset.id', '=', asset_id),
], limit=1, order=[('date', 'ASC')])
if (len(before) == 1) and (len(after) == 1):
result = cls.interpolate_linear(
(after[0].date, after[0].rate),
(before[0].date, before[0].rate),
query_date
)
return result
elif len(before) == 1:
return before[0].rate
elif len(after) == 1:
return after[0].rate
return super(ChartPoint, cls).get_interpolated_val(keyname, query_date)
@classmethod
def get_table_parts(cls):
""" return a list of tables to union,
table must contain the columns:
date, key, val
"""
pool = Pool()
Rate = pool.get('investment.rate')
tab_rate = Rate.__table__()
tabparts = super(ChartPoint, cls).get_table_parts()
# rate
tabparts.append(tab_rate.select(
tab_rate.date,
Concat2('asset', tab_rate.asset).as_('key'),
tab_rate.rate.as_('val'),
))
return tabparts
# end ChartPoint

16
diagram.xml Normal file
View file

@ -0,0 +1,16 @@
<?xml version="1.0"?>
<!-- This file is part of the investment-module from m-ds for Tryton.
The COPYRIGHT file at the top level of this repository contains the
full copyright notices and license terms. -->
<tryton>
<data depends="diagram">
<!-- views -->
<record model="ir.ui.view" id="graph_view_form">
<field name="model">diagram.graphdef</field>
<field name="inherit" ref="diagram.graph_view_form"/>
<field name="name">graph_form</field>
</record>
</data>
</tryton>

View file

@ -86,6 +86,10 @@ msgctxt "view:investment.asset:"
msgid "Currency and Units" msgid "Currency and Units"
msgstr "Währung und Einheiten" msgstr "Währung und Einheiten"
msgctxt "view:investment.asset:"
msgid "Gain and Loss"
msgstr "Gewinn und Verlust"
msgctxt "view:investment.asset:" msgctxt "view:investment.asset:"
msgid "Identifiers" msgid "Identifiers"
msgstr "Bezeichner" msgstr "Bezeichner"
@ -115,8 +119,8 @@ msgid "Currency"
msgstr "Währung" msgstr "Währung"
msgctxt "field:investment.asset,currency_digits:" msgctxt "field:investment.asset,currency_digits:"
msgid "Currency Digits" msgid "Digits"
msgstr "Nachkommastellen Währung" msgstr "Nachkommastellen"
msgctxt "field:investment.asset,product:" msgctxt "field:investment.asset,product:"
msgid "Product" msgid "Product"
@ -162,10 +166,22 @@ msgctxt "field:investment.asset,rates:"
msgid "Rates" msgid "Rates"
msgstr "Kurse" msgstr "Kurse"
msgctxt "field:investment.asset,name:"
msgid "Name"
msgstr "Name"
msgctxt "field:investment.asset,rate:" msgctxt "field:investment.asset,rate:"
msgid "Current Rate" msgid "Current Rate"
msgstr "aktueller Kurs" msgstr "aktueller Kurs"
msgctxt "field:investment.asset,date:"
msgid "Date"
msgstr "Datum"
msgctxt "help:investment.asset,date:"
msgid "Date of current rate"
msgstr "Datum des aktuellen Kurses"
msgctxt "field:investment.asset,updtsource:" msgctxt "field:investment.asset,updtsource:"
msgid "Update Source" msgid "Update Source"
msgstr "Kursquelle" msgstr "Kursquelle"
@ -178,9 +194,61 @@ msgctxt "field:investment.asset,updttime:"
msgid "Time" msgid "Time"
msgstr "Zeitpunkt" msgstr "Zeitpunkt"
msgctxt "field:investment.asset,updtneeded:" msgctxt "field:investment.asset,nextupdate:"
msgid "Course update needed" msgid "Next Update"
msgstr "Kursaktualisierung nötig" msgstr "nächste Aktualisierung"
msgctxt "field:investment.asset,change_day1:"
msgid "Previous Day"
msgstr "Vortag"
msgctxt "help:investment.asset,change_day1:"
msgid "percentage change in value compared to the previous day"
msgstr "prozentuale Wertänderung zum Vortag"
msgctxt "field:investment.asset,change_month1:"
msgid "1 Month"
msgstr "1 Monat"
msgctxt "help:investment.asset,change_month1:"
msgid "percentage change in value compared to last month"
msgstr "prozentuale Wertänderung zum letzten Monat"
msgctxt "field:investment.asset,change_month3:"
msgid "3 Months"
msgstr "3 Monate"
msgctxt "help:investment.asset,change_month3:"
msgid "percentage change in value during 3 months"
msgstr "Prozentuale Wertänderung während 3 Monate"
msgctxt "field:investment.asset,change_month6:"
msgid "6 Months"
msgstr "6 Monate"
msgctxt "help:investment.asset,change_month6:"
msgid "percentage change in value during 6 months"
msgstr "Prozentuale Wertänderung während 6 Monate"
msgctxt "field:investment.asset,change_month12:"
msgid "1 Year"
msgstr "1 Jahr"
msgctxt "help:investment.asset,change_month12:"
msgid "percentage change in value during 1 year"
msgstr "Prozentuale Wertänderung während 1 Jahr"
msgctxt "field:investment.asset,updtdays:"
msgid "Select days"
msgstr "Tage wählen"
msgctxt "selection:investment.asset,updtdays:"
msgid "Mon - Fri"
msgstr "Mo - Fr"
msgctxt "selection:investment.asset,updtdays:"
msgid "Mon - Sun"
msgstr "Mo - So"
##################### #####################
@ -393,3 +461,15 @@ msgstr "Wertpapierkennnummer (WKN)"
msgctxt "selection:product.identifier,type:" msgctxt "selection:product.identifier,type:"
msgid "Stock market symbol" msgid "Stock market symbol"
msgstr "Börsensymbol" msgstr "Börsensymbol"
####################
# diagram.graphdef #
####################
msgctxt "view:diagram.graphdef:"
msgid "Asset"
msgstr "Vermögenswert"
msgctxt "field:diagram.graphdef,asset:"
msgid "Asset"
msgstr "Vermögenswert"

View file

@ -58,6 +58,10 @@ msgctxt "view:investment.asset:"
msgid "Currency and Units" msgid "Currency and Units"
msgstr "Currency and Units" msgstr "Currency and Units"
msgctxt "view:investment.asset:"
msgid "Gain and Loss"
msgstr "Gain and Loss"
msgctxt "view:investment.asset:" msgctxt "view:investment.asset:"
msgid "Identifiers" msgid "Identifiers"
msgstr "Identifiers" msgstr "Identifiers"
@ -87,8 +91,8 @@ msgid "Currency"
msgstr "Currency" msgstr "Currency"
msgctxt "field:investment.asset,currency_digits:" msgctxt "field:investment.asset,currency_digits:"
msgid "Currency Digits" msgid "Digits"
msgstr "Currency Digits" msgstr "Digits"
msgctxt "field:investment.asset,product:" msgctxt "field:investment.asset,product:"
msgid "Product" msgid "Product"
@ -134,10 +138,22 @@ msgctxt "field:investment.asset,rates:"
msgid "Rates" msgid "Rates"
msgstr "Rates" msgstr "Rates"
msgctxt "field:investment.asset,name:"
msgid "Name"
msgstr "Name"
msgctxt "field:investment.asset,rate:" msgctxt "field:investment.asset,rate:"
msgid "Current Rate" msgid "Current Rate"
msgstr "Current Rate" msgstr "Current Rate"
msgctxt "field:investment.asset,date:"
msgid "Date"
msgstr "Date"
msgctxt "help:investment.asset,date:"
msgid "Date of current rate"
msgstr "Date of current rate"
msgctxt "field:investment.asset,updtsource:" msgctxt "field:investment.asset,updtsource:"
msgid "Update Source" msgid "Update Source"
msgstr "Update Source" msgstr "Update Source"
@ -150,9 +166,61 @@ msgctxt "field:investment.asset,updttime:"
msgid "Time" msgid "Time"
msgstr "Time" msgstr "Time"
msgctxt "field:investment.asset,updtneeded:" msgctxt "field:investment.asset,nextupdate:"
msgid "Course update needed" msgid "Next Update"
msgstr "Course update needed" msgstr "Next Update"
msgctxt "field:investment.asset,change_day1:"
msgid "Previous Day"
msgstr "Previous Day"
msgctxt "help:investment.asset,change_day1:"
msgid "percentage change in value compared to the previous day"
msgstr "percentage change in value compared to the previous day"
msgctxt "field:investment.asset,change_month1:"
msgid "1 Month"
msgstr "1 Month"
msgctxt "help:investment.asset,change_month1:"
msgid "percentage change in value compared to last month"
msgstr "percentage change in value compared to last month"
msgctxt "field:investment.asset,change_month3:"
msgid "3 Months"
msgstr "3 Months"
msgctxt "help:investment.asset,change_month3:"
msgid "percentage change in value during 3 months"
msgstr "percentage change in value during 3 months"
msgctxt "field:investment.asset,change_month6:"
msgid "6 Months"
msgstr "6 Months"
msgctxt "help:investment.asset,change_month6:"
msgid "percentage change in value during 6 months"
msgstr "percentage change in value during 6 months"
msgctxt "field:investment.asset,change_month12:"
msgid "1 Year"
msgstr "1 Year"
msgctxt "help:investment.asset,change_month12:"
msgid "percentage change in value during 1 year"
msgstr "percentage change in value during 1 year"
msgctxt "field:investment.asset,updtdays:"
msgid "Select days"
msgstr "Select days"
msgctxt "selection:investment.asset,updtdays:"
msgid "Mon - Fri"
msgstr "Mon - Fri"
msgctxt "selection:investment.asset,updtdays:"
msgid "Mon - Sun"
msgstr "Mon - Sun"
msgctxt "model:investment.source,name:" msgctxt "model:investment.source,name:"
msgid "Online Source" msgid "Online Source"
@ -350,3 +418,11 @@ msgctxt "selection:product.identifier,type:"
msgid "National Securities Identifying Number (NSIN)" msgid "National Securities Identifying Number (NSIN)"
msgstr "National Securities Identifying Number (NSIN)" msgstr "National Securities Identifying Number (NSIN)"
msgctxt "selection:product.identifier,type:"
msgid "Stock market symbol"
msgstr "Stock market symbol"
msgctxt "view:diagram.graphdef:"
msgid "Asset"
msgstr "Asset"

View file

@ -30,6 +30,7 @@ sel_rgxdatefmt = [
('%d.%m.%Y', 'dd.mm.yyyy'), ('%d.%m.%Y', 'dd.mm.yyyy'),
('%m/%d/%Y', 'mm/dd/yyyy'), ('%m/%d/%Y', 'mm/dd/yyyy'),
('%Y-%m-%d', 'yyyy-mm-dd'), ('%Y-%m-%d', 'yyyy-mm-dd'),
('%b %d %Y', 'mon dd yyyy'),
] ]
fields_check = ['url', 'nsin', 'isin', 'symbol', 'text', 'http_state', \ fields_check = ['url', 'nsin', 'isin', 'symbol', 'text', 'http_state', \

View file

@ -16,6 +16,35 @@ full copyright notices and license terms. -->
<field name="rgxident">WKN:.* ISIN: ([A-Z,0-9]+).*</field> <field name="rgxident">WKN:.* ISIN: ([A-Z,0-9]+).*</field>
<field name="rgxidtype">isin</field> <field name="rgxidtype">isin</field>
</record> </record>
<record model="investment.source" id="web_finanzen_fonds">
<field name="name">www.finanzen.net - Fonds</field>
<field name="url">https://www.finanzen.net/fonds/${isin}/tgt</field>
<field name="nohtml" eval="True"/>
<field name="rgxdate">\n\*\*Kursdatum\*\* (\d+\.\d+\.\d+).*\n</field>
<field name="rgxdatefmt">%d.%m.%Y</field>
<field name="rgxrate">\n\*\*Kurs\*\* (\d+,\d+) EUR.*\n</field>
<field name="rgxdecimal">,</field>
<field name="rgxident">WKN:.* ISIN: ([A-Z,0-9]+).*</field>
<field name="rgxidtype">isin</field>
</record>
<record model="investment.source" id="web_finanzen_rohstoffe">
<field name="name">www.finanzen.net - Rohstoffe</field>
<field name="url">https://www.finanzen.net/rohstoffe/${symbol}</field>
<field name="nohtml" eval="True"/>
<field name="rgxdate">\nKurszeit (\d+\.\d+\.\d+) \d+:\d+:\d+.*\n</field>
<field name="rgxdatefmt">%d.%m.%Y</field>
<field name="rgxrate">\nKurs ([\d+\.]*\d+,\d+) USD\n</field>
<field name="rgxdecimal">,</field>
</record>
<record model="investment.source" id="web_markets_ft_com">
<field name="name">Financial Times UK</field>
<field name="url">https://markets.ft.com/data/etfs/tearsheet/summary?s=${symbol}</field>
<field name="nohtml" eval="True"/>
<field name="rgxdate">Data delayed at least 15 minutes, as of (.*) \d+:\d+ GMT\.</field>
<field name="rgxdatefmt">%b %d %Y</field>
<field name="rgxrate">Price\D+([\d,]*\d+\.\d+)</field>
<field name="rgxdecimal">.</field>
</record>
</data> </data>
</tryton> </tryton>

View file

@ -8,7 +8,7 @@ from trytond.pool import Pool
from trytond.modules.company.tests import create_company from trytond.modules.company.tests import create_company
from trytond.transaction import Transaction from trytond.transaction import Transaction
from decimal import Decimal from decimal import Decimal
from datetime import time, date from datetime import time, date, datetime
class AssetTestCase(ModuleTestCase): class AssetTestCase(ModuleTestCase):
@ -61,7 +61,7 @@ class AssetTestCase(ModuleTestCase):
'currency_digits': 4, 'currency_digits': 4,
'uom': product.default_uom.id, 'uom': product.default_uom.id,
}]) }])
self.assertEqual(asset.rec_name, '%s [usd/%s]' % ( self.assertEqual(asset.rec_name, '%s - - usd/%s [-]' % (
product.rec_name, product.rec_name,
asset.uom.rec_name, asset.uom.rec_name,
)) ))
@ -84,6 +84,469 @@ class AssetTestCase(ModuleTestCase):
company=company, company=company,
product = product) product = product)
@with_transaction()
def test_asset_rec_name(self):
""" create asset
"""
Asset = Pool().get('investment.asset')
company = self.prep_asset_company()
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset = self.prep_asset_item(
company=company,
product = product)
self.assertEqual(asset.rec_name, 'Product 1 - - usd/Unit [-]')
Asset.write(*[
[asset],
{
'rates': [('create', [{
'date': date(2022, 5, 15),
'rate': Decimal('2.45'),
}])],
}])
self.assertEqual(asset.rec_name, 'Product 1 - 2.4500 usd/Unit [05/15/2022]')
self.assertEqual(Asset.search_count([('name', '=', 'Product 1')]), 1)
@with_transaction()
def test_asset_order_and_search_rate_and_date(self):
""" create asset, check order of rate + date
"""
Asset = Pool().get('investment.asset')
company = self.prep_asset_company()
product1 = self.prep_asset_product(
name='Product 1',
description='some asset')
product2 = self.prep_asset_product(
name='Product 2',
description='some asset')
asset1 = self.prep_asset_item(
company=company,
product = product1)
asset2 = self.prep_asset_item(
company=company,
product = product2)
Asset.write(*[
[asset1],
{
'rates': [('create', [{
'date': date(2022, 5, 18),
'rate': Decimal('3.5'),
}, {
'date': date(2022, 5, 15),
'rate': Decimal('2.45'),
}])],
},
[asset2],
{
'rates': [('create', [{
'date': date(2022, 5, 17),
'rate': Decimal('2.6'),
}, {
'date': date(2022, 5, 14),
'rate': Decimal('2.4'),
}])],
},
])
self.assertEqual(asset1.rec_name, 'Product 1 - 3.5000 usd/Unit [05/18/2022]')
self.assertEqual(asset2.rec_name, 'Product 2 - 2.6000 usd/Unit [05/17/2022]')
assets = Asset.search([], order=[('date', 'ASC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].date, date(2022, 5, 17))
self.assertEqual(assets[1].date, date(2022, 5, 18))
assets = Asset.search([], order=[('date', 'DESC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].date, date(2022, 5, 18))
self.assertEqual(assets[1].date, date(2022, 5, 17))
assets = Asset.search([], order=[('rate', 'ASC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].rate, Decimal('2.6'))
self.assertEqual(assets[1].rate, Decimal('3.5'))
assets = Asset.search([], order=[('rate', 'DESC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].rate, Decimal('3.5'))
self.assertEqual(assets[1].rate, Decimal('2.6'))
self.assertEqual(Asset.search_count([
('date', '=', date(2022, 5, 17)),
]), 1)
self.assertEqual(Asset.search_count([
('date', '>=', date(2022, 5, 17)),
]), 2)
self.assertEqual(Asset.search_count([
('date', '<', date(2022, 5, 17)),
]), 0)
@with_transaction()
def test_asset_percentages_dateselect1(self):
""" create asset, add rates, check selection of
specific date - fixed date
"""
Asset = Pool().get('investment.asset')
cursor = Transaction().connection.cursor()
company = self.prep_asset_company()
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset1 = self.prep_asset_item(
company=company,
product = product)
self.assertEqual(asset1.rec_name, 'Product 1 - - usd/Unit [-]')
Asset.write(*[
[asset1],
{
'rates': [('create', [{
'date': date(2022, 5, 15),
'rate': Decimal('2.45'),
}, {
'date': date(2022, 5, 16),
'rate': Decimal('2.6'),
}, {
'date': date(2022, 5, 12),
'rate': Decimal('2.0'),
}, {
'date': date(2022, 5, 3),
'rate': Decimal('3.6'),
}])],
},
])
self.assertEqual(asset1.rec_name, 'Product 1 - 2.6000 usd/Unit [05/16/2022]')
self.assertEqual(len(asset1.rates), 4)
self.assertEqual(asset1.rates[0].date, date(2022, 5, 16))
self.assertEqual(asset1.rates[1].date, date(2022, 5, 15))
self.assertEqual(asset1.rates[2].date, date(2022, 5, 12))
self.assertEqual(asset1.rates[3].date, date(2022, 5, 3))
# query fixed date
tab_percent = Asset.get_percentage_sql(['day1'], select_date = False)
query = tab_percent.select(
tab_percent.id,
tab_percent.date,
tab_percent.day1,
where=(tab_percent.date==date(2022, 5, 16)) & \
(tab_percent.id==asset1.id),
)
cursor.execute(*query)
records = cursor.fetchall()
# there should be one record, three colums
self.assertEqual(len(records), 1)
self.assertEqual(len(records[0]), 3)
self.assertEqual(records[0][0], asset1.id)
self.assertEqual(records[0][1], date(2022, 5, 16))
self.assertEqual(records[0][2].quantize(Decimal('0.01')), Decimal('6.12'))
@with_transaction()
def test_asset_percentages_dateselect2(self):
""" create asset, add rates, check selection of
specific date - date-column
"""
pool = Pool()
Asset = pool.get('investment.asset')
Rate = pool.get('investment.rate')
tab_rate = Rate.__table__()
cursor = Transaction().connection.cursor()
company = self.prep_asset_company()
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset1 = self.prep_asset_item(
company=company,
product = product)
asset2 = self.prep_asset_item(
company=company,
product = product)
Asset.write(*[
[asset1],
{
'rates': [('create', [{
'date': date(2022, 5, 15),
'rate': Decimal('2.45'),
}, {
'date': date(2022, 5, 16),
'rate': Decimal('2.6'),
}, {
'date': date(2022, 5, 12),
'rate': Decimal('2.0'),
}, {
'date': date(2022, 5, 3),
'rate': Decimal('3.6'),
}])],
},
[asset2],
{
'rates': [('create', [{
'date': date(2022, 5, 17),
'rate': Decimal('1.5'),
}, {
'date': date(2022, 5, 16),
'rate': Decimal('2.0'),
}, {
'date': date(2022, 5, 15),
'rate': Decimal('2.5'),
}, {
'date': date(2022, 5, 14),
'rate': Decimal('3.0'),
}, {
'date': date(2022, 5, 13),
'rate': Decimal('3.5'),
}, {
'date': date(2022, 5, 12),
'rate': Decimal('4.0'),
}, {
'date': date(2022, 5, 11),
'rate': Decimal('4.5'),
}, ])],
},
])
self.assertEqual(asset1.rec_name, 'Product 1 - 2.6000 usd/Unit [05/16/2022]')
self.assertEqual(len(asset1.rates), 4)
self.assertEqual(asset2.rec_name, 'Product 1 - 1.5000 usd/Unit [05/17/2022]')
self.assertEqual(len(asset2.rates), 7)
self.assertEqual(asset1.rates[0].date, date(2022, 5, 16))
self.assertEqual(asset1.rates[1].date, date(2022, 5, 15))
self.assertEqual(asset1.rates[2].date, date(2022, 5, 12))
self.assertEqual(asset1.rates[3].date, date(2022, 5, 3))
# query date-column
tab_percent = Asset.get_percentage_sql(['day1'], select_date = False)
query = tab_rate.join(tab_percent,
condition=(tab_percent.id_rate == tab_rate.id)
).select(
tab_percent.id,
tab_percent.date,
tab_percent.day1,
where=tab_percent.id==asset1.id,
order_by=[tab_percent.date.asc]
)
cursor.execute(*query)
records = cursor.fetchall()
# there should be 4x records, three colums
self.assertEqual(len(records), 4)
self.assertEqual(len(records[0]), 3)
self.assertEqual(records[0][0], asset1.id)
self.assertEqual(records[0][1], date(2022, 5, 3))
self.assertEqual(records[0][2], None)
self.assertEqual(records[1][0], asset1.id)
self.assertEqual(records[1][1], date(2022, 5, 12))
self.assertEqual(records[1][2], None)
self.assertEqual(records[2][0], asset1.id)
self.assertEqual(records[2][1], date(2022, 5, 15))
self.assertEqual(records[2][2].quantize(Decimal('0.01')), Decimal('22.5'))
self.assertEqual(records[3][0], asset1.id)
self.assertEqual(records[3][1], date(2022, 5, 16))
self.assertEqual(records[3][2].quantize(Decimal('0.01')), Decimal('6.12'))
@with_transaction()
def test_asset_percentages_daterange(self):
""" create asset, add rates, check selection of
value
"""
Asset = Pool().get('investment.asset')
company = self.prep_asset_company()
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset1 = self.prep_asset_item(
company=company,
product = product)
asset2 = self.prep_asset_item(
company=company,
product = product)
self.assertEqual(asset1.rec_name, 'Product 1 - - usd/Unit [-]')
self.assertEqual(asset2.rec_name, 'Product 1 - - usd/Unit [-]')
Asset.write(*[
[asset1],
{
'rates': [('create', [{
'date': date(2022, 5, 15),
'rate': Decimal('2.45'),
}, {
'date': date(2022, 5, 16),
'rate': Decimal('2.6'),
}])],
},
[asset2],
{
'rates': [('create', [{
'date': date(2022, 5, 14),
'rate': Decimal('5.75'),
}, {
'date': date(2022, 5, 15),
'rate': Decimal('5.25'),
}])],
},
])
self.assertEqual(asset1.rec_name, 'Product 1 - 2.6000 usd/Unit [05/16/2022]')
self.assertEqual(asset2.rec_name, 'Product 1 - 5.2500 usd/Unit [05/15/2022]')
self.assertEqual(asset1.change_day1, Decimal('6.12'))
self.assertEqual(asset2.change_day1, Decimal('-8.7'))
self.assertEqual(asset1.change_month1, None)
self.assertEqual(asset2.change_month1, None)
self.assertEqual(asset1.change_month3, None)
self.assertEqual(asset2.change_month3, None)
self.assertEqual(asset1.change_month6, None)
self.assertEqual(asset2.change_month6, None)
self.assertEqual(asset1.change_month12, None)
self.assertEqual(asset2.change_month12, None)
# check ordering
assets = Asset.search([
('change_day1', '!=', Decimal('0.0')),
], order=[('change_day1', 'ASC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].change_day1, Decimal('-8.7'))
self.assertEqual(assets[1].change_day1, Decimal('6.12'))
assets = Asset.search([
('change_day1', '!=', Decimal('0.0')),
], order=[('change_day1', 'DESC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].change_day1, Decimal('6.12'))
self.assertEqual(assets[1].change_day1, Decimal('-8.7'))
# check 5-day-range
# four days
Asset.write(*[
[asset1],
{
'rates': [('write', [asset1.rates[1]], {
'date': date(2022, 5, 12),
})],
}])
self.assertEqual(asset1.rates[0].date, date(2022, 5, 16))
self.assertEqual(asset1.rates[1].date, date(2022, 5, 12))
self.assertEqual(asset1.change_day1, Decimal('6.12'))
# five days
Asset.write(*[
[asset1],
{
'rates': [('write', [asset1.rates[1]], {
'date': date(2022, 5, 11),
})],
}])
self.assertEqual(asset1.rates[0].date, date(2022, 5, 16))
self.assertEqual(asset1.rates[1].date, date(2022, 5, 11))
self.assertEqual(asset1.change_day1, Decimal('6.12'))
# six days
Asset.write(*[
[asset1],
{
'rates': [('write', [asset1.rates[1]], {
'date': date(2022, 5, 10),
})],
}])
self.assertEqual(asset1.rates[0].date, date(2022, 5, 16))
self.assertEqual(asset1.rates[1].date, date(2022, 5, 10))
self.assertEqual(asset1.change_day1, None)
@with_transaction()
def test_asset_percentges_values(self):
""" create asset, add rates, check percentages
"""
Asset = Pool().get('investment.asset')
company = self.prep_asset_company()
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset1 = self.prep_asset_item(
company=company,
product = product)
self.assertEqual(asset1.rec_name, 'Product 1 - - usd/Unit [-]')
Asset.write(*[
[asset1],
{
'rates': [('create', [{
'date': date(2022, 5, 15),
'rate': Decimal('2.45'),
}, {
'date': date(2022, 5, 16),
'rate': Decimal('2.6'),
}, {
'date': date(2022, 4, 14),
'rate': Decimal('2.2'),
}, {
'date': date(2022, 2, 14),
'rate': Decimal('2.8'),
},])],
}])
self.assertEqual(asset1.rec_name, 'Product 1 - 2.6000 usd/Unit [05/16/2022]')
self.assertEqual(len(asset1.rates), 4)
self.assertEqual(asset1.rates[0].date, date(2022, 5, 16))
self.assertEqual(asset1.rates[1].date, date(2022, 5, 15))
self.assertEqual(asset1.rates[2].date, date(2022, 4, 14))
self.assertEqual(asset1.rates[3].date, date(2022, 2, 14))
self.assertEqual(asset1.change_day1, Decimal('6.12'))
self.assertEqual(asset1.change_month1, Decimal('18.18'))
self.assertEqual(asset1.change_month3, Decimal('-7.14'))
self.assertEqual(asset1.change_month6, None)
self.assertEqual(asset1.change_month12, None)
# call order-functions
Asset.search([], order=[('change_day1', 'ASC')])
Asset.search([], order=[('change_month1', 'ASC')])
Asset.search([], order=[('change_month3', 'ASC')])
Asset.search([], order=[('change_month6', 'ASC')])
Asset.search([], order=[('change_month12', 'ASC')])
# searcher
self.assertEqual(
Asset.search_count([('change_day1', '>', Decimal('6.1'))]),
1)
self.assertEqual(
Asset.search_count([('change_day1', '>', Decimal('6.15'))]),
0)
self.assertEqual(
Asset.search_count([('change_day1', '=', Decimal('6.12'))]),
1)
self.assertEqual(
Asset.search_count([('change_month1', '>', Decimal('18.0'))]),
1)
self.assertEqual(
Asset.search_count([('change_month1', '>', Decimal('18.18'))]),
0)
self.assertEqual(
Asset.search_count([('change_month1', '=', Decimal('18.18'))]),
1)
self.assertEqual(
Asset.search_count([('change_month3', '=', Decimal('-7.14'))]),
1)
self.assertEqual(
Asset.search_count([('change_month6', '=', None)]),
1)
@with_transaction() @with_transaction()
def test_asset_check_onlinesource_onoff(self): def test_asset_check_onlinesource_onoff(self):
""" create asset, switch online-source on/off """ create asset, switch online-source on/off
@ -106,7 +569,7 @@ class AssetTestCase(ModuleTestCase):
}]) }])
self.assertEqual(asset.updtsource, None) self.assertEqual(asset.updtsource, None)
self.assertEqual(asset.updttime, None) self.assertEqual(asset.updttime, time(14,0))
asset.updtsource = o_source asset.updtsource = o_source
asset.updttime = time(10, 45) asset.updttime = time(10, 45)
@ -146,81 +609,77 @@ class AssetTestCase(ModuleTestCase):
'updtsource': o_source.id, 'updtsource': o_source.id,
'updttime': time(10, 45), 'updttime': time(10, 45),
}]) }])
self.assertEqual(asset.updtsource.rec_name, 'Source 1')
self.assertEqual(asset.updttime, time(10, 45))
self.assertEqual(len(asset.rates), 0)
with Transaction().set_context({ with Transaction().set_context({
'qdate': date(2022, 10, 15), 'qdate': date(2022, 10, 14), # friday
'qtime': time(10, 30),
}): }):
# no rates exists - wait for 10:45 # re-read to make context work
self.assertEqual(asset.updtneeded, True) asset2, = Asset.browse([asset.id])
self.assertEqual(asset2.updtsource.rec_name, 'Source 1')
self.assertEqual(asset2.updttime, time(10, 45))
self.assertEqual(len(asset2.rates), 0)
# qdate = 2022-10-14 simulates existence of record at this day
# next call would be the 15. - but its saturday,
# next-call-date is moved to 17.
self.assertEqual(asset2.nextupdate, datetime(2022, 10, 17, 10, 45))
self.assertEqual( self.assertEqual(
Asset.search_count([('updtneeded', '=', True)]), Asset.search_count([('nextupdate', '<', datetime(2022, 10, 17, 10, 45))]),
0) 0)
with Transaction().set_context({
'qdate': date(2022, 10, 15),
'qtime': time(10, 46),
}):
# no rates exists - run at 10:46
self.assertEqual(asset.updtneeded, True)
self.assertEqual( self.assertEqual(
Asset.search_count([('updtneeded', '=', True)]), Asset.search_count([('nextupdate', '>=', datetime(2022, 10, 17, 10, 45))]),
1) 1)
# add rate at yesterday # add rate at next monday
Asset.write(*[ Asset.write(*[
[asset], [asset],
{ {
'rates': [('create', [{ 'rates': [('create', [{
'date': date(2022, 10, 14), 'date': date(2022, 10, 17), # monday
'rate': Decimal('1.5'), 'rate': Decimal('1.5'),
}])], }])],
}]) }])
self.assertEqual(len(asset.rates), 1) self.assertEqual(len(asset.rates), 1)
with Transaction().set_context({ asset2, = Asset.browse([asset.id])
'qdate': date(2022, 10, 15), self.assertEqual(asset.updtsource.rec_name, 'Source 1')
'qtime': time(10, 30), self.assertEqual(asset.updttime, time(10, 45))
}): self.assertEqual(len(asset.rates), 1)
# 1x rate exists - run at 10:30 self.assertEqual(asset.rates[0].date, date(2022, 10, 17))
self.assertEqual(asset.updtneeded, True) self.assertEqual(asset.nextupdate, datetime(2022, 10, 18, 10, 45))
self.assertEqual(
Asset.search_count([('updtneeded', '=', True)]),
0)
with Transaction().set_context({ self.assertEqual(
'qdate': date(2022, 10, 15), Asset.search_count([('nextupdate', '<', datetime(2022, 10, 18, 10, 45))]),
'qtime': time(10, 46), 0)
}): self.assertEqual(
# 1x rate exists yesterday - run at 10:46 Asset.search_count([('nextupdate', '>=', datetime(2022, 10, 18, 10, 45))]),
self.assertEqual(asset.updtneeded, True) 1)
self.assertEqual(
Asset.search_count([('updtneeded', '=', True)]),
1)
# add rate at today # add rate at today
Asset.write(*[ Asset.write(*[
[asset], [asset],
{ {
'rates': [('create', [{ 'rates': [('create', [{
'date': date(2022, 10, 15), 'date': date(2022, 10, 18),
'rate': Decimal('1.5'), 'rate': Decimal('1.5'),
}])], }])],
}]) }])
self.assertEqual(len(asset.rates), 2) self.assertEqual(len(asset.rates), 2)
with Transaction().set_context({ asset2, = Asset.browse([asset.id])
'qdate': date(2022, 10, 15), self.assertEqual(asset2.updtsource.rec_name, 'Source 1')
'qtime': time(10, 47), self.assertEqual(asset2.updttime, time(10, 45))
}): self.assertEqual(len(asset2.rates), 2)
# 1x rate exists today - run at 10:47 self.assertEqual(asset2.rates[0].date, date(2022, 10, 18))
self.assertEqual(asset.updtneeded, True) self.assertEqual(asset2.nextupdate, datetime(2022, 10, 19, 10, 45))
self.assertEqual(
Asset.search_count([('updtneeded', '=', True)]), self.assertEqual(
0) Asset.search_count([('nextupdate', '<', datetime(2022, 10, 19, 10, 45))]),
0)
self.assertEqual(
Asset.search_count([('nextupdate', '>=', datetime(2022, 10, 19, 10, 45))]),
1)
@with_transaction() @with_transaction()
def test_asset_indentifiers(self): def test_asset_indentifiers(self):
@ -280,6 +739,16 @@ class AssetTestCase(ModuleTestCase):
self.assertEqual(Asset.search_count([('isin', '=', 'XC0009655157')]), 1) self.assertEqual(Asset.search_count([('isin', '=', 'XC0009655157')]), 1)
self.assertEqual(Asset.search_count([('secsymb', '=', '1472977')]), 1) self.assertEqual(Asset.search_count([('secsymb', '=', '1472977')]), 1)
self.assertEqual(Asset.search_count([('rec_name', '=', '965515')]), 1)
self.assertEqual(Asset.search_count([('rec_name', '=', 'XC0009655157')]), 1)
self.assertEqual(Asset.search_count([('rec_name', '=', '1472977')]), 1)
self.assertEqual(Asset.search_count([('name', '=', '965515')]), 1)
self.assertEqual(Asset.search_count([('name', '=', 'XC0009655157')]), 1)
self.assertEqual(Asset.search_count([('name', '=', '1472977')]), 1)
self.assertEqual(Asset.search_count([('name', '=', 'Product unit')]), 1)
self.assertEqual(Asset.search_count([ self.assertEqual(Asset.search_count([
('wkn', 'ilike', '9655%'), ('wkn', 'ilike', '9655%'),
]), 1) ]), 1)
@ -291,6 +760,36 @@ class AssetTestCase(ModuleTestCase):
self.assertEqual(asset2.isin, 'XC0009653103') self.assertEqual(asset2.isin, 'XC0009653103')
self.assertEqual(asset2.secsymb, '1431157') self.assertEqual(asset2.secsymb, '1431157')
# order wkn
assets = Asset.search([], order=[('wkn', 'ASC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].wkn, '965310')
self.assertEqual(assets[1].wkn, '965515')
assets = Asset.search([], order=[('wkn', 'DESC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].wkn, '965515')
self.assertEqual(assets[1].wkn, '965310')
# order isin
assets = Asset.search([], order=[('isin', 'ASC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].isin, 'XC0009653103')
self.assertEqual(assets[1].isin, 'XC0009655157')
assets = Asset.search([], order=[('wkn', 'DESC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].isin, 'XC0009655157')
self.assertEqual(assets[1].isin, 'XC0009653103')
# order secsymb
assets = Asset.search([], order=[('secsymb', 'ASC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].secsymb, '1431157')
self.assertEqual(assets[1].secsymb, '1472977')
assets = Asset.search([], order=[('wkn', 'DESC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].secsymb, '1472977')
self.assertEqual(assets[1].secsymb, '1431157')
@with_transaction() @with_transaction()
def test_asset_check_product_update(self): def test_asset_check_product_update(self):
""" check update of product on asset """ check update of product on asset

View file

@ -8,7 +8,7 @@ from trytond.pool import Pool
from trytond.modules.company.tests import create_company from trytond.modules.company.tests import create_company
from trytond.transaction import Transaction from trytond.transaction import Transaction
from decimal import Decimal from decimal import Decimal
from datetime import time, date from datetime import time, date, datetime
from unittest.mock import MagicMock from unittest.mock import MagicMock
from requests import Response from requests import Response
import requests import requests
@ -66,26 +66,36 @@ class SourceTestCase(ModuleTestCase):
{ {
'updtsource': osource.id, 'updtsource': osource.id,
}]) }])
self.assertEqual(asset.wkn, '965515')
self.assertEqual(asset.isin, 'XC0009655157')
self.assertEqual(asset.secsymb, '1472977')
self.assertEqual(asset.updtsource.rec_name, 'Source 1')
self.assertEqual(len(asset.rates), 0)
# fake server-response with Transaction().set_context({
resp1 = Response() 'qdate': date(2022, 10, 1), # saturday
resp1._content = """<html><body>Response from finance-server 'qdatetime': datetime(2022, 10, 2, 10, 0, 0),
}):
asset2, = Asset.browse([asset])
self.assertEqual(asset2.wkn, '965515')
self.assertEqual(asset2.isin, 'XC0009655157')
self.assertEqual(asset2.secsymb, '1472977')
self.assertEqual(asset2.updttime, time(14, 0))
self.assertEqual(asset2.updtsource.rec_name, 'Source 1')
self.assertEqual(asset2.updtdays, 'work')
self.assertEqual(asset2.nextupdate, datetime(2022, 10, 3, 14, 0))
self.assertEqual(len(asset.rates), 0)
# fake server-response
resp1 = Response()
resp1._content = """<html><body>Response from finance-server
Course Date 14.08.2022 Today Course Date 14.08.2022 Today
High 34,87 EUR High 34,87 EUR
</body></html>""".encode('utf8') </body></html>""".encode('utf8')
resp1.status_code = 200 resp1.status_code = 200
resp1.reason = 'OK' resp1.reason = 'OK'
requests.get = MagicMock(return_value=resp1) requests.get = MagicMock(return_value=resp1)
OSource.update_rate(asset) OSource.update_rate(asset)
self.assertEqual(len(asset.rates), 1) self.assertEqual(len(asset.rates), 1)
self.assertEqual(asset.rates[0].date, date(2022, 8, 14)) self.assertEqual(asset.rates[0].date, date(2022, 8, 14))
self.assertEqual(asset.rates[0].rate, Decimal('34.87')) self.assertEqual(asset.rates[0].rate, Decimal('34.87'))
@with_transaction() @with_transaction()
def test_waitlist_source_check_regex(self): def test_waitlist_source_check_regex(self):

View file

@ -1,11 +1,13 @@
[tryton] [tryton]
version=6.0.0 version=6.0.7
depends: depends:
ir ir
res res
company company
currency currency
product product
extras_depends:
diagram
xml: xml:
icon.xml icon.xml
message.xml message.xml
@ -15,5 +17,6 @@ xml:
sources_def.xml sources_def.xml
update_wiz.xml update_wiz.xml
rate.xml rate.xml
diagram.xml
menu.xml menu.xml
cron.xml cron.xml

View file

@ -1 +1 @@
diagram;6.0.7;6.0.999;mds

View file

@ -2,24 +2,48 @@
<!-- This file is part of the investment-module from m-ds for Tryton. <!-- This file is part of the investment-module from m-ds for Tryton.
The COPYRIGHT file at the top level of this repository contains the The COPYRIGHT file at the top level of this repository contains the
full copyright notices and license terms. --> full copyright notices and license terms. -->
<form col="4"> <form col="6">
<label name="product" /> <label name="product" />
<field name="product" /> <field name="product" colspan="3"/>
<label name="rate" /> <label name="rate" />
<field name="rate" symbol="currency"/> <field name="rate" symbol="currency"/>
<separator id="sepunits" colspan="4" string="Currency and Units"/> <label id="labdate" string=" "/>
<label name="nextupdate" colspan="2"/>
<field name="nextupdate"/>
<label name="date" />
<field name="date"/>
<separator id="sepperc" colspan="6" string="Gain and Loss"/>
<label name="change_day1" />
<field name="change_day1" />
<label name="change_month1" />
<field name="change_month1" />
<label name="change_month3" />
<field name="change_month3" />
<label name="change_month6" />
<field name="change_month6" />
<label name="change_month12" />
<field name="change_month12" />
<newline/>
<separator id="sepunits" colspan="6" string="Currency and Units"/>
<label name="currency" /> <label name="currency" />
<field name="currency" /> <field name="currency" />
<label name="currency_digits" /> <label name="currency_digits" />
<field name="currency_digits" /> <field name="currency_digits" />
<newline/>
<label name="uom" /> <label name="uom" />
<field name="uom" /> <field name="uom" />
<label name="product_uom" /> <label name="product_uom" />
<field name="product_uom" /> <field name="product_uom" />
<newline/>
<notebook> <notebook colspan="6">
<page id="pgids" col="4" string="Identifiers"> <page id="pgids" col="4" string="Identifiers">
<label name="wkn" /> <label name="wkn" />
<field name="wkn" /> <field name="wkn" />
@ -37,6 +61,10 @@ full copyright notices and license terms. -->
<field name="updtsource"/> <field name="updtsource"/>
<label name="updttime"/> <label name="updttime"/>
<field name="updttime"/> <field name="updttime"/>
<label id="labupdt" colspan="2" string=" "/>
<label name="updtdays"/>
<field name="updtdays"/>
</page> </page>
</notebook> </notebook>

View file

@ -3,11 +3,15 @@
The COPYRIGHT file at the top level of this repository contains the The COPYRIGHT file at the top level of this repository contains the
full copyright notices and license terms. --> full copyright notices and license terms. -->
<tree> <tree>
<field name="rec_name"/> <field name="name" expand="2"/>
<field name="isin"/> <field name="isin" expand="1"/>
<field name="secsymb"/> <field name="wkn" expand="1"/>
<field name="wkn"/> <field name="change_day1" />
<field name="rate"/> <field name="change_month1" />
<field name="change_month3" />
<field name="change_month6" />
<field name="date" expand="1"/>
<field name="rate" expand="1"/>
<field name="currency"/> <field name="currency"/>
<field name="uom"/> <field name="uom" />
</tree> </tree>

14
view/graph_form.xml Normal file
View file

@ -0,0 +1,14 @@
<?xml version="1.0"?>
<!-- This file is part of the investment-module from m-ds for Tryton.
The COPYRIGHT file at the top level of this repository contains the
full copyright notices and license terms. -->
<data>
<xpath expr="/form/separator[@name='currency']" position="before">
<separator name="asset" colspan="6" string="Asset"/>
<label name="asset"/>
<field name="asset"/>
<newline/>
</xpath>
</data>