Login Page - Create Account

Support Board


Date/Time: Sun, 15 Jun 2025 15:55:24 +0000



[User Discussion] - Bookmarking chart locations?

View Count: 631

[2021-07-24 12:47:14]
User299440 - Posts: 1
Hi,

Is there any way to easily flip between multiple chart locations (price/time)? I've used the 'Goto DtTm' function but I have to keep rentering the two times and scroll up and down as the price level isn't included.

Many Thanks!

Tim.
[2025-05-19 21:45:48]
barucho - Posts: 17
I created a custom study to help navigate chart drawings during backtesting, it can be used for a bookmark feature also. Here’s how it works:

Setup:

Add three Custom Study Buttons to your Control Bar and note their IDs (check Control Bar settings).
Add the study to your chart and set the button IDs in the study inputs for Next, Previous, and Refresh actions.
In the study’s Drawing Text Keyword field, enter a unique keyword (e.g., “SETUP”).
Usage:

When adding drawings (arrows, lines, text, etc.) to your chart, set the keyword as the drawing's text (e.g., “SETUP”).
Use the Control Bar buttons to:
Move forward or backward to drawing locations (chart data must be loaded in order to jump to a drawing at that location).
Refresh the internal list after adding new drawings.
This is great for quickly reviewing marked setups (or whatever).

Source Code:
I’m sharing the source code for this study below for community use. Feel free to modify or adapt it as needed.

Disclaimer:
This custom study and its source code are provided “as is” for educational and personal use. I make no warranties, express or implied, regarding its functionality, accuracy, or suitability for trading. Use at your own risk, and thoroughly test in a simulated environment before applying to live trading. I’m not liable for any losses or damages resulting from its use. Always consult a financial advisor before making trading decisions.


#include "sierrachart.h"
#include <vector>
#include <algorithm>

SCDLLName("Drawing Navigator")

// Define button identifiers (these correspond to ACS_BUTTON_1, ACS_BUTTON_2, etc.)
const int REFRESH_BUTTON = ACS_BUTTON_1; // Button 1
const int NEXT_BUTTON = ACS_BUTTON_2; // Button 2
const int PREV_BUTTON = ACS_BUTTON_3; // Button 3

