Уровни. Определение важности

 

Есть задача про уровни. Интересно кто как решал подобное и что думает.

В советнике модуль уровней работает следующим образом:

1. создаёт список всех найденных уровней за заданный период истории 

здесь речь про уровни на основе фракталов и горизонтальные, но тоже самое можно отнести к уровням по профилю объемов, и к наклонным уровням.

Т.е. берет все фракталы и для каждого пишет горизонтальный уровень.

2. уровни которые близко друг к другу объединяет в зону

3. Считает для каждого уровня с момента его появления (самого старого фрактала) следующие количественные показатели (на примере сопротивления)

Касания. 0 < (нижняя цена уровня - Макс цена бара)  < m_thresold_touch

Тесты.  Close previous bar < нижняя цена уровня  &&  High >= нижняя цена уровня 

Пробои. Close previous bar <= верхняя цена уровня  && Close > верхняя цена уровня

Ложные Пробои. Предыдущий пробой противоположного направления произошёл на ранее Х баров назад. 

4. По мере работы и поступления новых баров обновляет весь список уровней по новым фракталам и их кол-ые показатели.

Идея следующая - найти по максимуму все что может считаться уровнем и на основе определенных критериев отсеять лишние/неактуальные.

Задача 1 - Проранжировать Уровни по Важности-Силе и Отсеять  лишние:)

Как лучше-правильнее задать критерий важности. У меня пока в голову приходят простейшие варианты типа

1) Max ( (Касания + Тесты)/Пробои)

2) Max ( (Касания + Тесты + Ложняки)/Пробои) 

3) Max ( (Тесты + Ложняки)/Пробои) 

Может есть идеи лучше?

Замечу - что понятно что есть варианты определения более сильных уровней если изменять пар-ры фракталов (увеличивая период), а интересует именно критерий для уровней от одного фрактала, которых собрана куча по максимуму.

 
/* iPulsar  Отображает количество периодов размером Scale(по умолчанию - дней), 
            на протяжении которых не были пробиты текущие значения High и Low. 
            Условно, характеризует "силу" тестируемых уровней. 
            Используемые фильтры: 
            1. Фильтр уровня горизонта ретроспективы, LevelFilter . При горизонте, 
            меньшем заданного числа периодов Scale, значение индикатора не отображается.
            2. Фильтр значимости сигнала (значения индикатора),SignalFilter . 
            Отображаются только те значения индикатора, которые "заглядывают" 
            в прошлое не менее, чем на заданное число баров глубже, чем предыдущее 
            отображенное значение. */
#property   copyright "Copyright 2012, 2015 Тарабанов А.В."
#property   link      "alextar@bk.ru"
#property   strict
#property   indicator_separate_window  // Атрибуты индикатора
#property   indicator_buffers 2
#property   indicator_color1 Magenta   // Атрибуты буферов
#property   indicator_width1 2
#property   indicator_color2 Teal
#property   indicator_width2 2
#define     Version  "iPulsar_v4"      // Константы
#define     Zero     0.00000001
bool        IsHighReported=false,      // Флаги срабатывания алертов
            IsLowReported =false;
int         Hval=0,Lval=0,             // Предыдущие значения
            Signals=0;                 // Количество сформированных сигналов
datetime    HT=0,LT=0,                 // Время предыдущих значений
            BarTime=0;                 // Время начала текущего бара
double      HP[],LP[],                 // Буферы
            Periods=0;                 // Число баров в единице шкалы
//--- Внешние переменные: 
extern bool    SetSignalFilter=true;   // Фильтровать сигнал?
extern double  LevelFilter    =10,     // Горизонт ретроспективы, Scales
               SignalFilter   =-5;     // Значимость сигнала,Bars(Scales при<0)
extern int     Scale          =1440,   // 
               ScaleDigits    =0;      // 
