View Javadoc
1   /*
2    * MoneyWise: Finance Application
3    * Copyright 2012-2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.buckets;
18  
19  import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
20  import io.github.tonywasher.joceanus.metis.data.MetisDataFieldValue;
21  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataList;
22  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem;
23  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem.MetisFieldTableItem;
24  import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
25  import io.github.tonywasher.joceanus.metis.list.MetisListIndexed;
26  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.base.MoneyWiseXAnalysisEvent;
27  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.base.MoneyWiseXAnalysisHistory;
28  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.buckets.MoneyWiseXAnalysisInterfaces.MoneyWiseXAnalysisBucketPriced;
29  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.buckets.MoneyWiseXAnalysisInterfaces.MoneyWiseXAnalysisCursor;
30  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.values.MoneyWiseXAnalysisSecurityAttr;
31  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.values.MoneyWiseXAnalysisSecurityValues;
32  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicResource;
33  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePortfolio;
34  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurity;
35  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurityHolding;
36  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCurrency;
37  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseSecurityClass;
38  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseSecurityType;
39  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseStaticDataType;
40  import io.github.tonywasher.joceanus.moneywise.exc.MoneyWiseDataException;
41  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
42  import io.github.tonywasher.joceanus.oceanus.date.OceanusDate;
43  import io.github.tonywasher.joceanus.oceanus.date.OceanusDateRange;
44  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusDecimal;
45  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
46  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusPrice;
47  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRatio;
48  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusUnits;
49  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
50  
51  import java.util.Comparator;
52  import java.util.Currency;
53  import java.util.Iterator;
54  import java.util.List;
55  
56  /**
57   * The Security Bucket class.
58   */
59  public final class MoneyWiseXAnalysisSecurityBucket
60          implements MetisFieldTableItem, MoneyWiseXAnalysisBucketPriced {
61      /**
62       * Local Report fields.
63       */
64      private static final MetisFieldSet<MoneyWiseXAnalysisSecurityBucket> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseXAnalysisSecurityBucket.class);
65  
66      /*
67       * Declare Fields.
68       */
69      static {
70          FIELD_DEFS.declareLocalField(MoneyWiseXAnalysisBucketResource.ANALYSIS_NAME, MoneyWiseXAnalysisSecurityBucket::getAnalysis);
71          FIELD_DEFS.declareLocalField(MoneyWiseBasicResource.ASSETTYPE_SECURITYHOLDING, MoneyWiseXAnalysisSecurityBucket::getSecurityHolding);
72          FIELD_DEFS.declareLocalField(MoneyWiseStaticDataType.SECURITYTYPE, MoneyWiseXAnalysisSecurityBucket::getSecurityType);
73          FIELD_DEFS.declareLocalField(MoneyWiseStaticDataType.CURRENCY, MoneyWiseXAnalysisSecurityBucket::getCurrency);
74          FIELD_DEFS.declareLocalField(MoneyWiseXAnalysisBucketResource.BUCKET_BASEVALUES, MoneyWiseXAnalysisSecurityBucket::getBaseValues);
75          FIELD_DEFS.declareLocalField(MoneyWiseXAnalysisBucketResource.BUCKET_HISTORY, MoneyWiseXAnalysisSecurityBucket::getHistoryMap);
76          FIELD_DEFS.declareLocalFieldsForEnum(MoneyWiseXAnalysisSecurityAttr.class, MoneyWiseXAnalysisSecurityBucket::getAttributeValue);
77      }
78  
79      /**
80       * The analysis.
81       */
82      private final MoneyWiseXAnalysisHolder theAnalysis;
83  
84      /**
85       * The security holding.
86       */
87      private final MoneyWiseSecurityHolding theHolding;
88  
89      /**
90       * The security.
91       */
92      private final MoneyWiseSecurity theSecurity;
93  
94      /**
95       * The portfolio.
96       */
97      private final MoneyWisePortfolio thePortfolio;
98  
99      /**
100      * The currency.
101      */
102     private final MoneyWiseCurrency theCurrency;
103 
104     /**
105      * Is this a foreign currency?
106      */
107     private final boolean isForeignCurrency;
108 
109     /**
110      * Is this a stock option?
111      */
112     private final boolean isStockOption;
113 
114     /**
115      * The security type.
116      */
117     private final MoneyWiseSecurityType theCategory;
118 
119     /**
120      * Values.
121      */
122     private final MoneyWiseXAnalysisSecurityValues theValues;
123 
124     /**
125      * The base values.
126      */
127     private final MoneyWiseXAnalysisSecurityValues theBaseValues;
128 
129     /**
130      * History Map.
131      */
132     private final MoneyWiseXAnalysisHistory<MoneyWiseXAnalysisSecurityValues, MoneyWiseXAnalysisSecurityAttr> theHistory;
133 
134     /**
135      * Constructor.
136      *
137      * @param pAnalysis the analysis
138      * @param pHolding  the security holding
139      */
140     MoneyWiseXAnalysisSecurityBucket(final MoneyWiseXAnalysisHolder pAnalysis,
141                                      final MoneyWiseSecurityHolding pHolding) {
142         /* Store the details */
143         theHolding = pHolding;
144         theCurrency = pHolding.getAssetCurrency();
145         theSecurity = pHolding.getSecurity();
146         thePortfolio = pHolding.getPortfolio();
147         theAnalysis = pAnalysis;
148 
149         /* Obtain category */
150         theCategory = theSecurity.getCategory();
151 
152         /* Determine currency */
153         final MoneyWiseCurrency myReportingCurrency = pAnalysis.getCurrency();
154         final MoneyWiseCurrency myHoldingCurrency = pHolding.getAssetCurrency();
155 
156         /* Determine whether we are a foreign currency */
157         isForeignCurrency = !MetisDataDifference.isEqual(myReportingCurrency, myHoldingCurrency);
158         final Currency myCurrency = MoneyWiseXAnalysisAccountBucket.deriveCurrency(myHoldingCurrency);
159         final Currency myRepCurrency = MoneyWiseXAnalysisAccountBucket.deriveCurrency(myReportingCurrency);
160 
161         /* Note stockOption */
162         isStockOption = theSecurity.getUnderlyingStock() != null;
163 
164         /* Create the history map */
165         final MoneyWiseXAnalysisSecurityValues myValues = new MoneyWiseXAnalysisSecurityValues(myCurrency, myRepCurrency);
166         theHistory = new MoneyWiseXAnalysisHistory<>(myValues);
167 
168         /* Access the key value maps */
169         theValues = theHistory.getValues();
170         theBaseValues = theHistory.getBaseValues();
171 
172         /* Register for price Updates */
173         final MoneyWiseXAnalysisCursor myCursor = theAnalysis.getCursor();
174         myCursor.registerForPriceUpdates(this);
175 
176         /* Store the security price */
177         recordSecurityPrice();
178 
179         /* If this is a foreign currency account */
180         if (isForeignCurrency) {
181             /* Register for xchangeRate Updates */
182             myCursor.registerForXchgRateUpdates(this);
183 
184             /* Record the exchangeRate and copy to base */
185             recordExchangeRate();
186             theBaseValues.setValue(MoneyWiseXAnalysisSecurityAttr.EXCHANGERATE, getValue(MoneyWiseXAnalysisSecurityAttr.EXCHANGERATE));
187 
188             /* Create a new reportedValuation */
189             final OceanusMoney myReported = new OceanusMoney(theAnalysis.getCurrency().getCurrency());
190             theValues.setValue(MoneyWiseXAnalysisSecurityAttr.VALUATION, myReported);
191             theBaseValues.setValue(MoneyWiseXAnalysisSecurityAttr.VALUATION, myReported);
192         }
193     }
194 
195     /**
196      * Constructor.
197      *
198      * @param pAnalysis the analysis
199      * @param pBase     the underlying bucket
200      * @param pDate     the date for the bucket
201      */
202     private MoneyWiseXAnalysisSecurityBucket(final MoneyWiseXAnalysisHolder pAnalysis,
203                                              final MoneyWiseXAnalysisSecurityBucket pBase,
204                                              final OceanusDate pDate) {
205         /* Copy details from base */
206         theHolding = pBase.getSecurityHolding();
207         theCurrency = pBase.getCurrency();
208         theSecurity = pBase.getSecurity();
209         thePortfolio = pBase.getPortfolio();
210         theCategory = pBase.getSecurityType();
211         theAnalysis = pAnalysis;
212         isForeignCurrency = pBase.isForeignCurrency();
213         isStockOption = pBase.isStockOption();
214 
215         /* Access the relevant history */
216         theHistory = new MoneyWiseXAnalysisHistory<>(pBase.getHistoryMap(), pDate);
217 
218         /* Access the key value maps */
219         theValues = theHistory.getValues();
220         theBaseValues = theHistory.getBaseValues();
221     }
222 
223     /**
224      * Constructor.
225      *
226      * @param pAnalysis the analysis
227      * @param pBase     the underlying bucket
228      * @param pRange    the range for the bucket
229      */
230     private MoneyWiseXAnalysisSecurityBucket(final MoneyWiseXAnalysisHolder pAnalysis,
231                                              final MoneyWiseXAnalysisSecurityBucket pBase,
232                                              final OceanusDateRange pRange) {
233         /* Copy details from base */
234         theHolding = pBase.getSecurityHolding();
235         theCurrency = pBase.getCurrency();
236         theSecurity = pBase.getSecurity();
237         thePortfolio = pBase.getPortfolio();
238         theCategory = pBase.getSecurityType();
239         theAnalysis = pAnalysis;
240         isForeignCurrency = pBase.isForeignCurrency();
241         isStockOption = pBase.isStockOption();
242 
243         /* Access the relevant history */
244         theHistory = new MoneyWiseXAnalysisHistory<>(pBase.getHistoryMap(), pRange);
245 
246         /* Access the key value maps */
247         theValues = theHistory.getValues();
248         theBaseValues = theHistory.getBaseValues();
249     }
250 
251     @Override
252     public MetisFieldSet<MoneyWiseXAnalysisSecurityBucket> getDataFieldSet() {
253         return FIELD_DEFS;
254     }
255 
256     @Override
257     public String formatObject(final OceanusDataFormatter pFormatter) {
258         return toString();
259     }
260 
261     @Override
262     public Long getBucketId() {
263         return theHolding.getExternalId();
264     }
265 
266     @Override
267     public String toString() {
268         return getDecoratedName() + " " + theValues;
269     }
270 
271     /**
272      * Obtain the name.
273      *
274      * @return the name
275      */
276     public String getSecurityName() {
277         return theSecurity.getName();
278     }
279 
280     /**
281      * Obtain the decorated name.
282      *
283      * @return the decorated name
284      */
285     public String getDecoratedName() {
286         return theHolding.getName();
287     }
288 
289     /**
290      * Obtain the holding.
291      *
292      * @return the holding
293      */
294     public MoneyWiseSecurityHolding getSecurityHolding() {
295         return theHolding;
296     }
297 
298     @Override
299     public MoneyWiseSecurity getSecurity() {
300         return theSecurity;
301     }
302 
303     /**
304      * Obtain the portfolio.
305      *
306      * @return the portfolio
307      */
308     public MoneyWisePortfolio getPortfolio() {
309         return thePortfolio;
310     }
311 
312     @Override
313     public MoneyWiseCurrency getCurrency() {
314         return theCurrency;
315     }
316 
317     /**
318      * Is this a foreign currency?
319      *
320      * @return true/false
321      */
322     public boolean isForeignCurrency() {
323         return isForeignCurrency;
324     }
325 
326     @Override
327     public boolean isStockOption() {
328         return isStockOption;
329     }
330 
331     @Override
332     public Integer getIndexedId() {
333         return theSecurity.getIndexedId();
334     }
335 
336     /**
337      * Obtain the security type.
338      *
339      * @return the security type
340      */
341     public MoneyWiseSecurityType getSecurityType() {
342         return theCategory;
343     }
344 
345     /**
346      * Is this bucket idle?
347      *
348      * @return true/false
349      */
350     public boolean isIdle() {
351         return theHistory.isIdle();
352     }
353 
354     /**
355      * Obtain the analysis.
356      *
357      * @return the analysis
358      */
359     MoneyWiseXAnalysisHolder getAnalysis() {
360         return theAnalysis;
361     }
362 
363     /**
364      * Obtain date range.
365      *
366      * @return the range
367      */
368     public OceanusDateRange getDateRange() {
369         return theAnalysis.getDateRange();
370     }
371 
372     /**
373      * Obtain the value map.
374      *
375      * @return the value map
376      */
377     public MoneyWiseXAnalysisSecurityValues getValues() {
378         return theValues;
379     }
380 
381     /**
382      * Obtain the base value map.
383      *
384      * @return the base value map
385      */
386     public MoneyWiseXAnalysisSecurityValues getBaseValues() {
387         return theBaseValues;
388     }
389 
390     /**
391      * Obtain values for event.
392      *
393      * @param pEvent the event
394      * @return the values (or null)
395      */
396     public MoneyWiseXAnalysisSecurityValues getValuesForEvent(final MoneyWiseXAnalysisEvent pEvent) {
397         return theHistory.getValuesForEvent(pEvent);
398     }
399 
400     /**
401      * Obtain previous values for event.
402      *
403      * @param pEvent the event
404      * @return the values (or null)
405      */
406     public MoneyWiseXAnalysisSecurityValues getPreviousValuesForEvent(final MoneyWiseXAnalysisEvent pEvent) {
407         return theHistory.getPreviousValuesForEvent(pEvent);
408     }
409 
410     /**
411      * Obtain delta for event.
412      *
413      * @param pEvent the event
414      * @param pAttr  the attribute
415      * @return the delta (or null)
416      */
417     public OceanusDecimal getDeltaForEvent(final MoneyWiseXAnalysisEvent pEvent,
418                                            final MoneyWiseXAnalysisSecurityAttr pAttr) {
419         /* Obtain delta for transaction */
420         return theHistory.getDeltaValue(pEvent, pAttr);
421     }
422 
423     /**
424      * Obtain money delta for event.
425      *
426      * @param pEvent the event
427      * @param pAttr  the attribute
428      * @return the delta (or null)
429      */
430     public OceanusMoney getMoneyDeltaForEvent(final MoneyWiseXAnalysisEvent pEvent,
431                                               final MoneyWiseXAnalysisSecurityAttr pAttr) {
432         /* Obtain delta for transaction */
433         return theHistory.getDeltaMoneyValue(pEvent, pAttr);
434     }
435 
436     /**
437      * Obtain units delta for event.
438      *
439      * @param pEvent the event
440      * @param pAttr  the attribute
441      * @return the delta (or null)
442      */
443     public OceanusUnits getUnitsDeltaForEvent(final MoneyWiseXAnalysisEvent pEvent,
444                                               final MoneyWiseXAnalysisSecurityAttr pAttr) {
445         /* Obtain delta for transaction */
446         return theHistory.getDeltaUnitsValue(pEvent, pAttr);
447     }
448 
449     /**
450      * Obtain the history map.
451      *
452      * @return the history map
453      */
454     private MoneyWiseXAnalysisHistory<MoneyWiseXAnalysisSecurityValues, MoneyWiseXAnalysisSecurityAttr> getHistoryMap() {
455         return theHistory;
456     }
457 
458     /**
459      * Set Attribute.
460      *
461      * @param pAttr  the attribute
462      * @param pValue the value of the attribute
463      */
464     public void setValue(final MoneyWiseXAnalysisSecurityAttr pAttr,
465                          final Object pValue) {
466         /* Set the value into the list */
467         theValues.setValue(pAttr, pValue);
468     }
469 
470     /**
471      * Get an attribute value.
472      *
473      * @param pAttr the attribute
474      * @return the value to set
475      */
476     private Object getAttributeValue(final MoneyWiseXAnalysisSecurityAttr pAttr) {
477         /* Access value of object */
478         final Object myValue = getValue(pAttr);
479 
480         /* Return the value */
481         return myValue != null
482                 ? myValue
483                 : MetisDataFieldValue.SKIP;
484     }
485 
486     /**
487      * Obtain an attribute value.
488      *
489      * @param pAttr the attribute
490      * @return the value of the attribute or null
491      */
492     private Object getValue(final MoneyWiseXAnalysisSecurityAttr pAttr) {
493         /* Obtain the value */
494         return theValues.getValue(pAttr);
495     }
496 
497     /**
498      * Adjust Units.
499      *
500      * @param pDelta the delta
501      */
502     public void adjustUnits(final OceanusUnits pDelta) {
503         OceanusUnits myValue = theValues.getUnitsValue(MoneyWiseXAnalysisSecurityAttr.UNITS);
504         myValue = new OceanusUnits(myValue);
505         myValue.addUnits(pDelta);
506         setValue(MoneyWiseXAnalysisSecurityAttr.UNITS, myValue);
507     }
508 
509     /**
510      * Adjust ResidualCost.
511      *
512      * @param pDelta the delta
513      */
514     public void adjustResidualCost(final OceanusMoney pDelta) {
515         OceanusMoney myValue = theValues.getMoneyValue(MoneyWiseXAnalysisSecurityAttr.RESIDUALCOST);
516         myValue = new OceanusMoney(myValue);
517         myValue.addAmount(pDelta);
518         setValue(MoneyWiseXAnalysisSecurityAttr.RESIDUALCOST, myValue);
519     }
520 
521     /**
522      * Adjust RealisedGains.
523      *
524      * @param pDelta the delta
525      */
526     public void adjustRealisedGains(final OceanusMoney pDelta) {
527         OceanusMoney myValue = theValues.getMoneyValue(MoneyWiseXAnalysisSecurityAttr.REALISEDGAINS);
528         myValue = new OceanusMoney(myValue);
529         myValue.addAmount(pDelta);
530         setValue(MoneyWiseXAnalysisSecurityAttr.REALISEDGAINS, myValue);
531     }
532 
533     /**
534      * Adjust Dividends.
535      *
536      * @param pDelta the delta
537      */
538     public void adjustDividend(final OceanusMoney pDelta) {
539         OceanusMoney myValue = theValues.getMoneyValue(MoneyWiseXAnalysisSecurityAttr.DIVIDEND);
540         myValue = new OceanusMoney(myValue);
541         myValue.addAmount(pDelta);
542         setValue(MoneyWiseXAnalysisSecurityAttr.DIVIDEND, myValue);
543     }
544 
545     /**
546      * Adjust Funded.
547      *
548      * @param pDelta the delta
549      */
550     public void adjustFunded(final OceanusMoney pDelta) {
551         OceanusMoney myValue = theValues.getMoneyValue(MoneyWiseXAnalysisSecurityAttr.FUNDED);
552         myValue = new OceanusMoney(myValue);
553         myValue.addAmount(pDelta);
554         setValue(MoneyWiseXAnalysisSecurityAttr.FUNDED, myValue);
555     }
556 
557     /**
558      * Ensure that start date is set.
559      *
560      * @param pDate the startDate
561      */
562     public void ensureStartDate(final OceanusDate pDate) {
563         if (theValues.getValue(MoneyWiseXAnalysisSecurityAttr.STARTDATE) == null) {
564             setValue(MoneyWiseXAnalysisSecurityAttr.STARTDATE, pDate);
565         }
566     }
567 
568     @Override
569     public void calculateUnrealisedGains() {
570         /* Unrealised gains is VALUATION - RESIDUALCOST */
571         OceanusMoney myValue = theValues.getMoneyValue(MoneyWiseXAnalysisSecurityAttr.VALUATION);
572         myValue = new OceanusMoney(myValue);
573         myValue.subtractAmount(theValues.getMoneyValue(MoneyWiseXAnalysisSecurityAttr.RESIDUALCOST));
574         setValue(MoneyWiseXAnalysisSecurityAttr.UNREALISEDGAINS, myValue);
575     }
576 
577     @Override
578     public void recordSecurityPrice() {
579         /* Access the current price */
580         final MoneyWiseXAnalysisCursor myCursor = theAnalysis.getCursor();
581         OceanusPrice myPrice = myCursor.getCurrentPrice(getSecurity());
582 
583         /* If this is a stockOption */
584         if (isStockOption) {
585             /* Price is any positive difference between stockPrice and optioonPrice */
586             myPrice = new OceanusPrice(myPrice);
587             myPrice.subtractPrice(getSecurity().getOptionPrice());
588             if (!myPrice.isPositive()) {
589                 myPrice.setZero();
590             }
591         }
592 
593         /* If we are funded */
594         final OceanusMoney myFunded = new OceanusMoney(theValues.getMoneyValue(MoneyWiseXAnalysisSecurityAttr.FUNDED));
595         if (myFunded.isNonZero()) {
596             /* Set funded to zero */
597             theValues.setZeroMoney(MoneyWiseXAnalysisSecurityAttr.FUNDED);
598 
599             /* If we have zero units, honour autoUnits */
600             final MoneyWiseSecurityClass mySecClass = getSecurity().getCategoryClass();
601             final OceanusUnits myUnits = theValues.getUnitsValue(MoneyWiseXAnalysisSecurityAttr.UNITS);
602             if (myUnits.isZero() && mySecClass.isAutoUnits()) {
603                 final OceanusUnits myAutoUnits = OceanusUnits.getWholeUnits(mySecClass.getAutoUnits());
604                 theValues.setValue(MoneyWiseXAnalysisSecurityAttr.UNITS, myAutoUnits);
605             }
606         }
607 
608         /* Store the price */
609         theValues.setValue(MoneyWiseXAnalysisSecurityAttr.PRICE, myPrice);
610     }
611 
612     @Override
613     public void recordExchangeRate() {
614         final MoneyWiseXAnalysisCursor myCursor = theAnalysis.getCursor();
615         final OceanusRatio myRate = myCursor.getCurrentXchgRate(getSecurity().getAssetCurrency());
616         theValues.setValue(MoneyWiseXAnalysisSecurityAttr.EXCHANGERATE, myRate);
617     }
618 
619     @Override
620     public void registerEvent(final MoneyWiseXAnalysisEvent pEvent) {
621         /* Register the event in the history */
622         theHistory.registerEvent(pEvent, theValues);
623     }
624 
625     @Override
626     public void valueAsset() {
627         /* Access units and price */
628         final OceanusUnits myUnits = theValues.getUnitsValue(MoneyWiseXAnalysisSecurityAttr.UNITS);
629         final OceanusPrice myPrice = theValues.getPriceValue(MoneyWiseXAnalysisSecurityAttr.PRICE);
630 
631         /* Calculate the value */
632         final OceanusMoney myLocalValue = myPrice.unitsAtPrice(myUnits);
633         setValue(MoneyWiseXAnalysisSecurityAttr.VALUE, myLocalValue);
634 
635         /* Adjust the valuation */
636         adjustValuation();
637     }
638 
639     @Override
640     public void adjustValuation() {
641         /* Calculate the value of the asset */
642         OceanusMoney myBalance = theValues.getMoneyValue(MoneyWiseXAnalysisSecurityAttr.VALUE);
643 
644         /* If this is a foreign asset */
645         if (isForeignCurrency) {
646             /* Calculate the value in the local currency */
647             final OceanusRatio myRate = theValues.getRatioValue(MoneyWiseXAnalysisSecurityAttr.EXCHANGERATE);
648             myBalance = myBalance.convertCurrency(theAnalysis.getCurrency().getCurrency(), myRate);
649         }
650 
651         /* If we have a funded value */
652         final OceanusMoney myFunded = theValues.getMoneyValue(MoneyWiseXAnalysisSecurityAttr.FUNDED);
653         if (myFunded.isNonZero()) {
654             /* Add to valuation */
655             myBalance.addAmount(myFunded);
656         }
657 
658         /* Record the valuation */
659         theValues.setValue(MoneyWiseXAnalysisSecurityAttr.VALUATION, myBalance);
660     }
661 
662     @Override
663     public OceanusMoney getDeltaValuation() {
664         /* Determine the delta */
665         final OceanusMoney myDelta = new OceanusMoney(theValues.getMoneyValue(MoneyWiseXAnalysisSecurityAttr.VALUATION));
666         myDelta.subtractAmount(theHistory.getLastValues().getMoneyValue(MoneyWiseXAnalysisSecurityAttr.VALUATION));
667         return myDelta;
668     }
669 
670     /**
671      * Obtain the delta of unrealisedGains.
672      *
673      * @return the delta
674      */
675     public OceanusMoney getDeltaUnrealisedGains() {
676         /* Determine the delta */
677         final OceanusMoney myDelta = new OceanusMoney(theValues.getMoneyValue(MoneyWiseXAnalysisSecurityAttr.UNREALISEDGAINS));
678         myDelta.subtractAmount(theHistory.getLastValues().getMoneyValue(MoneyWiseXAnalysisSecurityAttr.UNREALISEDGAINS));
679         return myDelta;
680     }
681 
682     /**
683      * calculate the deltas for a priced asset.
684      */
685     private void calculateDeltas() {
686         /* Obtain a copy of the value */
687         OceanusMoney myValue = theValues.getMoneyValue(MoneyWiseXAnalysisSecurityAttr.VALUATION);
688         myValue = new OceanusMoney(myValue);
689 
690         /* Subtract any base value */
691         myValue.subtractAmount(theBaseValues.getMoneyValue(MoneyWiseXAnalysisSecurityAttr.VALUATION));
692 
693         /* Set the delta */
694         setValue(MoneyWiseXAnalysisSecurityAttr.VALUEDELTA, myValue);
695     }
696 
697     /**
698      * Adjust to base.
699      */
700     void adjustToBase() {
701         /* Adjust to base values */
702         theValues.adjustToBaseValues(theBaseValues);
703         theBaseValues.resetBaseValues();
704     }
705 
706     /**
707      * Is the bucket active?
708      *
709      * @return true/false
710      */
711     public boolean isActive() {
712         return theValues.isActive();
713     }
714 
715     /**
716      * SecurityBucket list class.
717      */
718     public static final class MoneyWiseXAnalysisSecurityBucketList
719             implements MetisFieldItem, MetisDataList<MoneyWiseXAnalysisSecurityBucket> {
720         /**
721          * Local Report fields.
722          */
723         private static final MetisFieldSet<MoneyWiseXAnalysisSecurityBucketList> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseXAnalysisSecurityBucketList.class);
724 
725         /*
726          * Declare Fields.
727          */
728         static {
729             FIELD_DEFS.declareLocalField(MoneyWiseXAnalysisBucketResource.ANALYSIS_NAME, MoneyWiseXAnalysisSecurityBucketList::getAnalysis);
730         }
731 
732         /**
733          * The analysis.
734          */
735         private final MoneyWiseXAnalysisHolder theAnalysis;
736 
737         /**
738          * The list.
739          */
740         private final MetisListIndexed<MoneyWiseXAnalysisSecurityBucket> theList;
741 
742         /**
743          * Construct a top-level List.
744          *
745          * @param pAnalysis the analysis
746          */
747         MoneyWiseXAnalysisSecurityBucketList(final MoneyWiseXAnalysisHolder pAnalysis) {
748             theAnalysis = pAnalysis;
749             theList = new MetisListIndexed<>();
750             theList.setComparator(Comparator.comparing(MoneyWiseXAnalysisSecurityBucket::getSecurity));
751         }
752 
753         /**
754          * Construct a dated List.
755          *
756          * @param pAnalysis the analysis
757          * @param pBase     the base list
758          * @param pDate     the Date
759          */
760         MoneyWiseXAnalysisSecurityBucketList(final MoneyWiseXAnalysisHolder pAnalysis,
761                                              final MoneyWiseXAnalysisSecurityBucketList pBase,
762                                              final OceanusDate pDate) {
763             /* Initialise class */
764             this(pAnalysis);
765 
766             /* Loop through the buckets */
767             final Iterator<MoneyWiseXAnalysisSecurityBucket> myIterator = pBase.iterator();
768             while (myIterator.hasNext()) {
769                 final MoneyWiseXAnalysisSecurityBucket myCurr = myIterator.next();
770 
771                 /* Access the bucket for this date */
772                 final MoneyWiseXAnalysisSecurityBucket myBucket = new MoneyWiseXAnalysisSecurityBucket(pAnalysis, myCurr, pDate);
773 
774                 /*
775                  * Ignore idle securities. Note that we must include securities that have been
776                  * closed in order to adjust Market Growth.
777                  */
778                 if (!myBucket.isIdle()) {
779                     /* Add to the list */
780                     theList.add(myBucket);
781                 }
782             }
783         }
784 
785         /**
786          * Construct a ranged List.
787          *
788          * @param pAnalysis the analysis
789          * @param pBase     the base list
790          * @param pRange    the Date Range
791          */
792         MoneyWiseXAnalysisSecurityBucketList(final MoneyWiseXAnalysisHolder pAnalysis,
793                                              final MoneyWiseXAnalysisSecurityBucketList pBase,
794                                              final OceanusDateRange pRange) {
795             /* Initialise class */
796             this(pAnalysis);
797 
798             /* Loop through the buckets */
799             final Iterator<MoneyWiseXAnalysisSecurityBucket> myIterator = pBase.iterator();
800             while (myIterator.hasNext()) {
801                 final MoneyWiseXAnalysisSecurityBucket myCurr = myIterator.next();
802 
803                 /* Access the bucket for this range */
804                 final MoneyWiseXAnalysisSecurityBucket myBucket = new MoneyWiseXAnalysisSecurityBucket(pAnalysis, myCurr, pRange);
805 
806                 /* If the bucket is non-idle or active */
807                 if (myBucket.isActive() || !myBucket.isIdle()) {
808                     /* Adjust to base and add to the list */
809                     myBucket.adjustToBase();
810                     theList.add(myBucket);
811                 }
812             }
813         }
814 
815         @Override
816         public MetisFieldSet<MoneyWiseXAnalysisSecurityBucketList> getDataFieldSet() {
817             return FIELD_DEFS;
818         }
819 
820         @Override
821         public List<MoneyWiseXAnalysisSecurityBucket> getUnderlyingList() {
822             return theList.getUnderlyingList();
823         }
824 
825         @Override
826         public String formatObject(final OceanusDataFormatter pFormatter) {
827             return getDataFieldSet().getName();
828         }
829 
830         /**
831          * Obtain the analysis.
832          *
833          * @return the analysis
834          */
835         MoneyWiseXAnalysisHolder getAnalysis() {
836             return theAnalysis;
837         }
838 
839         /**
840          * Obtain item by id.
841          *
842          * @param pId the id to lookup
843          * @return the item (or null if not present)
844          */
845         public MoneyWiseXAnalysisSecurityBucket findItemById(final Integer pId) {
846             /* Return results */
847             return theList.getItemById(pId);
848         }
849 
850         /**
851          * SortBuckets.
852          */
853         void sortBuckets() {
854             theList.sortList();
855         }
856 
857         /**
858          * Obtain the SecurityBucket for a given security holding.
859          *
860          * @param pHolding the security holding
861          * @return the bucket
862          */
863         public MoneyWiseXAnalysisSecurityBucket getBucket(final MoneyWiseSecurityHolding pHolding) {
864             /* Locate the bucket in the list */
865             final MoneyWiseSecurity mySecurity = pHolding.getSecurity();
866             MoneyWiseXAnalysisSecurityBucket myItem = findItemById(mySecurity.getIndexedId());
867 
868             /* If the item does not yet exist */
869             if (myItem == null) {
870                 /* Create the new bucket */
871                 myItem = new MoneyWiseXAnalysisSecurityBucket(theAnalysis, pHolding);
872 
873                 /* Add to the list */
874                 theList.add(myItem);
875             }
876 
877             /* Return the bucket */
878             return myItem;
879         }
880 
881         /**
882          * Mark active securities.
883          *
884          * @return true/false are there active securities?
885          * @throws OceanusException on error
886          */
887         boolean markActiveSecurities() throws OceanusException {
888             /* Loop through the buckets */
889             boolean areActive = false;
890             final Iterator<MoneyWiseXAnalysisSecurityBucket> myIterator = iterator();
891             while (myIterator.hasNext()) {
892                 final MoneyWiseXAnalysisSecurityBucket myCurr = myIterator.next();
893                 final MoneyWiseSecurity mySecurity = myCurr.getSecurity();
894 
895                 /* If we are active */
896                 if (myCurr.isActive()) {
897                     /* Set the security as relevant */
898                     mySecurity.setRelevant();
899                     areActive = true;
900                 }
901 
902                 /* If we are closed */
903                 if (Boolean.TRUE.equals(mySecurity.isClosed())) {
904                     /* Ensure that we have correct closed dates */
905                     mySecurity.adjustClosed();
906 
907                     /* If we are Relevant */
908                     if (mySecurity.isRelevant()
909                             && theAnalysis.getData().checkClosedAccounts()) {
910                         /* throw exception */
911                         throw new MoneyWiseDataException(myCurr, "Illegally closed security");
912                     }
913                 }
914             }
915 
916             /* Return active indication */
917             return areActive;
918         }
919     }
920 }