#  Public and for release, CBEC v3.2.06 Build 4231+ [29th September, 2025]
#
#  Copyright(c) Ivyware Pty Ltd 2018-25  (all rights reserved)
#               MELBOURNE, VICTORIA, AUSTRALIA, 3000
#
#  This file is provided as-is by Ivyware.  No claims are made as
#  to fitness for any particular purpose.  No warranties of any kind
#  are expressed or implied.  The recipient agrees to determine
#  applicability of information provided.
#
#  Ivyware hereby grants the right to freely use the information
#  supplied in this file for the creation of Python Advisor and Scanner
#  scripts supporting the Chartboard Application, and to make copies of
#  this file in any form for internal or external distribution as long as
#  this notice remains attached.
#
#  No waranty or suitability for purpose is implied.
#
#  Chartboard Extension Classes (CBEC) for Python Advisor and Scanner
#  Automation scripts
#  NOTES: Set of aggregation classes based on Chartboard python scripting
#         extension functions.  Advisor and Scanner environments common.
#         Download latest version from
#         https://www.ivyware.com.au/PythonScripts/PythonCBEC.pyw
#       : The supporting TestPythonCBEC.pyw script is provided as both a test
#         and sample to run over your charts. Exposes most of the Chartboard
#         callbacks that can be used from the Advisor and Scanner scripts.
#         Download latest version from
#         https://www.ivyware.com.au/PythonScripts/TestPythonCBEC.pyw
#       : Advisor and Scanner scripts run under functionally separate Chartboard
#         environments (CViewTab's).  For this reason Advisor scripts are
#         allocated the python extension <name>.pya and Scanner scripts
#         are allocated the python extension <name>.pys  Both can reference
#         this PythonCBEC file
#       : Requirement is for python 3.8 to be installed
#       : Can be enhanced as circumstances dictate.  However, it should be
#         be renamed given each successive Chartboard release over-writes this
#         file.
#    ***: CBEC under development and subject to change without notice***
#
import string
import sys
from tkinter.tix import INTEGER
from xml.dom.pulldom import SAX2DOM
import P2Draw
import P2Model
import P2Series
import P2Seriesob
import P2Chart
import P2Stack               # Only referenced from Advisor scripts (*.pya)
import P2Scanner             # Only referenced from Scanner scripts (*.pys) 
import P2View
import P2Root
import P2Helpers
import ctypes                # An included library with Python install.   
from   datetime import datetime

######################
#   Constants - Period Units or Bar Interval (Chartboard internals)
PUNITS_Default: int = 0
PUNITS_Year: int = 1
PUNITS_Quarter: int = 2
PUNITS_Month: int = 3
PUNITS_Week: int = 4
PUNITS_Day: int = 5

######################
#   DSeriesob base class
#   NOTES: Some DSeries support the automatic fitting of DSeries objects
#          according to the calculated value.  Examples include "Harmonics"
#          and "Reversals"
#        : Charts support multiple DSeries and hence it is therefore possible
#          for Charts to support multiple DSeries object types.  Refer OHLCvs Charts.
#        : Usually generated via DSeries<type>.<object>Factory()
#        : Base class and derivatives usable from both Advisor and Scanner
#          environments
class DSeriesob:
    def __init__ ( self, hDSeriesob, sTypeob, hRefob, sRefVerb ):
        self.hDSeriesob = hDSeriesob   # Reference handle for this DSeriesob
        self.sTypeob: str  = sTypeob   # Type of DSeriesob 'Harmonics', 'Reversals' etc
        #self.hDSeriesob   = P2Series.GetObject(self.hSeries, self.sTypeob, hRefob, sRefVerb )
    # Validity of contained DSeriesob
    def IsEmpty ( self ) -> bool:
        if self.hDSeriesob == 0 :
            return True
        return False
    # Select both raw and calculated values from DSeriesob
    # NOTES: NULL return flags no-data or request out of range
    def GetValue_d(self, sValueName) -> float:
        return P2Seriesob.GetValue_d(self.hDSeriesob, sValueName)
    def GetValue_i(self,sValueName) -> int:
        return P2Seriesob.GetValue_i(self.hDSeriesob,sValueName)
    def GetValue_b(self,sValueName) -> bool:
        return P2Seriesob.GetValue_b(self.hDSeriesob,sValueName)
    def GetValue_s(self,sValueName) -> str:
        return P2Seriesob.GetValue_s(self.hDSeriesob,sValueName)
    def SetParam_i(self,sParamName,iParam) -> int:
        return P2Seriesob.SetParam_i(self.hDSeriesob,sParamName,iParam)
    def SetConfig_i(self,sConfigName,iValue) -> int:
        return P2Seriesob.SetConfig_i(self.hDSeriesob,sConfigName,iValue)
#
#   DSeries Harmonic object class
class DSeriesobHarmonic(DSeriesob):
    def __init__ ( self, hDSeriesob, hRefob, sRefVerb ):
        super().__init__ ( hDSeriesob, 'Harmonics', hRefob, sRefVerb )
        self.Sync()
    # Synchronise Harmonic Object parameters
    def Sync ( self ):
        self.dKvalue     = 0 #P2Series.GetParam_d(self.hDSeries,'Kvalue')
        self.iSMAperiods = 0 #P2Series.GetParam_i(self.hDSeries,'SMAperiods')
        return
#
#   DSeries Reversals object class
class DSeriesobReversal(DSeriesob):
    def __init__ ( self, hDSeriesob, hRefob, sRefVerb ):
        super().__init__ ( hDSeriesob, 'Reversals', hRefob, sRefVerb )
        self.Sync()
    # Synchronise Reversal Object parameters
    def Sync ( self ):
        self.iEMAperiods = 0 # P2Series.GetParam_i(self.hDSeriesob,'EMAperiods')
        return

######################
#   DSeries base class
#   NOTES: Multiple DSeries may exist on a single chart each containing its
#          own unique variant of the displayed data. Each chart indicator.
#          overlay etc is usually supported by a DSeries that can be referenced
#        : Usually generated via DSeries<type>.<object>Factory()
#        : Base class and derivatives usable from both Advisor and Scanner
#          environnments.
#        :'hChart' is the Chartboard handle for the parent chart of this DSeries
#        :'sDSeriesName' is the name of the DSeries
class DSeries:
    def __init__ ( self, hChart, sDSeriesName ):
        self.hChart = hChart
        self.sDSeriesName: str = sDSeriesName
        self.sDSeriesType: str = P2Chart.DSeriesType(self.hChart,self.sDSeriesName)
        self.hDSeries = P2Chart.DSeriesOpen(self.hChart, self.sDSeriesName)
        self.nPaintEoD: int = P2Series.Getenvar_i(self.hDSeries,'PaintEoD')
        self.nBarCount: int = P2Series.BarCount(self.hDSeries, 0 )
    # Check if nominated DSeries exists for nominated chart
    def Exists ( self ):
        if P2Chart.DSeriesExists(self.oChart.sChartName, self.sDSeriesName) == 'exists':
            return True
        return False
    def PaintEoD(self,bPaintEoD):
        self.nPaintEoD = P2Series.Setenvar_i(self.hDSeries,'PaintEoD',bPaintEoD)
    # Select both raw and calculated values from DSeries
    # NOTES: GetValue_i(sValueName,ePunits,nBoFset) returns an integer value
    #        passed parameters.  NULL return flags no-data or request out of range
    #      :'sValueName' is the name of the value to be retrieved
    #      :'sParamName' is the name of the parameter to be set or retrieved
    #      :'ePUnits' is the period units for the value to be retrieved
    #      :'nBoFset' is the bar offset from the current DSeries cursor position
    #
    # GetValue_d(sValueName,ePUnits,nBoFset) -> float:
    #   sValueName :'Open' - Raw market Open value
    #               'High' - Raw market High value
    #               'Low' - Raw market Low value
    #               'Close' - Raw market Close value
    #               'Volume' - Raw market Volume value
    #               'DATE' - Dataset market Date, both datetime and COleDateTime formats
    #   ePUnits    : PUNITS_Day, PUNITS_Week, PUNITS_Month etc
    #   nBOFset    : Bar offset(in ePUnits) from the current DSeries cursor position
    def GetValue_d(self,sValueName,ePUnits,nBoFset=int(0)) -> float:
        return P2Series.GetValue_d(self.hDSeries,sValueName,ePUnits,nBoFset)
    def GetValue_dt(self,sValueName,ePUnits,nBoFset=int(0)) -> datetime:
        return P2Series.GetValue_dt(self.hDSeries,sValueName,ePUnits,nBoFset)
    # Get calculated integer value reltive to the current DSeries cursor
    def GetValue_i(self,sValueName,ePUnits,nBoFset) -> int:
        return P2Series.GetValue_i(self.hDSeries,sValueName,ePUnits,nBoFset)
    def GetObject(self,sObjectType,ePUnits,hRefObject,sObjectVerb):
        return P2Series.GetObject(self.hDSeries,sObjectType,ePUnits,hRefObject,sObjectVerb)
    # Get calculation integer paremeter value from DSeries
    def GetParam_i(self,sParamName) -> int:
        return P2Series.GetParam_i(self.hDSeries,sParamName)
    # Set calculation integer paremeter value for DSeries
    def SetParam_i(self,sParamName,iParam) -> int:
        return P2Series.SetParam_i(self.hDSeries,sParamName,iParam)
    def SetConfig_i(self,sConfigName,iValue) -> int:
        return P2Series.SetConfig_i(self.hDSeries,sConfigName,iValue)
    # Manage Automation shade bars
    # NOTES: Either activates or clears nominated DSeries shade bar.
    #      : Period shade bars can be used to manage state and toggle display
    def PYCB_ShadeBarUpdate(self,ePUnits,nOffset,iState):
        P2Series.PYCB_ShadeBarUpdate(self.hDSeries,ePUnits,nOffset,iState)
        return
    def PYCB_ShadeBarSelect(self,ePUnits,iOffset):
        return P2Series.PYCB_ShadeBarSelect(self.hDSeries,ePUnits)
    def PYCB_ShadeBarClear(self,ePUnits):
        P2Series.PYCB_ShadeBarClear(self.hDSeries,ePUnits)
        return
    # Workspace environment related variables
    # NOTES: Used to interact with the workspace at an environmental,
    #        visual or summary level independant of DSeries calculations etc
    def Getenvar_i(self,sEnvarname,iEnvarvalue) -> int:
        return P2Series.Getenvar_i(self.hDSeries,sEnvarname)
    def Getenvar_s(self,sEnvarname,iEnvarvalue) -> str:
        return P2Series.Getenvar_s(self.hDSeries,sEnvarname)
    def Setenvar_i(self,sEnvarname,iValue) -> int:
        P2Series.Setenvar_i(self.hDSeries,sEnvarname,iValue)
    def Setenvar_s(self,sEnvarname,sValue):
        P2Series.Setenvar_s(self.hDSeries,sEnvarname,sValue)