//+------------------------------------------------------------------+
int init(){
   IndicatorShortName(Version);        // Атрибуты индикатора
   IndicatorDigits(ScaleDigits);
   SetIndexLabel(0,"High");            // Атрибуты буферов
   SetIndexStyle(0,DRAW_HISTOGRAM);
   SetIndexBuffer(0,HP);
   SetIndexLabel(1,"Low");
   SetIndexStyle(1,DRAW_HISTOGRAM);
   SetIndexBuffer(1,LP);
   if(Scale<0) Scale=-Scale;           // Контроль внешних переменных
   if(Scale==0) Scale=Period();
   if(ScaleDigits<0) ScaleDigits=-ScaleDigits;
   if(ScaleDigits>Digits()) ScaleDigits=Digits();
   Periods=Scale/Period();
   return(0);
}
//+------------------------------------------------------------------+
int start(){
   double H,L,HB,LB,dSF,
   dLF=LevelFilter*Periods+0.5;
   bool HD,LD;
   int LF=(int)dLF,                    // Горизонт ретроспективы, Bars
       SF,                             // Значимость сигнала, Bars
       i,k,Hbar=0,Lbar=0,History=Bars-1;
   Comment("History="+History);
   if( SignalFilter > -Zero ) dSF=SignalFilter+0.5;
   else dSF=-SignalFilter*Periods+0.5;
   SF=(int)dSF;
   if( HT > 0 ) Hbar=iBarShift(NULL,0,HT);
   if( LT > 0 ) Lbar=iBarShift(NULL,0,LT);
   int j=History-IndicatorCounted();   // Рассматриваемые бары
   while( j >= 0 ){
      H =High[j];
      L =Low[j];
      HD=false;                        // Флаги обнаружения пробоев
      LD=false;
      HB=0;                            // Число баров до пробоя High
      LB=0;                            // Число баров до пробоя Low
      i =j;
      //--- Поиск пробоев
      while( i < History ){
         if( HD && LD ) break;         // Пробои найдены
         i++;
         k=i-j-1;                      // Текущая глубина поиска
         if( !HD && High[i]-H > Zero ){
            HD=true;                   // Пробой High
            //--- Фильтрация уровня
            if( k >= LF ){
               if( i == History || !SetSignalFilter ){
                  HB=k;
                  Signals++;
                  if( j < Lbar ){
                     Lbar=0;           // Забыть предыдущий оппозитный сигнал
                     Lval=0;
                     LT  =0;
                  }
               }
               else HB=Signal(j,k,SF,Hbar,Hval,HT,Lbar,Lval,LT);
            }
         }
         if( !LD && L-Low[i] > Zero ){
            LD=true;                   // Пробой Low
            //--- Фильтрация уровня
            if( k  >= LF ){
               if( i == History || !SetSignalFilter ){
                  LB=k;
                  Signals++;
                  if( j < Hbar ){
                     Hbar=0;           // Забыть предыдущий оппозитный сигнал
                     Hval=0;
                     HT  =0;
                  }
               }
               else LB=Signal(j,k,SF,Lbar,Lval,LT,Hbar,Hval,HT);
            }
         }
      }  // i
      //--- Контроль обнаружения пробоев
       if( i == History || !SetSignalFilter ){          
         if( !HD ){
            HB=History-j-1;
            Signals++;
         }
         if( !LD ){
            LB=History-j-1;
            Signals++;
         }
      }
      //--- Приведение к заданной шкале и отображение:
      HP[j]= NormalizeDouble(HB/Periods,ScaleDigits);
      LP[j]=-NormalizeDouble(LB/Periods,ScaleDigits);
      if( !j ){           // Только в реальном времени
         if( IsNewBar() ){// Только на первом тике бара
            IsHighReported=false;
            IsLowReported =false;
         }
         //--- Не более, чем по одному алерту для каждого направления за бар:
         if( !IsHighReported && HP[j] > Zero ){
            IsHighReported=true;
            Alert(Symbol(),", ",Period(),": пробит максимум за ",DoubleToStr(HP[j],ScaleDigits),"*",Scale);
         }
         if( !IsLowReported && LP[j] < -Zero ){
            IsLowReported=true;
            Alert(Symbol(),", ",Period(),": пробит минимум  за ",DoubleToStr(-LP[j],ScaleDigits),"*",Scale);
         }
      }
      j--;
   }
   return(0);
}
//+------------------------------------------------------------------+
//| Определение значимости сигнала                                   |
//+------------------------------------------------------------------+
double Signal(int       Bar1,          // Текущий бар
              int       Val1,          // Текущее значение, Bars
              int       F,             // Значимость сигнала, Bars
              int&      Bar0,          // Предыдущий бар
              int&      Val0,          // Предыдущее значение, Bars
              datetime& T,             // Предыдущее время
              int&      contrBar,      // Предыдущий оппозитный бар
              int&      contrVal,      // Предыдущее оппозитное значение
              datetime& contrT){       // Предыдущее оппозитное время
   double S=0;                         // Значение текущего сигнала
   int    I=Val1-Val0-Bar0+Bar1;       // Преобладание текущего сигнала
   if( I >= F ){                       // Есть преобладающий сигнал
      Signals++;
      S   =Val1;                       // Запомнить параметры сигнала
      Bar0=Bar1;
      Val0=Val1;
      T   =Time[Bar1];
      if( Bar1 < contrBar ){
         contrBar=0;                   // Забыть предыдущий оппозитный сигнал
         contrVal=0;
         contrT  =0;
      }
   }
   return(S);
}
//+------------------------------------------------------------------+
bool IsNewBar(){                       // Регистрация нового бара
   bool NewBar=false;                  // Нового бара нет
   if( BarTime != Time[0] ){
      BarTime=Time[0];                 // Теперь время такое
      NewBar =true;                    // Поймался новый бар
   }
   return(NewBar);
}

