11
$\begingroup$

Functions TradingChart and InteractiveTradingChart are slow even with few hundreds of candles. On TradingChart I can draw trend lines with Drawing Tools but I cannot to enlarge regions of interest without creating the new TradingChart. On InteractiveTradingChart I cannot draw lines with Drawing Tools.

Does it possible to create something like this? This video is from the trading program QUIK. If it's needed, you can receive demo version here

I know how to draw japanese candles. But what is the best way to combine them in chart? How to add scaling? I would appreciate any ideas.

Graphics[
 {
  {FaceForm[Blue], EdgeForm[Blue], Rectangle[{0, 0}, {1, 2}]},
  {Blue, Line[{{0.5, 2}, {0.5, 3}}]},
  {Blue, Line[{{0.5, 0}, {0.5, -1}}]}
  }
 ]

enter image description here

Graphics[
 {
  {FaceForm[White], EdgeForm[Blue], Rectangle[{0, 0}, {1, 2}]},
  {Blue, Line[{{0.5, 2}, {0.5, 3}}]},
  {Blue, Line[{{0.5, 0}, {0.5, -1}}]}
  }
 ]

enter image description here

UPDATE

Example of my data. This is the tick data. Format of data is {ticker, year, month, day, hour, minute, second, millisecond, price, volume}. I aggregate them in 1-minute candles.

data = Import["RTS-3.17-170224.mx"];