SCSFExport scsf_DrawingNavigator(SCStudyInterfaceRef sc)
{
// Set defaults
if (sc.SetDefaults)
{
sc.GraphName = "Drawing Navigator";
sc.AutoLoop = 0; // Manual looping, no bar-by-bar processing needed
sc.UpdateAlways = 1; // Ensure study function is called continuously
sc.Input[0].Name = "Drawing Text Keyword";
sc.Input[0].SetString(""); // Default empty keywords
sc.Input[1].Name = "Refresh Button ID";
sc.Input[1].SetInt(REFRESH_BUTTON); // Default to refresh button ID
sc.Input[1].SetIntLimits(0, 255); // Limit to valid button IDs
sc.Input[2].Name = "Next Button ID";
sc.Input[2].SetInt(NEXT_BUTTON); // Default to next button ID
sc.Input[2].SetIntLimits(0, 255); // Limit to valid button IDs
sc.Input[3].Name = "Previous Button ID";
sc.Input[3].SetInt(PREV_BUTTON); // Default to previous button ID
sc.Input[3].SetIntLimits(0, 255); // Limit to valid button IDs

return;
}

// Inputs
SCInputRef KeywordsInput = sc.Input[0];
SCInputRef RefreshButtonId = sc.Input[1];
SCInputRef NextButtonId = sc.Input[2];
SCInputRef PrevButtonId = sc.Input[3];

// Persistent variables
SCString &prevKeywordStr = sc.GetPersistentSCString(1); // Previous keyword string for change detection
std::vector<SCDateTime> *&anchorList = reinterpret_cast<std::vector<SCDateTime> *&>(sc.GetPersistentPointer(0)); // List of anchor date-times
int &needsRefresh = sc.GetPersistentInt(3); // Flag to trigger refresh

// Initialize anchor list if not already done
if (anchorList == NULL)
{
anchorList = new std::vector<SCDateTime>();
sc.SetPersistentPointer(0, anchorList);
}

// Set button text and tooltips (do this only once, e.g., on first call)
if (sc.Index == 0)
{
sc.SetCustomStudyControlBarButtonText(1, "Refresh");
sc.SetCustomStudyControlBarButtonHoverText(1, "Refresh the anchor list");
sc.SetCustomStudyControlBarButtonText(2, "Next");
sc.SetCustomStudyControlBarButtonHoverText(2, "Navigate to the next anchor");
sc.SetCustomStudyControlBarButtonText(3, "Previous");
sc.SetCustomStudyControlBarButtonHoverText(3, "Navigate to the previous anchor");
}

// Handle button clicks
if (sc.PointerEventType == SC_ACS_BUTTON_ON)
{
if (sc.MenuEventID == RefreshButtonId.GetInt())
{
needsRefresh = 1; // Set flag to rebuild anchor list
sc.SetCustomStudyControlBarButtonEnable(RefreshButtonId.GetInt(), 0); // Turn off the button
}
else if (sc.MenuEventID == NextButtonId.GetInt())
{
if (!anchorList->empty())
{
int lastVisibleIndex = sc.IndexOfLastVisibleBar;
if (lastVisibleIndex >= 0 && lastVisibleIndex < sc.ArraySize)
{
SCDateTime visibleEndDT = sc.BaseDateTimeIn[lastVisibleIndex];
auto it = std::upper_bound(anchorList->begin(), anchorList->end(), visibleEndDT);
if (it != anchorList->end())
{
SCDateTime dt = *it;
sc.ScrollToDateTime = dt;
SCString msg;
msg.Format("Jumped to next anchor at %s", sc.DateTimeToString(dt, 0).GetChars());
sc.AddMessageToLog(msg, 1);
}
else
{
// Wrap around to the first anchor
SCDateTime dt = anchorList->front();
sc.ScrollToDateTime = dt;
SCString msg;
msg.Format("Wrapped to first anchor at %s", sc.DateTimeToString(dt, 0).GetChars());
sc.AddMessageToLog(msg, 1);
}

sc.ReCenterConstantRangeScale(sc.ChartNumber);
}
else
{
sc.AddMessageToLog("Invalid last visible bar index.", 1);
}
}
else
{
sc.AddMessageToLog("No anchors to navigate.", 1);
}
sc.SetCustomStudyControlBarButtonEnable(NextButtonId.GetInt(), 0); // Turn off the button
}
else if (sc.MenuEventID == PrevButtonId.GetInt())
{
if (!anchorList->empty())
{
int firstVisibleIndex = sc.IndexOfLastVisibleBar;
if (firstVisibleIndex >= 0 && firstVisibleIndex < sc.ArraySize)
{
SCDateTime visibleStartDT = sc.BaseDateTimeIn[firstVisibleIndex];
auto it = std::lower_bound(anchorList->begin(), anchorList->end(), visibleStartDT);
if (it != anchorList->begin())
{
--it;
SCDateTime dt = *it;
sc.ScrollToDateTime = dt;
SCString msg;
msg.Format("Jumped to previous anchor at %s", sc.DateTimeToString(dt, 0).GetChars());
sc.AddMessageToLog(msg, 1);
}
else
{
// Wrap around to the last anchor
SCDateTime dt = anchorList->back();
sc.ScrollToDateTime = dt;
SCString msg;
msg.Format("Wrapped to last anchor at %s", sc.DateTimeToString(dt, 0).GetChars());
sc.AddMessageToLog(msg, 1);
}

sc.ReCenterConstantRangeScale(sc.ChartNumber);
}
else
{
sc.AddMessageToLog("Invalid first visible bar index.", 1);
}
}
else
{
sc.AddMessageToLog("No anchors to navigate.", 1);
}
sc.SetCustomStudyControlBarButtonEnable(PrevButtonId.GetInt(), 0); // Turn off the button
}
}

// Get current keyword string
SCString keywordStr = KeywordsInput.GetString();

// Rebuild anchor list if keywords changed or refresh requested
if (keywordStr != prevKeywordStr || needsRefresh)
{
anchorList->clear();

int drawingIndex = 0;
s_UseTool drawing;
while (sc.GetUserDrawnChartDrawing(sc.ChartNumber, DRAWING_UNKNOWN, drawing, drawingIndex))
{
if (drawing.Text.CompareNoCase(keywordStr) == 0)
{
SCDateTime dt = drawing.EndDateTime;
anchorList->push_back(dt);
}
drawingIndex++;
}
std::sort(anchorList->begin(), anchorList->end()); // Sort chronologically

prevKeywordStr = keywordStr;
needsRefresh = 0; // Reset the refresh flag
if (anchorList->empty())
{
sc.AddMessageToLog("No drawings found with the specified keywords.", 1);
}
else
{
SCString msg;
msg.Format("Found %u drawings with specified keywords.", static_cast<unsigned int>(anchorList->size()));
sc.AddMessageToLog(msg, 1);
}
}

// Clean up when the study is removed
if (sc.LastCallToFunction)
{
if (anchorList != NULL)
{
delete anchorList;
sc.SetPersistentPointer(0, NULL);
}
}
}

Date Time Of Last Edit: 2025-05-19 21:50:13

To post a message in this thread, you need to log in with your Sierra Chart account:

Login

Login Page - Create Account