В CodeBase пока не обновил. Этот правильный. 

 

в МТ5 не проверял iFractals ,

а в МТ4 (делал несколько месяцев назад) - в МТ4 фрактал  iFractals может перерисовывать точно на Н1 (выставлял отложки по фракталу, а в режиме виз.тестирования обнаружил, что некоторые стрелки-фракталы могут исчезать)

т.е. искать уровни по фракталам, ну как бы очень спорно, по моему мнению - могут перерисовывать и в добавок зависит от ТФ, ну и итог получите не формализованную задачу, которая может в тестере стратегий показать не плохой результат ;)


если серьезно ищете по уровням, то или ЗигЗаг, ну или мне нравится графики Ренко - там сразу будут видны Ваши "Пробои" и "Ложные Пробои" - мини тренды и пила на Ренко соответственно, заодно уйдете от задачи "произошёл на ранее Х баров назад" - хотя может именно в ней и смысл?


ЗЫ: в КБ есть хороший ЗЗ с фракталами, там по крайней мере фракталы точно не перерисовывает, да и алгоритм автор в комментариях подписал, но под МТ4 код  https://www.mql5.com/ru/code/10041

 
ight "Copyright 2012, Тарабанов А.В."
#property link      "alextar@bk.ru"
#property indicator_chart_window                   // Индикатор в главном окне
#property indicator_buffers 4
#property indicator_width1 2                       // Толщина зигзага
#property indicator_color3 Green                   // Нижние вершины
#property indicator_width3 3
#property indicator_color4 Red                     // Верхние вершины
#property indicator_width4 3
double         Min[]       , Max[],                // Изломы зигзага
               Bottom[]    , Top[];                // Вершины
// Константы
#define  Version     "iSFZZ"                       // Версия
#define  Zero        0.00000001                    // Точность определения нуля
// Глобальные переменные
double         LastBottom  , LastTop;              // Предыдущие значения
int            iLastBottom , iLastTop;             //    и их бары
datetime       tLastBottom , tLastTop;             // Время предыдущих значений
bool           LastIsTop   , LastIsBottom;         // Предыдущий пик/впадина
extern int     БаровЛевееВершины    =119,          // Внешние переменные
               БаровПравееВершины   =1,
               СимволНижнейВершины  =161,
               СимволВерхнейВершины =161;
extern bool    ОтображатьЗигЗаг     =true,
               ОтображатьВершины    =true;