g1 = GroupBy[
   data[[;; , 2 ;;]],
   (#[[;; 5]] &) -> (#[[{8, 9}]] &),
   Append[Through[{First, Max, Min, Last}@#[[;; , 1]]], 
     Total@#[[;; , 2]]] &
   ];

InteractiveTradingChart[
 Tooltip[
    #,
    StringJoin[
     {
      "Date:     "  
       DateString[#[[1]], {"Year", "-", "Month", "-", "Day"}]  "\n",
      "Time:    "  DateString[#[[1]], {"Hour", ":", "Minute"}]  
       "\n",
      StringJoin[
       Riffle[Riffle[{"Open:    ", "High:     ", "Low:      ", 
          "Close:    ", "Volume:  "}, ToString /@ #[[2]]], "\n", 3]]
      }
     ]
    ] & /@ KeyValueMap[{#1, #2} &, g1][[ ;; ]]
 ,
 {
  Style["Volume", Lighter[Red, .25]],
  Style[FinancialIndicator["SimpleMovingAverage", 9], Red]
  }
 ,
 PerformanceGoal -> "Speed",
 Appearance -> "Candlestick",
 TrendStyle -> {FaceForm[White], FaceForm[Blue]},
 ChartBaseStyle -> Directive[EdgeForm[Blue], Blue, Thin],
 GridLines -> {Automatic, Range[50000, 150000, 100]},
 GridLinesStyle -> Directive[Dotted, Gray],
 BarSpacing -> Medium,
 ImageSize -> {800, 400}
 ]

This InteractiveTradingChart is very slow for me. And this is only one trading day with only 800 candles!

enter image description here

I WANT:

  1. trading chart should be fast with 4000 candles;

  2. the same color scheme (white up-candles, blue down-candles, red volume), tooltips support

  3. ability to scaling chart up and down (see video at 5-9 seconds)

  4. ability to draw trading channels (see video at 11-17 seconds). This feature is very important for me. And this channels should keep their positions when I will scale chart up/down or will increase/decrease region of interest via IntervalSlider (slider is not necessary should be the same as InteractiveTradingChart has).

  5. moving average. This is optional feature. If it will be, than cool.

$\endgroup$
8
+250
$\begingroup$

So here's some stuff to get you started:

We'll start with a more general candle image function:

candle[width : _Integer : 5, heightFactor : _Real : 1][{start_, end_, 
    x_: 0}] :=
  candle[{start, end, x}, width, heightFactor];
candle[{start_, end_, x_: 0},
  width : _Integer : 5,
  heightFactor : _Real : 1.
  ] :=
 Graphics[
  {
   Blue,
   Line[{{x, end}, {x, start}}],
   If[start < end,
    {
     Rectangle[
      {x - width/2, end + Min@{100, .1*(start - end)}},
      {x + width/2, start - Min@{100, .1*(start - end)}}
      ]
     },
    {
     EdgeForm@Directive[Thin, Blue], FaceForm@White,
     Rectangle[
      {x - width/2, start + Min@{100, .1*(end - start)}},
      {x + width/2, end - Min@{100, .1*(end - start)}}
      ]
     }
    ]
   },
  PlotRange -> {{x - width/2, x + width/2},
    Min@{start, end} + {0, Abs[heightFactor*(start - end)]}},
  ImageSize -> {width, Abs[heightFactor*(start - end)]},
  AspectRatio -> Abs[heightFactor*(start - end)/width]
  ]

Pull a test dataset out of what you provided:

ds = First /@ Values@g1;

Make a function for plotting these candles (we'll see why later):

candlePlot[k_: 10] :=
  Show[
   MapIndexed[
    candle[Join[##], IntegerPart@k, 2.] &,
    Partition[Riffle[ds, Append[Rest@ds, Last@ds]], 2]
    ],
   PlotRange -> {{1, Length@ds}, MinMax@ds},
   ImageSize -> {450, 300},
   AspectRatio -> Full
   ];

Get a moving average line:

linePlot =
  ListLinePlot[
   MovingAverage[ds, 10],
   PlotStyle -> Directive[Thickness[.002], Red]
   ];

Now is where our possible paths branch:

That candlePlot is pretty fast, but not blazing fast, so we can provide a faster version of it as a single line for our dynamic edits:

candlePlotFast =
  ListLinePlot[
   ds,
   PlotStyle -> Directive[Thickness[.01], Blue]
   ];

Then combine these for a slow and fast version:

slowPlot =
  Show[
   candlePlot,
   linePlot,
   ImageSize -> {450, 300},
   AspectRatio -> Full
   ];
fastPlot =
  Show[
   candlePlotFast,
   linePlot,
   ImageSize -> {450, 300},
   AspectRatio -> Full
   ];

Then we'll just stick this all together with appropriate axis scaling and shifting (plus that line drawing thing you wanted):

DynamicModule[{
  basePlot = slowPlot,
  slowPlot = slowPlot,
  fastPlot =(*slowPlot*)fastPlot,
  yshift = 0, xshift = 0,
  rescaleCandlePlot,
  lines = {},
  tmpPos, tmpLine = Graphics[],
  xrange, yrange,
  mouseCoords
  },
 {xrange, yrange} = PlotRange@basePlot;
 mouseCoords =
  Replace[MousePosition["GraphicsScaled"], {
     {x_, y_} :>
      {
       Rescale[x, {0, 1}, xshift + xrange],
       Rescale[y, {0, 1}, yshift + yrange]
       }
     }] &;
 "
 Note that we're just going off of the xrange scaling.
 I'm using the /160 factor because a width of 5 looked nice with 805 \
points initially.
 ";
 rescaleCandlePlot[] :=
  (
   slowPlot =
    Show[
     candlePlot[Max@{Abs[#[[1]] - #[[2]]]/160, 1.} &@xrange],
     linePlot,
     ImageSize -> {450, 300},
     AspectRatio -> Full
     ]
   );
 Grid@{
   {
    EventHandler[
     Dynamic@
      Show[
       basePlot,
       tmpLine,
       Graphics[{Pink, lines}],
       ImageSize -> {450, 300},
       Axes -> True,
       AspectRatio -> Full,
       PlotRange -> {xshift + xrange, yshift + yrange},
       AxesOrigin -> First /@ {xshift + xrange, yshift + yrange}
       ], {
      "MouseDown" :>
       With[{p = mouseCoords[]
         },
        tmpLine = Graphics[];
        If[NumberQ /@ p,
         tmpPos = p,
         tmpPos = None
         ]
        ],
      "MouseDragged" :>
       (basePlot = fastPlot;
        With[{line =
           {
            If[Not@ValueQ@tmpPos, tmpPos = mouseCoords[], tmpPos],
            mouseCoords[]
            }
          },
         If[AllTrue[line, AllTrue[NumericQ]],
          tmpLine = Graphics@Line@line,
          tmpLine = Graphics[]
          ]
         ]
        ),
      "MouseUp" :>
       With[{line =
          {
           If[Not@ValueQ@tmpPos, tmpPos = mouseCoords[], tmpPos],
           mouseCoords[]
           }
         },
        basePlot = slowPlot;
        tmpLine = Graphics[];
        tmpPos =.;
        If[AllTrue[line, AllTrue[NumericQ]],
         AppendTo[lines, Line@line]
         ]
        ]
      }
     ],
    IntervalSlider[
     Dynamic[yrange,
      {
       basePlot = fastPlot; &,
       yrange = #; &,
       basePlot = slowPlot; &
       }], yrange,
     Appearance -> "Vertical"],
    Slider[
     Dynamic[yshift,
      {
       basePlot = fastPlot; &,
       yshift = #; &,
       basePlot = slowPlot; &
       }],
     {-1, 1}*Abs[#[[2]] - #[[1]]] &@yrange,
     Appearance -> "Vertical"
     ]},
   {IntervalSlider[
     Dynamic[xrange,
      {
       basePlot = fastPlot; &,
       xrange = #; &,
       (
         rescaleCandlePlot[];
         basePlot = slowPlot;
         ) &
       }],
     xrange]
    },
   {Slider[
     Dynamic[xshift,
      {
       basePlot = fastPlot; &,
       xshift = #; &,
       basePlot = slowPlot; &
       }],
     {-1, 1}*Abs[#[[2]] - #[[1]]] &@xrange
     ]},
   {,
    Button["Print Trend Lines",
     Print@lines
     ]}
   }
 ]

By changing the candle width when the xrange changes we can ensure a consistent candle appearance.

Note that you can improve the quality here by changing that fastPlot assignment in the DynamicModule initialization to slowPlot (the commented out one). It'll look a better, but will be much slower to shift and draw.

Looks like this in the end (where I've done some x-shifting and scaling and drawn a trend line):

sample

Obviously this is in no means a perfect drop in for the thing you wanted, but it shows you how to go about it I think.

And if you, like Kuba, don't want to copy all of those sections, here's all the code at once:

candle[width : _Integer : 5, heightFactor : _Real : 1][{start_, end_, 
    x_: 0}] :=
  candle[{start, end, x}, width, heightFactor];
candle[{start_, end_, x_: 0},
  width : _Integer : 5,
  heightFactor : _Real : 1.
  ] :=
 Graphics[
  {
   Blue,
   Line[{{x, end}, {x, start}}],
   If[start < end,
    {
     Rectangle[
      {x - width/2, end + Min@{100, .1*(start - end)}},
      {x + width/2, start - Min@{100, .1*(start - end)}}
      ]
     },
    {
     EdgeForm@Directive[Thin, Blue], FaceForm@White,
     Rectangle[
      {x - width/2, start + Min@{100, .1*(end - start)}},
      {x + width/2, end - Min@{100, .1*(end - start)}}
      ]
     }
    ]
   },
  PlotRange -> {{x - width/2, x + width/2},
    Min@{start, end} + {0, Abs[heightFactor*(start - end)]}},
  ImageSize -> {width, Abs[heightFactor*(start - end)]},
  AspectRatio -> Abs[heightFactor*(start - end)/width]
  ]

ds = First /@ Values@g1;

candlePlot[k_: 10] :=
  Show[
   MapIndexed[
    candle[Join[##], IntegerPart@k, 2.] &,
    Partition[Riffle[ds, Append[Rest@ds, Last@ds]], 2]
    ],
   PlotRange -> {{1, Length@ds}, MinMax@ds},
   ImageSize -> {450, 300},
   AspectRatio -> Full
   ];
candlePlotFast =
  ListLinePlot[
   ds,
   PlotStyle -> Directive[Thickness[.01], Blue]
   ];
linePlot =
  ListLinePlot[
   MovingAverage[ds, 10],
   PlotStyle -> Directive[Thickness[.002], Red]
   ];
slowPlot =
  Show[
   candlePlot[],
   linePlot,
   ImageSize -> {450, 300},
   AspectRatio -> Full
   ];
fastPlot =
  Show[
   candlePlotFast,
   linePlot,
   ImageSize -> {450, 300},
   AspectRatio -> Full
   ];

DynamicModule[{
  basePlot = slowPlot,
  slowPlot = slowPlot,
  fastPlot =(*slowPlot*)fastPlot,
  yshift = 0, xshift = 0,
  rescaleCandlePlot,
  lines = {},
  tmpPos, tmpLine = Graphics[],
  xrange, yrange,
  mouseCoords
  },
 {xrange, yrange} = PlotRange@basePlot;
 mouseCoords =
  Replace[MousePosition["GraphicsScaled"], {
     {x_, y_} :>
      {
       Rescale[x, {0, 1}, xshift + xrange],
       Rescale[y, {0, 1}, yshift + yrange]
       }
     }] &;
 "
 Note that we're just going off of the xrange scaling.
 I'm using the /160 factor because a width of 5 looked nice with 805 \
points initially.
 ";
 rescaleCandlePlot[] :=
  (
   slowPlot =
    Show[
     candlePlot[Max@{Abs[#[[1]] - #[[2]]]/160, 1.} &@xrange],
     linePlot,
     ImageSize -> {450, 300},
     AspectRatio -> Full
     ]
   );
 Grid@{
   {
    EventHandler[
     Dynamic@
      Show[
       basePlot,
       tmpLine,
       Graphics[{Pink, lines}],
       ImageSize -> {450, 300},
       Axes -> True,
       AspectRatio -> Full,
       PlotRange -> {xshift + xrange, yshift + yrange},
       AxesOrigin -> First /@ {xshift + xrange, yshift + yrange}
       ], {
      "MouseDown" :>
       With[{p = mouseCoords[]
         },
        tmpLine = Graphics[];
        If[NumberQ /@ p,
         tmpPos = p,
         tmpPos = None
         ]
        ],
      "MouseDragged" :>
       (basePlot = fastPlot;
        With[{line =
           {
            If[Not@ValueQ@tmpPos, tmpPos = mouseCoords[], tmpPos],
            mouseCoords[]
            }
          },
         If[AllTrue[line, AllTrue[NumericQ]],
          tmpLine = Graphics@Line@line,
          tmpLine = Graphics[]
          ]
         ]
        ),
      "MouseUp" :>
       With[{line =
          {
           If[Not@ValueQ@tmpPos, tmpPos = mouseCoords[], tmpPos],
           mouseCoords[]
           }
         },
        basePlot = slowPlot;
        tmpLine = Graphics[];
        tmpPos =.;
        If[AllTrue[line, AllTrue[NumericQ]],
         AppendTo[lines, Line@line]
         ]
        ]
      }
     ],
    IntervalSlider[
     Dynamic[yrange,
      {
       basePlot = fastPlot; &,
       yrange = #; &,
       basePlot = slowPlot; &
       }], yrange,
     Appearance -> "Vertical"],
    Slider[
     Dynamic[yshift,
      {
       basePlot = fastPlot; &,
       yshift = #; &,
       basePlot = slowPlot; &
       }],
     {-1, 1}*Abs[#[[2]] - #[[1]]] &@yrange,
     Appearance -> "Vertical"
     ]},
   {IntervalSlider[
     Dynamic[xrange,
      {
       basePlot = fastPlot; &,
       xrange = #; &,
       (
         rescaleCandlePlot[];
         basePlot = slowPlot;
         ) &
       }],
     xrange]
    },
   {Slider[
     Dynamic[xshift,
      {
       basePlot = fastPlot; &,
       xshift = #; &,
       basePlot = slowPlot; &
       }],
     {-1, 1}*Abs[#[[2]] - #[[1]]] &@xrange
     ]},
   {,
    Button["Print Trend Lines",
     Print@lines
     ]}
   }
 ]
$\endgroup$
  • $\begingroup$ @MB1965 Thank you very much. First +1 (and only one at this moment) was mine. I will try to understand your code and modify it when I will have time. Candles overlaps if to enlarge region of interest. But your realization is very close to what I want. $\endgroup$ – Alexey Golyshev Mar 14 '17 at 10:43
  • $\begingroup$ @MB1965 Can you fix your candles? I don't see where the problem is. I have understood the main ideas in your code. See my answer. I had no experience with EventHandler before and had very small experience with Dynamic so it was very interesting experiment. $\endgroup$ – Alexey Golyshev Mar 14 '17 at 18:14
  • $\begingroup$ @AlexeyGolyshev I'll give it a look. What's the behavior you would like them to exhibit? $\endgroup$ – b3m2a1 Mar 14 '17 at 18:17
  • $\begingroup$ @AlexeyGolyshev I fixed the candle issue by regenerating that plot when x gets scaled. I also added axes shifting because I feel that is often crucial when you have scaled your plot a bunch. $\endgroup$ – b3m2a1 Mar 14 '17 at 18:44
2
$\begingroup$

Inspired by MB1965's great answer.

For scaling use left and right mouse buttons.

data = Import["RTS-3.17-170224.mx"];

g1 = GroupBy[
     data[[;; , 2 ;;]],
     (#[[;; 5]] &) -> (#[[{8, 9}]] &), 
     Append[Through[{First, Max, Min, Last}@#[[;; , 1]]], 
       Total@#[[;; , 2]]] &
     ] // Values // #[[;; , ;; 4]] &;

ClearAll[candle];
candle[i_Integer, {open_, high_, low_, close_}, spacing_Real: 0.25] := 
  List[
   (* body of candle *)
   {
    If[open < close, FaceForm[White], FaceForm[Blue]], EdgeForm[Blue],
    Rectangle[{i, open}, {i + 1 - spacing, close}]
    },
   (* upper shadow of candle *)
   {
    Blue,
    Line[{{i + (1 - spacing)/2, 
       If[open < close, close, open]}, {i + (1 - spacing)/2, high}}]
    },
   (* lower shadow of candle *)
   {
    Blue,
    Line[{{i + (1 - spacing)/2, 
       If[open < close, open, close]}, {i + (1 - spacing)/2, low}}]
    }
   ];

ClearAll[indexed];
indexed[data_List] := MapIndexed[{First@#2, #1} &, data];

bigChart = 
  candle @@@ (indexed@g1[[ ;; ]]) // 
   Graphics[#, Frame -> True, ImageSize -> {768, 400}, 
     AspectRatio -> Full] &;

DynamicModule[
 {k = 2, xrange},
 Grid[{
   {
    EventHandler[
     Show[
      {
       bigChart
       }
      ,
      PlotRange -> {Dynamic[xrange], 
        Dynamic[MinMax@g1[[xrange[[1]] ;; xrange[[2]]]]]},
      ImageSize -> {768, 400},
      AspectRatio -> Dynamic[1/k]
      ]
     ,
     {
      {"MouseClicked", 1} :> k++,
      {"MouseClicked", 2} :> If[k > 2, k--]
      }
     ]
    }
   ,
   {
    IntervalSlider[
     Dynamic[xrange], {1, Length@g1, 1},
     MinIntervalSize -> 59, Method -> "Stop", ImageSize -> {768, 20}
     ]
    }
   }]
 ]

enter image description here

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.