// This Pine Script® code is subject to the terms of the GNU Affero General Public License v3.0 (AGPL-3.0) // Contact the author for licensing information for use of this script in any way that would not be compliant the APGL. // You may use thia code freely in other work as long as that work is published with a license compatible with AGPL (it this case, you do not need to ask the author persmission) // TradingView, Inc is granted an irrevocable, perpetual, royalty-free license to publish and use this script within the TradingView platform. // © BruzX // ╔───────────────────────────────────────────────────────────────────────────────────╗ // │ │ // │ __/\\\\\\\\\\\\\______/\\\\\\\\\______/\\\________/\\\__/\\\\\\\\\\\\\\\_ │ // │ _\/\\\/////////\\\__/\\\///////\\\___\/\\\_______\/\\\_\////////////\\\__ │ // │ _\/\\\_______\/\\\_\/\\\_____\/\\\___\/\\\_______\/\\\___________/\\\/___ │ // │ _\/\\\\\\\\\\\\\\__\/\\\\\\\\\\\/____\/\\\_______\/\\\_________/\\\/_____ │ // │ _\/\\\/////////\\\_\/\\\//////\\\____\/\\\_______\/\\\_______/\\\/_______ │ // │ _\/\\\_______\/\\\_\/\\\____\//\\\___\/\\\_______\/\\\_____/\\\/_________ │ // │ _\/\\\_______\/\\\_\/\\\_____\//\\\__\//\\\______/\\\____/\\\/___________ │ // │ _\/\\\\\\\\\\\\\/__\/\\\______\//\\\__\///\\\\\\\\\/____/\\\\\\\\\\\\\\\_ │ // │ _\/////////////____\///________\///_____\/////////_____\///////////////__ │ // │ │ // ╚───────────────────────────────────────────────────────────────────────────────────╝ //@version=6 indicator("Camarilla Pivot Plays", overlay = true, max_boxes_count = 100, max_lines_count = 100) // DEVELOPER MODE const bool DEV_MODE = false // ---- Enums //#region ---- Enums //@enum Two day Camarilla range relationshop //@field higher Yesterday's range is higher than 2 day's ago //@field lower Yesterday's range is lower than 2 day's ago //@field neutral Yesterday's and 2 day's ago ranges are the same within a configurable tolerance enum PivotRange higher = "Higher Range" lower = "Lower Range" neutral = "Neutral Range" //@enum Controls how the Cams are calculate //@field auto Automically decides whether to use ETH or not depending on whether ETH range is witint RTH range //@field forceRTH Only use RTH close, high, low to calculate Cams //@field forceETH Always use ETH close, high, low to calculate Cams enum CalcMode auto = "Auto" forceRTH = "Force RTH Data" forceETH = "Force ETH Data" //@enum Play direction //@field long Play with a long entry //@field short Play with a short entry enum Direction long short //@enum Option for specifying how to behave with plays in the pre-market //@field none Don't evaluation plays //@field keep Evaluate and keep any activated plays active when the regular market starts //@field reset Evaluaye plays in the premarket but reset them when the regular market starts enum PremarketPlays none = "None" keep = "Keep" reset = "Reset" //@enum Option for specifying whether to use precondition checks //@field none Disable checks at all times //@field rth Use checks during regular market only. Checks disabled in premarket //@field always Use checks always. In the premarkat, the opening time is taken to be either premarket open and also ETH close time for futures if this is overridden enum PreconditionCheck none = "Disable" rth = "During RTH only" always = "Enable" //@enum Option for forcing a particular close in the calclations //@field auto Use the same close as decided by the calculation mode //@field rth Force RTH close in all cases //@field eth Force ETH close in all cases enum UseClose auto = "Auto" rth = "Force RTH close" eth = "Force ETH close" //@enum Same as UseClose but for futures and added an Inherit field enum UseCloseFutures inherit = "Inherit" auto = "Auto" rth = "Force RTH close" eth = "Force ETH close" //@enum Alert event type (used for alerts) //@field entry Play entry triggered //@field exit Play exit triggered enum PlayEvent entry exit //@enum Specifies available options presets. For any preset other than "none", it will override the options set in the UI by the user //@field none No preset applied. //@field defaults Applies the default option when first adding the indicator. //@field das Applies options to emulate the behavious of DAS Trader. //@field tos Applies options to emulate popular script in ToS used by followers of Thor Young //@field thor Applies options used by Thor Young on this indicator enum EOptionsPresets none = "None" defaults = "Defaults" das = "DAS" tos = "TOS (Futures)" thor = "Thor" bruz = "Bruz" //@enum A boolean supporting "na" enum TriBool Na False True //#endregion ---- Enums // ---- UDTs //#region ---- UDTs //@type Holds play state variables between bars. Just using vars in the script logic would have caused a huge number of variables across the 12 plays type PlayState bool playPossible bool exit_other_condition box entry_box line entry_line box exit_successBox box exit_failBox line exit_line //@type Holds the play specifications type PlaySpec string description Direction direction PivotRange validRange bool entry_precondition_open bool entry_precondition_atr bool entry_trigger bool exit_success bool exit_fail bool exit_other_condition bool exit_other_trigger float entry_lineVal float entry_innerVal float exit_failOuterVal float exit_successLineVal float exit_successOuterVal //@type Container for handles to a line and a label //@field h_line Line handle //@field h_label Line handle type ExtLine line h_line label h_label //@type Used to hold preset for options in a map type OptionsPreset TriBool showCP TriBool showPrices TriBool lineJoin TriBool extendLines TriBool showPastPlays TriBool showOther TriBool hidePlayExit TriBool showWeekly TriBool showMonthly TriBool useCurrentPre TriBool waitBarConfirm CalcMode calcMode UseClose useClose int playEagerness float switchingTolerance float pivotRangeThreshold float pivotWidthTheshold PreconditionCheck validRangePrecondition PreconditionCheck openPreconditionCheck PreconditionCheck atrPreconditionCheck PreconditionCheck neutralPlays PremarketPlays prePlays UseCloseFutures useCloseFutures string timezoneIn string futureRegSess TriBool preOverride TriBool closeOverride string closeOverrideSession //#endregion ---- UDTs // ---- Constants //#region ---- Constants // Specfies levels and plays const array CAM_LEVELS = array.from("R3", "R4", "R6", "S3", "S4", "S6") const array PLAYS = array.from("HA", "HB", "HC", "HD", "HE", "HF", "LA", "LB", "LC", "LD", "LE", "LF") // Contains play specifications const map playSpecMap = map.new() // Contains state of plays we need to keep over time (except play activation status, see below) var const map playStateMap = map.new() // It's not possible to put playActiveMap inside the PlayState UDT (even though that would be the natural place for it). // Putting a varip variable inside a UDT and/or using the history-referncing operator on it doesn't work in Pine Script // varip should prevent rollback on realtime bar (we want to active plays immediately in real-time and to stay active. Without varip they could flicker on and off, and we don't want to wait until bar close) varip const map playActiveMap = map.new() // Contains options preset values var const map optionsPresetMap = map.new() //#endregion ---- Constants // ---- Inputs //#region ---- Inputs // Options Presets EOptionsPresets optionPreset = input.enum(EOptionsPresets.none, "Options Preset", tooltip = "When a value other than \"None\" is chosen, pre-defined options are applied, overriding certain options defined by the preset.", display = display.all - display.status_line, group = "Options presets") bool showOptionsValues = input.bool(false, "Show options values", tooltip = "Show the options currently applied in the infomation table. Options overridden by the preset are marked with a star (*). Useful to see the options used in a preset or in screenshots when requesting support.", group = "Options presets") // Options affecting display bool lightMode = input.bool(false, "Light background mode", tooltip = "By default the label text colour is chosen for best contrast against its background taking play box transparency into account and assumes a dark chart canvas. This option selects the best text colour if the chart canvas is light.", group = "Display options") int numDays = input.int(1, "Number of prior days", 0, 20, tooltip = "Number of days to show prior to current day. This setting is not guaranteed to be exact.", display = display.all - display.status_line, group = "Display options") bool showCP = input.bool(false, "Show Central Pivot (CP)", tooltip = "Show the Central Pivot, which is either RTH close or ETH close from the previous day, depending on which data was in use on that day.", group = "Display options") bool showPrices = input.bool(false, "Show prices on labels", tooltip = "Show the price of the pivot lines on their labels.", group = "Display options") bool lineJoin = input.bool(false, "Join lines", tooltip = "Connects the pivot lines over session boundaries.", group = "Display options") bool extendLines = input.bool(false, "Extend Lines", tooltip = "Extend the pivot lines right for the current day.", group = "Display options") bool showPastPlays = input.bool(false, "Show past plays", tooltip = "Show all plays that were activated in the past. When deselected, only the currently active play is shown.", group = "Display options") int labelOffset = input.int(10, "Label offset", tooltip = "Horizontal label offset, prevents overlaps with other indicator labels.", display = display.all - display.status_line, group = "Display options") color supportColour = input.color(color.green, "Support colour", inline = "colours", group = "Display options") color resistanceColour = input.color(color.red, "Resistance colour", inline = "colours", group = "Display options") color cpColour = input.color(color.blue, "Central Pivot colour", inline = "colours", group = "Display options") bool showOther = input.bool(false, "Show other pivots       ", inline = "other", group = "Display options") int oLabelOffset = input.int(15, "Other pivots label offset", tooltip = "Normally only one set of (main) pivots is shown, either using RTH or ETH data, which is determined by the calculation mode, and which data is used is displayed in the top-right information table. This option also shows the other set of pivots (using ETH data when the main pivots are using RTH data, and vice versa).", display = display.all - display.status_line, inline = "other", group = "Display options") color oSupportColour = input.color(color.rgb(43, 100, 45), "Other support", inline = "ocolours", group = "Display options") color oResistanceColour = input.color(color.rgb(151, 50, 50), "Other resistance ", inline = "ocolours", group = "Display options") color oCpColour = input.color(color.rgb(20, 75, 121), "Other Central Pivot  ", inline = "ocolours", group = "Display options") float playEntryBoxTransparency = input.float(60, "Play entry box transparency %", 0, 100, step = 5, display = display.all - display.status_line, group = "Display options") bool hidePlayExit = input.bool(false, "Hide play exits", tooltip = "Hide play exit (success and failure) boxes", group = "Display options") float playExitBoxTransparency = input.float(90, "Play exit box transparency %", 0, 100, step = 5, display = display.all - display.status_line, group = "Display options") // Options for weekly/monthly pivots bool showWeekly = input.bool(false, "Show weekly pivots                     ", inline = "Show WM", group = "Weekly/Monthly options") bool showMonthly = input.bool(false, "Show monthly pivots", inline = "Show WM", group = "Weekly/Monthly options") int wLabelOffset = input.int(20, "Label offset: Weekly", display = display.all - display.status_line, inline = "WM", group = "Weekly/Monthly options") int mLabelOffset = input.int(25, "Monthly", tooltip = "Horizontal label offset, prevents overlaps with other indicator labels.", display = display.all - display.status_line, inline = "WM", group = "Weekly/Monthly options") color wSupportColour = input.color(color.rgb(108, 175, 147), "Weekly support ", inline = "wcolours", group = "Weekly/Monthly options") color wResistanceColour = input.color(color.rgb(248, 130, 130), "Weekly resistance ", inline = "wcolours", group = "Weekly/Monthly options") color wCpColour = input.color(color.rgb(119, 185, 240), "Weekly Central Pivot ", inline = "wcolours", group = "Weekly/Monthly options") color mSupportColour = input.color(color.rgb(144, 173, 161), "Monthly support", inline = "mcolours", group = "Weekly/Monthly options") color mResistanceColour = input.color(color.rgb(245, 192, 192), "Monthly resistance", inline = "mcolours", group = "Weekly/Monthly options") color mCpColour = input.color(color.rgb(176, 209, 236), "Monthly Central Pivot", inline = "mcolours", group = "Weekly/Monthly options") // Options affecting behaviour bool useCurrentPre = input.bool(false, "Use current day premarket", tooltip = "Adds current day premarket H/L range into the pivot calculations. This can cause the pivots to change dynamically during premarket. Forces use of ETH data.", group = "Behaviour options") bool waitBarConfirm = input.bool(false, "Evaluate on bar close", tooltip = "Normally a play box is drawn and alert triggered the instant the conditions are fulfilled. This can lead to so-called repainting, where there may be a discrepency between an alert triggering and what you see when you then switch to the chart. This is unavoidable consequence of getting instant alerts. This options waits until the current bar closes and evaluates conditions then, which will give consistent behaviour between alerts and what you see on a chart, also on historical charts, but at the expense of not getting instant alerts. This may be preferred if you never take a trade immediately when an alert triggers but wait for confirmation.", group = "Behaviour options") CalcMode calcMode = input.enum(CalcMode.auto, "Calculation mode", tooltip = "Controls whether to always or never use ETH data or whether to handle automatically", group = "Behaviour options") UseClose useClose = input.enum(UseClose.auto, "Use close", tooltip = "Which close to use in pivot calculations. Allows forcing of a particular close irrespective of the data currently in use. For example, if ETH data is in use and this option is set to \"Force RTH\", it will use the H/L from the ETH session but the RTH close price. For futures, this honours the session and override times, if set and active.", display = display.all - display.status_line, group = "Behaviour options") int playEagerness = input.int(5, "Play eagerness", 0, 10, tooltip = "A play eagerness parameter (0-10) which affects the play detection algorithm. The higher the number, the more eager it is to enter a play and more reluctant to exit. It is timeframe-dependant - lower timeframes are naturally more eager than higher timeframes. The recommended (default) value is 5 on a 5-min chart.", group = "Behaviour options") float switchingTolerance = input.float (0.1, "ETH switching tolerance %", 0, 100, step = 0.1, tooltip = "Controls the tolerance before switching from RTH to ETH data in automatic mode. It represents the percentage of the RTH high/low that the ETH high/low must exceed before switching from RTH data to ETH data. Useful for not being affected by minor differences between H/L data on an intra-day timeframe versus the daily timeframe.", display = display.all - display.status_line, group = "Behaviour options") float pivotRangeThreshold = input.float(0.3, "Neutral range threshold %", 0, 100, step = 0.1, tooltip = "If the difference of yesterday's and 2-days ago close is within this percentage, the range is considered to be neutral. Only affects plays available, not the pivots themselves.", display = display.all - display.status_line, group = "Behaviour options") float pivotWidthTheshold = input.float(10, "Similar pivot width threshold %", 0, 100, tooltip = "Specifies the threshold percentage which decides whether the pivots are considered similar width on the current day conpared to the previous day. The percentage is of the difference between the S3-R3 ranges.", display = display.all - display.status_line, group = "Behaviour options" ) // Preconditions PreconditionCheck validRangePrecondition = input.enum(PreconditionCheck.always, "Valid range precondition", tooltip = "When disabled, plays from both ranges will be shown irrespective of the current range.", display = display.all - display.status_line, group = "Play preconditions") PreconditionCheck openPreconditionCheck = input.enum(PreconditionCheck.always, "Open price precondition", tooltip = "The plays normally have a precondition about the market open price (or pre-market open during the pre-market). This option disables this check.", display = display.all - display.status_line, group = "Play preconditions") PreconditionCheck atrPreconditionCheck = input.enum(PreconditionCheck.always, "ATR precondition", tooltip = "When enabled the target must be within some factor of the ATR determined by the eagerness setting. Eagerness 5 means that the target needs to be within the ATR. Lower eagernesses strengthen this condition, so that there needs to be additional space between the target and the ATR. Higher eagerness settings weaken the condition.", display = display.all - display.status_line, group = "Play preconditions") PreconditionCheck neutralPlays = input.enum(PreconditionCheck.always, "Neutral range plays", tooltip = "When in a neutral range, controls whether to show plays. when enabled, plays from higher and lower ranges will be shown. When disabled, no plays will be shown.", display = display.all - display.status_line, group = "Play preconditions" ) PremarketPlays prePlays = input.enum(PremarketPlays.reset, "Plays in premarket", tooltip = "'None' prevents play evaluation in the premarket. 'Keep' keeps active plays from premarket after regular market opens. 'Reset' resets active plays at the regular market open.", display = display.all - display.status_line, group = "Play preconditions") // Futures options UseCloseFutures useCloseFutures = input.enum(UseCloseFutures.inherit, "Use close for futures", tooltip = "Same as the main \"Use close\" setting but allows a seperate setting for futures. Inherit means use the same values of the main setting.", display = display.all - display.status_line, group = "Futures options") string timezoneIn = input.string("Exchange", "Timezone for sessions", options = ["Exchange", "Pacific/Honolulu", "America/Anchorage", "America/Los_Angeles", "America/Denver", "America/Chicago", "America/New_York", "America/Halifax", "America/Argentina/Buenos_Aires", "Europe/London", "Europe/Berlin", "Europe/Moscow", "Asia/Dubai", "Asia/Karachi", "Asia/Kolkata", "Asia/Bangkok", "Asia/Shanghai", "Asia/Tokyo", "Australia/Brisbane", "Australia/Sydney", "Pacific/Auckland"], tooltip = "Set the timezone that the times in the sessions below are specifed in. \"Exchange\" uses the timezone of the exchange. Choose a location in the same timezone as you if want to specify times in your local timezone. They are ordered from largest negative offset from UTC to largest positive offset. Also affects the time shown in alert messages.", display = display.all - display.status_line, group = "Futures options" ) string futureRegSess = input.session("0830-1500", "Futures regular session", tooltip = "Specifies pseudo-RTH session times for futures in the exchange timezone (ES/NQ in on Central Time which is why default is 08:30-15:00 with the exchange timezone). This is where the most volume is. For example, despite ES and NQ on CME being traded 23 hours per day during the week (17:00 till 16:00 the next day, Sunday afternoon to Friday afternoon, Central Time), most of the trading activity happens between 08:30 and 15:00 Central Time. Before this (from 17:00 the previous day) we call it pseudo-premarket, after this (until 16:00) we call is pseudo-postmarket.", display = display.all - display.status_line, group = "Futures options") bool preOverride = input.bool(false, "Override ETH open                  ", inline = "overrides", group = "Futures options") bool closeOverride = input.bool(false, "Override ETH close", tooltip = "\"Override ETH open\" overrides the time the ETH is considered to open and it uses the start time of the \"Override times\" time range. The ETH then covers 24h from this time (always, it's not affected by the override ETH close setting). \"Override ETH close\" overrides the time at which the close price is taken when using ETH data, and uses the end time from the \"Override times\" time range. It only affects the price used for close in the pivot equations, not the time window for evaluating the H/L range which is always 24 hours starting at the ETH open time.", inline = "overrides", group = "Futures options") string closeOverrideSession = input.session("1700-1600", "Override times", tooltip = "Specifies the ETH open and close time overrides, when enabled above. Don't set either of these to a time when the futures market is closed or the indicator will break for futures.", display = display.all - display.status_line, group = "Futures options") // Developer options bool webhookAlert = DEV_MODE ? input.bool(false, "Webhook alert mode", tooltip = "Changes the alert message from human-readable text to a JSON string containing relevant information, and is suitable for use in a webhook call to an external service. Needs to be enabled when creating the alert (option has no effect afterwards).", group = "Developer options") : false bool enableDebug = DEV_MODE ? input.bool(false, "Debug mode", display = display.all - display.status_line, group = "Developer options") : false float debugBoolPrice = DEV_MODE ? input.float(100, "Debug bool price", display = display.all - display.status_line, group = "Developer options") : na string debugCategory = DEV_MODE ? input.string("", "Debug category", group = "Developer options") : na //#endregion ---- Input // ---- Functions //#region ---- Functions //@function Utility function for debug logging. //@param logPredicate Predicate to allow control on which bar the debug function creates output. Setting to true will produce a message on every bar. //@param message The string to log //@returns Nothing //Dependancy Global bool enableDebug controlling whether this produces any log output debug(bool logPredicate, string category, string message) => if enableDebug and logPredicate and debugCategory == category log.info(message) //@function Utility function to be used with the plot() function and useful for debugging. //@param category A debug category to allow filtering of debug calls. //@returns A level to be used in the plot() function debugPlot(float level, string category) => enableDebug and debugCategory == category ? level : na //@function Similar to debugPlot, but can't use a global variable in an overloaded function in Pine script so had to make a seperate function //@param _bool A bool to plot. True values will be plotted at the debugBoolPrice level //@returns A level to be used in the plot() function debugPlotBool(bool _bool, string category) => debugPlot(_bool ? debugBoolPrice : 0, category) //@function Function to get the number of minutes in the extended session for some common exchanges (defaulting to NASDAQ otherwise). There is no other way to get this info, given we need to assign to a "simple" variable //@param tfMin Time frame in minutes //@returns Number of bars getSessionBarCount(simple int tfMin) => simple string prefix = syminfo.prefix simple int sessionMinutes = syminfo.type == "index" ? 390 : // Indices qouted during regualr hours, like SPX: 08:30–15:00 CT. Non-US indices will probably be close, though you may see one day more or less than set in the option prefix == "NASDAQ" or prefix == "NASDAQ_DLY" or prefix == "NYSE" or prefix == "NYSE_DLY" or prefix == "AMEX" or prefix == "AMEX_DLY" or prefix == "BATS" or prefix == "BATS_DLY" or prefix == "ARCA" or prefix == "ARCA_DLY" or prefix == "IEX" or prefix == "IEX_DLY" or prefix == "EDGE" or prefix == "EDGE_DLY" ? 960 : // US stocks: 04:00–20:00 ET extended prefix == "CME" or prefix == "CME_DL" or prefix == "CME_MINI" or prefix == "CME_MINI_DL" or prefix == "CBOT" or prefix == "CBOT_DL" or prefix == "CBOT_MINI" or prefix == "CBOT_MINI_DL" or prefix == "NYMEX" or prefix == "NYMEX_DL" or prefix == "NYMEX_MINI" or prefix == "NYMEX__MINI_DL" or prefix == "COMEX" or prefix == "COMEX_DL" or prefix == "COMEX_MINI" or prefix == "COMEX_MINI_DL" ? 1380 : // US futures: 17:00–16:00 CT with 1-hour break prefix == "EUREX" or prefix == "EUREX_DLY" ? 840 : // EUREX futures: approx 01:10–15:50 UTC prefix == "EURONEXT" or prefix == "EURONEXT_DLY" ? 510 : // Euronext: 09:00–17:30 CET prefix == "XETR" or prefix == "XETR_DLY" or prefix == "XTRA" or prefix == "XTRA_DLY" or prefix == "FWB" or prefix == "FWB_DLY" or prefix == "GETTEX" or prefix == "GETTEX_DLY" or prefix == "TRADEGATE" or prefix == "TRADEGATE_DLY" ? 510 : // Germany: 09:00–17:30 CET prefix == "LSE" or prefix == "LSE_DLY" or prefix == "LSIN" or prefix == "LSIN_DLY" ? 510 : // UK: 08:00–16:30 local time prefix == "BME" or prefix == "BME_DLY" or prefix == "MC" or prefix == "MC_DLY" ? 510 : // Spain: 09:00–17:30 CET prefix == "MIL" or prefix == "MIL_DLY" or prefix == "BIT" or prefix == "BIT_DLY" ? 510 : // Italy: 09:00–17:30 CET prefix == "SIX" or prefix == "SIX_DLY" or prefix == "VTX" or prefix == "VTX_DLY" ? 510 : // Switzerland: 09:00–17:30 CET prefix == "NSE" or prefix == "NSE_DLY" or prefix == "NSE_IND" or prefix == "BSE" or prefix == "BSE_DLY" ? 377 : // India: 09:15–15:36 IST + 2 candles prefix == "TSE" or prefix == "TSE_DLY" or prefix == "JPX" or prefix == "JPX_DLY" ? 375 : // Japan: 09:00–15:00 with 1-hour lunch break prefix == "HKEX" or prefix == "HKEX_DLY" or prefix == "SEHK" or prefix == "SEHK_DLY" ? 330 : // Hong Kong: 09:30–12:00, 13:00–16:00 prefix == "KRX" or prefix == "KRX_DLY" ? 360 : // Korea: 09:00–15:30 prefix == "SGX" or prefix == "SGX_DLY" ? 450 : // Singapore: 09:00–17:00 with 1-hour lunch break prefix == "ASX" or prefix == "ASX_DLY" ? 360 : // Australia: 10:00–16:00 local prefix == "TSX" or prefix == "TSX_DLY" or prefix == "TSXV" or prefix == "TSXV_DLY" ? 390 : // Canada: 09:30–16:00 ET prefix == "AMS" or prefix == "AMS_DLY" ? 510 : // Netherlands (Euronext Amsterdam): 09:00–17:30 CET prefix == "VIE" or prefix == "VIE_DLY" or prefix == "XWBO" or prefix == "XWBO_DLY" ? 510 : // Austria (Vienna Stock Exchange): 09:00–17:30 CET prefix == "WSE" or prefix == "WSE_DLY" or prefix == "GPW" or prefix == "GPW_DLY" ? 470 : // Poland (Warsaw Stock Exchange): 09:00–16:50 CET 960 // fallback to US extended session math.floor(sessionMinutes / tfMin) //@function Calculate Camarilla pivot points. Only calculates 3, 4 and 6 since these are the only relevant ones in Thor's system //@param h Daily high //@param l Daily Low //@param c Daily Close //@returns A map of the cams calculateCams(float h, float l, float c) => map camMap = map.new() camMap.put("R3", c + (h - l) * 1.1 / 4) camMap.put("R4", c + (h - l) * 1.1 / 2) camMap.put("R6", c * h / l) camMap.put("S3", c - (h - l) * 1.1 / 4) camMap.put("S4", c - (h - l) * 1.1 / 2) camMap.put("S6", c - (camMap.get("R6") - c)) camMap //@function Compares 2 closing values to find whether the first is greater than the second within some tolerance determined by a percentage of a base close //@param primaryClose The compared which should be higher for the function to return true //@param SecondaryClose The comparing value //@param baseClose The value on which to apply a threshold percentage. Will normally be the primary or secondary close //@param threshold A percentage of the baseClose which specifes the tolerance //@returns A bool indicating the result of the comparison compareClose(float primaryClose, float secondaryClose, float baseClose, float threshold) => primaryClose - secondaryClose > baseClose * threshold / 100 //@function Get the offset highest value in a series since the bar where the sinceCondition was true //@param sinceCondition Boolean which controls how far back to look. It goes back until it reaches a bar where the condition is true //@param offset Gets the value for the bar "offset" bars prior to the found one (the bar or interest) //@returns The highest high on the bar of interest getHighestSince(bool sinceCondition, int offset) => ta.highest(high, nz(ta.barssince(sinceCondition)) + 1)[offset] //@function Get the offset lowest value in a series since the bar where the sinceCondition was true //@param sinceCondition Boolean which controls how far back to look. If goes back until it reaches a bar where the condition is true //@param offset Gets the value for the bar "offset" bars prior to the found one (the bar of interest) //@returns The lowest low on the bar of interest getLowestSince(bool sinceCondition, int offset) => ta.lowest(low, nz(ta.barssince(sinceCondition)) + 1)[offset] //@function Gets remembered closes, highs and lows, but only updates its memory on a condition //@param HLCondition Predicate specifying how far back to search for high/low //@param updateCondition Condition specifying when to update memory //@param shiftBackCondition Condition which if true causes the returned values to be shifted back by one remembered value (i.e. the remembered values before the last update) //@returns A tuple of the desired values f_pastData(bool HLCondition, bool updateCondition, bool shiftBackCondition) => var float close0 = na var float close1 = na var float close2 = na var float high0 = na var float low0 = na var float high1 = na var float low1 = na lastHigh = getHighestSince(HLCondition, 1) lastLow = getLowestSince(HLCondition, 1) if updateCondition close2 := close1 close1 := close0 close0 := close[1] high1 := high0 high0 := lastHigh low1 := low0 low0 := lastLow if shiftBackCondition [close1, close2, high1, low1] else [close0, close1, high0, low0] //@function Gets remembered RTH open and the premarket high and low for the current day, but only updates its memory on a condition //@param HLCondition Predicate specifying how far back to search for high/low //@param updateCondition Condition specifying when to update memory //@param preOpenCondition X //@param rthOpenBar Should be set to true when called on the RTH opening bar //@returns A tuple of the desired values f_currentData(bool HLCondition, bool updateCondition, bool refreshOpenBar, bool closeOverrideBar) => var float usedOpen = na var float preHigh = na var float preLow = na var float closeOverridePrice = na lastHigh = getHighestSince(HLCondition, 0) lastLow = getLowestSince(HLCondition, 0) if updateCondition preHigh := lastHigh preLow := lastLow if refreshOpenBar usedOpen := open if closeOverrideBar closeOverridePrice := close[1] [usedOpen, preHigh, preLow, closeOverridePrice] //@function Gets remembered high and low on the the previous bar since some HLCondition, but only updates its memory on a condition //@param HLCondition Predicate specifying how far back to search for high/low //@param updateCondition Condition specifying when to update memory //@returns A tuple of the desired values f_HLData(bool HLCondition, bool updateCondition) => var float remHigh = na var float remLow = na lastHigh = getHighestSince(HLCondition, 1) lastLow = getLowestSince(HLCondition, 1) if updateCondition remHigh := lastHigh remLow := lastLow [remHigh, remLow] //@function Get a previously remembered value and updates its memory on a condition //@param value The value on the previous bar to remember //@param updatePredicate Event on which to update memory //@returns The remembered value snapshotVal(float value, bool updatePredicate) => var float snapshotValue = na if updatePredicate snapshotValue := value[1] snapshotValue //@function Overloaded snapshotVal(float) for bools snapshotVal(bool _bool, bool updatePredicate) => _snapshotVal = snapshotVal(_bool == false ? 0.0 : 1.0, updatePredicate) _snapshotVal == 0.0 ? false : true //@function Function to give best text colour given background colour (taking into account transparency and the canvas, assuming lightMode is correctly set) //@param bgcolour The background colour of the element on which the text is drawn //@returns White or black, whichever is the best text colour contrastTextColour(color bgColour) => transparency = color.t(bgColour) / 100.0 r = ((1 - transparency) * color.r(bgColour) + (lightMode ? transparency * 255 : 0)) / 255 g = ((1 - transparency) * color.g(bgColour) + (lightMode ? transparency * 255 : 0)) / 255 b = ((1 - transparency) * color.b(bgColour) + (lightMode ? transparency * 255 : 0)) / 255 lum = 0.2126 * r + 0.7152 * g + 0.0722 * b lum > 0.5 ? color.rgb(0, 0, 0) : color.white //@function Clamp value to minimum or maximum if value is outside those //@param value Value to clamp //@param min The minimum possible value //@param max The maximum possible value //@returns The (possibly clamped) value clamp(float value, float min, float max) => math.min(math.max(value, min), max) //@function Calculate number of bars for the rising/falling ta function for play eagerness //@param eagerness Eagerness value between 1 and 10 //@returns Mappings {eagerness: return value}: {0:4}, {1:4}, {2:3}, {3:3}, {4:2}, {5:2}, {6:1}, {7:1}, {8:0}, {9:0}, {10:0} eagernessBars(int eagerness) => int(4 - math.min(4, math.floor(eagerness / 2))) //@function Calculate ratio between levels for play eagerness //@param eagerness Eagerness value between 1 and 10 //@returns Mappings {eagerness: return value}: {0:0.00},{1: 0.05}, {2:0.10}, {3:0.15}, {4:0.20}, {5:0.25}, {6:0.30}, {7:0.35}, {8:0.40}, {9:0.45}, {10:0.50} eagernessRatio(int eagerness) => 0.05 * eagerness //@function Calculate a ratio to apply to the difference between low/high and ATR top/bottom limit //@param eagerness Eagerness value between 1 and 10 //@returns Mappings {eagerness: return value}: {0:0.5}, {1:0.6}, {2:0.7}, {3:0.8}, {4:0.9}, {5:1.0}, {6:1.1}, {7:1.2}, {8:1.3}, {9:1.4}, {10:1.5)} eagernessAtr(int eagerness) => 0.5 + 0.1 * eagerness //@function Parse a session string to the start and end hour and minute parseSession(string session) => // Assumes session is always in the format "HHMM-HHMM", e.g. "0930-1600" string startStr = str.substring(session, 0, 4) string endStr = str.substring(session, 5, 9) int startHour = int(str.tonumber(str.substring(startStr, 0, 2))) int startMinute = int(str.tonumber(str.substring(startStr, 2, 4))) int endHour = int(str.tonumber(str.substring(endStr, 0, 2))) int endMinute = int(str.tonumber(str.substring(endStr, 2, 4))) [startHour, startMinute, endHour, endMinute] //@function Something like alertcondition() but allows variables in the message triggerAlert(bool condition, string message) => if condition and barstate.islast alert(message, alert.freq_once_per_bar) debug(true, "0", "Alert: " + message) //@function Convert a TriBool to a bool triBoolToBool(TriBool triBool, bool naValue) => switch triBool TriBool.False => false TriBool.True => true TriBool.Na => naValue //@function Specifies the options preset values for presets defined in the EOptionsPresets enum constructOptionsPresetMap(map _optionsPresetMap) => _optionsPresetMap.put(EOptionsPresets.defaults, OptionsPreset.new(showCP = TriBool.False, showPrices = TriBool.False, lineJoin = TriBool.False, extendLines = TriBool.False, showPastPlays = TriBool.False, showOther = TriBool.False, hidePlayExit = TriBool.False, showWeekly = TriBool.False, showMonthly = TriBool.False, useCurrentPre = TriBool.False, waitBarConfirm = TriBool.False, calcMode = CalcMode.auto, useClose = UseClose.auto, playEagerness = 5, switchingTolerance = 0.1, pivotRangeThreshold = 0.3, pivotWidthTheshold = 10, validRangePrecondition = PreconditionCheck.always, openPreconditionCheck = PreconditionCheck.always, atrPreconditionCheck = PreconditionCheck.always, neutralPlays = PreconditionCheck.always, prePlays = PremarketPlays.reset, useCloseFutures = UseCloseFutures.inherit, timezoneIn = "Exchange" , futureRegSess = "0830-1500", preOverride = TriBool.False, closeOverride = TriBool.False, closeOverrideSession = "1700-1600")) _optionsPresetMap.put(EOptionsPresets.das , OptionsPreset.new(showCP = TriBool.True , showPrices = TriBool.Na , lineJoin = TriBool.Na , extendLines = TriBool.Na , showPastPlays = TriBool.Na , showOther = TriBool.True , hidePlayExit = TriBool.Na , showWeekly = TriBool.Na , showMonthly = TriBool.Na , useCurrentPre = TriBool.Na , waitBarConfirm = TriBool.Na , calcMode = na , useClose = na , playEagerness = 5, switchingTolerance = 0.1, pivotRangeThreshold = na , pivotWidthTheshold = na, validRangePrecondition = na , openPreconditionCheck = na , atrPreconditionCheck = na , neutralPlays = na , prePlays = na , useCloseFutures = UseCloseFutures.eth , timezoneIn = "America/New_York", futureRegSess = "0930-1600", preOverride = TriBool.True , closeOverride = TriBool.False, closeOverrideSession = "0000-0000")) _optionsPresetMap.put(EOptionsPresets.tos , OptionsPreset.new(showCP = TriBool.True , showPrices = TriBool.Na , lineJoin = TriBool.Na , extendLines = TriBool.Na , showPastPlays = TriBool.Na , showOther = TriBool.True , hidePlayExit = TriBool.Na , showWeekly = TriBool.Na , showMonthly = TriBool.Na , useCurrentPre = TriBool.Na , waitBarConfirm = TriBool.Na , calcMode = na , useClose = na , playEagerness = 5, switchingTolerance = 0.1, pivotRangeThreshold = na , pivotWidthTheshold = na, validRangePrecondition = na , openPreconditionCheck = na , atrPreconditionCheck = na , neutralPlays = na , prePlays = na , useCloseFutures = UseCloseFutures.eth , timezoneIn = "America/New_York", futureRegSess = "0930-1600", preOverride = TriBool.False, closeOverride = TriBool.True, closeOverrideSession = "0000-0000")) _optionsPresetMap.put(EOptionsPresets.thor , OptionsPreset.new(showCP = TriBool.True , showPrices = TriBool.True , lineJoin = TriBool.False, extendLines = TriBool.False, showPastPlays = TriBool.True , showOther = TriBool.False, hidePlayExit = TriBool.False, showWeekly = TriBool.False, showMonthly = TriBool.True , useCurrentPre = TriBool.False, waitBarConfirm = TriBool.True , calcMode = CalcMode.auto, useClose = UseClose.rth , playEagerness = 5, switchingTolerance = 0.1, pivotRangeThreshold = 0.2, pivotWidthTheshold = 10, validRangePrecondition = PreconditionCheck.always, openPreconditionCheck = PreconditionCheck.always, atrPreconditionCheck = PreconditionCheck.always, neutralPlays = PreconditionCheck.always, prePlays = PremarketPlays.keep , useCloseFutures = UseCloseFutures.inherit, timezoneIn = "America/New_York", futureRegSess = "0930-1600", preOverride = TriBool.False, closeOverride = TriBool.True, closeOverrideSession = "0000-0000")) _optionsPresetMap.put(EOptionsPresets.bruz , OptionsPreset.new(showCP = TriBool.True , showPrices = TriBool.False, lineJoin = TriBool.False, extendLines = TriBool.False, showPastPlays = TriBool.True , showOther = TriBool.False, hidePlayExit = TriBool.False, showWeekly = TriBool.False, showMonthly = TriBool.True , useCurrentPre = TriBool.False, waitBarConfirm = TriBool.False, calcMode = CalcMode.auto, useClose = UseClose.auto, playEagerness = 4, switchingTolerance = 0.1, pivotRangeThreshold = 0.2, pivotWidthTheshold = 10, validRangePrecondition = PreconditionCheck.always, openPreconditionCheck = PreconditionCheck.rth , atrPreconditionCheck = PreconditionCheck.always, neutralPlays = PreconditionCheck.always, prePlays = PremarketPlays.reset, useCloseFutures = UseCloseFutures.rth , timezoneIn = "America/New_York", futureRegSess = "0930-1600", preOverride = TriBool.False, closeOverride = TriBool.False, closeOverrideSession = "1700-1600")) //@function Creates a long string to be used in the information table for viewing setting currenty in use. Useful for seeing the preset settings without having to look in the code constructOptionValuesString() => "Options preset: " + str.tostring(optionPreset) + "\n" + (optionPreset != EOptionsPresets.none and optionsPresetMap.get(optionPreset).showCP != TriBool.Na ? "*" : na) + "Show Central Pivot (CP): " + str.tostring(showCP) + "\n" + (optionPreset != EOptionsPresets.none and optionsPresetMap.get(optionPreset).showPrices != TriBool.Na ? "*" : na) + "Show prices on labels: " + str.tostring(showPrices) + "\n" + (optionPreset != EOptionsPresets.none and optionsPresetMap.get(optionPreset).lineJoin != TriBool.Na ? "*" : na) + "Join lines: " + str.tostring(lineJoin) + "\n" + (optionPreset != EOptionsPresets.none and optionsPresetMap.get(optionPreset).extendLines != TriBool.Na ? "*" : na) + "Extend lines: " + str.tostring(extendLines) + "\n" + (optionPreset != EOptionsPresets.none and optionsPresetMap.get(optionPreset).showPastPlays != TriBool.Na ? "*" : na) + "Show past plays: " + str.tostring(showPastPlays) + "\n" + (optionPreset != EOptionsPresets.none and optionsPresetMap.get(optionPreset).showOther != TriBool.Na ? "*" : na) + "Show other pivots: " + str.tostring(showOther) + "\n" + (optionPreset != EOptionsPresets.none and optionsPresetMap.get(optionPreset).hidePlayExit != TriBool.Na ? "*" : na) + "Show play exits: " + str.tostring(hidePlayExit) + "\n" + (optionPreset != EOptionsPresets.none and optionsPresetMap.get(optionPreset).showWeekly != TriBool.Na ? "*" : na) + "Show weekly pivots: " + str.tostring(showWeekly) + "\n" + (optionPreset != EOptionsPresets.none and optionsPresetMap.get(optionPreset).showMonthly != TriBool.Na ? "*" : na) + "Show monthly pivots: " + str.tostring(showMonthly) + "\n" + (optionPreset != EOptionsPresets.none and optionsPresetMap.get(optionPreset).useCurrentPre != TriBool.Na ? "*" : na) + "Use current day premarket: " + str.tostring(useCurrentPre) + "\n" + (optionPreset != EOptionsPresets.none and optionsPresetMap.get(optionPreset).waitBarConfirm != TriBool.Na ? "*" : na) + "Evaluate on bar close: " + str.tostring(waitBarConfirm) + "\n" + (optionPreset != EOptionsPresets.none and not na(optionsPresetMap.get(optionPreset).calcMode) ? "*" : na) + "Calculation mode: " + str.tostring(calcMode) + "\n" + (optionPreset != EOptionsPresets.none and not na(optionsPresetMap.get(optionPreset).useClose) ? "*" : na) + "Use close: " + str.tostring(useClose) + "\n" + (optionPreset != EOptionsPresets.none and not na(optionsPresetMap.get(optionPreset).playEagerness) ? "*" : na) + "Play eagerness: " + str.tostring(playEagerness) + "\n" + (optionPreset != EOptionsPresets.none and not na(optionsPresetMap.get(optionPreset).switchingTolerance) ? "*" : na) + "ETH switching tolerance %: " + str.tostring(switchingTolerance) + "\n" + (optionPreset != EOptionsPresets.none and not na(optionsPresetMap.get(optionPreset).pivotRangeThreshold) ? "*" : na) + "Neutral range threshold %: " + str.tostring(pivotRangeThreshold) + "\n" + (optionPreset != EOptionsPresets.none and not na(optionsPresetMap.get(optionPreset).pivotWidthTheshold) ? "*" : na) + "Similar pivot width theshold %: " + str.tostring(pivotWidthTheshold) + "\n" + (optionPreset != EOptionsPresets.none and not na(optionsPresetMap.get(optionPreset).validRangePrecondition) ? "*" : na) + "Valid range precondition: " + str.tostring(validRangePrecondition) + "\n" + (optionPreset != EOptionsPresets.none and not na(optionsPresetMap.get(optionPreset).openPreconditionCheck) ? "*" : na) + "Open price precondition: " + str.tostring(openPreconditionCheck) + "\n" + (optionPreset != EOptionsPresets.none and not na(optionsPresetMap.get(optionPreset).atrPreconditionCheck) ? "*" : na) + "ATR precondition: " + str.tostring(atrPreconditionCheck) + "\n" + (optionPreset != EOptionsPresets.none and not na(optionsPresetMap.get(optionPreset).neutralPlays) ? "*" : na) + "Neutral range plays: " + str.tostring(neutralPlays) + "\n" + (optionPreset != EOptionsPresets.none and not na(optionsPresetMap.get(optionPreset).prePlays) ? "*" : na) + "Plays in premarket: " + str.tostring(prePlays) + "\n" + (optionPreset != EOptionsPresets.none and not na(optionsPresetMap.get(optionPreset).useCloseFutures) ? "*" : na) + "Use close for futures: " + str.tostring(useCloseFutures) + "\n" + (optionPreset != EOptionsPresets.none and not na(optionsPresetMap.get(optionPreset).timezoneIn) ? "*" : na) + "Timezone for sessions: " + str.tostring(timezoneIn) + "\n" + (optionPreset != EOptionsPresets.none and not na(optionsPresetMap.get(optionPreset).futureRegSess) ? "*" : na) + "Futures regular session: " + str.tostring(futureRegSess) + "\n" + (optionPreset != EOptionsPresets.none and optionsPresetMap.get(optionPreset).preOverride != TriBool.Na ? "*" : na) + "Override ETH open: " + str.tostring(preOverride) + "\n" + (optionPreset != EOptionsPresets.none and optionsPresetMap.get(optionPreset).closeOverride != TriBool.Na ? "*" : na) + "Override ETH close: " + str.tostring(closeOverride) + "\n" + (optionPreset != EOptionsPresets.none and not na(optionsPresetMap.get(optionPreset).closeOverrideSession) ? "*" : na) + "Override times: " + str.tostring(closeOverrideSession) //#endregion ---- Functions // ---- Logic //#region --- Logic // Pre-checks if syminfo.type != "stock" and syminfo.type != "fund" and syminfo.type != "futures" and syminfo.type != "dr" and syminfo.type != "index" runtime.error("This indicator only works for stocks, ETFs, futures and indices.") constructOptionsPresetMap(optionsPresetMap) // Load preset if optionPreset != EOptionsPresets.none showCP := triBoolToBool(optionsPresetMap.get(optionPreset).showCP, showCP) showPrices := triBoolToBool(optionsPresetMap.get(optionPreset).showPrices, showPrices) lineJoin := triBoolToBool(optionsPresetMap.get(optionPreset).lineJoin, lineJoin) extendLines := triBoolToBool(optionsPresetMap.get(optionPreset).extendLines, extendLines) showPastPlays := triBoolToBool(optionsPresetMap.get(optionPreset).showPastPlays, showPastPlays) showOther := triBoolToBool(optionsPresetMap.get(optionPreset).showOther, showOther) hidePlayExit := triBoolToBool(optionsPresetMap.get(optionPreset).hidePlayExit, hidePlayExit) showWeekly := triBoolToBool(optionsPresetMap.get(optionPreset).showWeekly, showWeekly) showMonthly := triBoolToBool(optionsPresetMap.get(optionPreset).showMonthly, showMonthly) useCurrentPre := triBoolToBool(optionsPresetMap.get(optionPreset).useCurrentPre, useCurrentPre) waitBarConfirm := triBoolToBool(optionsPresetMap.get(optionPreset).waitBarConfirm, waitBarConfirm) calcMode := na(optionsPresetMap.get(optionPreset).calcMode) ? calcMode : optionsPresetMap.get(optionPreset).calcMode playEagerness := na(optionsPresetMap.get(optionPreset).playEagerness) ? playEagerness : optionsPresetMap.get(optionPreset).playEagerness useClose := na(optionsPresetMap.get(optionPreset).useClose) ? useClose : optionsPresetMap.get(optionPreset).useClose switchingTolerance := na(optionsPresetMap.get(optionPreset).switchingTolerance) ? switchingTolerance : optionsPresetMap.get(optionPreset).switchingTolerance pivotRangeThreshold := na(optionsPresetMap.get(optionPreset).pivotRangeThreshold) ? pivotRangeThreshold : optionsPresetMap.get(optionPreset).pivotRangeThreshold pivotWidthTheshold := na(optionsPresetMap.get(optionPreset).pivotWidthTheshold) ? pivotWidthTheshold : optionsPresetMap.get(optionPreset).pivotWidthTheshold validRangePrecondition := na(optionsPresetMap.get(optionPreset).validRangePrecondition) ? validRangePrecondition : optionsPresetMap.get(optionPreset).validRangePrecondition openPreconditionCheck := na(optionsPresetMap.get(optionPreset).openPreconditionCheck) ? openPreconditionCheck : optionsPresetMap.get(optionPreset).openPreconditionCheck atrPreconditionCheck := na(optionsPresetMap.get(optionPreset).atrPreconditionCheck) ? atrPreconditionCheck : optionsPresetMap.get(optionPreset).atrPreconditionCheck neutralPlays := na(optionsPresetMap.get(optionPreset).neutralPlays) ? neutralPlays : optionsPresetMap.get(optionPreset).neutralPlays prePlays := na(optionsPresetMap.get(optionPreset).prePlays) ? prePlays : optionsPresetMap.get(optionPreset).prePlays useCloseFutures := na(optionsPresetMap.get(optionPreset).useCloseFutures) ? useCloseFutures : optionsPresetMap.get(optionPreset).useCloseFutures timezoneIn := na(optionsPresetMap.get(optionPreset).timezoneIn) ? timezoneIn : optionsPresetMap.get(optionPreset).timezoneIn futureRegSess := na(optionsPresetMap.get(optionPreset).futureRegSess) ? futureRegSess : optionsPresetMap.get(optionPreset).futureRegSess preOverride := triBoolToBool(optionsPresetMap.get(optionPreset).preOverride, preOverride) closeOverride := triBoolToBool(optionsPresetMap.get(optionPreset).closeOverride, closeOverride) closeOverrideSession := na(optionsPresetMap.get(optionPreset).closeOverrideSession) ? closeOverrideSession : optionsPresetMap.get(optionPreset).closeOverrideSession // Get timezone in case "Exchange" is chosen in the options string timezone = timezoneIn == "Exchange" ? syminfo.timezone : timezoneIn // Markers for specific bars bool regStartBar = false bool preStartBar = false bool postStartBar = false bool closeOverrideBar = false bool isFutures = syminfo.type == "futures" // Mark the first bars of regular hours, premarket and postmarket. This works irrespective of whether the chart displays RTH or ETH // bar_index check is needed to force the value to true on the first bar or before a certain period of time (15000 bars) to prevent runtime error if isFutures bool isInReg = not na(time(timeframe.period, futureRegSess, timezone)) bool wasInReg = not na(time(timeframe.period, futureRegSess, timezone, 1)) // Hacky way to determine if the user has set futures RTH to full time range (i.e. all bars are in RTH). Need to handle this specially since regStartBar and postStarBar would never trigger without it. var bool allRth = true allRth := isInReg and allRth bool forceStart = false if allRth int startHour = int(str.tonumber(str.substring(futureRegSess, 0, 2))) int startMinute = int(str.tonumber(str.substring(futureRegSess, 2, 4))) int sessionStartTime = timestamp(timezone, year(time, timezone), month(time, timezone), dayofmonth(time, timezone), startHour, startMinute) forceStart := time == sessionStartTime [preStartHour, preStartMinute, closeHour, closeMin] = parseSession(closeOverrideSession) int preStartTime = timestamp(timezone, year(time, timezone), month(time, timezone), dayofmonth(time, timezone), preStartHour, preStartMinute, 0) int closeTime = timestamp(timezone, year(time, timezone), month(time, timezone), dayofmonth(time, timezone), closeHour, closeMin, 0) regStartBar := isInReg and not wasInReg or forceStart or bar_index == 0 preStartBar := isFutures and preOverride ? time == preStartTime or time[1] < preStartTime and time >= preStartTime and not session.isfirstbar or bar_index == 0 : session.isfirstbar or bar_index == 0 postStartBar := wasInReg and not isInReg or forceStart or bar_index == 0 closeOverrideBar := time == closeTime or time[1] < closeTime and time >= closeTime and not session.isfirstbar or bar_index == 0 debug(barstate.isnew, "tz", str.tostring(closeOverrideBar) + " " + str.tostring(time) + " " + str.tostring(time[1]) + " " + str.tostring(closeTime) + " " + str.tostring(time_close[1])) debug(barstate.isnew, "tz2", str.tostring(timezone) + " " + str.tostring(year(time, timezone)) + " " + str.tostring(month(time, timezone)) + " " + str.tostring(dayofmonth(time, timezone))) else regStartBar := session.isfirstbar_regular or bar_index == 0 or bar_index <= last_bar_index - 15000 preStartBar := session.ispremarket and session.isfirstbar or session.isfirstbar and session.isfirstbar_regular or bar_index == 0 or bar_index <= last_bar_index - 15000 postStartBar := session.ispostmarket and session.ismarket[1] or session.isfirstbar and session.ismarket[1] or bar_index == 0 or bar_index <= last_bar_index - 15000 // plot(debugPlotBool(preStartBar, "startbars"), color = color.fuchsia, display = display.all - display.status_line) // plot(debugPlotBool(regStartBar, "startbars"), color = color.lime, display = display.all - display.status_line) // plot(debugPlotBool(postStartBar, "startbars"), color = color.maroon, display = display.all - display.status_line) // plot(debugPlotBool(closeOverrideBar, "startbars"), color = color.navy, display = display.all - display.status_line) bool isPremarket = if isFutures ta.barssince(preStartBar) < ta.barssince(regStartBar) else session.ispremarket bool isPostmarket = if isFutures bool firstCondition = ta.barssince(postStartBar) < ta.barssince(preStartBar) bool secondCondition = ta.barssince(regStartBar) < ta.barssince(preStartBar) bool thirdCondition = ta.barssince(postStartBar) < ta.barssince(regStartBar) firstCondition and secondCondition and thirdCondition else session.ispostmarket bool isMarket = not isPremarket and not isPostmarket // Calculates bars from days simple int numBars = (numDays + 2) * getSessionBarCount(timeframe.multiplier) // Prevents runtime errors on non-supported security types or timeframe (allowing the managed runtime errors or warnings to trigger) numBars := na(numBars) or numBars == 0 ? 1 : numBars // Basically cripple the intra-day indicator on D/W/M timeframe. Avoids a runtime error. if timeframe.isdwm numBars := 1 regStartBar := true preStartBar := true postStartBar := true // Get ETH-enabled version of current symbol simple string ethTicker = ticker.modify(syminfo.main_tickerid, session.extended, adjustment.none) // Get all the numbers we need [yRthClose, yyRthClose, yRthHigh, yRthLow] = request.security(ethTicker, timeframe.period, f_pastData(regStartBar, postStartBar, isPostmarket), calc_bars_count = numBars) [yEthClose, yyEthClose, yEthHigh, yEthLow] = request.security(ethTicker, timeframe.period, f_pastData(preStartBar, preStartBar, false), calc_bars_count = numBars) [rthOpen, preHigh, preLow, closeOverridePrice] = request.security(ethTicker, timeframe.period, f_currentData(preStartBar, isPremarket, preStartBar or regStartBar or isFutures and (closeOverride and closeOverrideBar or postStartBar and not isPostmarket), closeOverrideBar), calc_bars_count = numBars) // In the previous statment, the strange condition "postStartBar and not isPostmarket" is needed for futures when you set the pseudo-RTH end time to after the start of the session the next day (which is a valid use-case for futures). // In this case, the postStartBar of a session will be after the preStartBar of the next session and there will be no post-market. The postStarBar is needed to refresh the rthOpen price after the RTH close bar to keep the preconditions in the pseudo-pre-market evaluating correctly, and the "not isPostmarket" is needed so that it doesn't trigger in the normal case where the postStartBar precedes the preStartBar of the following session. [yPostHigh, yPostLow] = request.security(ethTicker, timeframe.period, f_HLData(postStartBar, preStartBar), calc_bars_count = numBars) dailyATR = request.security(syminfo.main_tickerid, "D", ta.atr(14)) [sessionHigh, sessionLow] = request.security(ethTicker, timeframe.period, f_HLData(preStartBar, true), calc_bars_count = numBars) debug(barstate.isfirst, "tickerid", syminfo.main_tickerid + " " + syminfo.tickerid) // plot(debugPlot(sessionHigh - dailyATR, "atr"), color = color.red, display = display.all - display.status_line) // plot(debugPlot(sessionLow + dailyATR, "atr"), color = color.green, display = display.all - display.status_line) [wHigh, wLow, wClose] = request.security(syminfo.main_tickerid, "W", [high[1], low[1], close[1]], lookahead = barmerge.lookahead_on, calc_bars_count = 2) [mHigh, mLow, mClose] = request.security(syminfo.main_tickerid, "M", [high[1], low[1], close[1]], lookahead = barmerge.lookahead_on, calc_bars_count = 2) debug(barstate.islastconfirmedhistory, "rthopen", str.tostring(rthOpen)) debug(barstate.islastconfirmedhistory, "levels", str.tostring(yEthClose) + " " + str.tostring(yyEthClose)) // Overwrite ethClose if the ETH close time of overridden if isFutures and closeOverride yEthClose := closeOverridePrice yyEthClose := snapshotVal(closeOverridePrice, closeOverrideBar) //This is ok to be in a local scope since if it's called on one bar, it'll be called on every bar debug(barstate.islastconfirmedhistory, "closeoverride", str.tostring(yEthClose) + " " + str.tostring(yyEthClose) + " " + str.tostring(closeOverridePrice)) // Set calculation mode bool useEthForCams = switch calcMode CalcMode.auto => math.max(yRthHigh, yPostHigh, preHigh) > yRthHigh * (1 + switchingTolerance / 100) or math.min(yRthLow, yPostLow, preLow) < yRthLow * (1 - switchingTolerance / 100) or useCurrentPre CalcMode.forceETH => true CalcMode.forceRTH => false // Get the Central Pivots float CP = na float oCP = na if not isFutures or isFutures and useCloseFutures == UseCloseFutures.inherit switch useClose == UseClose.rth or useClose == UseClose.auto and not useEthForCams => CP := yRthClose oCP := yEthClose useClose == UseClose.eth or useClose == UseClose.auto and useEthForCams => CP := yEthClose oCP := yRthClose => runtime.error("Invalid CP/oCP") else switch useCloseFutures == UseCloseFutures.rth or useCloseFutures == UseCloseFutures.auto and not useEthForCams => CP := yRthClose oCP := yEthClose useCloseFutures == UseCloseFutures.eth or useCloseFutures == UseCloseFutures.auto and useEthForCams => CP := yEthClose oCP := yRthClose => runtime.error("Invalid CP/oCP") debug(barstate.islastconfirmedhistory, "closes", "yRthClose: " + str.tostring(yRthClose) + ", yyRthClose: " + str.tostring(yyRthClose)) bool yUseEthForCams = snapshotVal(useEthForCams, preStartBar) float yCP = if not isFutures or isFutures and useCloseFutures == UseCloseFutures.inherit useClose == UseClose.eth or useClose == UseClose.auto and yUseEthForCams ? yyEthClose : yyRthClose else useCloseFutures == UseCloseFutures.eth or useCloseFutures == UseCloseFutures.auto and yUseEthForCams ? yyEthClose : yyRthClose debug(barstate.islastconfirmedhistory, "yuseeth", str.tostring(yUseEthForCams)) // Determine Cam range PivotRange pivotRange = switch compareClose(CP, yCP, CP, pivotRangeThreshold) => PivotRange.higher compareClose(yCP, CP, CP, pivotRangeThreshold) => PivotRange.lower => PivotRange.neutral debug(barstate.islastconfirmedhistory, "range", str.tostring(CP) + " " + str.tostring(yCP)) // Calculate Cams var map camMap = na camMap := switch useCurrentPre => calculateCams(math.max(yEthHigh, preHigh), math.min(yEthLow, preLow), CP) useEthForCams => calculateCams(yEthHigh, yEthLow, CP) => calculateCams(yRthHigh, yRthLow, CP) // Other cams var map oCamMap = na oCamMap := switch not useEthForCams and useCurrentPre => calculateCams(math.max(yEthHigh, preHigh), math.min(yEthLow, preLow), oCP) not useEthForCams => calculateCams(yEthHigh, yEthLow, oCP) => calculateCams(yRthHigh, yRthLow, oCP) // Weekly/Monthly Cams map wCamMap = calculateCams(wHigh, wLow, wClose) map mCamMap = calculateCams(mHigh, mLow, mClose) // Calculate pivot width float yS3 = snapshotVal(camMap.get("S3"), preStartBar) float yR3 = snapshotVal(camMap.get("R3"), preStartBar) string pivotWidth = switch camMap.get("R3") - camMap.get("S3") > (yR3 - yS3) * (1 + pivotWidthTheshold / 100) => "Wide Pivots" camMap.get("R3") - camMap.get("S3") < (yR3 - yS3) * (1 - pivotWidthTheshold / 100) => "Narrow Pivots" => "Similar Pivots" // Plays //#region ---- Plays // For convenience in the play specifications float R3 = camMap.get("R3") float R4 = camMap.get("R4") float R6 = camMap.get("R6") float S3 = camMap.get("S3") float S4 = camMap.get("S4") float S6 = camMap.get("S6") PlaySpec playSpec = na PlayState playState = na // Initialise playStateMap on first bar if barstate.isfirst for play in PLAYS playStateMap.put(play, PlayState.new()) // Psuedo-constants related to play specifications // Following are ratios of distance to next level and distance between levels we're in between (incl. CP/PDC) float PLAY_ENTRY_APPROACH_LEVEL = eagernessRatio(playEagerness) float PLAY_ENTRY_ATR_LEVEL = playEagerness == 10 ? 0 : eagernessAtr(playEagerness) float PLAY_EXIT_OTHER_CONDITION_LEVEL = eagernessRatio(playEagerness) * 0.5 float PLAY_EXIT_OTHER_TRIGGER_LEVEL = eagernessRatio(playEagerness) * 1.5 float PLAY_EXIT_FAIL_TOLERANCE = PLAY_ENTRY_APPROACH_LEVEL int PLAY_ENTRY_BARS = eagernessBars(playEagerness) bool falling = ta.falling(hlc3, PLAY_ENTRY_BARS == 0 ? 1 : PLAY_ENTRY_BARS) or PLAY_ENTRY_BARS == 0 bool rising = ta.rising(hlc3, PLAY_ENTRY_BARS == 0 ? 1 : PLAY_ENTRY_BARS) or PLAY_ENTRY_BARS == 0 float smaHlc3 = ta.sma(hlc3, PLAY_ENTRY_BARS + 1) // plot(debugPlot(hlc3, "hlc3"), color = color.fuchsia, display = display.all - display.status_line) // plot(debugPlot(smaHlc3, "hlc3"), color = color.lime, display = display.all - display.status_line) //#region ---- Play specifications // Higher Range, Play A playSpecMap.put("HA", PlaySpec.new( description = "S3 to R3 traversal from inside S3-R3", direction = Direction.long, validRange = PivotRange.higher, entry_precondition_open = rthOpen > S3 and rthOpen < R3, entry_precondition_atr = R3 <= sessionLow + dailyATR * PLAY_ENTRY_ATR_LEVEL, entry_trigger = close > S3 and low < S3 + (CP - S3) * PLAY_ENTRY_APPROACH_LEVEL and falling, exit_success = high >= R3, exit_fail = close < S3 - (S3 - S4) * PLAY_EXIT_FAIL_TOLERANCE and barstate.isconfirmed, exit_other_condition = false, exit_other_trigger = false, entry_lineVal = S3, entry_innerVal = S3 + (CP - S3) * PLAY_ENTRY_APPROACH_LEVEL, exit_failOuterVal = S3 - (CP - S3) * PLAY_ENTRY_APPROACH_LEVEL, exit_successLineVal = R3, exit_successOuterVal = sessionLow + dailyATR * PLAY_ENTRY_ATR_LEVEL )) // Higher Range, Play B playSpecMap.put("HB", PlaySpec.new( description = "Trending R4 breakup from inside S3-R3", direction = Direction.long, validRange = PivotRange.higher, entry_precondition_open = rthOpen > S3 and rthOpen < R3, entry_precondition_atr = R6 <= sessionLow + dailyATR * PLAY_ENTRY_ATR_LEVEL, entry_trigger = barstate.isconfirmed and close > R4 and close < R4 + (R6 - R4) * PLAY_ENTRY_APPROACH_LEVEL and smaHlc3 > R4 and rising, exit_success = high >= R6, exit_fail = close < R4 - (R4 - R3) * PLAY_EXIT_FAIL_TOLERANCE and barstate.isconfirmed, exit_other_condition = false, exit_other_trigger = false, entry_lineVal = R4, entry_innerVal = R4 + (R6 - R4) * PLAY_ENTRY_APPROACH_LEVEL, exit_failOuterVal = R4 - (R6 - R4) * PLAY_ENTRY_APPROACH_LEVEL, exit_successLineVal = R6, exit_successOuterVal = sessionLow + dailyATR * PLAY_ENTRY_ATR_LEVEL )) // Higher Range, Play C playSpecMap.put("HC", PlaySpec.new( description = "Trending R4 breakup from outside R4", direction = Direction.long, validRange = PivotRange.higher, entry_precondition_open = rthOpen > R4 and rthOpen < R6, entry_precondition_atr = R6 <= sessionLow + dailyATR * PLAY_ENTRY_ATR_LEVEL, entry_trigger = low < R4 + (R6 - R4) * PLAY_ENTRY_APPROACH_LEVEL and close > R4 and falling, exit_success = high >= R6, exit_fail = close < R4 - (R4 - R3) * PLAY_EXIT_FAIL_TOLERANCE and barstate.isconfirmed, exit_other_condition = high > R6 - (R6 - R4) * PLAY_EXIT_OTHER_CONDITION_LEVEL, exit_other_trigger = barstate.isconfirmed and close < R6 - (R6 - R4) * PLAY_EXIT_OTHER_TRIGGER_LEVEL, entry_lineVal = R4, entry_innerVal = R4 + (R6 - R4) * PLAY_ENTRY_APPROACH_LEVEL, exit_failOuterVal = R4 - (R6 - R4) * PLAY_ENTRY_APPROACH_LEVEL, exit_successLineVal = R6, exit_successOuterVal = sessionLow + dailyATR * PLAY_ENTRY_ATR_LEVEL )) // Higher Range, Play D playSpecMap.put("HD", PlaySpec.new( description = "Counter-trending S4 breakdown from outside S4", direction = Direction.short, validRange = PivotRange.higher, entry_precondition_open = rthOpen < S4 and rthOpen > S6, entry_precondition_atr = S6 >= sessionHigh - dailyATR * PLAY_ENTRY_ATR_LEVEL, entry_trigger = close < S4 and high > S4 - (S4 - S6) * PLAY_ENTRY_APPROACH_LEVEL and rising, exit_success = low <= S6, exit_fail = close > S4 + (S3 - S4) * PLAY_EXIT_FAIL_TOLERANCE and barstate.isconfirmed, exit_other_condition = false, exit_other_trigger = false, entry_lineVal = S4, entry_innerVal = S4 - (S4 - S6) * PLAY_ENTRY_APPROACH_LEVEL, exit_failOuterVal = S4 + (S4 - S6) * PLAY_ENTRY_APPROACH_LEVEL, exit_successLineVal = S6, exit_successOuterVal = sessionHigh - dailyATR * PLAY_ENTRY_ATR_LEVEL )) // Higher Range, Play E playSpecMap.put("HE", PlaySpec.new( description = "R4 extreme reversal from inside R3-S3", direction = Direction.short, validRange = PivotRange.higher, entry_precondition_open = rthOpen > S3 and rthOpen < R3, entry_precondition_atr = S4 >= sessionHigh - dailyATR * PLAY_ENTRY_ATR_LEVEL, entry_trigger = close < R4 and high > R4 - (R4 - R3) * PLAY_ENTRY_APPROACH_LEVEL and smaHlc3 < R4 and falling, exit_success = low <= S4, exit_fail = close > R4 + (R6 - R4) * PLAY_EXIT_FAIL_TOLERANCE and barstate.isconfirmed, exit_other_condition = false, exit_other_trigger = false, entry_lineVal = R4, entry_innerVal = R4 - (R4 - R3) * PLAY_ENTRY_APPROACH_LEVEL, exit_failOuterVal = R4 + (R4 - R3) * PLAY_ENTRY_APPROACH_LEVEL, exit_successLineVal = S4, exit_successOuterVal = sessionHigh - dailyATR * PLAY_ENTRY_ATR_LEVEL )) // Higher Range, Play F playSpecMap.put("HF", PlaySpec.new( description = "R6 reversal from outside R4", direction = Direction.short, validRange = PivotRange.higher, entry_precondition_open = rthOpen > R4 and rthOpen < R6, entry_precondition_atr = CP >= sessionHigh - dailyATR * PLAY_ENTRY_ATR_LEVEL, entry_trigger = close < R6 and high > R6 - (R6 - R4) * PLAY_ENTRY_APPROACH_LEVEL and smaHlc3 < R6 and falling, exit_success = low <= CP, exit_fail = close > R6 + (R6 - R4) * PLAY_EXIT_FAIL_TOLERANCE and barstate.isconfirmed, //Tolerance as ratio to R6-R4 since no level above R6 exit_other_condition = false, exit_other_trigger = false, entry_lineVal = R6, entry_innerVal = R6 - (R6 - R4) * PLAY_ENTRY_APPROACH_LEVEL, exit_failOuterVal = R6 + (R6 - R4) * PLAY_ENTRY_APPROACH_LEVEL, exit_successLineVal = CP, exit_successOuterVal = sessionHigh - dailyATR * PLAY_ENTRY_ATR_LEVEL )) // Lower Range, Play A playSpecMap.put("LA", PlaySpec.new( description = "R3 to S3 traversal from inside S3-R3", direction = Direction.short, validRange = PivotRange.lower, entry_precondition_open = rthOpen > S3 and rthOpen < R3, entry_precondition_atr = S3 >= sessionHigh - dailyATR * PLAY_ENTRY_ATR_LEVEL, entry_trigger = close < R3 and high > R3 - (R3 - CP) * PLAY_ENTRY_APPROACH_LEVEL and rising, exit_success = low <= S3, exit_fail = close > R3 + (R4 - R3) * PLAY_EXIT_FAIL_TOLERANCE and barstate.isconfirmed, exit_other_condition = false, exit_other_trigger = false, entry_lineVal = R3, entry_innerVal = R3 - (R3 - CP) * PLAY_ENTRY_APPROACH_LEVEL, exit_failOuterVal = R3 + (R3 - CP) * PLAY_ENTRY_APPROACH_LEVEL, exit_successLineVal = S3, exit_successOuterVal = sessionHigh - dailyATR * PLAY_ENTRY_ATR_LEVEL )) // Lower Range, Play B playSpecMap.put("LB", PlaySpec.new( description = "Trending S4 breakdown from inside S3-R3", direction = Direction.short, validRange = PivotRange.lower, entry_precondition_open = rthOpen > S3 and rthOpen < R3, entry_precondition_atr = S6 >= sessionHigh - dailyATR * PLAY_ENTRY_ATR_LEVEL, entry_trigger = barstate.isconfirmed and close < S4 and close > S4 - (S4 - S6) * PLAY_ENTRY_APPROACH_LEVEL and smaHlc3 < S4 and falling, exit_success = low <= S6, exit_fail = close > S4 + (S3 - S4) * PLAY_EXIT_FAIL_TOLERANCE and barstate.isconfirmed, exit_other_condition = false, exit_other_trigger = false, entry_lineVal = S4, entry_innerVal = S4 - (S4 - S6) * PLAY_ENTRY_APPROACH_LEVEL, exit_failOuterVal = S4 + (S4 - S6) * PLAY_ENTRY_APPROACH_LEVEL, exit_successLineVal = S6, exit_successOuterVal = sessionHigh - dailyATR * PLAY_ENTRY_ATR_LEVEL )) // Lower Range, Play C playSpecMap.put("LC", PlaySpec.new( description = "Trending S4 breakdown from outside S4", direction = Direction.short, validRange = PivotRange.lower, entry_precondition_open = rthOpen < S4 and rthOpen > S6, entry_precondition_atr = S6 >= sessionHigh - dailyATR * PLAY_ENTRY_ATR_LEVEL, entry_trigger = high > S4 - (S4 - S6) * PLAY_ENTRY_APPROACH_LEVEL and close < S4 and rising, exit_success = low <= S6, exit_fail = close > S4 + (S3 - S4) * PLAY_EXIT_FAIL_TOLERANCE and barstate.isconfirmed, exit_other_condition = barstate.isconfirmed and low < S6 + (S4 - S6) * PLAY_EXIT_OTHER_CONDITION_LEVEL, exit_other_trigger = close > S6 + (S4 - S6) * PLAY_EXIT_OTHER_TRIGGER_LEVEL, entry_lineVal = S4, entry_innerVal = S4 - (S4 - S6) * PLAY_ENTRY_APPROACH_LEVEL, exit_failOuterVal = S4 + (S4 - S6) * PLAY_ENTRY_APPROACH_LEVEL, exit_successLineVal = S6, exit_successOuterVal = sessionHigh - dailyATR * PLAY_ENTRY_ATR_LEVEL )) // Lower Range, Play D playSpecMap.put("LD", PlaySpec.new( description = "Counter-trending R4 breakup from outside R4", direction = Direction.long, validRange = PivotRange.lower, entry_precondition_open = rthOpen > R4 and rthOpen < R6, entry_precondition_atr = R6 <= sessionLow + dailyATR * PLAY_ENTRY_ATR_LEVEL, entry_trigger = close > R4 and low < R4 + (R6 - R4) * PLAY_ENTRY_APPROACH_LEVEL and falling, exit_success = high >= R6, exit_fail = close < R4 - (R4 - R3) * PLAY_EXIT_FAIL_TOLERANCE and barstate.isconfirmed, exit_other_condition = false, exit_other_trigger = false, entry_lineVal = R4, entry_innerVal = R4 + (R6 - R4) * PLAY_ENTRY_APPROACH_LEVEL, exit_failOuterVal = R4 - (R6 - R4) * PLAY_ENTRY_APPROACH_LEVEL, exit_successLineVal = R6, exit_successOuterVal = sessionLow + dailyATR * PLAY_ENTRY_ATR_LEVEL )) // Lower Range, Play E playSpecMap.put("LE", PlaySpec.new( description = "S4 extreme reversal from inside R3-S3", direction = Direction.long, validRange = PivotRange.lower, entry_precondition_open = rthOpen > S3 and rthOpen < R3, entry_precondition_atr = R4 <= sessionLow + dailyATR * PLAY_ENTRY_ATR_LEVEL, entry_trigger = close > S4 and low < S4 + (S3 - S4) * PLAY_ENTRY_APPROACH_LEVEL and smaHlc3 > S4 and rising, exit_success = high >= R4, exit_fail = close < S4 - (S4 - S6) * PLAY_EXIT_FAIL_TOLERANCE and barstate.isconfirmed, exit_other_condition = false, exit_other_trigger = false, entry_lineVal = S4, entry_innerVal = S4 + (S3 - S4) * PLAY_ENTRY_APPROACH_LEVEL, exit_failOuterVal = S4 - (S3 - S4) * PLAY_ENTRY_APPROACH_LEVEL, exit_successLineVal = R4, exit_successOuterVal = sessionLow + dailyATR * PLAY_ENTRY_ATR_LEVEL )) // Lower Range, Play F playSpecMap.put("LF", PlaySpec.new( description = "S6 reversal from outside S4", direction = Direction.long, validRange = PivotRange.lower, entry_precondition_open = rthOpen < S4 and rthOpen > S6, entry_precondition_atr = CP <= sessionLow + dailyATR * PLAY_ENTRY_ATR_LEVEL, entry_trigger = close > S6 and low < S6 + (S4 - S6) * PLAY_ENTRY_APPROACH_LEVEL and smaHlc3 > S6 and rising, exit_success = high >= CP, exit_fail = close < S6 - (S4 - S6) * PLAY_EXIT_FAIL_TOLERANCE and barstate.isconfirmed, //Tolerance as ratio to S4-R6 since no level below S6 exit_other_condition = false, exit_other_trigger = false, entry_lineVal = S6, entry_innerVal = S6 + (S4 - S6) * PLAY_ENTRY_APPROACH_LEVEL, exit_failOuterVal = S6 - (S4 - S6) * PLAY_ENTRY_APPROACH_LEVEL, exit_successLineVal = CP, exit_successOuterVal = sessionLow + dailyATR * PLAY_ENTRY_ATR_LEVEL )) //#endregion ---- Play specifications // Process the plays for play in PLAYS bool playEntry = false bool playExit = false playSpec := playSpecMap.get(play) playState := playStateMap.get(play) bool premarketPlaysPrecondition = isMarket or prePlays != PremarketPlays.none and isPremarket bool pivotRangePrecondition = pivotRange == playSpec.validRange or validRangePrecondition == PreconditionCheck.none or isPremarket and validRangePrecondition == PreconditionCheck.rth or pivotRange == PivotRange.neutral bool atrPrecondition = atrPreconditionCheck == PreconditionCheck.none or isPremarket and atrPreconditionCheck == PreconditionCheck.rth or playSpec.entry_precondition_atr bool neutralPlaysPrecondition = pivotRange != PivotRange.neutral or neutralPlays == PreconditionCheck.always or isMarket and neutralPlays == PreconditionCheck.rth bool openPrecondition = openPreconditionCheck == PreconditionCheck.none or isPremarket and openPreconditionCheck == PreconditionCheck.rth or playSpec.entry_precondition_open bool preconditions = premarketPlaysPrecondition and pivotRangePrecondition and atrPrecondition and neutralPlaysPrecondition and openPrecondition debug(barstate.islastconfirmedhistory, "precond", play + ": " + str.tostring(preconditions)) playState.playPossible := preconditions playEntry := preconditions and playSpec.entry_trigger playState.exit_other_condition := playSpec.exit_other_condition or playState.exit_other_condition playExit := playSpec.exit_success or playSpec.exit_fail or playState.exit_other_condition and playSpec.exit_other_trigger or not preconditions playState.exit_other_condition := not playExit and playState.exit_other_condition playStateMap.put(play, playState) playActiveMap.put(play, playEntry == playExit ? playActiveMap.get(play) : playEntry) // Reset all plays when ... if postStartBar // Clear plays in the post-market or preStartBar // Safety net if chart is using RTH and a play remained active until the end of the previous day RTH. Also clear futures plays at new session or prePlays == PremarketPlays.reset and regStartBar and barstate.isnew // Reset plays when the regular market starts if the relevant user option is set or CP != CP[1] // If the CP moves for whatever reason, clear active plays for play in PLAYS playActiveMap.put(play, false) playState := playStateMap.get(play) playState.exit_other_condition := false playStateMap.put(play, playState) //#endregion ---- Plays //#endregion ---- Logic // ---- Visuals //#region ---- Visuals // The cams plotted on the first day are incorrect since if need data from the previous day with in not retrieved. So hide the first day. var bool firstCP = false var bool safePlot = false firstCP := na(CP[1]) and not na(CP) or firstCP safePlot := firstCP and CP != CP[1] or safePlot // plot(debugPlotBool(safePlot, "safePlot"), color = color.aqua, display = display.all - display.status_line) bool lineBreak = not lineJoin and preStartBar or isFutures and closeOverride and closeOverrideBar // Plot the Cams plot(safePlot and showCP ? CP : na, "CP", color = lineBreak ? color.new(cpColour, 100) : cpColour, linewidth = 1, display = display.all - display.status_line) plot(safePlot ? R3 : na, "R3", lineBreak ? color.new(resistanceColour, 100) : resistanceColour, 3, display = display.all - display.status_line) plot(safePlot ? R4 : na, "R4", lineBreak ? color.new(resistanceColour, 100) : resistanceColour, 2, display = display.all - display.status_line) plot(safePlot ? R6 : na, "R6", lineBreak ? color.new(resistanceColour, 100) : resistanceColour, 1, display = display.all - display.status_line) plot(safePlot ? S3 : na, "S3", lineBreak ? color.new(supportColour, 100) : supportColour, 3, display = display.all - display.status_line) plot(safePlot ? S4 : na, "S4", lineBreak ? color.new(supportColour, 100) : supportColour, 2, display = display.all - display.status_line) plot(safePlot ? S6 : na, "S6", lineBreak ? color.new(supportColour, 100) : supportColour, 1, display = display.all - display.status_line) plot(safePlot and showOther and showCP ? oCP : na, "OCP" , color = lineBreak ? color.new(oCpColour, 100) : oCpColour, linewidth = 1, display = display.all - display.status_line) plot(safePlot and showOther ? oCamMap.get("R3") : na, "OR3", lineBreak ? color.new(oResistanceColour, 100) : oResistanceColour, 3, display = display.all - display.status_line) plot(safePlot and showOther ? oCamMap.get("R4") : na, "OR4", lineBreak ? color.new(oResistanceColour, 100) : oResistanceColour, 2, display = display.all - display.status_line) plot(safePlot and showOther ? oCamMap.get("R6") : na, "OR6", lineBreak ? color.new(oResistanceColour, 100) : oResistanceColour, 1, display = display.all - display.status_line) plot(safePlot and showOther ? oCamMap.get("S3") : na, "OS3", lineBreak ? color.new(oSupportColour, 100) : oSupportColour, 3, display = display.all - display.status_line) plot(safePlot and showOther ? oCamMap.get("S4") : na, "OS4", lineBreak ? color.new(oSupportColour, 100) : oSupportColour, 2, display = display.all - display.status_line) plot(safePlot and showOther ? oCamMap.get("S6") : na, "OS6", lineBreak ? color.new(oSupportColour, 100) : oSupportColour, 1, display = display.all - display.status_line) bool wPredicate = weekofyear(timenow) == weekofyear and year(timenow) == year plot(safePlot and showWeekly and wPredicate and showCP ? wClose : na, "WCP", color = wCpColour, linewidth = 1, style = plot.style_linebr, display = display.all - display.status_line) plot(safePlot and showWeekly and wPredicate ? wCamMap.get("R3") : na, "WR3", color = wResistanceColour, linewidth = 3, style = plot.style_linebr, display = display.all - display.status_line) plot(safePlot and showWeekly and wPredicate ? wCamMap.get("R4") : na, "WR4", color = wResistanceColour, linewidth = 2, style = plot.style_linebr, display = display.all - display.status_line) plot(safePlot and showWeekly and wPredicate ? wCamMap.get("R6") : na, "WR6", color = wResistanceColour, linewidth = 1, style = plot.style_linebr, display = display.all - display.status_line) plot(safePlot and showWeekly and wPredicate ? wCamMap.get("S3") : na, "WS3", color = wSupportColour, linewidth = 3, style = plot.style_linebr, display = display.all - display.status_line) plot(safePlot and showWeekly and wPredicate ? wCamMap.get("S4") : na, "WS4", color = wSupportColour, linewidth = 2, style = plot.style_linebr, display = display.all - display.status_line) plot(safePlot and showWeekly and wPredicate ? wCamMap.get("S6") : na, "WS6", color = wSupportColour, linewidth = 1, style = plot.style_linebr, display = display.all - display.status_line) bool mPredicate = month(timenow) == month and year(timenow) == year plot(safePlot and showMonthly and mPredicate and showCP ? mClose : na, "MCP", color = mCpColour, linewidth = 1, style = plot.style_linebr, display = display.all - display.status_line) plot(safePlot and showMonthly and mPredicate ? mCamMap.get("R3") : na, "MR3", color = mResistanceColour, linewidth = 3, style = plot.style_linebr, display = display.all - display.status_line) plot(safePlot and showMonthly and mPredicate ? mCamMap.get("R4") : na, "MR4", color = mResistanceColour, linewidth = 2, style = plot.style_linebr, display = display.all - display.status_line) plot(safePlot and showMonthly and mPredicate ? mCamMap.get("R6") : na, "MR6", color = mResistanceColour, linewidth = 1, style = plot.style_linebr, display = display.all - display.status_line) plot(safePlot and showMonthly and mPredicate ? mCamMap.get("S3") : na, "MS3", color = mSupportColour, linewidth = 3, style = plot.style_linebr, display = display.all - display.status_line) plot(safePlot and showMonthly and mPredicate ? mCamMap.get("S4") : na, "MS4", color = mSupportColour, linewidth = 2, style = plot.style_linebr, display = display.all - display.status_line) plot(safePlot and showMonthly and mPredicate ? mCamMap.get("S6") : na, "MS6", color = mSupportColour, linewidth = 1, style = plot.style_linebr, display = display.all - display.status_line) //#region ---- Play Visuals // Remember the currently active play. If multiple are active at once, this will contain the last activation var string activePlay = na // Play event status (for alerts) PlayEvent playEvent = na string exitedPlay = na // Check high-level condition that should cause skipping processing of all plays if not timeframe.isdwm // Cripple on D/W/M timeframe and not barstate.isfirst // On the first bar, playActiveMap[1] is na and would cause a runtime error on (playActiveMap[1]).get(play) (used later) and (not waitBarConfirm or barstate.isconfirmed) // Do not allow play change intra-bar if the "wait for bar close" option is chosen. With this option the play conditions need to be fulfilled on the bar confirmation, not transiently intra-bar. and safePlot // Do not draw any plays until the pivots are correct // Loop over the plays to draw them for play in PLAYS // Retrieve play spec and state playSpec := playSpecMap.get(play) playState := playStateMap.get(play) // Get whether the play was active on this bar and the last bar bool isActive = playActiveMap.get(play) bool wasActive = (playActiveMap[1]).get(play) // If play entry triggered on this bar... if isActive and not wasActive activePlay := play playEvent := PlayEvent.entry [entryColour, exitColour] = switch playSpec.direction Direction.long => [supportColour, resistanceColour] Direction.short => [resistanceColour, supportColour] => [color.white, color.white] playState.entry_line := line.new(bar_index, playSpec.entry_lineVal, bar_index + 1, playSpec.entry_lineVal, color = entryColour, width = 3) playState.entry_box := box.new(bar_index, playSpec.entry_innerVal, bar_index + 1, playSpec.entry_lineVal, border_width = 0, bgcolor = color.new(entryColour, playEntryBoxTransparency), text = play + (playSpec.direction == Direction.long ? " ▲" : " ▼"), text_size = size.normal, text_color = contrastTextColour(color.new(entryColour, playEntryBoxTransparency))) playState.exit_line := not hidePlayExit ? line.new(bar_index, playSpec.exit_successLineVal, bar_index + 1, playSpec.exit_successLineVal, color = exitColour, width = 3) : na playState.exit_successBox := not hidePlayExit ? box.new(bar_index, playSpec.exit_successOuterVal, bar_index + 1, playSpec.exit_successLineVal, border_width = 0, bgcolor = color.new(exitColour, playExitBoxTransparency), text = play + " ✓", text_size = size.small, text_color = contrastTextColour(color.new(exitColour, playExitBoxTransparency))) : na playState.exit_failBox := not hidePlayExit ? box.new(bar_index, playSpec.entry_lineVal, bar_index + 1, playSpec.exit_failOuterVal, border_width = 0, bgcolor = color.new(entryColour, playExitBoxTransparency), text = play + " ✖", text_size = size.small, text_color = contrastTextColour(color.new(exitColour, playExitBoxTransparency))) : na // If play exit triggered on this bar... if not isActive and wasActive // Update the current active play in the top-right table exitedPlay := activePlay activePlay := play == activePlay ? na : activePlay playEvent := PlayEvent.exit // Clear play boxes if option to keep them is not set if not showPastPlays line.delete(playState.entry_line) box.delete(playState.entry_box) line.delete(playState.exit_line) box.delete(playState.exit_successBox) box.delete(playState.exit_failBox) // While play is active... if isActive and wasActive // Extend play line and active play boxes line.set_x2(playState.entry_line, bar_index) box.set_right(playState.entry_box, bar_index) line.set_x2(playState.exit_line, bar_index) box.set_right(playState.exit_successBox, bar_index) box.set_top(playState.exit_successBox, playSpec.exit_successOuterVal) box.set_right(playState.exit_failBox, bar_index) // If multiple plays were active, one of them exited, activePlay was set to an empty string above // However we still have an active play, so set activePlay to it // This works if there can at most two active stategies. // This is currently true unless you both higher and lower range plays allowed in a single day (e.g. by a setting or on a neutral range day). If this case, after the a play exits, it there a still some other plays active, the last one of those in the PLAYS array. if na(activePlay) activePlay := play // Save updated play state back to map playStateMap.put(play, playState) //#endregion ---- Plays Visuals //#region ---- Extension lines and labels // Extention line handles var map extLineMap = map.new() var map oExtLineMap = map.new() var map wExtLineMap = map.new() var map mExtLineMap = map.new() if barstate.islast and not timeframe.isdwm [usedData, otherData] = if useEthForCams ["(E)", "(R)"] else ["(R)", "(E)"] for cam in CAM_LEVELS // Check whether the extension line/label have already been created. if na(extLineMap.get(cam)) // Initialise the extension lines ExtLine extLine = ExtLine.new() color colour = switch str.substring(cam, 0, 1) "R" => resistanceColour "S" => supportColour => color.white int linewidth = 4 - int(str.tonumber(str.substring(cam, 1)) / 2) extLine.h_line := line.new(bar_index, camMap.get(cam), bar_index + labelOffset, camMap.get(cam), extend = extendLines ? extend.right : extend.none, color = colour, style = line.style_solid, width = linewidth) extLine.h_label := label.new(bar_index + labelOffset, camMap.get(cam), cam + (showOther ? usedData : na) + (showPrices ? ": " + str.tostring(camMap.get(cam), "#.00") : na), color = colour, style = label.style_label_left, textcolor = contrastTextColour(colour)) extLineMap.put(cam, extLine) if showOther ExtLine oExtLine = ExtLine.new() colour := switch str.substring(cam, 0, 1) "R" => oResistanceColour "S" => oSupportColour => color.white linewidth := 4 - int(str.tonumber(str.substring(cam, 1)) / 2) oExtLine.h_line := line.new(bar_index, oCamMap.get(cam), bar_index + oLabelOffset, oCamMap.get(cam), extend = extendLines ? extend.right : extend.none, color = colour, style = line.style_solid, width = linewidth) oExtLine.h_label := label.new(bar_index + oLabelOffset, oCamMap.get(cam), cam + (showOther ? otherData : na) + (showPrices ? ": " + str.tostring(oCamMap.get(cam), "#.00") : na), color = colour, style = label.style_label_left, textcolor = contrastTextColour(colour)) oExtLineMap.put(cam, oExtLine) if showWeekly ExtLine wExtLine = ExtLine.new() colour := switch str.substring(cam, 0, 1) "R" => wResistanceColour "S" => wSupportColour => color.white linewidth := 4 - int(str.tonumber(str.substring(cam, 1)) / 2) wExtLine.h_line := line.new(bar_index, wCamMap.get(cam), bar_index + wLabelOffset, wCamMap.get(cam), extend = extendLines ? extend.right : extend.none, color = colour, style = line.style_solid, width = linewidth) wExtLine.h_label := label.new(bar_index + wLabelOffset, wCamMap.get(cam), "W" + cam + (showPrices ? ": " + str.tostring(wCamMap.get(cam), "#.00") : na), color = colour, style = label.style_label_left, textcolor = contrastTextColour(colour)) wExtLineMap.put(cam, wExtLine) if showMonthly ExtLine mExtLine = ExtLine.new() colour := switch str.substring(cam, 0, 1) "R" => mResistanceColour "S" => mSupportColour => color.white linewidth := 4 - int(str.tonumber(str.substring(cam, 1)) / 2) mExtLine.h_line := line.new(bar_index, mCamMap.get(cam), bar_index + mLabelOffset, mCamMap.get(cam), extend = extendLines ? extend.right : extend.none, color = colour, style = line.style_solid, width = linewidth) mExtLine.h_label := label.new(bar_index + mLabelOffset, mCamMap.get(cam), "M" + cam + (showPrices ? ": " + str.tostring(mCamMap.get(cam), "#.00") : na), color = colour, style = label.style_label_left, textcolor = contrastTextColour(colour)) mExtLineMap.put(cam, mExtLine) // Update the extension lines and labels on the last bar line h_line = extLineMap.get(cam).h_line label h_label = extLineMap.get(cam).h_label line.set_xy1(h_line, bar_index, camMap.get(cam)) line.set_xy2(h_line, bar_index + labelOffset, camMap.get(cam)) label.set_xy(h_label, bar_index + labelOffset, camMap.get(cam)) label.set_text(h_label, cam + (showOther ? usedData : na) + (showPrices ? ": " + str.tostring(camMap.get(cam), "#.00") : na)) if showOther h_line := oExtLineMap.get(cam).h_line h_label := oExtLineMap.get(cam).h_label line.set_xy1(h_line, bar_index, oCamMap.get(cam)) line.set_xy2(h_line, bar_index + oLabelOffset, oCamMap.get(cam)) label.set_xy(h_label, bar_index + oLabelOffset, oCamMap.get(cam)) label.set_text(h_label, cam + (showOther ? otherData : na) + (showPrices ? ": " + str.tostring(oCamMap.get(cam), "#.00") : na)) if showWeekly h_line := wExtLineMap.get(cam).h_line h_label := wExtLineMap.get(cam).h_label line.set_xy1(h_line, bar_index, wCamMap.get(cam)) line.set_xy2(h_line, bar_index + wLabelOffset, wCamMap.get(cam)) label.set_xy(h_label, bar_index + wLabelOffset, wCamMap.get(cam)) label.set_text(h_label, "W" + cam + (showPrices ? ": " + str.tostring(wCamMap.get(cam), "#.00") : na)) if showMonthly h_line := mExtLineMap.get(cam).h_line h_label := mExtLineMap.get(cam).h_label line.set_xy1(h_line, bar_index, mCamMap.get(cam)) line.set_xy2(h_line, bar_index + mLabelOffset, mCamMap.get(cam)) label.set_xy(h_label, bar_index + mLabelOffset, mCamMap.get(cam)) label.set_text(h_label, "M" + cam + (showPrices ? ": " + str.tostring(mCamMap.get(cam), "#.00") : na)) // Show Central Pivots if showCP var line lineClose = line.new(bar_index, CP, bar_index + labelOffset, CP, extend = extendLines ? extend.right : extend.none, color = cpColour, style = line.style_solid, width = 1) var label labelClose = label.new(bar_index + labelOffset, CP, "CP" + (showPrices ? ": " + str.tostring(CP, "#.00") : na), color = cpColour, style = label.style_label_left, textcolor = contrastTextColour(cpColour)) line.set_xy1(lineClose, bar_index, CP) line.set_xy2(lineClose, bar_index + labelOffset, CP) label.set_xy(labelClose, bar_index + labelOffset, CP) label.set_text(labelClose, "CP" + (showOther ? usedData : na) + (showPrices ? ": " + str.tostring(CP, "#.00") : na)) if showCP and showOther var line lineClose = line.new(bar_index, oCP, bar_index + oLabelOffset, oCP, extend = extendLines ? extend.right : extend.none, color = oCpColour, style = line.style_solid, width = 1) var label labelClose = label.new(bar_index + oLabelOffset, oCP, "OCP" + (showPrices ? ": " + str.tostring(oCP, "#.00") : na), color = oCpColour, style = label.style_label_left, textcolor = contrastTextColour(oCpColour)) line.set_xy1(lineClose, bar_index, oCP) line.set_xy2(lineClose, bar_index + oLabelOffset, oCP) label.set_xy(labelClose, bar_index + oLabelOffset, oCP) label.set_text(labelClose, "CP" + (showOther ? otherData : na) + (showPrices ? ": " + str.tostring(oCP, "#.00") : na)) if showCP and showWeekly var line lineClose = line.new(bar_index, wClose, bar_index + wLabelOffset, wClose, extend = extendLines ? extend.right : extend.none, color = wCpColour, style = line.style_solid, width = 1) var label labelClose = label.new(bar_index + wLabelOffset, wClose, "WCP" + (showPrices ? ": " + str.tostring(wClose, "#.00") : na), color = wCpColour, style = label.style_label_left, textcolor = contrastTextColour(wCpColour)) line.set_xy1(lineClose, bar_index, wClose) line.set_xy2(lineClose, bar_index + wLabelOffset, wClose) label.set_xy(labelClose, bar_index + wLabelOffset, wClose) label.set_text(labelClose, "WCP" + (showPrices ? ": " + str.tostring(wClose, "#.00") : na)) if showCP and showMonthly var line lineClose = line.new(bar_index, mClose, bar_index + mLabelOffset, mClose, extend = extendLines ? extend.right : extend.none, color = mCpColour, style = line.style_solid, width = 1) var label labelClose = label.new(bar_index + mLabelOffset, mClose, "MCP" + (showPrices ? ": " + str.tostring(mClose, "#.00") : na), color = mCpColour, style = label.style_label_left, textcolor = contrastTextColour(mCpColour)) line.set_xy1(lineClose, bar_index, mClose) line.set_xy2(lineClose, bar_index + mLabelOffset, mClose) label.set_xy(labelClose, bar_index + mLabelOffset, mClose) label.set_text(labelClose, "MCP" + (showPrices ? ": " + str.tostring(mClose, "#.00") : na)) //#endregion ---- Extension lines and labels //#region ---- Table string playText = na for play in PLAYS playText := playText + (playStateMap.get(play).playPossible ? play + ", " : na) if na(playText) playText := "No Play Possible" else playText := "Possible Plays: " + str.substring(playText, 0, str.length(playText) - 2) if not(na(activePlay)) playText := activePlay + ": " + playSpecMap.get(activePlay).description // Top right info table. (Re)Draw the table on the last bar if barstate.islast var table infoTable = table.new(position.top_right, 3, showOptionsValues ? 3 : 2, color.black, color.white, 1, color.white, 1) table.merge_cells(infoTable, 0, 1, 2, 1) if not timeframe.isdwm table.cell(infoTable, 0, 0, useEthForCams ? "Using ETH Data" : "Using RTH Data", text_color = color.white) table.cell(infoTable, 1, 0, str.tostring(pivotRange), text_color = color.white) table.cell(infoTable, 2, 0, pivotWidth, text_color = color.white) table.cell(infoTable, 0, 1, playText, text_color = color.white) if showOptionsValues table.merge_cells(infoTable, 0, 2, 2, 2) table.cell(infoTable, 0, 2, constructOptionValuesString(), text_color = color.white, text_halign = text.align_left) else table.cell(infoTable, 0, 0, "───────", text_color = color.white) table.cell(infoTable, 1, 0, "───────", text_color = color.white) table.cell(infoTable, 2, 0, "───────", text_color = color.white) table.cell(infoTable, 0, 1, "Deactivated on D/W/M timeframe", text_color = color.white) //#endregion ---- Table //#endregion ---- Visuals // ---- Alerts //#region ---- Alerts if not webhookAlert triggerAlert(playEvent == PlayEvent.entry, activePlay + " entry event on " + syminfo.ticker + " at " + str.format_time(time, "HH:mm:ss", timezone)) else triggerAlert(playEvent == PlayEvent.entry, "{\"ticker\": \"" + syminfo.ticker + "\", \"event\": \"entry\", \"play\":\"" + activePlay + "\", \"time\":\"" + str.format_time(time, "yyyy-MM-dd HH:mm:ss", timezone) + "\"}") //#endregion ---- Alerts