//+------------------------------------------------------------------+
int init(){
   int DrawFractals=DRAW_NONE, DrawZigZag=DRAW_NONE;
   if( ОтображатьВершины ) DrawFractals=DRAW_ARROW;
   if( ОтображатьЗигЗаг  ) DrawZigZag  =DRAW_ZIGZAG;
   // Атрибуты буферов
   SetIndexLabel(0,"Max");
   SetIndexBuffer(0,Max);
   SetIndexStyle(0,DrawZigZag);
   SetIndexEmptyValue(0,0);
   SetIndexLabel(1,"Min");
   SetIndexBuffer(1,Min);
   SetIndexStyle(1,DrawZigZag);
   SetIndexEmptyValue(1,0);
   SetIndexLabel(2,"Bottom");
   SetIndexBuffer(2,Bottom);
   SetIndexStyle(2,DrawFractals);
   SetIndexArrow(2,СимволНижнейВершины);
   SetIndexEmptyValue(2,0);
   SetIndexLabel(3,"Top");
   SetIndexBuffer(3,Top);
   SetIndexStyle(3,DrawFractals);
   SetIndexArrow(3,СимволВерхнейВершины);
   SetIndexEmptyValue(3,0);
   if( БаровЛевееВершины <1 ) БаровЛевееВершины=1; // Контроль значений
   if( БаровПравееВершины <1 ) БаровПравееВершины=1;
   LastBottom  =0;
   LastTop     =0;
   iLastBottom =0;
   iLastTop    =0;
   tLastBottom =0;
   tLastTop    =0;
   LastIsTop   =false;
   LastIsBottom=false;
   return(0);
}
//+------------------------------------------------------------------+
int start(){
   int i, History=Bars-1, BarsLost=History-IndicatorCounted();
   if( BarsLost<History ){
      if( BarsLost<1 ) return(0);                  // Не повторять на том-же баре
      i=BarsLost+БаровПравееВершины+1;             // Число баров пересчета
   }
   else i=History-БаровЛевееВершины;               // Просмотр на всей истории
   if( tLastTop>0 ) iLastTop=iBarShift(NULL,0,tLastTop);
   if( tLastBottom>0 ) iLastBottom=iBarShift(NULL,0,tLastBottom);
   while( i>БаровПравееВершины ){                  // Перебор слева направо
      fSoftFractals(i,Bottom,Top,БаровЛевееВершины,БаровПравееВершины);
      if( Top[i]>Zero ){                           // Выбор вершины зигзага
         if( Bottom[i]>Zero ){                     // Top и Bottom
            if( LastIsTop || LastIsBottom ){       // Не начало зигзага
               Min[i]     =Bottom[i];              // Вертикальное колено
               Max[i]     =Top[i];
               LastBottom =Bottom[i];              // Запомнить Top и Bottom
               iLastBottom=i;
               LastTop    =Top[i];
               iLastTop   =i;
         }  }
         else{                                     // Top
            if( !LastIsTop ){                      // LastIsBottom, или
               Max[i]      =Top[i];                //    начало зигзага
               LastIsTop   =true;
               LastIsBottom=false; 
               LastTop     =Top[i];                // Запомнить Top
               iLastTop    =i;
            }
            else{                                  // LastIsTop
               if( Top[i]-LastTop>-Zero ){         // Перерисовка
                  Max[iLastTop]=0;
                  Max[i]       =Top[i];
                  LastTop      =Top[i];            // Запомнить Top
                  iLastTop     =i;
      }  }  }  }
      else{
         if( Bottom[i]>Zero ){                     // Bottom
            if( !LastIsBottom ){                   // LastIsTop, или
               Min[i]      =Bottom[i];             //    начало зигзага
               LastIsTop   =false;
               LastIsBottom=true; 
               LastBottom  =Bottom[i];             // Запомнить Bottom
               iLastBottom =i;
            }
            else{                                  // LastIsBottom
               if( LastBottom-Bottom[i]>-Zero ){   // Перерисовка
                  Min[iLastBottom]=0;
                  Min[i]          =Bottom[i];
                  LastBottom      =Bottom[i];      // Запомнить Bottom
                  iLastBottom     =i;
      }   }  }  }
      i--;
   }
   if( iLastTop>0 ) tLastTop=Time[iLastTop];       // Время крайних изломов
   if( iLastBottom>0 ) tLastBottom=Time[iLastBottom];
   return(0);
}
//+------------------------------------------------------------------+
void fSoftFractals(int i                           // Текущий бар
                  ,double& B[],double& T[]         // Буферные массивы фракталов
                  ,int Left=1,int Right=1){        // Размерность фракталов
   double C[];
   int dim=ArrayResize(C,Left+1+Right), j;
   j=0;          // Поиск нижнего фрактала
   B[i]=0;
   while( j < dim ){
      C[j]=Low[j+i-Right];
      j++;
   }
   if( C[Right+1]-C[Right]>-Zero
    && C[Right-1]-C[Right]>-Zero ) {
      B[i]=C[Right];                         // Локальный минимум
      j=1;
      while ( j < dim ) {
         if( ( j<Right && B[i]-C[j-1]>Zero )
          || ( j>Right && B[i]-C[j]  >Zero ) ) {
            B[i]=0;                          // Нет фрактала
            break;
         }
         j++;
   }  }
   j=0;             // Поиск верхнего фрактала
   T[i]=0;
   while( j<dim ){
      C[j]=High[j+i-Right];
      j++;
   }
   if( C[Right]-C[Right+1]>-Zero
    && C[Right]-C[Right-1]>-Zero ) {
      T[i]=C[Right];                            // Локальный максимум
      j=1;
      while ( j < dim ) {
         if( ( j<Right && C[j-1]-T[i]>Zero )
          || ( j>Right && C[j]  -T[i]>Zero ) ) {
            T[i]=0;                             // Нет фрактала
            break;
         }
         j++;
   }  }
   return;
}
 
