-
- Функции
Переменная - это временное хранилище для данных (число, строка, прочие объекты), используемых в расчетах ATF. После создания, переменным можно задавать значения и использовать их далее в арифметических выражениях.
Итого можно выделить три способа работы с переменными:
Создание переменной осуществляется с помощью операторов var, static либо extern. В этом разделе мы будем использовать переменные типа var, описание разницы между этими типами и их поведением можно прочитать далее в этом разделе.
Общий синтаксис объявления выглядит следующим образом:
var x; // Здесь x - имя переменной
В качестве имени переменной может выступать любая последовательность цифр, букв латинского алфавита и символов подчеркивания «_». Не допустимы лишь имена переменных, начинающихся цифрой. Важен так же регистр букв. Переменные «myvariable» и «myVariable» - это разные переменные, которые могут иметь разные значения. Желательно придерживаться какого-то одного соглашения по наименованию переменных. В примерах, предоставляемых на сайте, мы называем все переменные строчными буквами, а если их название состоит из нескольких слов, разделяем их символом прочерка.
Примеры допустимых и недопустимых, удачных и неудачных объявлений:
var x; // Допустимо var x1; // Допустимо var 1x; // Не допустимо - имя начинается с цифры var buy_counter; // Хорошее название - понятно что // содержится в переменной var buy counter; // Не допустимое название нельзя // использовать пробел в имени var bcnt; // Название допустимо но неудачно - // не ясно что содержит переменная var buyCounter; // Хорошее название, но надо помнить // о том, что следует придерживаться // единого соглашения о наименовании
Для записи значений используется следующий синтаксис
переменная = значение;
В качестве значения может выступать любое арифметическое выражение, например:
x = 123 * sqrt(14);
Для получения значения переменной достаточно просто использовать ее имя в подходящем для этого месте, например, в арифметическом выражении или в качестве аргумента функции:
x = sqrt(9); // x = 3 y = x + 4; // y = 7
До того, как в переменную явно не записано какое-либо значение, ее содержимое не определено. По этой причине всегда следует определять переменную и тут же задавать ей значение, чтобы в дальнейшем не возникла ошибка из-за его неопределенности. Для этой цели доступна сокращенная запись определение-объявление:
var переменная = начальное_значение;
Объявление переменных (кроме переменных extern) может находиться в любом месте программы, и это влияет на их область видимости. Синтаксически переменная существует лишь от момента ее объявления, до первой непарной закрывающейся фигурной скобки. Причем если внутри некоторой пары фигурных скобок Следующий пример иллюстрирует эту идею:
function calc() { var a = 1; { var a = 2; // Временно замещаем объявленную выше "a" var b = 3; line[0] = a; // Здесь a = 2 } // Здесь замещение более не актуально, и снова a = 1 // Так же с этого момента более не существует переменной "b" line[1] = a; // Здесь a = 1; line[2] = b; // Ошибка - переменной "b" не существует! }
Приведенный выше пример довольно вырожденный, и хотя синтаксически здесь все верно, на практике использовать отдельно стоящие фигурные скобки для ограничения области видимости нет смысла. Обычно область видимости становится существенна при использовании операторов таких как if и while, а так же при определении функций, где фигурные скобки синтаксически необходимы.
Общая рекомендация здесь - объявлять переменные как можно ближе к тому месту, где вы их использовали. Если у вас переменная используется только в одной функции, нет смысла делать ее глобальной. Если переменная используется только внутри какого-то цикла или условия и больше нигде не нужна, то и объявлять ее нужно именно там, чтобы не путаться.
Область видимости переменных или их локальность для пар фигурных скобок имеет не столько синтаксический, сколько логический смысл. Если во время выполнения программы она в один и тот же блок входит несколько раз, то каждый раз создаются новые копии переменных. Это критично при рекурсивном вызове функций. Рассмотрим пример, вычисляющий с заданной точностью квадратный корень по итерационной формуле Герона:
var err = .00000001; function Sqrt_rec(var a, var x) { if (abs(x*x - a) < err) {return x;} return Sqrt_rec((x + a/x) / 2); } function Sqrt(var a) { return Sqrt_rec(a, a / 2); }
Здесь функция Sqrt_rec принимает в качестве аргументов переменную a, из которой надо вычислить корень, и переменную x, которая является некоторым приближением этого корня. Если x недостаточно хороша (квадрат x отклоняется от a более чем на err), то по правилу Герона вычисляется новое значение корня (x + a/x)/2
и опять вызывается функция Sqrt_rec с уже новым значением. Здесь существенно, что для переменной x в каждом вызове создается отдельная копия, которая локальна именно для данного вызова.
Данное поведение является сильным аргументом против использования рекурсии в ATF (и других языков программирования, не ориентированных на функциональную парадигму программирования).
Стоит отдельно отметить поведение переменных, содержащих в себе ссылки на объекты. Рассмотрим следующий пример:
function f(var x) { x += 1; // значение локальной переменной x инкрементируется } function g(var arr) { arr.append(1); } function h(var arr) { arr = 15; } function test() { var v = 123; f(v); signal::output(v); // Выведет 123 var a = new_object("array"); g(a); g(a); g(a); signal::output(a.size()); // Выведет 3 h(a); signal::output(a); // Выведет "object_reference" }
Поведение кажется на первый взгляд нелогичным: в функции f() была создана локальная копия v, которая и икрементировалась, и поэтому вне функции f() значение v не изменилось. Функция h() так же не изменила объект, а вот функция g() по каким-то причинам модифицировала массив a, выполнив над ним функцию push().
Данное поведение обусловлено тем, что переменные на самом деле не содержат в себе сами объекты, а лишь ссылки на него. Переменные с объектами на самом деле содержат лишь идентификатор, по которому уже ATF обращается к объекту. При вызове функции каждый раз действительно создается локальная копия переменной, однако в случае с функцией g(), эта локальная копия содержит ровно тот же идентификатор, и в действительности правит ровно тот же объект. Сама функция new_object() возвращает на самом деле ни что иное, как идентификатор объекта в общем хранилище объектов, а не сам объект. Объекты так же игнорируют разницу в поведении переменных var и static, о которых речь пойдет в следующем параграфе.
Таким образом можно сформулировать общее правило, которое может быть удобным с практической точки зрения: обычные переменные ATF передаются в функции по значению (то есть создается локальная копия), а объекты передаются в функции по ссылке (то есть изменение объекта внутри функции приведет к изменению самого передаваемого объекта).
Начиная с версии 1.16, в ATF реализованы короткие вычисления логических операций. Рассмотрим следующий пример:
if (check1() and check2()) { ... }
Если выражение check1() возвращает false, то смысла рассчитывать check2() на самом деле уже нет. Следующий фрагмент кода будет работать по-разному в версиях ATF 1.16 и более ранних:
function f() { line[0] = 1; return true; } function calc() { var x = false and f(); }
В ранних версиях выполнение этого кода приведет к тому, что будет вызвана функиця f(), и значение линии станет равно 1. Начиная с версии 1.16, ATF не будет вызывать f(), поскольку значения false достаточно для того, чтобы понять, что значение всего выражения в любом случае будет false.
Этот прием часто удобно использовать при проверке допустимости используемых параметров. Допустим, функция f проверяет, что i-ый элемент массива больше нуля, но у нас нет уверенности, что массив вообще содержит такое количество элементов (если элементов меньше, функция возвратит false).
Старый вариант когда будет выглядеть так:
function f(var array, var i) { if (i < array.size()) { return array[i] > 0; } else { return false; } }
Начиная же с версии ATF 1.16 тот же самый код можно сократить следующим образом:
function f(var array, int i) { return i <= array.size() and array[i] > 0; }
Если записать такую функцию в старых версиях ATF, то попытка считать array[i] приведет к ошибке, поскольку значения i не существует. В ATF 1.16 вначале будет выполнена проверка i ⇐ array.size()
, и лишь затем, если она пройдет, будет вычислено второе выражение.
Схема коротких вычислений используется для операторов &&, ||, or, and. Вычисление всегда выполняется слева направо.
В ATF существует три типа переменных, каждый из которых имеет собственное поведение:
Переменные типа extern должны быть объявлены в самом начале программы в глобальной области видимости. По умолчанию они принимают лишь числовые значения. Чтобы иметь возможность задавать в качестве значения произвольную строку, можно сказать это отдельно:
extern "string" x;
Альтернативой для типа «string» является «number», который используется по умолчанию. Следующие две строчки равносильны как объявления:
extern "number" x1; extern x2; // Все равно что "number"
Тип может быть указан лишь для переменных extern и не применим к переменным var и static, поскольку в данном случае он является типом для диалогового окна, а не для интерпретатора ATF.
Переменные static и var различаются лишь своим поведением при обработке функции calc(). static ведет себя как обычная переменная, но var каждый раз сбрасывает свое значение при вызове calc() до состояния переменной на последней сделке прошлой свечи. Рассмотрим это на примерах.
Исключительно в демонстрационных целях предположим в этом разделе, что нам не доступна функция MovAvg, и мы решили реализовать ее самостоятельно. Первый способ сделать это — задействовать циклы, где на каждый вызов calc() честно рассчитывается среднее арифметическое последних N свечей. Это плохой способ, так как расчет циклов чаще всего оказывается весьма трудоемким. Правильнее было бы запоминать сумму последних N значений во временной переменной (можно для этого использовать само значение line, но для демонстрационных целей нам понадобится все же переменная), и потом для каждой новой свечи использовать это запомненное значение, предварительно удаляя из него самый старый элемент, и добавляя новый:
#samewindow extern period = 14; var sum = 0; function init() { if (countCandles() < period) {lackHistory();} var i = 0; while (i < period) { sum += close[i]; i += 1; } setInitCandles(period); setBounds(0, period, 0); } function calc() { sum += close; sum -= close[-period]; line[0] = sum / period; }
Важно понимать, что calc() вызывается при каждом новом трейде, то есть на каждую свечку calc() будет вызван многократно. При этом казалось бы к переменной sum будет прибавлен один и тот же close и вычтен один и тот же close[-period] многократно, что должно привести к некорректной работе индикатора. Однако это не так: ATF понимает, что при приходе нового трейда индикатор должен пересчитать свое последнее значение заново. По этой причине ATF запоминает последнее состояние глобальных переменных, и каждый раз при вызове calc() восстанавливает те значения, которые были актуальны для предыдущей свечки. В противном же случае, это приводило бы к неожиданному поведению индикаторов, зависящему от количества трейдов в свече.
Такое поведение переменных var в ATF мотивировано тем, что индикаторы в литературе, да и вообще людьми, чаще мыслятся в терминах уже законченных свечек. Последняя свечка при этом постоянно пересчитывается. То есть по идее значение индикатора на последней свечке при обычном расчете не учитывает те колебания цены, которые происходили внутри нее. То есть индикатор должен при каждом трейде как бы рассчитываться «с нуля», то есть якобы мы сразу имели последнюю свечку, без каких-либо расчетов «внутри» нее.
Таким образом каждый раз при вызове calc() переменные имеют то значение, которое они имели после последнего выполнения calc() на последней завершенной свечке. Этим отсекаются промежуточные значения индикатора, рассчитанные по неполной свече.
Общие рекомендации при выбора типа переменной могут быть следующими:
*, / | Умножение и деление |
+, - | Сложение и вычитание |
! | Логическое «НЕТ» |
<, >, >=, <= | Сравнения на больше/меньше |
==, != | Сравнение на равенство |
&& | Логическое «И» |
|| | Логическое «ИЛИ» |
not | Логическое «НЕТ» |
and | Логическое «И» |
or | Логическое «ИЛИ» |
xor | Логическое «ИЛИ/И» |
=, +=, -=, *=, /= | Присвоение. В отличие от C++ не возвращают присвоенного значения. |
Операторы &&, ||, ! и and, or, not отличаются лишь приоритетом выполнения и в чаще всего могут использоваться произвольно на усмотрение пользователя. Словесные же идентификаторы кажутся нам более уместным и в дальнейшем разработчикам рекомендуется придерживаться именно их.
Про использование последних трех констант смотрите статью Время.
high, low, open, close, trades, volume и open_interest имеют семантику массивов, хотя в действительности ими не являются, а это функции указатели для доступа к данным торгового интервала.