#
#   BB DSeries class - Bollinger Bands, ChartOHLCvs overlay
#   NOTES: Bollinger Bands are a volatility indicator that consists of a
#          middle band (SMA) and two outer bands (standard deviations from the
#          middle band). They are used to identify overbought and oversold
#          conditions in the market.
#        : Refer CRoot->CView->CStackOHLCvs->ChartBB.DSeriesFactory('BB')
#          for DSeriesBB object creation path.
class DSeriesBB(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise BB parameters
    # NOTES: 'Kvalue' is the number of standard deviations used to calculate BB
    #      : 'SMAperiods' is the number of periods used to calculate the SMA    
    def Sync ( self ):
        self.dKvalue: float   = P2Series.GetParam_d(self.hDSeries,'Kvalue')
        self.iSMAperiods: int = P2Series.GetParam_i(self.hDSeries,'SMAperiods')
    # DSeries extensions
    # GetParam_i(sParamName,iParam) -> int:
    #   sParamName :'SMAperiods' - number of periods used to calculate the SMA
    # GetParam_d(sParamName,dParam) -> float
    #   sParamName :'KValue' - number of standard deviations used to calculate BB's
    # GetValue_i(sValueName,ePUnits,nBoFset) -> int:
    #   sValueName :'n/a' - not applicable
    #   ePUnits    : PUNITS_Day, PUNITS_Week, PUNITS_Month etc
    #   nBOFset    : Bar offset(in ePUnits) from the current DSeries cursor position
    # GetValue_d(sValueName,ePUnits,nBoFset) -> float:
    #   sValueName :'BBUpper' - Upper Bollinger Band value
    #               'BBMiddle'- Middle Bollinger Band value
    #               'BBLower' - Lower Bollinger Band value
    #   ePUnits    : PUNITS_Day, PUNITS_Week, PUNITS_Month etc
    #   nBOFset    : Bar offset(in ePUnits) from the current DSeries cursor position
#
#   CCI DSeries class - Commodity Channel Index
#   NOTES: CCI is a momentum-based oscillator that measures the deviation of
#          the price from its average. It is used to identify overbought and
#          oversold conditions in the market.
#        : Refer CRoot->CView->CStackOHLCvs->ChartCCI.DSeriesFactory('CCI')
#          for DSeriesCCI object creation path.
class DSeriesCCI(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise CCI parameters
    # NOTES: 'CCIperiods' is the number of periods used to calculate the CCI
    def Sync ( self ):
        self.iCCIperiods: int = P2Series.GetParam_i(self.hDSeries,'CCIperiods')
        return
    # DSeries extensions
    # GetParam_i(sParamName,iParam) -> int:
    #   sParamName :'CCIperiods' - number of periods used to calculate the CCI
    # GetParam_d(sParamName,dParam) -> float
    #   sParamName  'n/a' - not applicable
    # GetValue_i(sValueName,ePUnits,nBoFset) -> int:
    #   sValueName :'n/a' - not applicable
    #   ePUnits    : PUNITS_Day, PUNITS_Week, PUNITS_Month etc
    #   nBOFset    : Bar offset(in ePUnits) from the current DSeries cursor position
    # GetValue_d(sValueName,ePUnits,nBoFset) -> float:
    #   sValueName :'CCI' - CCI value
    #   ePUnits    : PUNITS_Day, PUNITS_Week, PUNITS_Month etc
    #   nBOFset    : Bar offset(in ePUnits) from the current DSeries cursor position
#
#   Chaikin DSeries class - Chaikin Oscillator
#   NOTES: Chaikin Oscillator is a volume-based indicator that measures the
#          difference between the 3-day and 10-day exponential moving averages
#          of the Accumulation/Distribution Line. It is used to identify
#          potential trend reversals and confirm price movements.
#        : Refer CRoot->CView->CStackOHLCvs->ChartChaikin.DSeriesFactory('Chaikin')
#          for DSeriesChaikin object creation path.
class DSeriesChaikin(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise Chaikin parameters
    # NOTES: DSeriesChaikin.GetParam_i(self.hDSeries,sParamName)
    #       'FASTperiods' is the number of periods used to calculate the fast EMA
    #       'SLOWperiods' is the number of periods used to calculate the slow EMA
    def Sync ( self ):
        self.iFASTperiods: int = P2Series.GetParam_i(self.hDSeries,'FASTperiods')
        self.iSLOWperiods: int = P2Series.GetParam_i(self.hDSeries,'SLOWperiods')
        return
#
#   Chandelier DSeries class - Short and Long exit strategies, ChartOHLCvs overlay
#   NOTES: Chandelier is a volatility-based exit strategy that uses the Average True
#          Range (ATR) to determine the exit points for a trade. 
#        : Refer CRoot->CView->CStackOHLCvs->ChartChandelier.DSeriesFactory('Chandelier')
#          for DSeriesChandelier object creation path.
class DSeriesChandelier(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise unique Chandelier parameters
    # NOTES: 'SHORTperiods' is the number of periods used to calculate the short exit
    #      : 'LONGperiods' is the number of periods used to calculate the long exit
    #      : 'SHORTmultATR' is the multiplier used to calculate the short exit
    #      : 'LONGmultATR' is the multiplier used to calculate the long exit
    def Sync ( self ):
        self.iSHORTperiods: int   = P2Series.GetParam_i(self.hDSeries,'SHORTperiods')
        self.iLONGperiods : int   = P2Series.GetParam_i(self.hDSeries,'LONGperiods')
        self.dSHORTmultATR: float = P2Series.GetParam_d(self.hDSeries,'SHORTmultATR')
        self.dLONGmultATR:  float = P2Series.GetParam_d(self.hDSeries,'LONGmultATR')
        return
#
#   CMF DSeries class - Chaikin Money Flow
#   NOTES: CMF is a volume-based indicator that measures the buying and selling pressure
#          in the market. It is calculated by multiplying the volume by the
#          Accumulation/Distribution Line and then dividing it by the total volume.
#        : It is used to identify potential trend reversals and confirm price movements.
#        : Refer CRoot->CView->CStackOHLCvs->ChartCMF.DSeriesFactory('CMF')
#          for DSeriesCMF object creation path.
class DSeriesCMF(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise CMF parameters
    def Sync ( self ):
        self.iCMFperiods: int = P2Series.GetParam_i(self.hDSeries,'CMFperiods')
        return
#
#   Coppock DSeries class - Coppock Indicator
#   NOTES: Coppock is a momentum-based indicator that measures the rate of change
#          of the price over a specified period. It is used to identify potential
#          trend reversals and confirm price movements.
#        : It is calculated by taking the rate of change of the price over a
#          specified period and then applying a weighted moving average to it.
#        : Refer CRoot->CView->CStackOHLCvs->ChartCoppock.DSeriesFactory('Coppock')
#          for DSeriesCoppock object creation path.
class DSeriesCoppock(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise unique Coppock parameters
    def Sync ( self ):
        self.iROCAperiods: int = P2Series.GetParam_i(self.hDSeries,'ROCAperiods')
        self.iROCBperiods: int = P2Series.GetParam_i(self.hDSeries,'ROCBperiods')
        self.iWMAperiods:  int = P2Series.GetParam_i(self.hDSeries,'WMAperiods')
        return
#
#   EFI DSeries class - Elder Ray or Force Index
#   NOTES: Elder Ray is a volume-based indicator that measures the buying and selling
#          pressure in the market. It is calculated by taking the difference between
#          the price and the exponential moving average (EMA) of the price over a
#          specified period. It is used to identify potential trend reversals and
#          confirm price movements.
#        : It is also known as the Force Index and is used to measure the strength
#          of a trend. The Elder Ray consists of two lines: the Bull Power line and
#          the Bear Power line. The Bull Power line is the difference between the
#          price and the EMA of the price over a specified period, while the Bear   
#          Power line is the difference between the price and the EMA of the price
#          over a specified period.
#        : Refer CRoot->CView->CStackOHLCvs->ChartEFI.DSeriesFactory('EFI')
#          for DSeriesEFI object creation path.
class DSeriesEFI(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise EFI parameters
    def Sync ( self ):
        self.iEFIperiods: int = P2Series.GetParam_i(self.hDSeries,'EFIperiods')
        return
#
#   EhlerFT DSeries class - Ehlers Fisher Transform (EhlerFT)
#   NOTES: Ehlers Fisher Transform is a technical indicator that transforms
#          the price data into a Gaussian distribution. It is used to identify
#          potential trend reversals and confirm price movements. The Ehlers
#          Fisher Transform is a variation of the Fisher Transform that uses
#          a different calculation method to transform the price data.
#        : It is calculated by taking the difference between the price and the
#          exponential moving average (EMA) of the price over a specified period
#          and then applying a Fisher Transform to it. The Fisher Transform
#          is a mathematical function that transforms the price data into a
#          Gaussian distribution.
#        : Refer CRoot->CView->CStackOHLCvs->ChartEhlerFT.DSeriesFactory('EhlerFT')
#          for DSeriesEhlerFt object creation path.
class DSeriesEhlerFT(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise EhlerFT parameters
    def Sync ( self ):
        self.iEhlerFTperiods: int = P2Series.GetParam_i(self.hDSeries,'EhlerFTperiods')
        self.iSignalperiods: int = P2Series.GetParam_i(self.hDSeries,'Signalperiods')
        return
#
#   EMAnnn DSeries class - Exponential moving average
#   NOTES: Name format EMAnnn[y|q|m|w|d]
#class DSeriesEMAnnn(DSeries):
#    def __init__ ( self, hChart, sDSeriesName ):
#        super().__init__ ( hChart, sDSeriesName )
#        self.Sync()
#    # Synchronise EMAnnn data sets
#    def Sync ( self ):
#        self.iEMAperiods = P2Series.GetParam_i ( self.hDSeries, "EMAperiods" )
#        self.ePUnits = P2Series.GetParam_i ( self.hDSeries, "PUnits" )
#        return
#
#   DPO DSeries class - Detrended Price Oscillator
#   NOTES: Detrended Price Oscillator (DPO) is a technical indicator that
#          measures the difference between the price and a moving average of the
#          price over a specified period. It is used to identify potential trend
#          reversals and confirm price movements. The DPO is calculated by taking
#          the difference between the price and a moving average of the price
#          over a specified period and then applying a detrending function to it.
#        : The detrending function is used to remove the trend from the price data
#          and to make it easier to identify potential trend reversals. The DPO
#          is a variation of the Moving Average Convergence Divergence (MACD)
#          indicator that uses a different calculation method to detrend the price
#          data.
#        : Refer CRoot->CView->CStackOHLCvs->ChartDPO.DSeriesFactory('DPO')
#          for DSeriesDPO object creation path.
class DSeriesDPO(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise DPO parameters
    def Sync ( self ):
        self.iDPOperiods = P2Series.GetParam_i(self.hDSeries,'DPOperiods')
        return
#
#   Ichimoku DSeries class - Ichimoku Cloud, ChartOHLCvs overlay
#   NOTES: Ichimoku Cloud is a technical indicator that consists of five lines:
#          Tenkan-sen, Kijun-sen, Senkou Span A, Senkou Span B, and Chikou Span.
#          It is used to identify potential trend reversals and confirm price
#          movements. The Ichimoku Cloud is a comprehensive indicator that provides
#          a complete picture of the market by combining multiple indicators into
#          a single chart. It is used to identify potential trend reversals and
#          confirm price movements by providing a complete picture of the market.
#        : The Ichimoku Cloud is calculated by taking the average of the highest
#          and lowest prices over a specified period and then applying a
#          moving average to it. The Tenkan-sen is the average of the highest and
#          lowest prices over a specified period, while the Kijun-sen is the average
#          of the highest and lowest prices over a longer period. The Senkou Span A
#          is the average of the Tenkan-sen and Kijun-sen, while the Senkou Span B
#          is the average of the highest and lowest prices over a longer period.
#          The Chikou Span is the closing price of the current period shifted back
#          by a specified number of periods.
#        : It is used to identify potential trend reversals and confirm price
#          movements by providing a complete picture of the market.
#        : Refer CRoot->CView->CStackOHLCvs->ChartIchimoku.DSeriesFactory('Ichimoku')
#          for DSeriesIchimoku object creation path.
class DSeriesIchimoku(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise Ichimoku parameters
    def Sync ( self ):
        self.iTenkanperiods: int = P2Series.GetParam_i(self.hDSeries,'Tenkanperiods')
        self.iKijunperiods: int  = P2Series.GetParam_i(self.hDSeries,'Kijunperiods')
        self.iSenkouperiods: int = P2Series.GetParam_i(self.hDSeries,'Senkouperiods')
        self.iChikouperiods: int = P2Series.GetParam_i(self.hDSeries,'Chikouperiods')
        return
#
#   KAMA DSeries class - Kaufmans Adaptive Moving Average, ChartOHLCvs overlay
#   NOTES: KAMA is a technical indicator that adapts to the volatility of the market
#          by adjusting the length of the moving average based on the price
#          movement. It is used to identify potential trend reversals and confirm
#          price movements. The KAMA is calculated by taking the difference between
#          the price and the exponential moving average (EMA) of the price over a
#          specified period and then applying a Kaufman Adaptive Moving Average
#          (KAMA) to it.
#          The KAMA is a variation of the Moving Average Convergence
#          Divergence (MACD) indicator that uses a different calculation method to
#          adapt to the volatility of the market.
#        : Refer CRoot->CView->CStackOHLCvs->ChartKAMA.DSeriesFactory('KAMA')
#          for DSeriesKAMA object creation path.
class DSeriesKAMA(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise KAMA parameters
    def Sync ( self ):
        self.iERperiods: int   = P2Series.GetParam_i(self.hDSeries,'ERperiods')
        self.iFASTperiods: int = P2Series.GetParam_i(self.hDSeries,'FASTperiods')
        self.iSLOWperiods: int = P2Series.GetParam_i(self.hDSeries,'SLOWperiods')
        return
#
#   Keltner DSeries class - Keltner Channels, ChartOHLCvs overlay
#   NOTES: Keltner Channels are a volatility-based indicator that consists of a
#          middle band (Exponential Moving Average) and two outer bands (Average
#          True Range). They are used to identify overbought and oversold
#          conditions in the market. The Keltner Channels are calculated by taking
#          the Exponential Moving Average (EMA) of the price over a specified
#          period and then applying the Average True Range (ATR) to it. The
#          Keltner Channels are a variation of the Bollinger Bands that uses a
#          different calculation method to determine the outer bands.
#        : The Keltner Channels are used to identify potential trend reversals and
#          confirm price movements by providing a complete picture of the market.
#        : Refer CRoot->CView->CStackOHLCvs->ChartKeltner.DSeriesFactory('Keltner')
#          for DSeriesKeltner object creation path.
class DSeriesKeltner(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise Keltner parameters
    def Sync ( self ):
        self.iEMAperiods: int = P2Series.GetParam_i(self.hDSeries,'EMAperiods')
        self.iATRperiods: int = P2Series.GetParam_i(self.hDSeries,'ATRperiods')
        self.dATRoffset: float= P2Series.GetParam_d(self.hDSeries,'ATRoffset')
        return
#
#   KST DSeries class - Pring's Know Sure Thing
#   NOTES: KST is a momentum-based oscillator that measures the rate of change
#          of the price over a specified period. It is used to identify potential
#          trend reversals and confirm price movements. The KST is calculated by
#          taking the rate of change of the price over a specified period and
#          then applying a weighted moving average to it. The KST is a variation
#          of the Moving Average Convergence Divergence (MACD) indicator that uses
#          a different calculation method to measure the rate of change of the
#          price over a specified period.
#        : Refer CRoot->CView->CStackOHLCvs->ChartKST.DSeriesFactory('KST')
#          for DSeriesKST object creation path.
class DSeriesKST(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise KST parameters
    def Sync ( self ):
        self.iROC1periods: int   = P2Series.GetParam_i(self.hDSeries,'ROC1periods')
        self.iROC2periods: int   = P2Series.GetParam_i(self.hDSeries,'ROC2periods')
        self.iROC3periods: int   = P2Series.GetParam_i(self.hDSeries,'ROC3periods')
        self.iROC4periods: int   = P2Series.GetParam_i(self.hDSeries,'ROC4periods')
        self.iSMA1periods: int   = P2Series.GetParam_i(self.hDSeries,'SMA1periods')
        self.iSMA2periods: int   = P2Series.GetParam_i(self.hDSeries,'SMA2periods')
        self.iSMA3periods: int   = P2Series.GetParam_i(self.hDSeries,'SMA3periods')
        self.iSMA4periods: int   = P2Series.GetParam_i(self.hDSeries,'SMA4periods')
        self.iSignalperiods: int = P2Series.GetParam_i(self.hDSeries,'Signalperiods')
        return
    # DSeries extensions
    # GetParam_i(sParamName,iParam) -> int:
    #   sParamName :'ROC1periods' - Number of periods used to calculate the first Rate of Change
    #   sParamName :'ROC2periods' - Number of periods used to calculate the second Rate of Change
    #   sParamName :'ROC3periods' - Number of periods used to calculate the third Rate of Change
    #   sParamName :'ROC4periods' - Number of periods used to calculate the fourth Rate of Change
    #   sParamName :'SMA1periods' - Number of periods used to calculate the first SMA
    #   sParamName :'SMA2periods' - Number of periods used to calculate the second SMA
    #   sParamName :'SMA3periods' - Number of periods used to calculate the third SMA
    #   sParamName :'SMA4periods' - Number of periods used to calculate the fourth SMA
    #   sParamName :'Signalperiods' - Number of periods used to calculate the signal line
    # GetParam_d(sParamName,dParam) -> float
    #   sParamName :'n/a' - not applicable
    # GetValue_i(sValueName,ePUnits,nBoFset) -> int:
    #   sValueName :'BoS' - Buy(1) or Sell(-1) signal
    #               'BoSage' - Buy(1) or Sell(-1) signal age in ePUnits
    #   ePUnits    : PUNITS_Day, PUNITS_Week, PUNITS_Month etc
    #   nBOFset    : Bar offset(in ePUnits) from the current DSeries cursor position
    # GetValue_d(sValueName,ePUnits,nBoFset) -> float:
    #   sValueName :'KST' - Calaculated KST value
    #               'KSTsignal' - Calculated KST signal line value
    #   ePUnits    : PUNITS_Day, PUNITS_Week, PUNITS_Month etc
    #   nBOFset    : Bar offset(in ePUnits) from the current DSeries cursor position
#
#   MACD DSeries class - Moving Average Convergence Divergence
#   NOTES: MACD is a momentum-based oscillator that measures the difference
#          between two exponential moving averages (EMAs) of the price over a
#          specified period. It is used to identify potential trend reversals
#          and confirm price movements. The MACD is calculated by taking the
#          difference between the 12-day EMA and the 26-day EMA of the price
#          and then applying a 9-day EMA to the result. The MACD is a variation
#          of the Moving Average Convergence Divergence (MACD) indicator that uses
#          a different calculation method to measure the difference between two
#          exponential moving averages (EMAs) of the price over a specified period.
#        : Refer CRoot->CView->CStackOHLCvs->ChartMACD.DSeriesFactory('MACD')
#          for DSeriesMACD object creation path.
class DSeriesMACD(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise MACD parameters
    def Sync ( self ):
        self.iEMA1periods: int   = P2Series.GetParam_i(self.hDSeries,'EMA1periods')
        self.iEMA2periods: int   = P2Series.GetParam_i(self.hDSeries,'EMA2periods')
        self.iSignalperiods: int = P2Series.GetParam_i(self.hDSeries,'Signalperiods')
        return
    # DSeries extensions
    # GetParam_i(sParamName) -> int
    #   sParamName :'EMA1periods' - number of periods used to calculate the first EMA
    #              :'EMA2periods' - number of periods used to calculate the second EMA
    #              :'Signalperiods' - number of periods used to calculate the signal line
    # GetParam_d(sParamName) -> float
    #   sParamName :'n/a' - not applicable
    # GetValue_i(sValueName,ePUnits,nBoFset) -> int
    #   sValueName :'BoS' - Buy(1) or Sell(-1) signal
    #               'BoSage' - Buy(1) or Sell(-1) signal age in ePUnits
    #   ePUnits    : PUNITS_Day, PUNITS_Week, PUNITS_Month etc
    #   nBOFset    : Bar offset(in ePUnits) from the current DSeries cursor position
    # GetValue_d(sValueName,ePUnits,nBoFset) -> float
    #   sValueName :'MACD' - MACD value
    #               'MACDsignal' - MACD signal line value
    #               'MACDiff' - MACD/MACDsignal difference value
    #   ePUnits    : PUNITS_Day, PUNITS_Week, PUNITS_Month etc
    #   nBOFset    : Bar offset(in ePUnits) from the current DSeries cursor position
#
#   MAMA DSeries class - Ehlers MESA Adaptive Moving Average, ChartOHLCvs overlay
#   NOTES: MAMA is a technical indicator that adapts to the volatility of the market
#          by adjusting the length of the moving average based on the price
#          movement. It is used to identify potential trend reversals and confirm
#          price movements. The MAMA is calculated by taking the difference between
#          the price and the exponential moving average (EMA) of the price over a
#          specified period and then applying a MESA Adaptive Moving Average
#          (MAMA) to it. The MAMA is a variation of the Moving Average Convergence
#          Divergence (MACD) indicator that uses a different calculation method to
#          adapt to the volatility of the market.
#        : The MAMA is used to identify potential trend reversals and confirm
#          price movements by providing a complete picture of the market.
#        : Refer CRoot->CView->CStackOHLCvs->ChartMAMA.DSeriesFactory('MAMA')
#          for DSeriesMAMA object creation path.
class DSeriesMAMA(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise MAMA parameters
    def Sync ( self ):
        self.dFastLimit: float = P2Series.GetParam_d(self.hDSeries,'FastLimit')
        self.dSlowLimit: float = P2Series.GetParam_d(self.hDSeries,'SlowLimit')
        return
#
#   MFI DSeries class - Money Flow Index
#   NOTES: MFI is a volume-based oscillator that measures the buying and selling
#          pressure in the market. It is calculated by taking the difference
#          between the price and the exponential moving average (EMA) of the
#          price over a specified period and then applying a Money Flow Index
#          (MFI) to it. The MFI is a variation of the Moving Average Convergence
#          Divergence (MACD) indicator that uses a different calculation method to
#          measure the buying and selling pressure in the market.
#        : The MFI is used to identify potential trend reversals and confirm
#          price movements by providing a complete picture of the market.
#   NOTES: Refer CRoot->CView->CStackOHLCvs->ChartMFI.DSeriesFactory('MFI')
#          for DSeriesMFI object creation path.
class DSeriesMFI(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise MFI data sets
    def Sync ( self ):
        self.iMFIperiods: int= P2Series.GetParam_i ( self.hDSeries, "MFIperiods" )
        self.iOBought: int   = P2Series.GetParam_i ( self.hDSeries, "OBought" )
        self.iOSold: int     = P2Series.GetParam_i ( self.hDSeries, "OSold" )
        return
#
#   MSA DSeries class - Momentum Structural Analysis
#   NOTES: MSA is a technical indicator that measures the momentum of the price
#          movement by comparing the current price to the previous price over a
#          specified period. It is used to identify potential trend reversals
#          and confirm price movements. The MSA is calculated by taking the
#          difference between the current price and the previous price over a
#          specified period and then applying a Momentum Structural Analysis
#          (MSA) to it. The MSA is a variation of the Moving Average Convergence
#          Divergence (MACD) indicator that uses a different calculation method to
#          measure the momentum of the price movement by comparing the current
#          price to the previous price over a specified period.
#        : Refer CRoot->CView->CStackOHLCvs->ChartMSA.DSeriesFactory('MSA')
#          for DSeriesMSA object creation path.
class DSeriesMSA(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise MSA data sets
    def Sync ( self ):
        self.iMSAperiods: int= P2Series.GetParam_i ( self.hDSeries, "MSAperiods" )
        return
#
#   OBV DSeries class - On Balance Volume
#   NOTES: OBV is a volume-based indicator that measures the buying and selling
#          pressure in the market. It is calculated by taking the difference
#          between the price and the exponential moving average (EMA) of the
#          price over a specified period and then applying an On Balance Volume
#          (OBV) to it. The OBV is a variation of the Moving Average Convergence
#          Divergence (MACD) indicator that uses a different calculation method to
#          measure the buying and selling pressure in the market.
#        : Refer CRoot->CView->CStackOHLCvs->ChartOBV.DSeriesFactory('OBV')
#          for DSeriesOBV object creation path.
class DSeriesOBV(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise OBV data sets
    def Sync ( self ):
        return
#
#   OHLCvs DSeries class - Open, High, Low, Close
class DSeriesOHLCvs(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise OHLCvs data sets
    def Sync ( self ):
        return
#
#   Donchian DSeries class - Donchian Price Channels, ChartOHLCvs overlay
#   NOTES: Donchian Channels are a volatility-based indicator that consists of
#          two outer bands (highest high and lowest low) and a middle band
#          (average of the highest high and lowest low). They are used to
#          identify overbought and oversold conditions in the market. The
#          Donchian Channels are calculated by taking the highest high and
#          lowest low over a specified period and then applying a moving average
#          to it. The Donchian Channels are a variation of the Bollinger Bands
#          that uses a different calculation method to determine the outer bands.
#        : The Donchian Channels are used to identify potential trend reversals
#          and confirm price movements by providing a complete picture of the market.
#        : Refer CRoot->CView->CStackOHLCvs->ChartDonchian.DSeriesFactory('Donchian')
#          for DSeriesDonchian object creation path.
class DSeriesDonchian(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise PChan data sets
    def Sync ( self ):
        self.iPCperiods: int = P2Series.GetParam_i ( self.hDSeries, "PCperiods" )
        return
#
#   PBars DSeries class - Price Variation Bars
#   NOTES: Refer CRoot->CView->CStackOHLCvs->ChartPBars.DSeriesFactory('PBars')
#          for DSeriesPBars object creation path.
class DSeriesPBars(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise PBars data sets
    def Sync ( self ):
        return
#
#   PFigure DSeries class - Primary Point and Figure DSeries for ChartPFigure
#                           in CStackPFigure
#   NOTES: PFigure is a price-based indicator that consists of a series of
#          price levels that are used to identify potential trend reversals
#          and confirm price movements. It is used to identify potential trend
#          reversals and confirm price movements by providing a complete picture
#          of the market. The PFigure is calculated by taking the price levels
#          over a specified period and then applying a PFigure to it. The PFigure
#          is a variation of the Moving Average Convergence Divergence (MACD)
#          indicator that uses a different calculation method to measure the
#          price levels over a specified period.
#   NOTES: Refer CRoot->CView->CStackPFigure->ChartPFigure.DSeriesFactory('PFigure')
#          for DSeriesPFigure object creation path.
CalcType_PanF_TRADITIONAL: int = 1
CalcType_PanF_MANUAL: int  = 2
CalcType_PanF_PERCENT: int = 3
class DSeriesPFigure(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise PFigure data sets
    def Sync ( self ):
        self.iCalcType: int = P2Series.GetParam_i ( self.hDSeries, "CalcType" )
        return
#
#   PMO DSeries class - Price Momentum Oscillator
#   NOTES: PMO is a momentum-based oscillator that measures the rate of change
#          of the price over a specified period. It is used to identify potential
#          trend reversals and confirm price movements. The PMO is calculated by
#          taking the rate of change of the price over a specified period and
#          then applying a Price Momentum Oscillator (PMO) to it. The PMO is a
#          variation of the Moving Average Convergence Divergence (MACD) indicator
#          that uses a different calculation method to measure the rate of change
#          of the price over a specified period.
#        : The PMO is used to identify potential trend reversals and confirm
#          price movements by providing a complete picture of the market.
#        : Refer CRoot->CView->CStackOHLCvs->ChartPMO.DSeriesFactory('PMO')
#          for DSeriesPMO object creation path.
class DSeriesPMO(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise PMO data sets
    def Sync ( self ):
        self.iPMO1periods: int = P2Series.GetParam_i ( self.hDSeries, "PMO1periods" )
        self.iPMO2periods: int = P2Series.GetParam_i ( self.hDSeries, "PMO2periods" )
        self.iEMAperiods: int  = P2Series.GetParam_i ( self.hDSeries, "EMAperiods" )
        return
#
#   PPO DSeries class - Percentage Volume Oscillator
##   NOTES: PPO is a momentum-based oscillator that measures the rate of change
#          of the price over a specified period. It is used to identify potential
#          trend reversals and confirm price movements. The PPO is calculated by
#          taking the rate of change of the price over a specified period and
#          then applying a Percentage Price Oscillator (PPO) to it. The PPO is a
#          variation of the Moving Average Convergence Divergence (MACD) indicator
#          that uses a different calculation method to measure the rate of change
#          of the price over a specified period.
#        : The PPO is used to identify potential trend reversals and confirm
#          price movements by providing a complete picture of the market.
#        : Refer CRoot->CView->CStackOHLCvs->ChartPPO.DSeriesFactory('PPO')
#          for DSeriesPPO object creation path.
class DSeriesPPO(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise PPO data sets
    def Sync ( self ):
        self.iEMA1periods: int   = P2Series.GetParam_i(self.hDSeries,'EMA1periods')
        self.iEMA2periods: int   = P2Series.GetParam_i(self.hDSeries,'EMA2periods')
        self.iSignalperiods: int = P2Series.GetParam_i(self.hDSeries,'Signalperiods')
    # DSeries extensions
    # GetParam_i(sParamName) -> int
    #   sParamName :'EMA1periods' - number of periods used to calculate the first EMA
    #              :'EMA2periods' - number of periods used to calculate the second EMA
    #              :'Signalperiods' - number of periods used to calculate the signal line
    # GetParam_d(sParamName) -> float
    #   sParamName :'n/a' - not applicable
    # GetValue_i(sValueName,ePUnits,nBoFset) -> int
    #   sValueName :'BoS' - Buy(1) or Sell(-1) signal
    #               'BoSage' - Buy(1) or Sell(-1) signal age in ePUnits
    #   ePUnits    : PUNITS_Day, PUNITS_Week, PUNITS_Month etc
    #   nBOFset    : Bar offset(in ePUnits) from the current DSeries cursor position
    # GetValue_d(sValueName,ePUnits,nBoFset) -> float
    #   sValueName :'PPO' - Calculated PPO value
    #               'PPOsignal' - Calculated PPO signal line value
    #               'PPOiff' - PPO/PPOsignal difference value
    #   ePUnits    : PUNITS_Day, PUNITS_Week, PUNITS_Month etc
    #   nBOFset    : Bar offset(in ePUnits) from the current DSeries cursor position
#
#   PVO DSeries class - Percentage Volume Oscillator
#   NOTES: PVO is a volume-based oscillator that measures the rate of change
#          of the volume over a specified period. It is used to identify potential
#          trend reversals and confirm price movements. The PVO is calculated by
#          taking the rate of change of the volume over a specified period and
#          then applying a Percentage Volume Oscillator (PVO) to it. The PVO is a
#          variation of the Moving Average Convergence Divergence (MACD) indicator
#          that uses a different calculation method to measure the rate of change
#          of the volume over a specified period.
#        : The PVO is used to identify potential trend reversals and confirm
#          price movements by providing a complete picture of the market.
#        : Refer CRoot->CView->CStackOHLCvs->ChartPVO.DSeriesFactory('PVO')
#          for DSeriesPVO object creation path.
class DSeriesPVO(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise PVO data sets
    def Sync ( self ):
        self.iHIperiods: int  = P2Series.GetParam_i ( self.hDSeries, "HIperiods" )
        self.iLOperiods: int  = P2Series.GetParam_i ( self.hDSeries, "LOperiods" )
        self.iPVOperiods: int = P2Series.GetParam_i ( self.hDSeries, "PVOperiods" )
        return
#
#   ROC DSeries class - Rate of Change
#   NOTES: ROC is a momentum-based oscillator that measures the rate of change
#          of the price over a specified period. It is used to identify potential
#          trend reversals and confirm price movements. The ROC is calculated by
#          taking the rate of change of the price over a specified period and
#          then applying a Rate of Change (ROC) to it. The ROC is a variation
#          of the Moving Average Convergence Divergence (MACD) indicator that uses
#          a different calculation method to measure the rate of change of the
#          price over a specified period.
#        : The ROC is used to identify potential trend reversals and confirm
#          price movements by providing a complete picture of the market.
#        : Refer CRoot->CView->CStackOHLCvs->ChartROC.DSeriesFactory('ROC')
#          for DSeriesROC object creation path.
class DSeriesROC(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise ROC data sets
    def Sync ( self ):
        self.iROCperiods: int = P2Series.GetParam_i ( self.hDSeries, "ROCperiods" )
        return
#
#   EMAnnn DSeries class - Exponential moving average, ChartOHLCvs overlay
#   NOTES: Name format EMAnnn[y|q|m|w|d]
#   NOTES: EMAnnn is a technical indicator that adapts to the volatility of the market
#          by adjusting the length of the moving average based on the price
#          movement. It is used to identify potential trend reversals and confirm
#          price movements. The EMAnnn is calculated by taking the difference between
#          the price and the exponential moving average (EMA) of the price over a
#          specified period and then applying an Exponential Moving Average
#          (EMAnnn) to it.   
#        : Refer CRoot->CView->CStackOHLCvs->ChartOHLCvs.DSeriesEMAnnnFactory(nEMAperiods,ePUnits)
#          for DSeriesEMAnnn object creation path.
#         'nEMAPeriods' is the number of periods for the EMA, and
#         'ePUnits' is the period units as per PUNITS_Year=1, PUNITS_Quarter=2,
#          PUNITS_Month=3, PUNITS_Week=4, PUNITS_Day=5
def MakeDSeriesEMAname (nEMAperiods,ePUnits):
    #f"{num:0{length}d}"
    if ePUnits == 5 :
        return f"EMA{nEMAperiods:0{3}d}d"
    if ePUnits == 4 :
        return f"EMA{nEMAperiods:0{3}d}w"
    if ePUnits == 3 :
        return f"EMA{nEMAperiods:0{3}d}m"
    if ePUnits == 2 :
        return f"EMA{nEMAperiods:0{3}d}q"
    if ePUnits == 1 :
        return f"EMA{nEMAperiods:0{3}d}y"
    return f"EMA{nEMAperiods:0{3}d}?"
class DSeriesEMAnnn(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise EMAnnn data sets
    def Sync ( self ):
        self.iEMAperiods: int = P2Series.GetParam_i ( self.hDSeries, "EMAperiods" )
        self.ePUnits: int = P2Series.GetParam_i ( self.hDSeries, "PUnits" )
        return
#
#   SMAnnn DSeries class - Simple moving average, ChartOHLCvs overlay
#   NOTES: Name format SMAnnn[y|q|m|w|d]
#        : Refer CRoot->CView->CStackOHLCvs->ChartOHLCvs.DSeriesSMAnnnFactory(nSMAperiods,ePUnits)
#          for DSeriesSMAnnn object creation path.
#         'nSMAPeriods' is the number of periods for the SMA, and
#         'ePUnits' is the period units as per PUNITS_Year=1, PUNITS_Quarter=2,
#          PUNITS_Month=3, PUNITS_Week=4, PUNITS_Day=5
def MakeDSeriesSMAname (nSMAperiods,ePUnits):
    #f"{num:0{length}d}"
    if ePUnits == 5 :
        return f"SMA{nSMAperiods:0{3}d}d"
    if ePUnits == 4 :
        return f"SMA{nSMAperiods:0{3}d}w"
    if ePUnits == 3 :
        return f"SMA{nSMAperiods:0{3}d}m"
    if ePUnits == 2 :
        return f"SMA{nSMAperiods:0{3}d}q"
    if ePUnits == 1 :
        return f"SMA{nSMAperiods:0{3}d}y"
    return f"SMA{nSMAperiods:0{3}d}?"
class DSeriesSMAnnn(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise SMAnnn data sets
    def Sync ( self ):
        self.iSMAperiods: int = P2Series.GetParam_i ( self.hDSeries, "SMAperiods" )
        self.ePUnits: int = P2Series.GetParam_i ( self.hDSeries, "PUnits" )
        return
#
#   RSI DSeries class - Relative Strength Index
#   NOTES: RSI is a momentum-based oscillator that measures the speed and change
#          of price movements. It is used to identify overbought and oversold
#          conditions in the market. The RSI is calculated by taking the average
#          of the gains and losses over a specified period and then applying a
#          Relative Strength Index (RSI) to it.
#        : Refer CRoot->CView->CStackOHLCvs->ChartRSI.DSeriesFactory('RSI')
#          for DSeriesRSI object creation path.
class DSeriesRSI(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise RSI data sets
    def Sync ( self ):
        self.iRSIperiods: int = P2Series.GetParam_i ( self.hDSeries, "RSIperiods" )
        self.iOBought: int    = P2Series.GetParam_i ( self.hDSeries, "OBought" )
        self.iOSold: int      = P2Series.GetParam_i ( self.hDSeries, "OSold" )
        self.iOSold: int      = DSeries.GetParam_i ( self, "OSold" )
        return
    # DSeries extensions
    # GetParam_i(sParamName) -> int
    #   sParamName :'RSIperiods' - number of periods used to calculate the RSI
    #              :'OBought' - Overbought threshold level
    #              :'OSold' - Oversold threshold level
    # GetParam_d(sParamName) float
    #   sParamName :'n/a' - not applicable
    # GetValue_i(sValueName,ePUnits,nBoFset) returns an integer value
    #   sValueName :'n/a' - not applicable
    #   ePUnits    : PUNITS_Day, PUNITS_Week, PUNITS_Month etc
    #   nBOFset    : Bar offset(in ePUnits) from the current DSeries cursor position
    # GetValue_d(sValueName,ePUnits,nBoFset) float:
    #     #   sValueName :'RSI' - RSI value
    #   ePUnits    : PUNITS_Day, PUNITS_Week, PUNITS_Month etc
    #   nBOFset    : Bar offset(in ePUnits) from the current DSeries cursor position
#
#   SLOPE DSeries class - Linear Regression oscillator (SLOPE)
#   NOTES: Refer CRoot->CView->CStackOHLCvs->ChartSLOPE.DSeriesFactory('SLOPE')
#          for DSeriesSLOPE object creation path.
class DSeriesSLOPE(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise SLOPE data sets
    def Sync ( self ):
        self.iPeriodsLINEAR: int = P2Series.GetParam_i ( self.hDSeries, "PeriodsLINEAR" )
        self.iPeriodsPOLY2: int = P2Series.GetParam_i ( self.hDSeries, "PeriodsPOLY2" )
        self.iPeriodsSAVITZKY: int = P2Series.GetParam_i ( self.hDSeries, "PeriodsSAVITZKY" )
        self.iSmoothPeriods: int = P2Series.GetParam_i ( self.hDSeries, "SmoothPeriods" )
        self.iSmoothEoD: int = P2Series.GetParam_i ( self.hDSeries, "SmoothEoD" )
        return
#
#   StochRSI DSeries class - Stohastics RSI
#   NOTES: StochRSI is a momentum-based oscillator that measures the speed and
#          change of price movements relative to the Relative Strength Index (RSI).
#          It is used to identify overbought and oversold conditions in the market.
#          The StochRSI is calculated by taking the RSI and applying a stochastic
#          oscillator to it. The StochRSI is a variation of the Moving Average
#          Convergence Divergence (MACD) indicator that uses a different calculation
#          method to measure the speed and change of price movements relative to
#          the RSI.
#        : The StochRSI is used to identify potential trend reversals and confirm
#          price movements by providing a complete picture of the market.
#        : Refer CRoot->CView->CStackOHLCvs->ChartStochRSI.DSeriesFactory('StochRSI')
#          for DSeriesStochRSI object creation path.
class DSeriesStochRSI(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise StochRSI data sets
    def Sync ( self ):
        self.iStochRSIperiods: int = P2Series.GetParam_i ( self.hDSeries, "StochRSIperiods" )
        self.dOBought: float   = P2Series.GetParam_d ( self.hDSeries, "OBought" )
        self.dOSold: float     = P2Series.GetParam_d ( self.hDSeries, "OSold" )
        return
#
#   SAR DSeries class - Parabolic Stop and Reverse, ChartOHLCvs overlay
#   NOTES: SAR is a trend-following indicator that is used to identify potential
#          trend reversals and confirm price movements. It is calculated by taking
#          the difference between the price and the exponential moving average (EMA)
#          of the price over a specified period and then applying a Parabolic Stop
#          and Reverse (SAR) to it.
#        : Refer CRoot->CView->CStackOHLCvs->ChartOHLCvs.DSeriesFactory('SAR')
#          for DSeriesSAR object creation path.
class DSeriesSAR(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise SAR data sets
    def Sync ( self ):
        self.dAF: float   = P2Series.GetParam_d ( self.hDSeries, "AF" )
        self.dAFmax: float= P2Series.GetParam_d ( self.hDSeries, "AFmax" )
        return
#
#   STO DSeries class
#   NOTES: STO is a momentum-based oscillator that measures the speed and change
#          of price movements. It is used to identify overbought and oversold
#          conditions in the market. The STO is calculated by taking the average
#          of the gains and losses over a specified period and then applying a
#          Stochastic Oscillator (STO) to it.
#        : The STO is used to identify potential trend reversals and confirm
#          price movements by providing a complete picture of the market.
#   NOTES: Refer CRoot->CView->CStackOHLCvs->ChartSTO.DSeriesFactory('TDMA')
#          for DSeriesTDMA object creation path.
class DSeriesSTO(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise STO data sets
    def Sync ( self ):
        self.iKperiods: int = P2Series.GetParam_i ( self.hDSeries, "Kperiods" )
        self.iDperiods: int = P2Series.GetParam_i ( self.hDSeries, "Dperiods" )
        self.iXperiods: int = P2Series.GetParam_i ( self.hDSeries, "Xperiods" )
        return
#
#   STDEV DSeries class - Volatility or Standard Deviation
#   NOTES: STDEV is a volatility-based indicator that measures the standard
#          deviation of the price over a specified period. It is used to identify
#          potential trend reversals and confirm price movements. The STDEV is
#          calculated by taking the standard deviation of the price over a
#          specified period and then applying a Standard Deviation (STDEV) to it.
#        : The STDEV is used to identify potential trend reversals and confirm
#          price movements by providing a complete picture of the market.
#        : Refer CRoot->CView->CStackOHLCvs->ChartSTDEV.DSeriesFactory('STDEV')
#          for DSeriesSTDEV object creation path.
class DSeriesSTDEV(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise STDEV data sets
    def Sync ( self ):
        self.iSTDEVperiods: int = P2Series.GetParam_i ( self.hDSeries, "STDEVperiods" )
        return
#
#   Shorts DSeries class
#   NOTES: Refer CRoot->CView->CStackOHLCvs->ChartShorts.DSeriesFactory('Shorts')
#          for DSeriesShorts object creation path, or
#        : Refer CRoot->CView->CStackPFigure->ChartShorts.DSeriesFactory('Shorts')
#          for DSeriesShorts object creation path.
class DSeriesShorts(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise Shorts data sets
    def Sync ( self ):
        return
#
#   TDMA DSeries class - Tom Demark Moving Average I, ChartOHLCvs overlay 
#   NOTES: TDMA is a technical indicator that adapts to the volatility of the market
#          by adjusting the length of the moving average based on the price
#          movement. It is used to identify potential trend reversals and confirm
#          price movements. The TDMA is calculated by taking the difference between
#          the price and the exponential moving average (EMA) of the price over a
#          specified period and then applying a Tom Demark Moving Average (TDMA) to it.
#        : The TDMA is used to identify potential trend reversals and confirm
#          price movements by providing a complete picture of the market.
#        : Refer CRoot->CView->CStackOHLCvs->ChartTDMA.DSeriesFactory('TDMA')
#          for DSeriesTDMA object creation path.
class DSeriesTDMA(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise TDMA parameters
    def Sync ( self ):
        self.iLBperiodsI: int = P2Series.GetParam_i(self.hDSeries,'LBperiodsI')
        self.iAveragePeriodsI: int = P2Series.GetParam_i(self.hDSeries,'AveragePeriodsI')
        self.iExtendPeriodsI: int = P2Series.GetParam_i(self.hDSeries,'ExtendPeriodsI')
        return
#
#   TDemark DSeries class - Tom Demark, ChartOHLCvs overlay
#   NOTES: TDemark is a technical indicator that adapts to the volatility of the market
#          by adjusting the length of the moving average based on the price
#          movement. It is used to identify potential trend reversals and confirm
#          price movements. The TDemark is calculated by taking the difference between
#          the price and the exponential moving average (EMA) of the price over a
#          specified period and then applying a Tom Demark (TDemark) to it.
#        : The TDemark is used to identify potential trend reversals and confirm
#          price movements by providing a complete picture of the market.
#        : Refer CRoot->CView->CStackOHLCvs->ChartOHLCvs.DSeriesFactory('TDemark')
#          for DSeriesTDemark object creation path.
class DSeriesTDemark(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise TDemark parameters
    def Sync ( self ):
        self.iLBperiods: int = P2Series.GetParam_i(self.hDSeries,'LBperiods')
        self.iSetupPeriods: int = P2Series.GetParam_i(self.hDSeries,'SetupPeriods')
        self.iCountdownLBperiods: int = P2Series.GetParam_i(self.hDSeries,'CountdownLBperiods')
        self.iCountdownPeriods: int = P2Series.GetParam_i(self.hDSeries,'CountdownPeriods')
        self.iComboLBperiods: int = P2Series.GetParam_i(self.hDSeries,'ComboLBperiods')
        self.iComboPeriods: int = P2Series.GetParam_i(self.hDSeries,'ComboPeriods')
        return
#
#   TRIX DSeries class - Triple Smoothed Exponential Moving Average
#   NOTES: TRIX is a momentum-based oscillator that measures the rate of change
#          of the price over a specified period. It is used to identify potential
#          trend reversals and confirm price movements. The TRIX is calculated by
#          taking the rate of change of the price over a specified period and
#          then applying a Triple Smoothed Exponential Moving Average (TRIX) to it.
#        : The TRIX is used to identify potential trend reversals and confirm
#          price movements by providing a complete picture of the market.
#        : Refer CRoot->CView->CStackOHLCvs->ChartATR.DSeriesFactory('TRIX')
#          for DSeriesTRIX object creation path.
class DSeriesTRIX(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise TRIX data sets
    def Sync ( self ):
        self.iEMAperiods: int    = P2Series.GetParam_i ( self.hDSeries, "EMAperiods" )
        self.iSignalperiods: int = P2Series.GetParam_i ( self.hDSeries, "Signalperiods" )
        return
    # DSeries extensions
    # GetParam_i(sParamName) -> int
    #   sParamName :'EMAperiods' - Number of periods used to calculate the EMA
    #              :'Signalperiods' - number of periods used to calculate the signal line
    # GetParam_d(sParamName) -> float
    #   sParamName :'n/a' - not applicable
    # GetValue_i(sValueName,ePUnits,nBoFset) -> int
    #   sValueName :'BoS' - Buy(1) or Sell(-1) signal
    #               'BoSage' - Buy(1) or Sell(-1) signal age in ePUnits
    #   ePUnits    : PUNITS_Day, PUNITS_Week, PUNITS_Month etc
    #   nBOFset    : Bar offset(in ePUnits) from the current DSeries cursor position
    # GetValue_d(sValueName,ePUnits,nBoFset) -> float
    #   sValueName :'TRIX' - Calculated TRIX value
    #               'TRIXsignal' - Calculated TRIX signal line value
    #               'TRIXdiff' - TRIX/TRIXsignal difference value
    #   ePUnits    : PUNITS_Day, PUNITS_Week, PUNITS_Month etc
    #   nBOFset    : Bar offset(in ePUnits) from the current DSeries cursor position
#
#   TSI DSeries class - True Strength Index
#   NOTES: TSI is a momentum-based oscillator that measures the rate of change
#          of the price over a specified period. It is used to identify potential
#          trend reversals and confirm price movements. The TSI is calculated by
#          taking the rate of change of the price over a specified period and
#          then applying a True Strength Index (TSI) to it.
#        : The TSI is used to identify potential trend reversals and confirm
#          price movements by providing a complete picture of the market.
#   NOTES: Refer CRoot->CView->CStackOHLCvs->ChartTSI.DSeriesFactory('TSI')
#          for DSeriesTSI object creation path.
class DSeriesTSI(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise TSI data sets
    def Sync ( self ):
        self.iPC1periods: int    = P2Series.GetParam_i ( self.hDSeries, "PC1periods" )
        self.iPC2periods: int    = P2Series.GetParam_i ( self.hDSeries, "PC2periods" )
        self.iSignalperiods: int = P2Series.GetParam_i ( self.hDSeries, "Signalperiods" )
        return
    # DSeries extensions
    # GetParam_i(sParamName) -> int
    #   sParamName :'PC1periods' - Number of periods used to calculate the first price change
    #              :'PC2periods' - Number of periods used to calculate the second price change
    #              :'Signalperiods' - number of periods used to calculate the signal line
    # GetParam_d(sParamName) -> float
    #   sParamName :'n/a' - not applicable
    # GetValue_i(sValueName,ePUnits,nBoFset) -> int
    #   sValueName :'BoS' - Buy(1) or Sell(-1) signal
    #               'BoSage' - Buy(1) or Sell(-1) signal age in ePUnits
    #   ePUnits    : PUNITS_Day, PUNITS_Week, PUNITS_Month etc
    #   nBOFset    : Bar offset(in ePUnits) from the current DSeries cursor position
    # GetValue_d(sValueName,ePUnits,nBoFset) -> float
    #   sValueName :'TSI' - Calculated TSI value
    #               'TSIsignal' - Calculated TSI signal line value
    #               'TSIdiff' - TSI/TSIsignal difference value
    #   ePUnits    : PUNITS_Day, PUNITS_Week, PUNITS_Month etc
    #   nBOFset    : Bar offset(in ePUnits) from the current DSeries cursor position
#
#   ADX DSeries class - Average Directional Index
#   NOTES: ADX is a trend-following indicator that is used to identify the strength
#          of a trend in the market. It is calculated by taking the difference
#          between the price and the exponential moving average (EMA) of the price
#          over a specified period and then applying an Average Directional Index (ADX)
#          to it.
#        : Refer CRoot->CView->CStackOHLCvs->ChartATR.DSeriesFactory('ATR')
#          for DSeriesATR object creation path.
class DSeriesADX(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise ADX data sets
    def Sync ( self ):
        self.iADXperiods: int = P2Series.GetParam_i ( self.hDSeries, "ADXperiods" )
        return
#
#   ATR DSeries class - Average True Range
#   NOTES: ATR is a volatility-based indicator that measures the average true range
#          of the price over a specified period. It is used to identify potential
#          trend reversals and confirm price movements. The ATR is calculated by
#          taking the average true range of the price over a specified period and
#          then applying an Average True Range (ATR) to it.
#        : The ATR is used to identify potential trend reversals and confirm
#          price movements by providing a complete picture of the market.
#        : The ATR is calculated by taking the average true range of the price
#          over a specified period and then applying an Average True Range (ATR)
#          to it. The ATR is a variation of the Moving Average Convergence Divergence
#        : Refer CRoot->CView->CStackOHLCvs->ChartATR.DSeriesFactory('ATR')
#          for DSeriesATR object creation path.
class DSeriesATR(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise ATR data sets
    def Sync ( self ):
        self.iATRperiods: int = P2Series.GetParam_i ( self.hDSeries, "ATRperiods" )
        return
#
#   Aroon DSeries class
#   NOTES: Aroon is a trend-following indicator that is used to identify potential
#          trend reversals and confirm price movements. It is calculated by taking
#          the difference between the price and the exponential moving average (EMA)
#          of the price over a specified period and then applying an Aroon (Aroon)
#          to it.
#        : The Aroon is used to identify potential trend reversals and confirm
#          price movements by providing a complete picture of the market.
#        : Refer CRoot->CView->CStackOHLCvs->ChartAroon.DSeriesFactory('Aroon')
#          for DSeriesAroon object creation path.
class DSeriesAroon(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise Aroon data sets
    def Sync ( self ):
        self.iAroonperiods: int = P2Series.GetParam_i ( self.hDSeries, "Aroonperiods" )
        return
    # DSeries extensions
    # GetParam_i(sParamName) -> int:
    #   sParamName :'Aroonperiods' - Number of periods used to calculate the Aroon
    # GetParam_d(sParamName) -> float:
    #   sParamName :'n/a' - not applicable
    # GetValue_i(sValueName,ePUnits,nBoFset) -> int:
    #   sValueName :'BoS' - Buy(1) or Sell(-1) signal
    #               'BoSage' - Buy(1) or Sell(-1) signal age in ePUnits
    #   ePUnits    : PUNITS_Day, PUNITS_Week, PUNITS_Month etc
    #   nBOFset    : Bar offset(in ePUnits) from the current DSeries cursor position
    # GetValue_d(sValueName,ePUnits,nBoFset) -> float
    #   sValueName :'AroonHi' - Aroon High value
    #              :'AroonLo' - Aroon Low value
    #              :'AroonDiff' - Aroon Difference value
    #   ePUnits    : PUNITS_Day, PUNITS_Week, PUNITS_Month etc
    #   nBOFset    : Bar offset(in ePUnits) from the current DSeries cursor position
#
#   Reversal DSeries class - Reversal Patterns, OHLCvs Chart overlay
#   NOTES: Reversal Patterns are a set of technical indicators that are used to
#          identify potential trend reversals and confirm price movements. They
#          are based on the concept of price action and are used to identify
#          potential trend reversals by looking for specific patterns in the price
#          action.
#        : The Reversal Patterns are used to identify potential trend reversals
#          and confirm price movements by providing a complete picture of the market.
#        : Refer CRoot->CView->CStackOHLCvs->ChartOHLCvs.DSeriesFactory('Reversal')
#          for DSeriesReversal object creation path.
#        : Based on internal Chartboard Reversal Patterns. Not a complete
#          list, only those that are supported by Chartboard
#        : Allocated types have an internal Chartboard dependancy
RPType_BULL_NONE = 0
RPType_BULL_Enable = (1<<0)
RPType_BULL_ENGULFING  = (1<<1)
RPType_BULL_HAMMER = (1<<2)
RPType_BULL_PIERCING = (1<<3)
RPType_BULL_MORNING_STAR = (1<<4)
RPType_BULL_3WHITE_SOLDIERS = (1<<5)
RPType_BULL_WHITE_MARUBOZU = (1<<6)
RPType_BULL_3INSIDE_UP = (1<<7)
RPType_BULL_HARAMI = (1<<8)
RPType_BULL_ABANDONED_BABY = (1<<9)
RPType_BULL_INVERTED_HAMMER = (1<<10)
RPType_BULL_3OUTSIDE_UP = (1<<11)
RPType_BULL_MATCHING_LOW = (1<<12)
RPType_BULL_DELIBERATION = (1<<13)
RPType_BULL_TRISTAR = (1<<14)
RPType_BULL_SQUEEZE_ALERT = (1<<15)
RPType_BULL_3GAPDOWN = (1<<16)
RPType_BULL_HOMING_PIGEON = (1<<17)
RPMask_BULLs_NONE = 0
RPMask_BULLs = (RPType_BULL_Enable|RPType_BULL_ENGULFING|RPType_BULL_HAMMER
               |RPType_BULL_PIERCING|RPType_BULL_MORNING_STAR|RPType_BULL_3WHITE_SOLDIERS
               |RPType_BULL_WHITE_MARUBOZU|RPType_BULL_3INSIDE_UP|RPType_BULL_HARAMI
               |RPType_BULL_ABANDONED_BABY|RPType_BULL_INVERTED_HAMMER|RPType_BULL_3OUTSIDE_UP
               |RPType_BULL_MATCHING_LOW|RPType_BULL_DELIBERATION|RPType_BULL_TRISTAR
               |RPType_BULL_SQUEEZE_ALERT|RPType_BULL_3GAPDOWN|RPType_BULL_HOMING_PIGEON)

RPType_BEAR_NONE = 0
RPType_BEAR_Enable = (1<<0)
RPType_BEAR_ENGULFING = (1<<1)
RPType_BEAR_HANGING_MAN = (1<<2)
RPType_BEAR_DARK_CLOUD_COVER = (1<<3)
RPType_BEAR_EVENING_STAR = (1<<4)
RPType_BEAR_3BLACK_CROWS = (1<<5)
RPType_BEAR_BLACK_MARUBOZU = (1<<6)
RPType_BEAR_3INSIDE_DOWN = (1<<7)
RPType_BEAR_HARAMI = (1<<8)
RPType_BEAR_SHOOTING_STAR = (1<<9)
RPType_BEAR_ABANDONED_BABY = (1<<10)
RPType_BEAR_3OUTSIDE_DOWN = (1<<11)
RPType_BEAR_MATCHING_HIGH = (1<<12)
RPType_BEAR_DELIBERATION = (1<<13)
RPType_BEAR_TRISTAR = (1<<14)
RPType_BEAR_SQUEEZE_ALERT = (1<<15)
RPType_BEAR_3GAPUP = (1<<16)
RPType_BEAR_DESCENDING_HAWK = (1<<17)
RPMask_BEARs_NONE = 0
RPMask_BEARs = (RPType_BEAR_Enable|RPType_BEAR_ENGULFING|RPType_BEAR_HANGING_MAN
               |RPType_BEAR_DARK_CLOUD_COVER|RPType_BEAR_EVENING_STAR|RPType_BEAR_3BLACK_CROWS
               |RPType_BEAR_BLACK_MARUBOZU|RPType_BEAR_3INSIDE_DOWN|RPType_BEAR_HARAMI
               |RPType_BEAR_SHOOTING_STAR|RPType_BEAR_ABANDONED_BABY|RPType_BEAR_3OUTSIDE_DOWN
               |RPType_BEAR_MATCHING_HIGH|RPType_BEAR_DELIBERATION|RPType_BEAR_TRISTAR
               |RPType_BEAR_SQUEEZE_ALERT|RPType_BEAR_3GAPUP|RPType_BEAR_DESCENDING_HAWK)

class DSeriesReversals(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise Reversals data parameters
    def Sync ( self ):
        return
    # Use DSeriesob.IsNull() method to check for validity
    def ReversalobFactory(self,ePUnits,hRefob,sObjectVerb):
        hDSeriesob = DSeries.GetObject(self,'Reversals',ePUnits,hRefob,sObjectVerb)
        return DSeriesobReversal(hDSeriesob,hRefob,sObjectVerb)
#
#   Harmonics DSeries class - Harmonics Patterns, ChartOHLCvs overlay
#   NOTES: Refer CRoot->CView->CStackOHLCvs->ChartOHLCvs.DSeriesFactory('Harmonics')
#          for DSeriesHarmonics object creation path.
#        : Based on internal Chartboard Harmonics Patterns. Not a complete
#          list, only those that are supported by Chartboard
#        : Allocated types have an internal Chartboard dependancy
HPType_BULL_NONE: int = 0
HPType_BULL_Enable: int = (1<<0)
HPType_BULL_GARTLEY: int = (1<<1)
HPType_BULL_BUTTERFLY: int = (1<<2)
HPType_BULL_BAT: int = (1<<3)
HPType_BULL_CRAB: int = (1<<4)
HPType_BULL_SHARK: int = (1<<5)
HPType_BULL_CYPHER = (1<<6)
HPType_BULL_ABeCD: int = (1<<7)
HPType_BULL_PATTERN50: int = (1<<8)
HPType_BULL_HaS: int = (1<<9)
HPMask_BULLs_NONE: int = 0
HPMask_BULLs: int = (HPType_BULL_Enable
                  |HPType_BULL_GARTLEY|HPType_BULL_BUTTERFLY|HPType_BULL_BAT
                  |HPType_BULL_CRAB|HPType_BULL_SHARK|HPType_BULL_CYPHER
                  |HPType_BULL_ABeCD|HPType_BULL_ABeCD|HPType_BULL_PATTERN50
                  |HPType_BULL_HaS)

HPType_BEAR_NONE: int = 0
HPType_BEAR_Enable: int = (1<<0)
HPType_BEAR_GARTLEY: int = (1<<1)
HPType_BEAR_BUTTERFLY: int = (1<<2)
HPType_BEAR_BAT: int = (1<<3)
HPType_BEAR_CRAB: int = (1<<4)
HPType_BEAR_SHARK: int = (1<<5)
HPType_BEAR_CYPHER: int = (1<<6)
HPType_BEAR_ABeCD: int = (1<<7)
HPType_BEAR_PATTERN50: int = (1<<8)
HPType_BEAR_HaS: int = (1<<9)
HPMask_BEARs_NONE: int = 0
HPMask_BEARs: int = (HPType_BEAR_Enable
                   |HPType_BEAR_GARTLEY|HPType_BEAR_BUTTERFLY|HPType_BEAR_BAT
                   |HPType_BEAR_CRAB|HPType_BEAR_SHARK|HPType_BEAR_CYPHER
                   |HPType_BEAR_ABeCD|HPType_BEAR_ABeCD|HPType_BEAR_PATTERN50
                   |HPType_BEAR_HaS)

class DSeriesHarmonics(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise Harmonics data parameters
    def Sync ( self ):
        self.iHPTypesMask: int = P2Series.GetParam_i ( self.hDSeries, "HPTypesMask" )
        self.dZigZagXApc: float  = P2Series.GetParam_d ( self.hDSeries, "ZigZagXApc" )
        self.dBullishOSpc: float = P2Series.GetParam_d ( self.hDSeries, "BullishOSpc" )
        self.dBearishOSpc: float = P2Series.GetParam_d ( self.hDSeries, "BearishOSpc" )
        return
    # Use DSeriesob.IsNull() method to check for validity
    def HarmonicobFactory(self,ePUnits,hRefob,sObjectVerb):
        hDSeriesob = DSeries.GetObject(self,'Harmonics',ePUnits,hRefob,sObjectVerb)
        return DSeriesobHarmonic(hDSeriesob,hRefob,sObjectVerb)
#
#   VTX DSeries class - VORTEX indicator
#   NOTES: VTX is a trend-following indicator that is used to identify potential
#          trend reversals and confirm price movements. It is calculated by taking
#          the difference between the price and the exponential moving average (EMA)
#          of the price over a specified period and then applying a Vortex (VTX)
#          to it.
#        : The VTX is used to identify potential trend reversals and confirm
#          price movements by providing a complete picture of the market.
#        : VTX is a trend-following indicator that is used to identify potential
#          trend reversals and confirm price movements. It is calculated by taking
#          the difference between the price and the exponential moving average (EMA)
#          of the price over a specified period and then applying a Vortex (VTX)
#          to it.
#        : Refer CRoot->CView->CStackOHLCvs->ChartVTX.DSeriesFactory('VTX')
#          for DSeriesVTX object creation path.
class DSeriesVTX(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise VTX data sets
    def Sync ( self ):
        self.iVTXperiods: int = P2Series.GetParam_i ( self.hDSeries, "VTXperiods" )
        return
#
#   Volume DSeries class - Volume indicator
#   NOTES: Refer CRoot->CView->CStackOHLCvs->ChartVolume.DSeriesFactory('Volume')
#          for DSeriesVolume object creation path, or
#        : Refer CRoot->CView->CStackPFigure->ChartVolume.DSeriesFactory('Volume')
#          for DSeriesVolume object creation path.
class DSeriesVolume(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise Volume data sets
    def Sync ( self ):
        return
#
#   Williams DSeries class - Williams %R Momentum indicator
#   NOTES: Williams %R is a momentum-based oscillator that measures the speed and
#          change of price movements relative to the highest high and lowest low
#          over a specified period. It is used to identify overbought and oversold
#          conditions in the market. The Williams %R is calculated by taking the
#          difference between the highest high and the lowest low over a specified
#          period and then applying a Williams %R (WmR) to it.
#        : The Williams %R is used to identify potential trend reversals and confirm
#          price movements by providing a complete picture of the market.
#        : Refer CRoot->CView->CStackOHLCvs->ChartZigZag.DSeriesFactory('WmR')
#          for DSeriesWmR object creation path.
class DSeriesWmR(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise WmR data sets
    def Sync ( self ):
        self.iWmRperiods: int = P2Series.GetParam_i ( self.hDSeries, 'WmRperiods' )
        return
#
#   ZigZag DSeries class - High and Low limits overlay
#   NOTES: Refer CRoot->CView->CStackOHLCvs->ChartZigZag.DSeriesFactory('ZigZag')
#          for DSeriesZigZag object creation path.
class DSeriesZigZag(DSeries):
    def __init__ ( self, hChart, sDSeriesName ):
        super().__init__ ( hChart, sDSeriesName )
        self.Sync()
    # Synchronise ZigZag parameters
    def Sync ( self ):
        self.dPercent: float = P2Series.GetParam_d(self.hDSeries,'Percent')
        return

####################
#   Chart base class
#   NOTES: Multiple charts may exist in a chart stack
#        : Usually generated via CStack<type>.ChartFactory()
#        : Base class and derivatives usable from both Advisor and Scanner
#          environnments.
class Chart:
    def __init__ ( self, hCStack, sChartName ):
        self.hCStack = hCStack
        self.sChartName: str = sChartName
        self.hChart = P2Stack.ChartOpen(hCStack,sChartName)
        self.nPaintEoD: int = P2Chart.Getenvar_i(self.hChart,'PaintEoD')
    # Check if nominated Chart exists with stack
    def DSeriesExists(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) == 'exists':
            return True
        else:
            return False
    def PaintEoD(self,bPaintEoD):
        self.nPaintEoD: int = P2Chart.Setenvar_i(self.hChart,'PaintEoD',bPaintEoD)
    # Manage period shade bars (vertical shaded bars of period width)
    # NOTES: Direct access to internal DSeriesCTA shade bar.
    #      : Charts within a CStack are automatically assigned a single
    #        DSeriesCTA
    def PYCB_ShadeBarUpdate(self,ePUnits,nBoFset,eSBType,iState):
        return P2Chart.PYCB_ShadeBarUpdate(self.hChart,ePUnits,nBoFset,eSBType,iState)
    def PYCB_ShadeBarSelect(self,ePUnits,nBoFset,eSBType):
        return P2Chart.PYCB_ShadeBarSelect(self.hChart,ePUnits,nBoFset,eSBType)
    # Create DSeries instances and Prerequisites
    def Prerequisites(self,sDSeriesType):
        return P2Chart.Prerequisites(self.hChart,sDSeriesType)
        return None
#
#   ADX Chart class - Average Directional Index
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('ADX') for ChartADX object creation path
class ChartADX(Chart):
    def __init__(self, hCStack, sChartName):
        super().__init__(hCStack, sChartName)
    ## Create DSeries instances supported by ADX
    def DSeriesFactory(self, sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart, sDSeriesName) != 'exists':
            raise ValueError(f"DSeries: {sDSeriesName} does not exist within Chart: {self.sChartName}")
        if P2Chart.DSeriesType(self.hChart, sDSeriesName) == 'ADX':
            return DSeriesADX(self.hChart, sDSeriesName)
        raise TypeError(f"DSeries: {sDSeriesName} not supported by Chart: {self.sChartName}")
    ## Synchronise ADX environment with Chartboard container
    def Sync(self):
        pass
#   ATR Chart class - Average True Range
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('ATR') for ChartATR object creation path
class ChartATR(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'ATR': 
            return DSeriesATR(self.hChart,sDSeriesName)
        raise TypeError(f"DSeries: {sDSeriesName} not supported by Chart: {self.sChartName}")
    # Synchronise ATR data sets
    def Sync ( self ):
        return
#
#   Aroon Chart class - Aroon Indicator
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('Aroon') for ChartAroon object creation path
class ChartAroon(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'Aroon': 
            return DSeriesAroon(self.hChart,sDSeriesName)
        raise TypeError(f"DSeries: {sDSeriesName} not supported by Chart: {self.sChartName}")
    # Synchronise Aroon data sets
    def Sync ( self ):
        return
#
#   CCI Chart class - Commodity Channel Index
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('CCI') for ChartCCI object creation path
class ChartCCI(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'CCI': 
            return DSeriesCCI(self.hChart,sDSeriesName)
        raise TypeError(f"DSeries: {sDSeriesName} not supported by Chart: {self.sChartName}")
    # Synchronise CCI data sets
    def Sync ( self ):
        return
#
#   Chaikin Chart class - Chaikin Oscillator
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('Chaikin') for ChartChaikin object creation path
class ChartChaikin(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'Chaikin': 
            return DSeriesChaikin(self.hChart,sDSeriesName)
        raise TypeError(f"DSeries: {sDSeriesName} not supported by Chart: {self.sChartName}")
    # Synchronise Chaikin data sets
    def Sync ( self ):
        return
#
#   CMF Chart class - Chaikin Money Flow
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('CMF') for ChartCMF object creation path
class ChartCMF(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'CMF': 
            return DSeriesCMF(self.hChart,sDSeriesName)
        raise TypeError(f"DSeries: {sDSeriesName} not supported by Chart: {self.sChartName}")
    # Synchronise CMF data sets
    def Sync ( self ):
        return
#
#   Coppock Chart class - Coppock Indicator
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('Coppock') for ChartCoppock object creation path
class ChartCoppock(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'Coppock': 
            return DSeriesCoppock(self.hChart,sDSeriesName)
        raise TypeError(f"DSeries: {sDSeriesName} not supported by Chart: {self.sChartName}")
    # Synchronise Coppock data sets
    def Sync ( self ):
        return
#
#   EFI Chart class - Elder Ray or Force Index
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('EFI') for ChartEFI object creation path
class ChartEFI(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'EFI': 
            return DSeriesEFI(self.hChart,sDSeriesName)
        raise TypeError(f"DSeries: {sDSeriesName} not supported by Chart: {self.sChartName}")
    # Synchronise EFI data sets
    def Sync ( self ):
        return
#
#   EhlerFT Chart class - Ehlers Fisher Transform (EhlerFT)
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('EhlerFT') for ChartEhlerFT object creation path
class ChartEhlerFT(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'EhlerFT': 
            return DSeriesEhlerFT(self.hChart,sDSeriesName)
        print('DSeries: ' + sDSeriesName + ' not supported by Chart: ' + self.sChartName )
        return None
    # Synchronise EhlerFT data sets
    def Sync ( self ):
        return
#
#   DPO Chart class - Detrended Price Oscillator
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('DPO') for ChartDPO object creation path
class ChartDPO(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'DPO': 
            return DSeriesDPO(self.hChart,sDSeriesName)
        print('DSeries: ' + sDSeriesName + ' not supported by Chart: ' + self.sChartName )
        return None
    # Synchronise DPO data sets
    def Sync ( self ):
        return
#
#   OHLCvs Chart class - Open High Low Close volume, shorts
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('OHLCvs') for ChartOHLCvs object creation path
class ChartOHLCvs(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            print ('DSeries: ' + sDSeriesName + ' does not exist within Chart: ' + self.sChartName)
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'OHLCvs': 
            return DSeriesOHLCvs(self.hChart,sDSeriesName)
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'OHLC': 
            return DSeriesOHLCvs(self.hChart,sDSeriesName)
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'BB': 
            return DSeriesBB(self.hChart,sDSeriesName)
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'KAMA': 
            return DSeriesKAMA(self.hChart,sDSeriesName)
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'MAMA': 
            return DSeriesMAMA(self.hChart,sDSeriesName)
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'Reversals':
            return DSeriesReversals(self.hChart,sDSeriesName)
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'SAR': 
            return DSeriesSAR(self.hChart,sDSeriesName)
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'Keltner': 
            return DSeriesKeltner(self.hChart,sDSeriesName)
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'Ichimoku': 
            return DSeriesIchimoku(self.hChart,sDSeriesName)
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'Donchian': 
            return DSeriesDonchian(self.hChart,sDSeriesName)
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'Coppock': 
            return DSeriesCoppock(self.hChart,sDSeriesName)
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'SMAnnn': 
            return DSeriesSMAnnn(self.hChart,sDSeriesName)
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'EMAnnn': 
            return DSeriesEMAnnn(self.hChart,sDSeriesName)
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'Harmonics':
            return DSeriesHarmonics(self.hChart,sDSeriesName)
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'Chandelier':
            return DSeriesChandelier(self.hChart,sDSeriesName)
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'TDMAI-A':
            return DSeriesTDMA(self.hChart,sDSeriesName)
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'TDMAI-B':
            return DSeriesTDMA(self.hChart,sDSeriesName)
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'TDMAI-C':
            return DSeriesTDMA(self.hChart,sDSeriesName)
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'TDemark':
            return DSeriesTDemark(self.hChart,sDSeriesName)
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'ZigZag':
            return DSeriesZigZag(self.hChart,sDSeriesName)
        print( 'DSeries: ' + sDSeriesName + ' not supported by Chart: ' + self.sChartName )
        return None
    def DSeriesSMAnnnFactory(self,nSMAperiods,ePUnits):
        sDSeriesSMAname = MakeDSeriesSMAname(nSMAperiods,ePUnits)
        P2Chart.Prerequisites ( self.hChart, sDSeriesSMAname )
        if P2Chart.DSeriesExists(self.hChart,sDSeriesSMAname) != 'exists' :
            print ('DSeries: ' + sDSeriesSMAname + ' does not exist within Chart: ' + self.sChartName)
            return None
        return DSeriesSMAnnn(self.hChart,sDSeriesSMAname)
    def DSeriesEMAnnnFactory(self,nEMAperiods,ePUnits):
        sDSeriesEMAname = MakeDSeriesEMAname(nEMAperiods,ePUnits)
        P2Chart.Prerequisites ( self.hChart, sDSeriesEMAname )
        if P2Chart.DSeriesExists(self.hChart,sDSeriesEMAname) != 'exists' :
            print ('DSeries: ' + sDSeriesEMAname + ' does not exist within Chart: ' + self.sChartName)
            return None
        return DSeriesEMAnnn(self.hChart,sDSeriesEMAname)
    # Synchronise OHLCvs data sets
    def Sync ( self ):
        return
#
#   PFigure Chart class - Point and Figure chart
#   NOTES: Refer CRoot->CView->CStackPFigure.ChartFactory('PFigure') for ChartPFigure object creation path
class ChartPFigure(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            print ('DSeries: ' + sDSeriesName + ' does not exist within Chart: ' + self.sChartName)
            return None
        print( 'DSeries: ' + sDSeriesName + ' not supported by Chart: ' + self.sChartName )
        return None
    # Synchronise PFigure data sets
    def Sync ( self ):
        return
#
#   KST Chart class - Pring's Know Sure Thing
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('KST') for ChartKST object creation path
class ChartKST(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            print('DSeriesKST does not exist')
            print('DSeries: ' + sDSeriesName + ' does not exist within Chart: ' + self.sChartName)
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'KST': 
            return DSeriesKST(self.hChart,sDSeriesName)
        print('ChartKST failure')
        print ('DSeries: '+sDSeriesName+' not supported by Chart: '+self.sChartName)
        return None
    # Synchronise KST data sets
    def Sync ( self ):
        return
#
#   MACD Chart class - Moving Average Convergence Divergence
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('MACD') for ChartMACD object creation path
class ChartMACD(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            print('DSeriesMACD does not exist')
            print('DSeries: ' + sDSeriesName + ' does not exist within Chart: ' + self.sChartName)
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'MACD': 
            return DSeriesMACD(self.hChart,sDSeriesName)
        print('ChartMACD failure')
        print ('DSeries: '+sDSeriesName+' not supported by Chart: '+self.sChartName)
        return None
    # Synchronise MACD data sets
    def Sync ( self ):
        return
#
#   MFI Chart class - Money Flow Index
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('MFI') for ChartMFI object creation path
class ChartMFI(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'MFI': 
            return DSeriesMFI(self.hChart,sDSeriesName)
        assert 0, "DSeries: " + sDSeriesName + " not supported by Chart: " + self.sChartName
        return None
    # Synchronise MFI data sets
    def Sync ( self ):
        return
#
#   MSA Chart class - Momentum Structural Analysis
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('MSA') for ChartMSA object creation path
class ChartMSA(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            print('DSeriesMSA does not exist')
            print('DSeries: ' + sDSeriesName + ' does not exist within Chart: ' + self.sChartName)
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'MSA': 
            return DSeriesMSA(self.hChart,sDSeriesName)
        print('ChartMSA failure')
        print ('DSeries: '+sDSeriesName+' not supported by Chart: '+self.sChartName)
        return None
    # Synchronise MSA data sets
    def Sync ( self ):
        return
#
#   OBV Chart class - On Balance Volume
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('OBV') for ChartOBV object creation path
class ChartOBV(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'OBV': 
            return DSeriesOBV(self.hChart,sDSeriesName)
        assert 0, "DSeries: " + sDSeriesName + " not supported by Chart: " + self.sChartName
        return None
    # Synchronise OBV data sets
    def Sync ( self ):
        return
#
#   PBars Chart class - Price Variation Bars
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('PBars') for ChartPBars object creation path
class ChartPBars(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'PBars': 
            return DSeriesPBars(self.hChart,sDSeriesName)
        assert 0, "DSeries: " + sDSeriesName + " not supported by Chart: " + self.sChartName
        return None
    # Synchronise PBars data sets
    def Sync ( self ):
        return
#
#   PMO Chart class - Price Momentum Oscillator
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('PMO') for ChartPMO object creation path
class ChartPMO(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'PMO': 
            return DSeriesPMO(self.hChart,sDSeriesName)
        assert 0, "DSeries: " + sDSeriesName + " not supported by Chart: " + self.sChartName
        return None
    # Synchronise PMO data sets
    def Sync ( self ):
        return
#
#   PPO Chart class - Percentage Price Oscillator
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('PPO') for ChartPPO object creation path
#        : MACD equivalent but uses Percentage Price Oscillator
class ChartPPO(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'PPO': 
            return DSeriesPPO(self.hChart,sDSeriesName)
        assert 0, "DSeries: " + sDSeriesName + " not supported by Chart: " + self.sChartName
        return None
    # Synchronise PPO data sets
    def Sync ( self ):
        return
#
#   PVO Chart class - Price Volume Oscillator
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('PVO') for ChartPVO object creation path
class ChartPVO(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'PVO': 
            return DSeriesPVO(self.hChart,sDSeriesName)
        assert 0, "DSeries: " + sDSeriesName + " not supported by Chart: " + self.sChartName
        return None
    # Synchronise PVO data sets
    def Sync ( self ):
        return
#
#   ROC Chart class - Rate of Change
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('ROC') for ChartROC object creation path
class ChartROC(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'ROC': 
            return DSeriesROC(self.hChart,sDSeriesName)
        assert 0, "DSeries: " + sDSeriesName + " not supported by Chart: " + self.sChartName
        return None
    # Synchronise ROC data sets
    def Sync ( self ):
        return
#
#   RSI Chart class - Relative Strength Index
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('RSI') for ChartRSI object creation path
class ChartRSI(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'RSI': 
            return DSeriesRSI(self.hChart,sDSeriesName)
        assert 0, "DSeries: " + sDSeriesName + " not supported by Chart: " + self.sChartName
        return None
    # Synchronise RSI data sets
    def Sync ( self ):
        return
#
#   SLOPE Chart class - SLOPE indicator
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('SLOPE') for ChartSLOPE object creation path
class ChartSLOPE(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'SLOPE': 
            return DSeriesSLOPE(self.hChart,sDSeriesName)
        assert 0, "DSeries: " + sDSeriesName + " not supported by Chart: " + self.sChartName
        return None
    # Synchronise SLOPE data sets
    def Sync ( self ):
        return
#
#   StochRSI Chart class - Stochastic Relative Strength Index
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('StochRSI') for ChartStochRSI object creation path
class ChartStochRSI(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'StochRSI': 
            return DSeriesStochRSI(self.hChart,sDSeriesName)
        assert 0, "DSeries: " + sDSeriesName + " not supported by Chart: " + self.sChartName
        return None
    # Synchronise StochRSI data sets
    def Sync ( self ):
        return
#
#   STO Chart class - Stochastic Oscillator
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('STO') for ChartSTO object creation path
class ChartSTO(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'STO': 
            return DSeriesSTO(self.hChart,sDSeriesName)
        assert 0, "DSeries: " + sDSeriesName + " not supported by Chart: " + self.sChartName
        return None
    # Synchronise STO data sets
    def Sync ( self ):
        return
#
#   STDEV Chart class - Standard Deviation
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('STDEV') for ChartSTDEV object creation path
class ChartSTDEV(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'STDEV': 
            return DSeriesSTDEV(self.hChart,sDSeriesName)
        assert 0, "DSeries: " + sDSeriesName + " not supported by Chart: " + self.sChartName
        return None
    # Synchronise STDEV data sets
    def Sync ( self ):
        return
#
#   Shorts Chart class - Shorts Indicator
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('Shorts') for ChartShorts
#          object creation path, or
#        : Refer CRoot->CView->CStackPFigure.ChartFactory('Shorts') for ChartShorts
#          object creation path
class ChartShorts(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'Shorts': 
            return DSeriesShorts(self.hChart,sDSeriesName)
        assert 0, "DSeries: " + sDSeriesName + " not supported by Chart: " + self.sChartName
        return None
    # Synchronise Shorts data sets
    def Sync ( self ):
        return
#
#   TRIX Chart class - Triple Exponential Moving Average
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('TRIX') for ChartTRIX object creation path
class ChartTRIX(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'TRIX': 
            return DSeriesTRIX(self.hChart,sDSeriesName)
        assert 0, "DSeries: " + sDSeriesName + " not supported by Chart: " + self.sChartName
        return None
    # Synchronise TRIX data sets
    def Sync ( self ):
        return
#
#   TSI Chart class - True Strength Index
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('TSI') for ChartTSI object creation path
class ChartTSI(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'TSI': 
            return DSeriesTSI(self.hChart,sDSeriesName)
        assert 0, "DSeries: " + sDSeriesName + " not supported by Chart: " + self.sChartName
        return None
    # Synchronise TSI data sets
    def Sync ( self ):
        return
#
#   VTX Chart class - VORTEX Indicator
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('VTX') for ChartVTX object creation path
class ChartVTX(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'VTX': 
            return DSeriesVTX(self.hChart,sDSeriesName)
        assert 0, "DSeries: " + sDSeriesName + " not supported by Chart: " + self.sChartName
        return None
    # Synchronise VTX data sets
    def Sync ( self ):
        return
#
#   Volume Chart class - Volume Indicator
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('Volume') for ChartVolume
#          object creation path
#        : Refer CRoot->CView->CStackPFigure.ChartFactory('Volume') for ChartVolume
#          object creation path
class ChartVolume(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'Volume': 
            return DSeriesVolume(self.hChart,sDSeriesName)
        assert 0, "DSeries: " + sDSeriesName + " not supported by Chart: " + self.sChartName
        return None
    # Synchronise Volume data sets
    def Sync ( self ):
        return
#
#   WmR Chart class - Williams %R
#   NOTES: Refer CRoot->CView->CStackOHLCvs.ChartFactory('WmR') for ChartWmR object creation path
class ChartWmR(Chart):
    def __init__ ( self, hCStack, sChartName ):
        super().__init__ ( hCStack, sChartName )
    def DSeriesFactory(self,sDSeriesName):
        if P2Chart.DSeriesExists(self.hChart,sDSeriesName) != 'exists' :
            assert 0, "DSeries: " + sDSeriesName + " does not exist within Chart: " + self.sChartName
            return None
        if P2Chart.DSeriesType(self.hChart,sDSeriesName) == 'WmR': 
            return DSeriesWmR(self.hChart,sDSeriesName)
        assert 0, "DSeries: " + sDSeriesName + " not supported by Chart: " + self.sChartName
        return None
    # Synchronise WmR data sets
    def Sync ( self ):
        return

####################
#   CStack base class
#   NOTES: Adjusts according to referenced View tab type
#        : Reference view tab under which script activated as 'this'
#        : Base class and derivatives usable from both Advisor chart stack 
#          environments environnments only.  Otherwise run-time error generated
#        : Allocated types have an internal Chartboard dependancy
SBTYPE_Bullish: int = 0      # Bullish shade bar type
SBTYPE_OBought: int = 1      # Over bought
SBTYPE_Sell: int = 2         # Sell
SBTYPE_Bearish: int = 3      # Bearish
SBTYPE_OSold: int = 4        # Over sold
SBTYPE_Buy: int = 5          # Buy
class CStack:
    def __init__(self,hCView):
        self.hCView = hCView                                           # Parent CView reference handle
        self.hCStack = P2View.OpenCStack(hCView)                       # Reference handle for this CStack
        self.sStackType: str = P2Stack.StackType(self.hCStack)         # OHLCvs or PFigure
        self.sPUnits: str = P2Stack.Getenvar_s(self.hCStack,'PUnits')  # YEAR=1, QUARTER=2, MONTH=3, WEEK=4, DAY=5
        self.nPUnits: int = P2Stack.Getenvar_i(self.hCStack,'PUnits')
        self.nPaintEoD: int = P2Stack.Getenvar_i(self.hCStack,'PaintEoD')   # 
        self.sStockCode: str = P2Stack.StockCode(self.hCStack)         # MSFT, TSLA, GDX, etc.
    # Synchronise CStack data sets
    def Sync ( self ):
        return
    # CStack operations
    def ChartExists(self,sChartname):
        return P2Stack.ChartSummary(self.hCStack,sChartname) == 'exists'
    def Rewind(self):
        return P2Stack.Rewind(self.hCStack)
    def Step(self,ePUnits,nBoFset):
        return P2Stack.Step(self.hCStack,ePUnits,nBoFset)
    # Shade bars
    def PYCB_ShadeBarUpdate(self,ePUnits,nBoFset,eSBType,iValue):
        return P2Stack.CA_Update(self.hCStack,ePUnits,nBoFset,eSBType,iValue)
    def PYCB_ShadeBarClear(self,ePUnits):
        P2Stack.PYCB_ShadeBarClear(self.hCStack,ePUnits)
        return
    def PYCB_ShadeBarMask(self,ePUnits,wMask):
        P2Stack.PYCB_ShadeBarMask(self.hCStack,ePUnits,wMask)
        return
    def isCategory(self,sStackType) -> bool:
        if CStack.sStackType == sStackType:
            return False
        return True
    def PaintEoD(self,bPaintEoD):
        self.nPaintEoD = P2Stack.Setenvar_i(self.hCStack,'PaintEoD',bPaintEoD)
        return
    # Create chart instances and Prerequisites
    def Prerequisites(self,sChartType):
        return P2Stack.Prerequisites(self.hCStack,Chart=sChartType)
    def ChartCreate(self,sChartype):
        if P2Stack.ChartSummary(self.hCStack,sChartName) != 'nochart' :
            assert 0, "Category: " + sChartName + " already exists within CStack"
            return None
        return P2Stack.ChartCreate(self.hCStack,sChartName)
    def CategoryFactory(self):
        if self.sStackType == 'OHLCvs': 
            return CStackOHLCvs(self)
        if self.sStackType == 'OHLC': 
            return CStackOHLCvs(self)
        if self.sStackType == 'PFigure' or self.sStackType == 'PFigure': 
            return CStackPFigure(self)
        assert 0, "Category: " + type + " not supported by CStack (OHLCvs or PFigure)"
    # Properties
    def PeriodUnits(self):
        self.sPUnits: str = P2Stack.Getenvar_s(self.hCStack,'PUnits')
        self.nPUnits: int = P2Stack.Getenvar_i(self.hCStack,'PUnits')
        return self.sPUnits; # aka Stepping period
        # Sets the period units (1, 2, 3, 4, 5) for the CStack
    def SetPUnits(self,ePUnits) -> int:
        P2Stack.Setenvar_i(self.hCStack,'PUnits',ePUnits)
        self.sPUnits: str = P2Stack.Getenvar_s(self.hCStack,'PUnits')
        self.nPUnits: int = P2Stack.Getenvar_i(self.hCStack,'PUnits')
        return self.nPUnits;
        # Exposes contained primarly stock code
    def StockCode(self) -> str:
        return self.sStockCode
    def DATE(self):
        return P2Stack.DATE(self.hCStack);
        # Checks for CStackOHLCvs type 
    def IsOHLCvs(self):
        return self.sStackType == 'OHLCvs'
        # Checks for PFigure CStack type
    def IsPFigure(self):
        return self.sStackType == 'PFigure'
    # Workspace environment related variables
    # NOTES: Used to interact with the workspace at an environmental,
    #        visual or summary level independant of DSeries calculations etc
    def Getenvar_i(self,sEnvarname) -> int:
        return P2Stack.Getenvar_i(self.hCStack,sEnvarname)
    def Getenvar_d(self,sEnvarname) -> float:
        return P2Stack.Getenvar_d(self.hCStack,sEnvarname)
    def Getenvar_dt(self,sEnvarname) -> datetime:
        return P2Stack.Getenvar_dt(self.hCStack,sEnvarname)
    # Environment settings
    def Setenvar_i(self,sEnvarname,iEnvar):
        P2Stack.Setenvar_i(self.hCStack,sEnvarname,iEnvar)
#
#   CStackOHLCvs class (Open-High-Low-Close type chart stack)
#   NOTES: Must be supported by referenced environment
class CStackOHLCvs(CStack):
    def __init__ (self,hCView):
        super().__init__(hCView)
        self.Sync()
    # Synchronise CStackOHLCvs environment
    # CStack.Getenvar_dt(sEnvarName)
    #   'ModelBegin' - Start date for modelling
    #   'ModelEnd' - End date for modelling
    def Sync ( self ):
        self.dtModelBegin: datetime = CStack.Getenvar_dt(self,'ModelBegin')
        self.dtModelEnd: datetime = CStack.Getenvar_dt(self,'ModelEnd')
        self.nModelBegin: float = CStack.Getenvar_d(self, 'ModelBegin')
        self.nModelEnd: float = CStack.Getenvar_d(self, 'ModelEnd')
        self.bModelLimitsEoD: int = CStack.Getenvar_i(self,'ModelLimitsEoD')
        return

    # Chart operations
    def ChartSummary(self,sChartName):
        return CStack.ChartExists(self,sChartName)
    # Generate chart instances
    def ChartFactory(self,sChartName):
        if P2Stack.ChartSummary(self.hCStack,sChartName) != 'exists' :
            print('Chart: ' + sChartName + ' does not exist within CStackOHLCvs')
            return None
        if P2Stack.ChartType(self.hCStack,sChartName) == 'OHLCvs': 
            print('ChartFactory: Create ChartOHLCvs')
            return ChartOHLCvs(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'OHLC': 
            print('ChartFactory: Create ChartOHLC')
            return ChartOHLCvs(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'CCI': 
            return ChartCCI(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'Chaikin': 
            return ChartChaikin(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'CMF': 
            return ChartCMF(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'Coppock': 
            return ChartCoppock(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'EhlerFT': 
            return ChartEhlerFT(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'EFI': 
            return ChartEFI(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'DPO': 
            return ChartDPO(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'KST': 
            return ChartKST(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'MACD': 
            return ChartMACD(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'MFI': 
            return ChartMFI(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'MSA': 
            return ChartMSA(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'OBV': 
            return ChartOBV(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'PBars': 
            return ChartPBars(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'PMO': 
            return ChartPMO(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'PPO': 
            return ChartPPO(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'PVO': 
            return ChartPVO(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'ROC': 
            return ChartROC(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'RSI': 
            return ChartRSI(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'SLOPE': 
            return ChartSLOPE(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'StochRSI': 
            return ChartStochRSI(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'STDEV': 
            return ChartSTDEV(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'STO': 
            return ChartSTO(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'Shorts': 
            return ChartShorts(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'TRIX': 
            return ChartTRIX(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'ADX': 
            return ChartADX(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'ATR': 
            return ChartATR(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'Aroon': 
            return ChartAroon(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'TSI': 
            return ChartTSI(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'Volume': 
            return ChartVolume(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'VTX': 
            return ChartVTX(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'WmR': 
            return ChartWmR(self.hCStack,sChartName)
        print('Chart: ' + sChartName + ' not supported by CStackOHLCvs')
        return None
    # Generate modelling instance
    # NOTES: Requires an existing Modelling Attachment to act as results repository
    #      : Logically models MUST be run against a chart stack
    def ModelFactory(self,sAttachmentName):
        if P2Model.AttachmentSummary(sAttachmentName) != 'exists' :
            print('Modelling Attachment: ' + sAttachmentName + ' does not exist within workspace')
            return None
        print ( 'ModelFactory oCStack.sStockCode:' + self.sStockCode )
        return Model(self,sAttachmentName)
#
#   CStackPFigure class (Point and Figure chart stack category)
#   NOTES: Must be supported by referenced environment, concept under
#          development
class CStackPFigure(CStack):
    def __init__(self):
        super().__init__()
    # Synchronise CStackPFigure data sets
    def Sync ( self ):
        return
    # Chart operations
    def ChartSummary(self,sChartName):
        return CStack.ChartExists(self,sChartName)
    # Generate chart instances
    def ChartFactory(self,sChartName):
        if P2Stack.ChartSummary(self.hCStack,sChartName) != 'exists' :
            print('Chart: ' + sChartName + ' does not exist within CStackPFigure (PFigure only)')
            return None
        if P2Stack.ChartType(self.hCStack,sChartName) == 'PFigure': 
            return ChartPFigure(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'Shorts': 
            return ChartShorts(self.hCStack,sChartName)
        if P2Stack.ChartType(self.hCStack,sChartName) == 'Volume': 
            return ChartVolume(self.hCStack,sChartName)
        print('Chart: ' + sChartName + ' not supported by CStackPFigure (PFigure only)')
        return None

####################
#   CScanner base class
#   NOTES: Adjusts according to referenced View tab
#        : Reference view tab under which script activated as 'this'
#        : Base class and derivatives usable from both stock scanner 
#          environments environnments only.  Otherwise run-time error generated
#        : Allocated types have an internal Chartboard dependancy
SBTYPE_Bullish = 0           # Bullish shade bar type
SBTYPE_OBought = 1           # Over bought
SBTYPE_Sell = 2              # Sell
SBTYPE_Bearish = 3           # Bearish
SBTYPE_OSold = 4             # Over sold
SBTYPE_Buy = 5               # Buy
class CScanner:
    def __init__(self,hCView):
        self.hCView = hCView                                              # Parent CView reference handle
        self.hCScanner = P2View.OpenCScanner(hCView)                      # Reference handle for this CScanner
        self.sCScanType: str = P2Scanner.CScanType(self.hCScanner)        # CScanOHLCvs or CScanPFigure
        self.sPUnits: str = P2Scanner.Getenvar_s(self.hCScanner,'PUnits') # YEAR=1, QUARTER=2, MONTH=3, WEEK=4, DAY=5
        self.nPUnits: int = P2Scanner.Getenvar_i(self.hCScanner,'PUnits')
        self.nPaintEoD: int = P2Scanner.Getenvar_i(self.hCScanner,'PaintEoD')   # 
        self.sStockCode: str = P2Scanner.StockCode(self.hCScanner)
        self.dRefDATE: float = P2Scanner.Getenvar_d(self.hCScanner,'RefDATE');
        self.dtRefDATE: datetime = P2Scanner.Getenvar_dt(self.hCScanner,'RefDATE');
        self.nLBPeriods: int = P2Scanner.Getenvar_i(self.hCScanner,'LBPeriods')
    # Synchronise Scanner data sets
    def Sync ( self ):
        return
    # CScanner cursor operations
    def Rewind(self):
        return P2Scanner.Rewind(self.hCScanner)
    def FastForward(self):
        return P2Scanner.FastForward(self.hCScanner)
    def Step(self,ePUnits,nBoFset):
        return P2Scanner.Step(self.hCScanner,ePUnits,nBoFset)
    def SetCursorPos(self,nDATE,ePUnits) -> float:
        return P2Scanner.SetCursorPos(self.hCScanner,nDATE,ePUnits)
    def GetCursorPos(self,ePUnits) -> float:
        return P2Scanner.GetCursorPos(self.hCScanner)
    def GetDSetDATE(self,sDATEname,ePUnits) -> float:
        return P2Scanner.GetDSetDATE(self.hCScanner,sDATEname,ePUnits)
    # Create chart instances and Prerequisites
    def ChartExists(self,sChartname):
        return P2Scanner.ChartSummary(self.hCScanner,sChartname) == 'exists'
    def Prerequisites(self,sChartType):
        return P2Scanner.Prerequisites(self.hCScanner,Chart=sChartType)
    def isCategory(self,sCScannerType):
        if CScanner.sCScannerType == sCScannerType:
            return False
        return True
    def ChartCreate(self,sChartype):
        if P2Scanner.ChartSummary(self.hCScanner,sChartName) != 'nochart' :
            assert 0, "Chart: " + sChartName + " already exists within CStack"
            return None
        return P2Scanner.ChartCreate(self.hCScanner,sChartName)
    # Create descendant CStack and CScan instances
    def CStackFactory(self):
        print ( "CScanOHLCvs.CStackFactory Entry" )
        if self.sCScanType == 'CScanOHLCvs':
            return CStackOHLCvs(self.hCScanner)
        if self.sCScanType == 'CScanPFigure': 
            return CStackPFigure(self.hCScanner)
        assert 0, "CStackFactory: " + type + " not supported by CView (OHLCvs, PFigure)"
    # Results
    def SetSelected(self,bSelect):
        P2Scanner.ES_Selected(self.hCScanner,bSelect)
        return
    # Properties
    def Refresh(self):
        self.nPUnits: int = P2Scanner.Getenvar_i(self.hCScanner,'PUnits')
        self.sPUnits: str = P2Scanner.Getenvar_s(self.hCScanner,'PUnits')
        self.sStockCode: str = P2Scanner.StockCode(self.hCScanner)
        self.nLBPeriods: int = P2Scanner.Getenvar_i(self.hCScanner,'LBPeriods')
        return
    def PeriodUnits(self):
        return self.sPUnits; # aka Stepping period
    def StockCode(self) -> str:
        return self.sStockCode
    #def DATE(self):
    #    return P2Scanner.DATE(self.hCScanner);
    def IsOHLCvs(self):
        if self.sCScannerType == 'OHLCvs': return 1
        return 0
    def IsPFigure(self):
        if self.sCScannerType == 'PFigure': return 1
        return 0
    # Environment settings
    def Setenvar_i(self,sEnvarname,iValue) -> int:
        P2Scanner.Setenvar_i(self.hCScanner,sEnvarname,iValue)
        self.Refresh()
    def Setenvar_s(self,sEnvarname,sValue) -> str:
        P2Scanner.Setenvar_s(self.hCScanner,sEnvarname,sValue)
        self.Refresh()
    def Getenvar_d(self,sEnvarname,sValue) -> float:
        P2Scanner.Getenvar_s(self.hCScanner,sEnvarname)
    def Getenvar_dt(self,sEnvarname,sValue) -> datetime:
        P2Scanner.Getenvar_dt(self.hCScanner,sEnvarname)
#
#   CScanOHLCvs class (Open-High-Low-Close type chart scanner)
#   NOTES: Must be supported by referenced environment.  Generates non
#          visual chart stacks and then exposes the calculated parameters
#          to your custom scanning algorithms
class CScanOHLCvs(CScanner):
    def __init__ (self,hCView):
        super().__init__(hCView)
    # Synchronise CScanOHLCvs data sets
    def Sync ( self ):
        return
    # Chart operations
    def ChartSummary(self,sCScanname):
        return CScanner.ChartExists(self,sCScanname)
    # Generate chart instances
    def ChartFactory(self,sChartName):
        if P2Scanner.ChartSummary(self.hCScanner,sChartName) != 'exists' :
            print('Chart: ' + sChartName + ' does not exist within CScanOHLC')
            return None
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'OHLCvs': 
            return ChartOHLCvs(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'OHLC': 
            return ChartOHLCvs(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'CCI': 
            return ChartCCI(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'Chaikin': 
            return ChartChaikin(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'CMF': 
            return ChartCMF(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'Coppock': 
            return ChartCoppock(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'EhlerFT': 
            return ChartEhlerFT(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'EFI': 
            return ChartEFI(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'DPO': 
            return ChartDPO(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'KST': 
            return ChartKST(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'MACD': 
            return ChartMACD(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'MFI': 
            return ChartMFI(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'MSA': 
            return ChartMSA(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'OBV': 
            return ChartOBV(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'PBars': 
            return ChartPBars(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'PMO': 
            return ChartPMO(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'PPO': 
            return ChartPPO(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'PVO': 
            return ChartPVO(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'ROC': 
            return ChartROC(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'RSI': 
            return ChartRSI(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'SLOPE': 
            return ChartSLOPE(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'StochRSI': 
            return ChartStochRSI(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'STDEV': 
            return ChartSTDEV(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'STO': 
            return ChartSTO(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'Shorts': 
            return ChartShorts(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'TRIX': 
            return ChartTRIX(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'ADX': 
            return ChartADX(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'ATR': 
            return ChartATR(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'Aroon': 
            return ChartAroon(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'TSI': 
            return ChartTSI(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'Volume': 
            return ChartVolume(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'VTX': 
            return ChartVTX(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'WmR': 
            return ChartWmR(self.hCScanner,sChartName)
        print('Chart: ' + sChartName + ' not supported by CScanOHLCvs')
        return None
#
#   CScanPFigure class (Point and Figure chart scanner)
#   NOTES: Must be supported by referenced environment.  Generates non
#          visual chart stacks and then exposes the calculated parameters
#          to your custom scanning algorithms
#        : Under development
class CScanPFigure(CScanner):
    def __init__ (self,sCWndParent,sCScannername):
        super().__init__(sCWndParent,sCScannername)
    #def __init__ ( self,sCScanname ):
    #    super().__init__(sCScanname)
    # Synchronise CSannerPFigure data sets
    def Sync ( self ):
        return
    # Chart operations
    def ChartSummary(self,sCScanname):
        return CScanner.ChartExists(self,sCScanname)
    # Generate chart instances
    def ChartFactory(self,sChartName):
        if P2Scanner.ChartSummary(self.hCScanner,sChartName) != 'exists' :
            print('Chart: ' + sChartName + ' does not exist within CScanPFigure')
            return None
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'PFigure': 
            return ChartPFigure(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'Volume': 
            return ChartVolume(self.hCScanner,sChartName)
        if P2Scanner.ChartType(self.hCScanner,sChartName) == 'Shorts': 
            return ChartShorts(self.hCScanner,sChartName)
        print('Chart: ' + sChartName + ' not supported by CScanPFigure')
        return None

####################
#   CView base class
#   NOTES: Adjusts according to referenced View tab
#        : Reference view tab under which script activated as 'this'
#        : Base class and derivatives usable from all View Tab environments
#        : Allocated types have an internal Chartboard dependancy
class CView:
    def __init__(self,sCViewname):
        #self.sCWndParent: str = sCWndParent
        self.sCViewname: str = sCViewname
        self.hCView = P2View.Open(sCViewname)
        self.sCViewType: str = P2View.TabbedViewType(self.hCView)
        #self.sPUnits: str = P2View.Getenvar_s(self.hCView,'PUnits')
        #self.nPUnits: int = P2View.Getenvar_i(self.hCView,'PUnits')
        #self.nPaintEoD: int = P2View.Getenvar_i(self.hCView,'PaintEoD')
    # Synchronise CView data sets
    def Sync ( self ):
        return
    # CView operations
    # Create descendant CStack and CScan instances
    def CStackFactory(self):
        print ( "CView.CStackFactory Entry" )
        if self.sCViewType == 'OHLCvs':
            oCStackOHLCvs = CStackOHLCvs(self.hCView)
            return oCStackOHLCvs
        if self.sCViewType == 'PFigure': 
            return CStackPFigure('CView','this')
        assert 0, "CStackFactory: " + type + " not supported by CView (OHLCvs, PFigure)"
    def CScanFactory(self):
        print ( "CView.CScanFactory Entry" )
        if self.sCViewType == 'CScanOHLCvs':
            oCScanOHLCvs = CScanOHLCvs(self.hCView)
            return oCScanOHLCvs
        if self.sCViewType == 'CScanPFigure': 
            return CScanPFigure(self.hCView)
        assert 0, "CScanFactory: " + type + " not supported by CView (ScamOHLCvs, ScanPFigure)"
    # Properties
    def PaintEoD(self,bPaintEoD):
        self.nPaintEoD = P2View.Setenvar_i(self.hCView,'PaintEoD',bPaintEoD)
    def PUnits(self):
        self.sPUnits: str = P2View.Getenvar_s(self.hCView,'PUnits')
        self.nPUnits: int = P2View.Getenvar_i(self.hCView,'PUnits')
        return self.sPUnits; # aka Stepping period
    def SetPUnits(self,ePUnits) -> int:
        P2View.Setenvar_i(self.hCView,'PUnits',ePUnits)
        self.sPUnits: str = P2View.Getenvar_s(self.hCView,'PUnits')
        self.nPUnits: int = P2View.Getenvar_i(self.hCView,'PUnits')
        return self.nPUnits;
    def IsCViewOHLCvs(self):
        if self.sCViewType == 'OHLCvs': return 1
        return 0
    def IsCViewPFigure(self):
        if self.sCViewType == 'PFigure': return 1
        return 0
    def IsCScanOHLCvs(self):
        if self.sCViewType == 'CScanOHLCvs': return 1
        return 0
    def IsCScanPFigure(self):
        if self.sCViewType == 'CScanPFigure': return 1
        return 0

####################
#   CRoot base class
#   NOTES: Parent of all subsequent CBEC-CView descendants (Stack, CScanner)
#        : Valid in both Chart Stack and Stock Scanning environments
#        : Base class and derivatives usable from both Advisor and
#          Scanner environnments.
class CRoot:
    def __init__(self):
        print ( 'CRoot.__init__ doneas' )
        self.Sync()
    # Synchronise Root data parameters
    def Sync ( self ):
        self.sVersionCBEC: str = '3.2.04'                  # Version of PythonCBEC.pyw
        self.sVersion: str = P2Root.Getenvar_s('Version')  # Version of Chartboard
        self.nBuildCBEC: int = 3204                        # Build of PythonCBEC.pyw                     
        self.nBuild: int = P2Root.Getenvar_i('Build')      # Build of Chartboard
        return
    # CView Factory - Create instance from existing view tabs
    # NOTES: Exclusively used on existing visual tab Views
    #      : Use 'This' to reference CView from which script has been run
    #      : Subsequently the return CView can be used to retrieve properties
    #        and generate specific type instance
    def CViewFactory(self,sCViewname):
        return CView ( sCViewname )
        #sCViewType = CRoot.CWndType(self,'CViewTabs',sCViewname)
        #if sCViewType == 'CViewOHLCvs': 
        #    return CView('CViewTabs',sCViewname)
        #if sCViewType == 'CViewPFigure': 
        #    return CView('CViewTabs',sCViewname)
        #if sCViewType == 'CViewScanner': 
        #    return CView('CViewTabs',sCViewname)
        #raise TypeError(f"CViewTabs." + sCViewname + " not supported CView[OHLCvs|PFigure|Scanner]")
    # CStack Factories - Create Chart Stack for designated category
    # NOTES: Exclusively used on existing visual chart stacks
    #      : Use 'This' to reference CStack from which script has been run
    #def CStackFactory(self,sCWndParent,sCStackname):
    #    sStackType = CRoot.CWndType(self,sCWndParent,sCStackname)
    #    hCStack    = P2Stack.Open(sCWndParent,sCStackname)
    #    if sStackType == 'CStackOHLCvs': 
    #        return CStackOHLC(sCWndParent,sCStackname)
    #    if sStackType == 'CStackPFigure': 
    #        return CStackPFigure(sCWndParent,sCStackname)
    #    assert 0, "CStack type: " + sStackType + " not supported CStack[OHLCvs|PFigure]"
    # CScanner Factories - Create scanner for destignated category
    # NOTES: Exclusively used for scanning through non-visual datasets
    #      : Use 'This' to reference CScanner from which script has been run
    def CScannerFactory(self,sCWndParent,sCScannername):
        sScannerType = CRoot.CWndType(self,sCWndParent,sCScannername)
        #hCScanner    = P2Scanner.Open(sCWndParent,sCScannername)
        if sScannerType == 'CScanOHLCvs': 
            return CScanOHLCvs(sCWndParent,sCScannername)
        if sScannerType == 'CScanPFigure': 
            return CScanPFigure(sCWndParent,sCScannername)
        assert 0, "CScanner type: " + sScannerType + " not supported CScanner[OHLCvs|PFigure]"
    # Properties
    def Instance():
        return P2Root.Instance()    # Instance or pass number
    def CWndType(self,sCWndParent,sCWndChild):
        return P2Root.CWndType(sCWndParent,sCWndChild)

####################
#   Draw base class
class Draw:
    def __init__ ( self, oDrawChart, oDrawDSeries ):
        self.oChart = oDrawChart
        self.oDSeries = oDrawDSeries
    # Synchronise Draw data sets
    def Sync ( self ):
        return
    # Check if nominated Chart exists with stack
    def Exists ( self ):
        if P2Chart.DSeries(self.oChart.sChartName) == 'exists':
            return 1
        else:
            return 0
#
#   Draw chart tag
class DrawTag(Draw):
    def __init__ ( self, oDrawChart, oDrawDSeries ):
        super().__init__ ( oDrawChart, oDrawDSeries )
    # Synchronise DrawTag data sets
    def Sync ( self ):
        return
    # Check if nominated Chart exists with stack
    def Exists ( self ):
        if P2Chart.DSeries(self.oChart.sChartName) == 'exists':
            return 1
        else:
            return 0
    # Perform Draw operation
    def DoDrawTag ( self, ePUnits, sTag, sTagMessage ):
        print ( 'Chartname:' + self.oChart.sChartName )
        print ( 'Tag:' + sTag )
        print ( 'TagMessage:' + sTagMessage)
        P2Draw.AttachTag(self.oChart.hChart,ePUnits,sTag,sTagMessage)

####################
#   Modelling base class (Modelling only, not actual trades) (UNDER DEVELOPMENT)
#   NOTES: Parent of all subsequent "ModelTrade" descendants
#        : Valid in CStack environments only, restricted to CStack domain
#        : Modelling 'Attachment' MUST have been pre-loaded in Chartboard
#          Refer Ribbon Bar >> WS Attachments >> Modelling further details
#        : Multiple "Model" and/or 'Attachment' instances may exist 
class Model:
    def __init__ ( self, oCStack, sAttachmentName ):
        self.oCStack = oCStack
        self.sAttachmentName: str = sAttachmentName
        self.hAttachment: int = P2Model.AttachmentOpen(sAttachmentName)
    # Synchronise Model data sets
    def Sync ( self ):
        return
    # Generate ModelTrade instances
    # NOTES: sUserDefinedTag allows for multiple unique ModelTrade instances
    #        for the same Account
    def ModelTradeFactory(self,sAccountName,sUserDefinedTag):
        if P2Model.AccountSummary(self.hAttachment,sAccountName) != 'exists' :
            print('Account: ' + sAccountName + ' does not exist within Attachment ' + self.sAttachmentName )
            return None
        return ModelTrade(self,sAccountName,sUserDefinedTag)
#
#   Model trade class (Modelling only, not actual trades) (UNDER DEVELOPMENT)
#   NOTES: Restricted by parent "Model", "StockCode" and "UserDefinedTag"
#          which is usually unique to a particular modelling script
#        : Defined Account MUST already exist
#        : Multiple "ModelTrade" instances may exist 
TMARKUP_NONE: int    =     0;     # None, clear previous etc
TMARKUP_SELL: int    = (1<<1);
TMARKUP_BUY: int     = (1<<2);
TMARKUP_OPEN: int    = (1<<3);
TMARKUP_CLOSED: int  = (1<<4);
TMARKUP_MATCHED: int = (1<<5);
TMARKUP_PENDING: int = (1<<6);
TMARKUP_SUMMARY: int = (1<<9);    # Summary for all trades
class ModelTrade:
    def __init__ ( self, oModel, sAccountName, sUserDefinedTag ):
        self.oModel = oModel
        self.oCStack = oModel.oCStack
        self.hAccount = P2Model.AccountOpen(oModel.hAttachment,sAccountName,self.oCStack.sStockCode,sUserDefinedTag)
        self.sUserDefinedTag: str = sUserDefinedTag
        self.sStockCode: str = oModel.oCStack.sStockCode
    # Synchronise ModelTrade data sets
    def Sync ( self ):
        return
    # Perform modelled trade with preset parameters
    # NOTES: Trades may dropped via CleanupTrades() or manually through Chartboard
    #        modelled trade context menu. Details may be viewed through the
    #        grid properties associated with each trade.
    def BuyQuantity ( self, dDATE, dQuantity, dPrice ):
        P2Model.BuyQuantity( self.hAccount,dDATE,dQuantity,dPrice )
    def BuyValue ( self, dDATE, dValue, dPrice ):
        P2Model.BuyValue( self.hAccount,dDATE,dValue,dPrice )
    def SellQuantity ( self, dDATE, dQuantity, dPrice ):
        P2Model.SellQuantity( self.hAccount,dDATE,dQuantity,dPrice )
    def SellValue ( self, dDATE, dValue ):
        P2Model.SellValue( self.hAccount,dDATE,dValue )
    # Cleanup previous modelled trades
    # NOTES: Identified via 'Attachment' and "sStockCode" this object relates
    #        to and then internal "sUserDefinedTag" 
    def CleanupTrades ( self ):
        P2Model.Cleanup(self.hAccount,self.sUserDefinedTag)
    # Select both raw and calculated values from Model
    # NOTES: None return flags no-data or request out of range
    #      : Supported sValueName's = 'Quantity', 'Value'
    def GetValue_d(self,sValueName) -> float:
        return P2Model.GetValue_d(self.hAccount,sValueName)
    # Markup modelled trades on Chart(s)
    # NOTES: Modelled trades persist after completion and/or displacement of
    #        script and as such may be managed manually via the [Markups>>Modelling]
    #        context menu item for the respective Charts in stack.
    #      : Trades displayed through this sequence will only persist for the
    #        life cycle of the orginating script.
    #      : Any legacy modelled trades for the account will also be displayed
    def MarkupTrades ( self, oChart, uiTradeTypes ):
        P2Model.Markups ( self.hAccount, oChart.hChart, uiTradeTypes )