Aleksey Mavrin:

1. создаёт список всех найденных уровней за заданный период истории 


Извините, не удержуть : TotalLevels=(Highest-Lowest)/_Point

то есть для любого уровня можно придумать (особенно потом уже) почему он супер-пупер-важен

 
Igor Makanu:

в МТ5 не проверял iFractals ,

а в МТ4 (делал несколько месяцев назад) - в МТ4 фрактал  iFractals может перерисовывать точно на Н1 (выставлял отложки по фракталу, а в режиме виз.тестирования обнаружил, что некоторые стрелки-фракталы могут исчезать)

т.е. искать уровни по фракталам, ну как бы очень спорно, по моему мнению - могут перерисовывать и в добавок зависит от ТФ, ну и итог получите не формализованную задачу, которая может в тестере стратегий показать не плохой результат ;)


если серьезно ищете по уровням, то или ЗигЗаг, ну или мне нравится графики Ренко - там сразу будут видны Ваши "Пробои" и "Ложные Пробои" - мини тренды и пила на Ренко соответственно, заодно уйдете от задачи "произошёл на ранее Х баров назад" - хотя может именно в ней и смысл?


ЗЫ: в КБ есть хороший ЗЗ с фракталами, там по крайней мере фракталы точно не перерисовывает, да и алгоритм автор в комментариях подписал, но под МТ4 код  https://www.mql5.com/ru/code/10041

Алексей Тарабанов:
ight "Copyright 2012, Тарабанов А.В."

Игорь, Алексей, я всё знаю о фракталах и зигзагах в плане индикаторов, прежде чем приступить к написанию своих проектов я изучил их опыт по КБ и маркету и др. и изучил немало мат.части.

Вы думаете я не учёл эти особенности и не знаю как их есть? Я специально написал что вопрос только об ранжировании полученных уже уровней. И естественно фрактал я сделал себе свой чтобы контролировать работу (он выложен если интересно), это же просто нахождение экстремумов, просто дядя Вильямс стибзил умное слово у Мандельброта )) Любой ведь экстремум на графике мы потенциально рассматриваем как уровень, не так ли? Просто какие то сразу отсеиваем, вот про это и речь.

Ренко прикольная штука, но я пока не понимаю что она может дать, чего нельзя взять с графика японских свечей, а потери информации в ней есть (время). Мне ближе свечки. Наверное это просто удобство восприятия. Хотя и в свечах потери информации. Если надо получить максимум информации, то надо скармливать поток тиков, благо производительность ПК теперь позволяет, но это другого класса задачи. Для устранения потерь информации планирую профиль объемов ещё прикрутить и пока поиграться хватит думаю)

Алексей Тарабанов:

В CodeBase пока не обновил. Этот правильный. 

Спасибо, отсюда идея про время, прошедшее с последнего действия. Вот только интересно как его интерпретировать. Скорее всего нужен логарифм, ведь 2 года или 10 лет прошло с последнего пробоя гораздо менее разницы, чем 2 дня и месяц.


Maxim Kuznetsov:

Извините, не удержуть : TotalLevels=(Highest-Lowest)/_Point

то есть для любого уровня можно придумать (особенно потом уже) почему он супер-пупер-важен

Узко мыслите, между каждыми двумя _Point цена еще проходит бесконечное количество уровней по умам людей и зодиакальным созвездиям )))

 
Aleksey Mavrin:

Я специально написал что вопрос только об ранжировании полученных уже уровней.

