402 lines
13 KiB
Python
402 lines
13 KiB
Python
import datetime
|
|
from trytond.model import ModelSQL, ModelView, fields
|
|
from trytond.pool import Pool
|
|
from trytond.transaction import Transaction
|
|
from sql import Literal
|
|
from sql.functions import CurrentTimestamp
|
|
from trytond.wizard import Button, StateTransition, StateView, Wizard
|
|
from trytond.pyson import Bool, Eval, Id, If
|
|
|
|
class DerivativeMatch(ModelSQL, ModelView):
|
|
"Derivative Match"
|
|
__name__ = 'derivative.match'
|
|
|
|
long_position = fields.Many2One('derivative.derivative', 'Long Position', required=True)
|
|
short_position = fields.Many2One('derivative.derivative', 'Short Position', required=True)
|
|
quantity = fields.Numeric('Matched Quantity', digits='unit', required=True)
|
|
match_date = fields.Date('Match Date', required=True)
|
|
|
|
method = fields.Selection([
|
|
('fifo', 'FIFO'),
|
|
('lifo', 'LIFO'),
|
|
('manual', 'Manual'),
|
|
], 'Method', required=True)
|
|
|
|
pnl = fields.Function(fields.Numeric('PnL', digits='currency'), 'get_pnl')
|
|
|
|
def get_pnl(self, name):
|
|
return (self.short_position.price - self.long_position.price) * self.quantity
|
|
|
|
class Derivative(ModelSQL,ModelView):
|
|
"Derivative"
|
|
__name__ = 'derivative.derivative'
|
|
|
|
purchase = fields.Many2One('purchase.purchase',"Purchase")
|
|
line = fields.Many2One('purchase.line',"Purch. Line")
|
|
efp = fields.Boolean("EFP")
|
|
price_index = fields.Many2One('price.price',"Curve")
|
|
nb_ct = fields.Integer("Nb ct")
|
|
price = fields.Numeric("Entry price",digits='currency')
|
|
exit_price = fields.Numeric("Exit price",digits='currency')
|
|
product = fields.Many2One('product.product',"Product")
|
|
party = fields.Many2One('party.party','Supplier')
|
|
currency = fields.Function(fields.Many2One('currency.currency',"Cur"),'get_cur')
|
|
quantity = fields.Function(fields.Numeric("Quantity",digits='unit'),'get_qt')
|
|
alloc_qty = fields.Function(fields.Numeric("Alloc. Qty",digits='unit'),'get_alloc_qt')
|
|
unit = fields.Function(fields.Many2One('product.uom',"Unit"),'get_unit')
|
|
amount = fields.Function(fields.Numeric("Amount",digits='currency'),'get_amount')
|
|
currency2 = fields.Function(fields.Many2One('currency.currency',"Cur"),'get_cur2')
|
|
|
|
trade_date = fields.Date('Trade Date')
|
|
maturity_date = fields.Date('Maturity')
|
|
open_qty = fields.Numeric('Open Quantity', digits='unit')
|
|
|
|
matches_long = fields.One2Many('derivative.match', 'long_position', 'Matches (Long)', states={'invisible': (Eval('direction') == 'long')})
|
|
matches_short = fields.One2Many('derivative.match', 'short_position', 'Matches (Short)', states={'invisible': (Eval('direction') == 'short')})
|
|
|
|
direction = fields.Selection([
|
|
('long', 'Long'),
|
|
('short', 'Short'),
|
|
], 'Direction', required=True)
|
|
|
|
state = fields.Selection([
|
|
('open', 'Open'),
|
|
('closed', 'Closed'),
|
|
], 'State', required=True)
|
|
|
|
@classmethod
|
|
def default_state(cls):
|
|
return 'open'
|
|
|
|
def get_amount(self,name):
|
|
if self.price_index and self.line and self.price and self.nb_ct:
|
|
#pi = Pool().get('price.price')(self.price_index)
|
|
return self.price_index.get_amount_nb_ct(self.price,self.nb_ct,self.line.unit,self.line.purchase.currency if self.line.purchase else self.sale_line.sale.currency)
|
|
|
|
def get_cur(self,name):
|
|
if self.price_index:
|
|
#pi = Pool().get('price.price')(self.price_index)
|
|
return self.price_index.price_currency
|
|
|
|
def get_cur2(self,name):
|
|
if self.purchase:
|
|
return self.purchase.currency
|
|
|
|
def get_unit(self,name):
|
|
if self.line:
|
|
return self.line.unit
|
|
|
|
def get_qt(self,name):
|
|
if self.line:
|
|
line = self.line
|
|
else:
|
|
if self.purchase and self.purchase.lines:
|
|
line = self.purchase.lines[0]
|
|
else:
|
|
line = None
|
|
if self.price_index and self.nb_ct and line:
|
|
return self.price_index.get_qt(self.nb_ct,line.unit)
|
|
|
|
def get_alloc_qt(self,name):
|
|
if self.line:
|
|
line = self.line
|
|
else:
|
|
if self.purchase and self.purchase.lines:
|
|
line = self.purchase.lines[0]
|
|
else:
|
|
line = None
|
|
if self.price_index and self.nb_ct and line:
|
|
if line.price_summary:
|
|
return line.price_summary[0].fixed_qt
|
|
|
|
class MatchWizardStart(ModelView):
|
|
"Match Selected Derivatives"
|
|
__name__ = 'derivative.match.start'
|
|
|
|
method = fields.Selection([
|
|
('fifo', 'FIFO'),
|
|
('lifo', 'LIFO'),
|
|
('manual', 'Manual'),
|
|
], 'Method', required=True)
|
|
|
|
quantity = fields.Numeric(
|
|
'Quantity to Match',
|
|
digits='unit',
|
|
required=True
|
|
)
|
|
|
|
class DerivativeMatchWizard(Wizard):
|
|
"Derivative Match Wizard"
|
|
__name__ = 'derivative.match.wizard'
|
|
|
|
start = StateView(
|
|
'derivative.match.start',
|
|
'purchase_trade.derivative_match_start_form_view',
|
|
[
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Match', 'match', 'tryton-ok', default=True),
|
|
]
|
|
)
|
|
|
|
match = StateTransition()
|
|
|
|
def transition_match(self):
|
|
pool = Pool()
|
|
Derivative = pool.get('derivative.derivative')
|
|
Date = pool.get('ir.date')
|
|
Match = pool.get('derivative.match')
|
|
|
|
active_ids = Transaction().context.get('active_ids', [])
|
|
if not active_ids:
|
|
return 'end'
|
|
|
|
positions = Derivative.browse(active_ids)
|
|
|
|
longs = [
|
|
p for p in positions
|
|
if p.direction == 'long'
|
|
and p.state == 'open'
|
|
and p.open_qty > 0
|
|
]
|
|
|
|
shorts = [
|
|
p for p in positions
|
|
if p.direction == 'short'
|
|
and p.state == 'open'
|
|
and p.open_qty > 0
|
|
]
|
|
|
|
if not longs or not shorts:
|
|
return 'end'
|
|
|
|
# FIFO / LIFO ordering
|
|
reverse = self.start.method == 'lifo'
|
|
longs.sort(key=lambda p: p.trade_date or datetime.date.min, reverse=reverse)
|
|
shorts.sort(key=lambda p: p.trade_date or datetime.date.min, reverse=reverse)
|
|
|
|
remaining_qty = self.start.quantity
|
|
|
|
for long_pos in longs:
|
|
if remaining_qty <= 0:
|
|
break
|
|
|
|
for short_pos in shorts:
|
|
if remaining_qty <= 0:
|
|
break
|
|
|
|
match_qty = min(
|
|
long_pos.open_qty,
|
|
short_pos.open_qty,
|
|
remaining_qty
|
|
)
|
|
|
|
if match_qty <= 0:
|
|
continue
|
|
|
|
Match.create([{
|
|
'long_position': long_pos.id,
|
|
'short_position': short_pos.id,
|
|
'quantity': match_qty,
|
|
'method': self.start.method,
|
|
'match_date': Date.today(),
|
|
}])
|
|
|
|
long_pos.open_qty -= match_qty
|
|
short_pos.open_qty -= match_qty
|
|
remaining_qty -= match_qty
|
|
|
|
if long_pos.open_qty == 0:
|
|
long_pos.state = 'closed'
|
|
if short_pos.open_qty == 0:
|
|
short_pos.state = 'closed'
|
|
|
|
long_pos.save()
|
|
short_pos.save()
|
|
|
|
return 'end'
|
|
|
|
class DerivativeReport(ModelSQL, ModelView):
|
|
"Derivative Position Report"
|
|
__name__ = 'derivative.report'
|
|
|
|
r_derivative = fields.Many2One(
|
|
'derivative.derivative',
|
|
"Derivative"
|
|
)
|
|
|
|
r_trade_date = fields.Date("Trade Date")
|
|
r_maturity_date = fields.Date("Maturity")
|
|
|
|
r_product = fields.Many2One(
|
|
'product.product',
|
|
"Product"
|
|
)
|
|
|
|
r_party = fields.Many2One(
|
|
'party.party',
|
|
"Counterparty"
|
|
)
|
|
|
|
r_direction = fields.Selection([
|
|
(None, ''),
|
|
('long', 'Long'),
|
|
('short', 'Short'),
|
|
], 'Direction')
|
|
|
|
r_state = fields.Selection([
|
|
('open', 'Open'),
|
|
('closed', 'Closed'),
|
|
], 'State')
|
|
|
|
r_quantity = fields.Function(fields.Numeric("Initial Qty",digits='unit'),'get_qt')
|
|
|
|
r_open_qty = fields.Numeric(
|
|
"Open Qty",
|
|
digits='unit'
|
|
)
|
|
|
|
r_price = fields.Numeric(
|
|
"Entry Price",
|
|
digits='currency'
|
|
)
|
|
|
|
r_currency = fields.Function(fields.Many2One('currency.currency',"Currency"),'get_cur')
|
|
|
|
def get_cur(self,name):
|
|
if self.r_derivative:
|
|
if self.r_derivative.price_index:
|
|
return self.r_derivative.price_index.price_currency
|
|
|
|
def get_qt(self,name):
|
|
if self.r_derivative:
|
|
if self.r_derivative.line:
|
|
line = self.r_derivative.line
|
|
else:
|
|
if self.r_derivative.purchase and self.r_derivative.purchase.lines:
|
|
line = self.r_derivative.purchase.lines[0]
|
|
else:
|
|
line = None
|
|
if self.r_derivative.price_index and self.r_derivative.nb_ct and line:
|
|
return self.r_derivative.price_index.get_qt(self.r_derivative.nb_ct,line.unit)
|
|
|
|
# ------------------------------------------------------------
|
|
# TABLE QUERY
|
|
# ------------------------------------------------------------
|
|
@classmethod
|
|
def table_query(cls):
|
|
pool = Pool()
|
|
Derivative = pool.get('derivative.derivative')
|
|
d = Derivative.__table__()
|
|
|
|
context = Transaction().context
|
|
|
|
product = context.get('product')
|
|
party = context.get('party')
|
|
direction = context.get('direction')
|
|
state = context.get('state')
|
|
trade_from = context.get('trade_from')
|
|
trade_to = context.get('trade_to')
|
|
maturity_from = context.get('maturity_from')
|
|
maturity_to = context.get('maturity_to')
|
|
open_only = context.get('open_only')
|
|
|
|
wh = Literal(True)
|
|
|
|
if product:
|
|
wh &= (d.product == product)
|
|
if party:
|
|
wh &= (d.party == party)
|
|
if direction:
|
|
wh &= (d.direction == direction)
|
|
if state:
|
|
wh &= (d.state == state)
|
|
if open_only:
|
|
wh &= (d.open_qty > 0)
|
|
|
|
if trade_from:
|
|
wh &= (d.trade_date >= trade_from)
|
|
if trade_to:
|
|
wh &= (d.trade_date <= trade_to)
|
|
|
|
if maturity_from:
|
|
wh &= (d.maturity_date >= maturity_from)
|
|
if maturity_to:
|
|
wh &= (d.maturity_date <= maturity_to)
|
|
|
|
query = d.select(
|
|
# mandatory technical fields
|
|
Literal(0).as_('create_uid'),
|
|
CurrentTimestamp().as_('create_date'),
|
|
Literal(None).as_('write_uid'),
|
|
Literal(None).as_('write_date'),
|
|
|
|
d.id.as_('id'),
|
|
d.id.as_('r_derivative'),
|
|
d.trade_date.as_('r_trade_date'),
|
|
d.maturity_date.as_('r_maturity_date'),
|
|
d.product.as_('r_product'),
|
|
d.party.as_('r_party'),
|
|
d.direction.as_('r_direction'),
|
|
d.state.as_('r_state'),
|
|
#d.quantity.as_('r_quantity'),
|
|
d.open_qty.as_('r_open_qty'),
|
|
d.price.as_('r_price'),
|
|
where=wh
|
|
)
|
|
|
|
return query
|
|
|
|
# ------------------------------------------------------------
|
|
# SEARCH NAME
|
|
# ------------------------------------------------------------
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
_, operator, operand, *extra = clause
|
|
return [
|
|
'OR',
|
|
('r_product', operator, operand, *extra),
|
|
('r_party', operator, operand, *extra),
|
|
]
|
|
|
|
class DerivativeReportContext(ModelView):
|
|
"Derivative Report Context"
|
|
__name__ = 'derivative.report.context'
|
|
|
|
trade_from = fields.Date("Trade Date From")
|
|
trade_to = fields.Date("Trade Date To")
|
|
|
|
maturity_from = fields.Date("Maturity From")
|
|
maturity_to = fields.Date("Maturity To")
|
|
|
|
product = fields.Many2One(
|
|
'product.product',
|
|
"Product"
|
|
)
|
|
|
|
party = fields.Many2One(
|
|
'party.party',
|
|
"Counterparty"
|
|
)
|
|
|
|
direction = fields.Selection([
|
|
('long', 'Long'),
|
|
('short', 'Short'),
|
|
], 'Direction')
|
|
|
|
state = fields.Selection([
|
|
('open', 'Open'),
|
|
('closed', 'Closed'),
|
|
], 'State')
|
|
|
|
open_only = fields.Boolean("Open Positions Only")
|
|
|
|
@classmethod
|
|
def default_trade_from(cls):
|
|
return datetime.date(1999, 1, 1)
|
|
|
|
@classmethod
|
|
def default_trade_to(cls):
|
|
pool = Pool()
|
|
Date = pool.get('ir.date')
|
|
return Date.today()
|