Initial import from Docker volume
This commit is contained in:
401
modules/purchase_trade/derivative.py
Normal file
401
modules/purchase_trade/derivative.py
Normal file
@@ -0,0 +1,401 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user