все равно результаты оценки, от методики определения уровней будут зависеть

ну раз фракталы, тогда фракталы

не знаю других способов определения важности, кроме как посчитать, выгружайте цены фракталов в Эксель по указанным критериям отбора, каких совпадений больше, те и важнее будут


ЗЫ: по моему быстрее написать советника и оптимизатором прогнать все варианты

 
Igor Makanu:

все равно результаты оценки, от методики определения уровней будут зависеть

ну раз фракталы, тогда фракталы

не знаю других способов определения важности, кроме как посчитать, выгружайте цены фракталов в Эксель по указанным критериям отбора, каких совпадений больше, те и важнее будут


ЗЫ: по моему быстрее написать советника и оптимизатором прогнать все варианты

Не совсем понял. По каким критериям? Оптимизировать по какому показателю?

Совпадения двух фракталов (с указанным допуском в п.) я объединяю в один уровень-зону, при этом кол-во тестов у него уже минимум два, и т.д.

К слову, про совпадения верно мыслите я тоже сразу об этом подумал. чем больше экстремумов в районе какой то цены, тем важнее наверное этот уровень.

При этом если посмотреть на кол-во пробоев и если оно гораздо больше тестов (фракталов), то такой уровень скорее мнимый, хоть у него и много фракталов. Т.е. за этот период цена и отскакивала от него, но гораздо чаще проходила его как масло, понимаете?

У меня в итоге есть динамический массив уровней, который я могу сортировать по одному или нескольким условиям, например по цене, дате возникновения, датам последних событий, а вот показатель важности - думаю как, про него и вопрос.

 
Aleksey Mavrin:

Не совсем понял. По каким критериям? Оптимизировать по какому показателю?

думаю, чтобы не в Эксель анализировать, пишите ЕА, который выставляет ордер при появлении и совпадении 1,2..Х фракталов с заданной точностью в пп

и оптимизируете, но не на предмет "красивой картинки тестера" - баланс ))) , а на предмет количества ордеров - сразу посчитаете при каких параметрах больше ордеров = больше совпадений


т.е. оптите кол-во фракталов на уровне и точность оценки "попадания" в пипсах

 
Igor Makanu:

думаю, чтобы не в Эксель анализировать, пишите ЕА, который выставляет ордер при появлении и совпадении 1,2..Х фракталов с заданной точностью в пп

и оптимизируете, но не на предмет "красивой картинки тестера" - баланс ))) , а на предмет количества ордеров - сразу посчитаете при каких параметрах больше ордеров = больше совпадений


т.е. оптите кол-во фракталов на уровне и точность оценки "попадания" в пипсах

Вот сейчас вообще не понял. Зачем выставлять ордера чтобы что-то посчитать, если это можно посчитать и так? :)) 

Прочитайте мой первый пост внимательно, кол-во совпадений фракталов будет отражено в параметре тесты, если из пробоев вычесть тесты, то будет кол-во отскоков, ещё есть так называемые "касания" или по другому "подходы к уровню".

Я привел те кол-ые хар-ки,  которые просты и очевидны и на основе которых я хочу считать важность.

Вы мне объясняете как сделать то что я уже сделал, причем предлагаете какие-то кустарные методы )) или я не так понял.

 
Aleksey Mavrin:

Вы мне объясняете как сделать то что я уже сделал, причем предлагаете какие-то кустарные методы )) или я не так понял.

метод действительно кустарный, я не так давно перестал себе верить, но тестеру верю


Вот Вы пишете - вот "на основе которых я хочу считать важность." , это уже сузило значимость и достоверность исследования, т.к. возможно, Вы не все виды оценки события пробой/отбой/ложный пробой проверили, а проверили лишь то, что считали нужным проверить


И почему нужно писать ЕА и тестировать:  в первом сообщении Вы сказали "Как лучше-правильнее задать критерий важности" , что значит лучше? - лучше относительно чего то? Вы разбили свою статистику исследования на периоды по годам? по месяцам? - да хоть на 3 части всю историю - прошлое, настоящее будущее . Критерий лучше подразумевает относительно чего то

Вот поэтому и предлагаю все в тестер и там оценивать на любые вариации лучше/хуже, хоть по кол-ву ордеров, хоть по балансу 


в общем по сабжу у меня больше мыслей нет, в целом мысли вслух

Причина обращения: