Mtm new
This commit is contained in:
@@ -21,7 +21,10 @@ PHYSICAL_VALUATION_TYPES = [
|
|||||||
'market',
|
'market',
|
||||||
]
|
]
|
||||||
DERIVATIVE_VALUATION_TYPES = ['derivative']
|
DERIVATIVE_VALUATION_TYPES = ['derivative']
|
||||||
ALL_VALUATION_TYPES = PHYSICAL_VALUATION_TYPES + DERIVATIVE_VALUATION_TYPES
|
MTM_VALUATION_TYPES = ['mtm']
|
||||||
|
ALL_VALUATION_TYPES = (
|
||||||
|
PHYSICAL_VALUATION_TYPES + DERIVATIVE_VALUATION_TYPES
|
||||||
|
+ MTM_VALUATION_TYPES)
|
||||||
|
|
||||||
|
|
||||||
class CTRMValuationContextMixin:
|
class CTRMValuationContextMixin:
|
||||||
@@ -249,7 +252,8 @@ class CTRMNetPosition(ModelSQL, ModelView):
|
|||||||
|
|
||||||
context = Transaction().context
|
context = Transaction().context
|
||||||
where = val.type.in_(
|
where = val.type.in_(
|
||||||
PHYSICAL_VALUATION_TYPES + DERIVATIVE_VALUATION_TYPES)
|
PHYSICAL_VALUATION_TYPES + DERIVATIVE_VALUATION_TYPES
|
||||||
|
+ MTM_VALUATION_TYPES)
|
||||||
if context.get('date'):
|
if context.get('date'):
|
||||||
where &= val.date == context['date']
|
where &= val.date == context['date']
|
||||||
if context.get('product'):
|
if context.get('product'):
|
||||||
@@ -268,14 +272,17 @@ class CTRMNetPosition(ModelSQL, ModelView):
|
|||||||
where &= val.state == context['state']
|
where &= val.state == context['state']
|
||||||
|
|
||||||
is_derivative = val.type.in_(DERIVATIVE_VALUATION_TYPES)
|
is_derivative = val.type.in_(DERIVATIVE_VALUATION_TYPES)
|
||||||
|
is_mtm = val.type.in_(MTM_VALUATION_TYPES)
|
||||||
physical_quantity = Case(
|
physical_quantity = Case(
|
||||||
(is_derivative, 0), else_=Coalesce(val.quantity, 0))
|
(is_derivative | is_mtm, 0), else_=Coalesce(val.quantity, 0))
|
||||||
derivative_quantity = Case(
|
derivative_quantity = Case(
|
||||||
(is_derivative, Coalesce(val.quantity, 0)), else_=0)
|
(is_derivative, Coalesce(val.quantity, 0)), else_=0)
|
||||||
physical_amount = Case(
|
physical_amount = Case(
|
||||||
(is_derivative, 0), else_=Coalesce(val.amount, 0))
|
(is_derivative | is_mtm, 0), else_=Coalesce(val.amount, 0))
|
||||||
derivative_amount = Case(
|
derivative_amount = Case(
|
||||||
(is_derivative, Coalesce(val.amount, 0)), else_=0)
|
(is_derivative, Coalesce(val.amount, 0)), else_=0)
|
||||||
|
net_quantity = Case(
|
||||||
|
(is_mtm, 0), else_=Coalesce(val.quantity, 0))
|
||||||
|
|
||||||
group_by = [
|
group_by = [
|
||||||
val.date,
|
val.date,
|
||||||
@@ -302,7 +309,7 @@ class CTRMNetPosition(ModelSQL, ModelView):
|
|||||||
val.strategy.as_('strategy'),
|
val.strategy.as_('strategy'),
|
||||||
Sum(physical_quantity).as_('physical_quantity'),
|
Sum(physical_quantity).as_('physical_quantity'),
|
||||||
Sum(derivative_quantity).as_('derivative_quantity'),
|
Sum(derivative_quantity).as_('derivative_quantity'),
|
||||||
Sum(Coalesce(val.quantity, 0)).as_('net_quantity'),
|
Sum(net_quantity).as_('net_quantity'),
|
||||||
Sum(physical_amount).as_('physical_amount'),
|
Sum(physical_amount).as_('physical_amount'),
|
||||||
Sum(derivative_amount).as_('derivative_amount'),
|
Sum(derivative_amount).as_('derivative_amount'),
|
||||||
Sum(Coalesce(val.amount, 0)).as_('net_amount'),
|
Sum(Coalesce(val.amount, 0)).as_('net_amount'),
|
||||||
@@ -396,7 +403,8 @@ class CTRMRealizedPnl(ModelSQL, ModelView):
|
|||||||
context = Transaction().context
|
context = Transaction().context
|
||||||
realization = context.get(
|
realization = context.get(
|
||||||
'realization', 'purchase_and_sale_invoiced')
|
'realization', 'purchase_and_sale_invoiced')
|
||||||
where = val.type.in_(ALL_VALUATION_TYPES)
|
where = val.type.in_(
|
||||||
|
PHYSICAL_VALUATION_TYPES + DERIVATIVE_VALUATION_TYPES)
|
||||||
if context.get('date'):
|
if context.get('date'):
|
||||||
where &= val.date == context['date']
|
where &= val.date == context['date']
|
||||||
if context.get('product'):
|
if context.get('product'):
|
||||||
@@ -487,6 +495,7 @@ class CTRMMtmPnl(ModelSQL, ModelView):
|
|||||||
('sale fee', 'Sale fee'),
|
('sale fee', 'Sale fee'),
|
||||||
('shipment fee', 'Shipment fee'),
|
('shipment fee', 'Shipment fee'),
|
||||||
('market', 'Market'),
|
('market', 'Market'),
|
||||||
|
('mtm', 'Mtm'),
|
||||||
('derivative', 'Derivative'),
|
('derivative', 'Derivative'),
|
||||||
], "Type")
|
], "Type")
|
||||||
reference = fields.Char("Reference")
|
reference = fields.Char("Reference")
|
||||||
@@ -610,8 +619,10 @@ class CTRMPnlExplain(ModelSQL, ModelView):
|
|||||||
'shipment fee',
|
'shipment fee',
|
||||||
])
|
])
|
||||||
is_derivative = val.type.in_(DERIVATIVE_VALUATION_TYPES)
|
is_derivative = val.type.in_(DERIVATIVE_VALUATION_TYPES)
|
||||||
|
is_mtm = val.type.in_(MTM_VALUATION_TYPES)
|
||||||
physical_pnl = Case(
|
physical_pnl = Case(
|
||||||
(is_fee | is_derivative, 0), else_=Coalesce(val.amount, 0))
|
(is_fee | is_derivative | is_mtm, 0),
|
||||||
|
else_=Coalesce(val.amount, 0))
|
||||||
fee_pnl = Case((is_fee, Coalesce(val.amount, 0)), else_=0)
|
fee_pnl = Case((is_fee, Coalesce(val.amount, 0)), else_=0)
|
||||||
derivative_pnl = Case(
|
derivative_pnl = Case(
|
||||||
(is_derivative, Coalesce(val.amount, 0)), else_=0)
|
(is_derivative, Coalesce(val.amount, 0)), else_=0)
|
||||||
@@ -737,7 +748,9 @@ class CTRMPnlDimension(ModelSQL, ModelView):
|
|||||||
val.unit.as_('unit'),
|
val.unit.as_('unit'),
|
||||||
val.state.as_('state'),
|
val.state.as_('state'),
|
||||||
val.strategy.as_('strategy'),
|
val.strategy.as_('strategy'),
|
||||||
Sum(Coalesce(val.quantity, 0)).as_('quantity'),
|
Sum(Case(
|
||||||
|
(val.type.in_(MTM_VALUATION_TYPES), 0),
|
||||||
|
else_=Coalesce(val.quantity, 0))).as_('quantity'),
|
||||||
Sum(Coalesce(val.amount, 0)).as_('amount'),
|
Sum(Coalesce(val.amount, 0)).as_('amount'),
|
||||||
Sum(Coalesce(val.mtm, 0)).as_('mtm'),
|
Sum(Coalesce(val.mtm, 0)).as_('mtm'),
|
||||||
Sum(Coalesce(val.mtm, 0) - Coalesce(val.amount, 0)).as_(
|
Sum(Coalesce(val.mtm, 0) - Coalesce(val.amount, 0)).as_(
|
||||||
@@ -1613,7 +1626,8 @@ class CTRMAccruals(ModelSQL, ModelView):
|
|||||||
else_='physical')
|
else_='physical')
|
||||||
context = Transaction().context
|
context = Transaction().context
|
||||||
where = (
|
where = (
|
||||||
val.type.in_(ALL_VALUATION_TYPES)
|
val.type.in_(
|
||||||
|
PHYSICAL_VALUATION_TYPES + DERIVATIVE_VALUATION_TYPES)
|
||||||
& ((val.lot == Null)
|
& ((val.lot == Null)
|
||||||
| ((lot.invoice_line == Null)
|
| ((lot.invoice_line == Null)
|
||||||
& (lot.sale_invoice_line == Null))))
|
& (lot.sale_invoice_line == Null))))
|
||||||
|
|||||||
@@ -1037,6 +1037,27 @@ class Lot(metaclass=PoolMeta):
|
|||||||
Move.save([nm])
|
Move.save([nm])
|
||||||
return nm.id
|
return nm.id
|
||||||
|
|
||||||
|
def create_shipment_move(self, shipment_origin):
|
||||||
|
Move = Pool().get('stock.move')
|
||||||
|
line = self.line
|
||||||
|
if not line or not getattr(line, 'purchase', None):
|
||||||
|
return
|
||||||
|
unit = self.lot_unit or self.lot_unit_line or line.unit
|
||||||
|
quantity = self.get_current_quantity_converted(0, unit)
|
||||||
|
move = Move()
|
||||||
|
move.from_location = line.purchase.from_location
|
||||||
|
move.to_location = line.purchase.to_location
|
||||||
|
move.product = self.lot_product
|
||||||
|
move.unit = unit
|
||||||
|
move.quantity = quantity
|
||||||
|
move.origin = line
|
||||||
|
move.shipment = shipment_origin
|
||||||
|
move.currency = line.currency
|
||||||
|
move.unit_price = line.unit_price
|
||||||
|
move.lot = self
|
||||||
|
Move.save([move])
|
||||||
|
return move
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, vlist):
|
def create(cls, vlist):
|
||||||
vlist = [x.copy() for x in vlist]
|
vlist = [x.copy() for x in vlist]
|
||||||
@@ -3095,10 +3116,16 @@ class LotShipping(Wizard):
|
|||||||
elif self.ship.shipment == 'int':
|
elif self.ship.shipment == 'int':
|
||||||
l.lot_shipment_internal = self.ship.shipment_internal
|
l.lot_shipment_internal = self.ship.shipment_internal
|
||||||
logger.info("IN_SHIPPING2:%s",l.move)
|
logger.info("IN_SHIPPING2:%s",l.move)
|
||||||
|
move = None
|
||||||
if not l.move:
|
if not l.move:
|
||||||
continue
|
move = l.create_shipment_move(shipment_origin)
|
||||||
|
if not move:
|
||||||
|
raise UserError(
|
||||||
|
"Cannot create shipment move for lot %s."
|
||||||
|
% l)
|
||||||
logger.info("IN_SHIPPING3:%s",r)
|
logger.info("IN_SHIPPING3:%s",r)
|
||||||
move = Move(l.move)
|
if not move:
|
||||||
|
move = Move(l.move)
|
||||||
move.shipment = shipment_origin
|
move.shipment = shipment_origin
|
||||||
Move.save([move])
|
Move.save([move])
|
||||||
linked_transit_move = move.get_linked_transit_move()
|
linked_transit_move = move.get_linked_transit_move()
|
||||||
|
|||||||
@@ -440,6 +440,60 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
with self.assertRaises(UserError):
|
with self.assertRaises(UserError):
|
||||||
wizard.transition_shipping()
|
wizard.transition_shipping()
|
||||||
|
|
||||||
|
def test_lot_shipping_physical_without_move_creates_shipment_move(self):
|
||||||
|
'linking a physical lot to shipment creates the missing stock move'
|
||||||
|
wizard = lot_module.LotShipping()
|
||||||
|
shipment = Mock(id=40)
|
||||||
|
wizard.ship = Mock(
|
||||||
|
shipment='in',
|
||||||
|
shipment_in=shipment,
|
||||||
|
shipment_out=None,
|
||||||
|
shipment_internal=None,
|
||||||
|
create_new_shipment=False,
|
||||||
|
quantity=None,
|
||||||
|
)
|
||||||
|
record = Mock(
|
||||||
|
id=2014,
|
||||||
|
r_lot_type='physic',
|
||||||
|
)
|
||||||
|
wizard.records = [record]
|
||||||
|
move = Mock()
|
||||||
|
move.get_linked_transit_move.return_value = None
|
||||||
|
lot = Mock(
|
||||||
|
id=2014,
|
||||||
|
move=None,
|
||||||
|
line=Mock(),
|
||||||
|
sale_line=None,
|
||||||
|
)
|
||||||
|
lot.get_current_quantity_converted.return_value = Decimal('22')
|
||||||
|
lot.getVlot_p.return_value = Mock()
|
||||||
|
lot.getVlot_s.return_value = None
|
||||||
|
lot.create_shipment_move.return_value = move
|
||||||
|
Lot = Mock()
|
||||||
|
Lot.return_value = lot
|
||||||
|
Lot.skip_quantity_consistency.return_value.__enter__ = Mock()
|
||||||
|
Lot.skip_quantity_consistency.return_value.__exit__ = Mock()
|
||||||
|
LotQt = Mock()
|
||||||
|
Move = Mock()
|
||||||
|
|
||||||
|
pool = Mock()
|
||||||
|
pool.get.side_effect = [Lot, LotQt, Move]
|
||||||
|
with patch('trytond.modules.purchase_trade.lot.Pool',
|
||||||
|
return_value=pool):
|
||||||
|
state = wizard.transition_shipping()
|
||||||
|
|
||||||
|
self.assertEqual(state, 'end')
|
||||||
|
self.assertEqual(lot.lot_shipment_in, shipment)
|
||||||
|
lot.create_shipment_move.assert_called_once_with(
|
||||||
|
'stock.shipment.in,40')
|
||||||
|
self.assertEqual(move.shipment, 'stock.shipment.in,40')
|
||||||
|
Move.save.assert_called_once_with([move])
|
||||||
|
lot.updateVirtualPart.assert_called_once_with(
|
||||||
|
Decimal('-22'), 'stock.shipment.in,40', None)
|
||||||
|
Lot.save.assert_called()
|
||||||
|
Lot.assert_lines_quantity_consistency.assert_called_once_with([
|
||||||
|
lot.line])
|
||||||
|
|
||||||
def test_lot_shipping_scheduled_lotqt_keeps_planned_locations(self):
|
def test_lot_shipping_scheduled_lotqt_keeps_planned_locations(self):
|
||||||
'scheduled lot.qt keeps the planned from and to locations'
|
'scheduled lot.qt keeps the planned from and to locations'
|
||||||
wizard = lot_module.LotShipping()
|
wizard = lot_module.LotShipping()
|
||||||
@@ -556,8 +610,8 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
Decimal('-349167.53'))
|
Decimal('-349167.53'))
|
||||||
|
|
||||||
@with_transaction()
|
@with_transaction()
|
||||||
def test_strategy_mtm_lines_split_by_curve_with_previous_price(self):
|
def test_strategy_mtm_lines_are_separate_from_realized_price(self):
|
||||||
'strategy MTM creates one valuation line per curve with previous price'
|
'strategy MTM creates separate curve lines and preserves realized line'
|
||||||
Valuation = Pool().get('valuation.valuation')
|
Valuation = Pool().get('valuation.valuation')
|
||||||
valuation_date = datetime.date(2026, 6, 5)
|
valuation_date = datetime.date(2026, 6, 5)
|
||||||
previous_date = datetime.date(2026, 6, 4)
|
previous_date = datetime.date(2026, 6, 4)
|
||||||
@@ -583,6 +637,7 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
price_index=curve_b,
|
price_index=curve_b,
|
||||||
ratio=Decimal('40')),
|
ratio=Decimal('40')),
|
||||||
])
|
])
|
||||||
|
line.mtm = [strategy]
|
||||||
values = {
|
values = {
|
||||||
'type': 'pur. priced',
|
'type': 'pur. priced',
|
||||||
'price': Decimal('10'),
|
'price': Decimal('10'),
|
||||||
@@ -596,22 +651,28 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
|
|
||||||
with patch('trytond.modules.purchase_trade.valuation.Pool') as PoolMock:
|
with patch('trytond.modules.purchase_trade.valuation.Pool') as PoolMock:
|
||||||
PoolMock.return_value.get.return_value = price_value
|
PoolMock.return_value.get.return_value = price_value
|
||||||
Valuation._append_strategy_mtm_lines(
|
Valuation._append_pnl_values(target, values, line)
|
||||||
target, values, strategy, line)
|
|
||||||
|
|
||||||
self.assertEqual(len(target), 2)
|
self.assertEqual(len(target), 3)
|
||||||
self.assertEqual(target[0]['mtm_curve'], curve_a.id)
|
self.assertEqual(target[0], values)
|
||||||
self.assertEqual(target[0]['amount'], Decimal('-60.00'))
|
self.assertEqual(target[1]['type'], 'mtm')
|
||||||
self.assertEqual(target[0]['mtm_price'], Decimal('60.0000'))
|
self.assertEqual(target[1]['mtm_curve'], curve_a.id)
|
||||||
self.assertEqual(target[0]['mtm_price_prev'], Decimal('54.0000'))
|
self.assertEqual(target[1]['price'], None)
|
||||||
self.assertEqual(target[0]['amount_prev'], Decimal('-540.00'))
|
self.assertEqual(target[1]['amount'], Decimal('0'))
|
||||||
self.assertEqual(target[0]['mtm'], Decimal('-600.00'))
|
self.assertEqual(target[1]['quantity'], Decimal('10'))
|
||||||
self.assertEqual(target[1]['mtm_curve'], curve_b.id)
|
self.assertEqual(target[1]['mtm_price'], Decimal('100.0000'))
|
||||||
self.assertEqual(target[1]['amount'], Decimal('-40.00'))
|
self.assertEqual(target[1]['mtm_price_prev'], Decimal('90.0000'))
|
||||||
self.assertEqual(target[1]['mtm_price'], Decimal('20.0000'))
|
self.assertEqual(target[1]['amount_prev'], Decimal('-540.00'))
|
||||||
self.assertEqual(target[1]['mtm_price_prev'], Decimal('18.0000'))
|
self.assertEqual(target[1]['mtm'], Decimal('-600.00'))
|
||||||
self.assertEqual(target[1]['amount_prev'], Decimal('-180.00'))
|
self.assertEqual(target[2]['type'], 'mtm')
|
||||||
self.assertEqual(target[1]['mtm'], Decimal('-200.00'))
|
self.assertEqual(target[2]['mtm_curve'], curve_b.id)
|
||||||
|
self.assertEqual(target[2]['price'], None)
|
||||||
|
self.assertEqual(target[2]['amount'], Decimal('0'))
|
||||||
|
self.assertEqual(target[2]['quantity'], Decimal('10'))
|
||||||
|
self.assertEqual(target[2]['mtm_price'], Decimal('50.0000'))
|
||||||
|
self.assertEqual(target[2]['mtm_price_prev'], Decimal('45.0000'))
|
||||||
|
self.assertEqual(target[2]['amount_prev'], Decimal('-180.00'))
|
||||||
|
self.assertEqual(target[2]['mtm'], Decimal('-200.00'))
|
||||||
|
|
||||||
def test_purchase_pnl_uses_partial_lotqt_match_quantity(self):
|
def test_purchase_pnl_uses_partial_lotqt_match_quantity(self):
|
||||||
'open matched purchase and sale pnl use the matched lot.qt quantity'
|
'open matched purchase and sale pnl use the matched lot.qt quantity'
|
||||||
@@ -1095,6 +1156,7 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
Valuation._get_generate_types('derivatives'),
|
Valuation._get_generate_types('derivatives'),
|
||||||
{'derivative'})
|
{'derivative'})
|
||||||
self.assertIn('pur. priced', Valuation._get_generate_types('goods'))
|
self.assertIn('pur. priced', Valuation._get_generate_types('goods'))
|
||||||
|
self.assertIn('mtm', Valuation._get_generate_types('goods'))
|
||||||
|
|
||||||
def test_filter_values_by_types_keeps_matching_entries_only(self):
|
def test_filter_values_by_types_keeps_matching_entries_only(self):
|
||||||
'type filtering keeps only the requested valuation entries'
|
'type filtering keeps only the requested valuation entries'
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ VALTYPE = [
|
|||||||
('sale fee', 'Sale fee'),
|
('sale fee', 'Sale fee'),
|
||||||
('shipment fee', 'Shipment fee'),
|
('shipment fee', 'Shipment fee'),
|
||||||
('market', 'Market'),
|
('market', 'Market'),
|
||||||
|
('mtm', 'Mtm'),
|
||||||
('derivative', 'Derivative'),
|
('derivative', 'Derivative'),
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -75,7 +76,7 @@ class ValuationBase(ModelSQL):
|
|||||||
'fees': {'line fee', 'pur. fee', 'sale fee', 'shipment fee'},
|
'fees': {'line fee', 'pur. fee', 'sale fee', 'shipment fee'},
|
||||||
'goods': {
|
'goods': {
|
||||||
'priced', 'pur. priced', 'pur. efp',
|
'priced', 'pur. priced', 'pur. efp',
|
||||||
'sale priced', 'sale efp', 'market',
|
'sale priced', 'sale efp', 'market', 'mtm',
|
||||||
},
|
},
|
||||||
'derivatives': {'derivative'},
|
'derivatives': {'derivative'},
|
||||||
}
|
}
|
||||||
@@ -573,11 +574,6 @@ class ValuationBase(ModelSQL):
|
|||||||
scenario.valuation_date,
|
scenario.valuation_date,
|
||||||
line.unit,
|
line.unit,
|
||||||
strategy.currency)
|
strategy.currency)
|
||||||
if component.ratio:
|
|
||||||
ratio = Decimal(component.ratio) / Decimal(100)
|
|
||||||
value *= ratio
|
|
||||||
if previous is not None:
|
|
||||||
previous *= ratio
|
|
||||||
return round(value, 4), (
|
return round(value, 4), (
|
||||||
round(previous, 4) if previous is not None else None)
|
round(previous, 4) if previous is not None else None)
|
||||||
|
|
||||||
@@ -587,6 +583,34 @@ class ValuationBase(ModelSQL):
|
|||||||
return value
|
return value
|
||||||
return round(Decimal(value) * share, digits)
|
return round(Decimal(value) * share, digits)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _mtm_sign(cls, values):
|
||||||
|
amount = values.get('amount')
|
||||||
|
if amount and amount < 0:
|
||||||
|
return Decimal(-1)
|
||||||
|
if not amount and values.get('type') in {'pur. priced', 'pur. efp'}:
|
||||||
|
return Decimal(-1)
|
||||||
|
return Decimal(1)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _mtm_line_values(cls, values, strategy, mtm_price, mtm_amount,
|
||||||
|
mtm_curve=None, mtm_price_prev=None, mtm_amount_prev=None):
|
||||||
|
line_values = dict(values)
|
||||||
|
line_values.update({
|
||||||
|
'type': 'mtm',
|
||||||
|
'reference': 'MTM/%s' % (values.get('reference') or ''),
|
||||||
|
'price': None,
|
||||||
|
'amount': Decimal(0),
|
||||||
|
'base_amount': Decimal(0),
|
||||||
|
'mtm_price': mtm_price,
|
||||||
|
'mtm_price_prev': mtm_price_prev,
|
||||||
|
'mtm_curve': mtm_curve,
|
||||||
|
'amount_prev': mtm_amount_prev,
|
||||||
|
'mtm': mtm_amount,
|
||||||
|
'strategy': strategy,
|
||||||
|
})
|
||||||
|
return line_values
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _append_strategy_mtm_lines(cls, target, values, strategy, line):
|
def _append_strategy_mtm_lines(cls, target, values, strategy, line):
|
||||||
components = list(strategy.components or [])
|
components = list(strategy.components or [])
|
||||||
@@ -595,76 +619,53 @@ class ValuationBase(ModelSQL):
|
|||||||
if component.price_source_type == 'curve'
|
if component.price_source_type == 'curve'
|
||||||
and component.price_index]
|
and component.price_index]
|
||||||
if not curve_components:
|
if not curve_components:
|
||||||
line_values = dict(values)
|
target.append(cls._mtm_line_values(
|
||||||
line_values['mtm_price'] = cls._get_strategy_mtm_price(
|
values,
|
||||||
strategy, line)
|
strategy,
|
||||||
line_values['amount_prev'] = None
|
cls._get_strategy_mtm_price(strategy, line),
|
||||||
line_values['mtm'] = cls._signed_strategy_mtm(
|
cls._signed_strategy_mtm(values, strategy, line)))
|
||||||
values, strategy, line)
|
|
||||||
line_values['strategy'] = strategy
|
|
||||||
target.append(line_values)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
curve_component_ids = {id(component) for component in curve_components}
|
curve_component_ids = {id(component) for component in curve_components}
|
||||||
total_weight = sum(
|
|
||||||
cls._component_weight(component)
|
|
||||||
for component in components) or Decimal(100)
|
|
||||||
total_mtm_price = cls._get_strategy_mtm_price(strategy, line)
|
total_mtm_price = cls._get_strategy_mtm_price(strategy, line)
|
||||||
curve_mtm_price = Decimal(0)
|
curve_mtm_price = Decimal(0)
|
||||||
signed_factor = Decimal(1)
|
signed_factor = cls._mtm_sign(values)
|
||||||
amount = values.get('amount')
|
|
||||||
if amount and amount < 0:
|
|
||||||
signed_factor = Decimal(-1)
|
|
||||||
elif not amount and values.get('type') in {'pur. priced', 'pur. efp'}:
|
|
||||||
signed_factor = Decimal(-1)
|
|
||||||
|
|
||||||
for component in curve_components:
|
for component in curve_components:
|
||||||
share = cls._component_weight(component) / total_weight
|
ratio = cls._component_weight(component) / Decimal(100)
|
||||||
mtm_price, mtm_price_prev = cls._curve_component_price(
|
mtm_price, mtm_price_prev = cls._curve_component_price(
|
||||||
component, line, strategy)
|
component, line, strategy)
|
||||||
curve_mtm_price += mtm_price
|
weighted_mtm_price = mtm_price * ratio
|
||||||
line_values = dict(values)
|
curve_mtm_price += weighted_mtm_price
|
||||||
line_values['price'] = cls._split_value(
|
mtm_amount_prev = (
|
||||||
values.get('price'), share, digits=4)
|
|
||||||
line_values['amount'] = cls._split_value(
|
|
||||||
values.get('amount'), share)
|
|
||||||
line_values['base_amount'] = cls._split_value(
|
|
||||||
values.get('base_amount'), share)
|
|
||||||
line_values['mtm_price'] = mtm_price
|
|
||||||
line_values['mtm_price_prev'] = mtm_price_prev
|
|
||||||
line_values['mtm_curve'] = component.price_index.id
|
|
||||||
line_values['amount_prev'] = (
|
|
||||||
round(
|
round(
|
||||||
mtm_price_prev * Decimal(values['quantity'])
|
mtm_price_prev * ratio * Decimal(values['quantity'])
|
||||||
* signed_factor, 2)
|
* signed_factor, 2)
|
||||||
if mtm_price_prev is not None else None)
|
if mtm_price_prev is not None else None)
|
||||||
line_values['mtm'] = round(
|
target.append(cls._mtm_line_values(
|
||||||
mtm_price * Decimal(values['quantity']) * signed_factor, 2)
|
values,
|
||||||
line_values['strategy'] = strategy
|
strategy,
|
||||||
target.append(line_values)
|
mtm_price,
|
||||||
|
round(
|
||||||
|
mtm_price * ratio * Decimal(values['quantity'])
|
||||||
|
* signed_factor, 2),
|
||||||
|
mtm_curve=component.price_index.id,
|
||||||
|
mtm_price_prev=mtm_price_prev,
|
||||||
|
mtm_amount_prev=mtm_amount_prev))
|
||||||
|
|
||||||
residual_weight = sum(
|
residual_weight = sum(
|
||||||
cls._component_weight(component)
|
cls._component_weight(component)
|
||||||
for component in components
|
for component in components
|
||||||
if id(component) not in curve_component_ids)
|
if id(component) not in curve_component_ids)
|
||||||
if residual_weight:
|
if residual_weight:
|
||||||
share = residual_weight / total_weight
|
|
||||||
mtm_price = round(total_mtm_price - curve_mtm_price, 4)
|
mtm_price = round(total_mtm_price - curve_mtm_price, 4)
|
||||||
line_values = dict(values)
|
target.append(cls._mtm_line_values(
|
||||||
line_values['price'] = cls._split_value(
|
values,
|
||||||
values.get('price'), share, digits=4)
|
strategy,
|
||||||
line_values['amount'] = cls._split_value(
|
mtm_price,
|
||||||
values.get('amount'), share)
|
round(
|
||||||
line_values['base_amount'] = cls._split_value(
|
mtm_price * Decimal(values['quantity']) * signed_factor,
|
||||||
values.get('base_amount'), share)
|
2)))
|
||||||
line_values['mtm_price'] = mtm_price
|
|
||||||
line_values['mtm_price_prev'] = None
|
|
||||||
line_values['mtm_curve'] = None
|
|
||||||
line_values['amount_prev'] = None
|
|
||||||
line_values['mtm'] = round(
|
|
||||||
mtm_price * Decimal(values['quantity']) * signed_factor, 2)
|
|
||||||
line_values['strategy'] = strategy
|
|
||||||
target.append(line_values)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_basis_component_total(record):
|
def _get_basis_component_total(record):
|
||||||
@@ -813,6 +814,7 @@ class ValuationBase(ModelSQL):
|
|||||||
def _append_pnl_values(cls, price_lines, values, mtm_source):
|
def _append_pnl_values(cls, price_lines, values, mtm_source):
|
||||||
if (values and getattr(mtm_source, 'mtm', None)
|
if (values and getattr(mtm_source, 'mtm', None)
|
||||||
and cls._supports_strategy_mtm(values)):
|
and cls._supports_strategy_mtm(values)):
|
||||||
|
price_lines.append(values)
|
||||||
for strat in mtm_source.mtm:
|
for strat in mtm_source.mtm:
|
||||||
cls._append_strategy_mtm_lines(
|
cls._append_strategy_mtm_lines(
|
||||||
price_lines, values, strat, mtm_source)
|
price_lines, values, strat, mtm_source)
|
||||||
@@ -1014,26 +1016,14 @@ class ValuationBase(ModelSQL):
|
|||||||
state='unfixed',
|
state='unfixed',
|
||||||
pnl_type='sale priced'
|
pnl_type='sale priced'
|
||||||
)
|
)
|
||||||
if sale_line.mtm and cls._supports_strategy_mtm(values):
|
cls._append_pnl_values(price_lines, values, sale_line)
|
||||||
for strat in sale_line.mtm:
|
|
||||||
cls._append_strategy_mtm_lines(
|
|
||||||
price_lines, values, strat, sale_line)
|
|
||||||
else:
|
|
||||||
if values:
|
|
||||||
price_lines.append(values)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for pc in summaries:
|
for pc in summaries:
|
||||||
values = cls._build_basis_pnl_from_sale_line(
|
values = cls._build_basis_pnl_from_sale_line(
|
||||||
sale_line=sale_line, lot=lot, pc=pc,
|
sale_line=sale_line, lot=lot, pc=pc,
|
||||||
extra_price=premium_delta)
|
extra_price=premium_delta)
|
||||||
if sale_line.mtm and cls._supports_strategy_mtm(values):
|
cls._append_pnl_values(price_lines, values, sale_line)
|
||||||
for strat in sale_line.mtm:
|
|
||||||
cls._append_strategy_mtm_lines(
|
|
||||||
price_lines, values, strat, sale_line)
|
|
||||||
else:
|
|
||||||
if values:
|
|
||||||
price_lines.append(values)
|
|
||||||
|
|
||||||
elif sale_line.price_type in ('priced', 'efp'):
|
elif sale_line.price_type in ('priced', 'efp'):
|
||||||
price = cls._get_sale_lot_price(sale_line, lot)
|
price = cls._get_sale_lot_price(sale_line, lot)
|
||||||
@@ -1046,13 +1036,7 @@ class ValuationBase(ModelSQL):
|
|||||||
state='fixed' if sale_line.price_type == 'priced' else 'not fixed',
|
state='fixed' if sale_line.price_type == 'priced' else 'not fixed',
|
||||||
pnl_type=f'sale {sale_line.price_type}'
|
pnl_type=f'sale {sale_line.price_type}'
|
||||||
)
|
)
|
||||||
if sale_line.mtm and cls._supports_strategy_mtm(values):
|
cls._append_pnl_values(price_lines, values, sale_line)
|
||||||
for strat in sale_line.mtm:
|
|
||||||
cls._append_strategy_mtm_lines(
|
|
||||||
price_lines, values, strat, sale_line)
|
|
||||||
else:
|
|
||||||
if values:
|
|
||||||
price_lines.append(values)
|
|
||||||
|
|
||||||
return price_lines
|
return price_lines
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<field name="fee" optional="1"/>
|
<field name="fee" optional="1"/>
|
||||||
<field name="type"
|
<field name="type"
|
||||||
widget="badge"
|
widget="badge"
|
||||||
badge_colors="priced:#2563eb,Price:#2563eb,pur. priced:#1d4ed8,Pur. price:#1d4ed8,pur. efp:#38bdf8,Pur. efp:#38bdf8,sale priced:#16a34a,Sale price:#16a34a,sale efp:#86efac,Sale efp:#86efac,market:#64748b,Market:#64748b,line fee:#d97706,Line fee:#d97706,pur. fee:#f59e0b,Pur. fee:#f59e0b,sale fee:#fb923c,Sale fee:#fb923c,shipment fee:#0f766e,Shipment fee:#0f766e,derivative:#8b5cf6,Derivative:#8b5cf6,*:#94a3b8"/>
|
badge_colors="priced:#2563eb,Price:#2563eb,pur. priced:#1d4ed8,Pur. price:#1d4ed8,pur. efp:#38bdf8,Pur. efp:#38bdf8,sale priced:#16a34a,Sale price:#16a34a,sale efp:#86efac,Sale efp:#86efac,market:#64748b,Market:#64748b,line fee:#d97706,Line fee:#d97706,pur. fee:#f59e0b,Pur. fee:#f59e0b,sale fee:#fb923c,Sale fee:#fb923c,shipment fee:#0f766e,Shipment fee:#0f766e,mtm:#9f1239,Mtm:#9f1239,derivative:#8b5cf6,Derivative:#8b5cf6,*:#94a3b8"/>
|
||||||
<field name="reference"/>
|
<field name="reference"/>
|
||||||
<field name="counterparty"/>
|
<field name="counterparty"/>
|
||||||
<field name="state"/>
|
<field name="state"/>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ this repository contains the full copyright notices and license terms. -->
|
|||||||
<field name="fee" optional="1"/>
|
<field name="fee" optional="1"/>
|
||||||
<field name="type"
|
<field name="type"
|
||||||
widget="badge"
|
widget="badge"
|
||||||
badge_colors="priced:#2563eb,Price:#2563eb,pur. priced:#1d4ed8,Pur. price:#1d4ed8,pur. efp:#38bdf8,Pur. efp:#38bdf8,sale priced:#16a34a,Sale price:#16a34a,sale efp:#86efac,Sale efp:#86efac,market:#64748b,Market:#64748b,line fee:#d97706,Line fee:#d97706,pur. fee:#f59e0b,Pur. fee:#f59e0b,sale fee:#fb923c,Sale fee:#fb923c,shipment fee:#0f766e,Shipment fee:#0f766e,derivative:#8b5cf6,Derivative:#8b5cf6,*:#94a3b8"/>
|
badge_colors="priced:#2563eb,Price:#2563eb,pur. priced:#1d4ed8,Pur. price:#1d4ed8,pur. efp:#38bdf8,Pur. efp:#38bdf8,sale priced:#16a34a,Sale price:#16a34a,sale efp:#86efac,Sale efp:#86efac,market:#64748b,Market:#64748b,line fee:#d97706,Line fee:#d97706,pur. fee:#f59e0b,Pur. fee:#f59e0b,sale fee:#fb923c,Sale fee:#fb923c,shipment fee:#0f766e,Shipment fee:#0f766e,mtm:#9f1239,Mtm:#9f1239,derivative:#8b5cf6,Derivative:#8b5cf6,*:#94a3b8"/>
|
||||||
<field name="reference"/>
|
<field name="reference"/>
|
||||||
<field name="counterparty"/>
|
<field name="counterparty"/>
|
||||||
<field name="state"/>
|
<field name="state"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user