Source code: mas_gui/Chart.java
1 /* Copyright 1998 - 2003: Jim Cochrane - see file forum.txt */
2
3 package mas_gui;
4
5 import java.awt.*;
6 import java.awt.event.*;
7 import java.util.Vector;
8 import java.util.Hashtable;
9 import java.util.Enumeration;
10 import java.util.Properties;
11 import java.io.*;
12 import graph.*;
13 import support.*;
14 import common.*;
15
16 class WindowSettings implements Serializable {
17 public WindowSettings(Dimension s, Point l) {
18 size_ = s;
19 location_ = l;
20 }
21
22 public Point location() { return location_; }
23 public Dimension size() { return size_; }
24
25 private Point location_;
26 private Dimension size_;
27 }
28
29 class ChartSettings implements Serializable {
30 public ChartSettings(Dimension sz, Properties printprop, Point loc,
31 Vector upperind, Vector lowerind, boolean replace_ind) {
32 size_ = sz;
33 print_properties_ = printprop;
34 location_ = loc;
35 upper_indicators_ = upperind;
36 lower_indicators_ = lowerind;
37 replace_indicators_ = replace_ind;
38 window_settings = new Hashtable();
39 }
40 public Dimension size() { return size_; }
41 public Point location() { return location_; }
42 public Properties print_properties() { return print_properties_; }
43 public Vector upper_indicators() { return upper_indicators_;}
44 public Vector lower_indicators() { return lower_indicators_;}
45 public boolean replace_indicators() { return replace_indicators_; }
46 public WindowSettings wsettings(String title) {
47 WindowSettings result = (WindowSettings) window_settings.get(title);
48 return result;
49 }
50 public void add_window_setting(WindowSettings ws, String title) {
51 window_settings.put(title, ws);
52 }
53
54 private Dimension size_;
55 private Properties print_properties_;
56 private Point location_;
57 private Vector upper_indicators_;
58 private Vector lower_indicators_;
59 private boolean replace_indicators_;
60 private Hashtable window_settings;
61 }
62
63 /** Market analysis GUI chart component */
64 public class Chart extends Frame implements Runnable, NetworkProtocol {
65 public Chart(DataSetBuilder builder, String sfname) {
66 super("Chart");
67 ++window_count;
68 data_builder = builder;
69 this_chart = this;
70 Vector _tradables = null;
71 saved_dialogs = new Vector();
72 _indicators = null;
73
74 if (sfname != null) serialize_filename = sfname;
75
76 if (window_count == 1) {
77 ChartSettings settings;
78 try {
79 FileInputStream chartfile =
80 new FileInputStream(serialize_filename);
81 ObjectInputStream ios = new ObjectInputStream(chartfile);
82 settings = (ChartSettings) ios.readObject();
83 window_settings = settings;
84 }
85 catch (IOException e) {
86 // Most likely the file hasn't been created yet - no error.
87 }
88 catch (ClassNotFoundException e) {
89 System.err.println("Class not found!" + e);
90 }
91 }
92 try {
93 if (_tradables == null) {
94 _tradables = data_builder.tradable_list();
95 if (! _tradables.isEmpty()) {
96 // Each tradable has its own period type list; but for now,
97 // just retrieve the list for the first tradable and use
98 // it for all tradables.
99 _period_types = data_builder.trading_period_type_list(
100 (String) _tradables.elementAt(0));
101 if (data_builder.connection.error_occurred()) {
102 abort(data_builder.connection.result().toString(),
103 null);
104 }
105 current_period_type = initial_period_type(_period_types);
106 } else {
107 abort("Server's list of tradables is empty.", null);
108 }
109 }
110 }
111 catch (IOException e) {
112 System.err.println("IO exception occurred: " + e + " - aborting");
113 e.printStackTrace();
114 quit(-1);
115 }
116 String s = null;
117 if (_tradables != null && _tradables.size() > 0) {
118 s = (String) _tradables.elementAt(0);
119 }
120 initialize_GUI_components(s);
121 new Thread(this).start();
122 }
123
124 // From parent Runnable - for threading
125 public void run() {
126 // null method
127 }
128
129 // List of all tradables in the server's database
130 public Vector tradables() {
131 Vector result = null;
132 try {
133 result = data_builder.tradable_list();
134 }
135 catch (IOException e) {
136 System.err.println("IO exception occurred, bye ...");
137 quit(-1);
138 }
139 return result;
140 }
141
142 // indicators
143 public Hashtable indicators() {
144 if (_indicators == null || new_indicators) {
145 new_indicators = false;
146 Vector inds_from_server = data_builder.last_indicator_list();
147 if (previous_open_interest != data_builder.open_interest() ||
148 ! Utilities.lists_match(inds_from_server,
149 old_indicators_from_server)) {
150 // The old indicators are not the same as the indicators
151 // just obtained from the server (or there are not yet
152 // any old indicators), so the indicator lists need to
153 // be rebuilt.
154 old_indicators_from_server = inds_from_server;
155 make_indicator_lists(inds_from_server);
156 }
157 previous_open_interest = data_builder.open_interest();
158 }
159 return _indicators;
160 }
161
162 private void make_indicator_lists(Vector inds_from_server) {
163 Enumeration ind_iter;
164 String s;
165 int ind_count = inds_from_server.size();
166 Hashtable valid_indicators;
167 if (ind_count > 0) {
168 valid_indicators = new Hashtable(inds_from_server.size());
169 } else {
170 valid_indicators = new Hashtable();
171 }
172 ordered_indicator_list = new Vector();
173 int i;
174 for (i = 0; i < inds_from_server.size(); ++i) {
175 Object o = inds_from_server.elementAt(i);
176 valid_indicators.put(o, new Integer(i + 1));
177 }
178 // User-selected indicators, in order:
179 ind_iter = Configuration.instance().indicator_order().elements();
180 _indicators = new Hashtable();
181 Vector special_indicators = new Vector();
182 special_indicators.addElement(No_lower_indicator);
183 special_indicators.addElement(No_upper_indicator);
184 special_indicators.addElement(Volume);
185 if (data_builder.open_interest()) {
186 special_indicators.addElement(Open_interest);
187 }
188 // Insert into _indicators all user-selected indicators that
189 // are either in the list returned by the server or are one of
190 // the special strings for no upper/lower indicator, volume,
191 // or open interest.
192 while (ind_iter.hasMoreElements()) {
193 s = (String) ind_iter.nextElement();
194 if (valid_indicators.containsKey(s)) {
195 // Add valid indicators (from the server's point of view)
196 // to both `_indicators' and `ordered_indicator_list'.
197 _indicators.put(s, valid_indicators.get(s));
198 ordered_indicator_list.addElement(s);
199 }
200 else {
201 for (i = 0; i < special_indicators.size(); ++i) {
202 if (s.equals(special_indicators.elementAt(i))) {
203 // Add special indicators only to
204 // `ordered_indicator_list'.
205 ordered_indicator_list.addElement(s);
206 special_indicators.removeElement(s);
207 break;
208 }
209 }
210 }
211 }
212 // Insert the special indicators (no-upper indicator, ...) if
213 // they aren't already there.
214 for (i = 0; i < special_indicators.size(); ++i) {
215 s = (String) special_indicators.elementAt(i);
216 if (!_indicators.containsKey(s)) {
217 _indicators.put(s, new Integer(_indicators.size() + 1));
218 ordered_indicator_list.addElement(s);
219 }
220 }
221
222 // Update current_lower_indicators and current_upper_indicators:
223 // remove any elements that are no longer valid.
224 Vector remove_list = new Vector();
225 for (ind_iter = current_lower_indicators.elements();
226 ind_iter.hasMoreElements(); ) {
227 s = (String) ind_iter.nextElement();
228 if (! ordered_indicator_list.contains(s)) {
229 remove_list.addElement(s);
230 }
231 }
232 for (i = 0; i < remove_list.size(); ++i) {
233 while (current_lower_indicators.lastIndexOf(
234 remove_list.elementAt(i)) != -1) {
235 current_lower_indicators.removeElement(
236 remove_list.elementAt(i));
237 }
238 }
239 remove_list.removeAllElements();
240 for (ind_iter = current_upper_indicators.elements();
241 ind_iter.hasMoreElements(); ) {
242 s = (String) ind_iter.nextElement();
243 if (! ordered_indicator_list.contains(s)) {
244 remove_list.addElement(s);
245 }
246 }
247 for (i = 0; i < remove_list.size(); ++i) {
248 while (current_upper_indicators.lastIndexOf(
249 remove_list.elementAt(i)) != -1) {
250 current_upper_indicators.removeElement(
251 remove_list.elementAt(i));
252 }
253 }
254 if (ma_menu_bar != null) ma_menu_bar.update_indicators();
255 }
256
257 // Indicators in user-specified order
258 public Vector ordered_indicators() {
259 if (ordered_indicator_list == null) {
260 // Force creation of ordered_indicator_list.
261 indicators();
262 }
263 return ordered_indicator_list;
264 }
265
266 // Result of last request to the server
267 public int request_result() {
268 return data_builder.request_result();
269 }
270
271 // Register `d' to save its size and location on exit with its title
272 // as a key.
273 public void register_dialog_for_save_settings(Dialog d) {
274 saved_dialogs.addElement(d);
275 }
276
277 // Settings for dialog with title `s'
278 public WindowSettings settings_for(String s) {
279 WindowSettings result = null;
280 if (window_settings != null) {
281 result = window_settings.wsettings(s);
282 }
283 return result;
284 }
285
286 // Take action when notified that period type changed.
287 void notify_period_type_changed(String new_period_type) {
288 if (! current_period_type.equals(new_period_type)) {
289 current_period_type = new_period_type;
290 period_type_change = true;
291 if (current_tradable != null) {
292 request_data(current_tradable);
293 }
294 period_type_change = false;
295 }
296 }
297
298 // Request data for the specified tradable and display it.
299 void request_data(String tradable) {
300 DataSet dataset, main_dataset;
301 Configuration conf = Configuration.instance();
302 int count;
303 String current_indicator;
304 // Don't redraw the data if it's for the same tradable as before.
305 if (period_type_change || ! tradable.equals(current_tradable)) {
306 GUI_Utilities.busy_cursor(true, this);
307 try {
308 data_builder.send_market_data_request(tradable,
309 current_period_type);
310 if (request_result() == OK) {
311 // Ensure that the indicator list is up-to-date with
312 // respect to `tradable'.
313 data_builder.send_indicator_list_request(tradable,
314 current_period_type);
315 // Force call to `indicators()' to create new indicator
316 // lists with the result of the above request.
317 new_indicators = true;
318 indicators();
319 } else {
320 if (request_result() == Invalid_symbol) {
321 handle_nonexistent_sybmol(tradable);
322 } else if (request_result() == Warning) {
323 new ErrorBox("Warning", "Error occurred retrieving " +
324 "data for " + tradable, this_chart);
325 }
326 GUI_Utilities.busy_cursor(false, this);
327 return;
328 }
329 }
330 catch (Exception e) {
331 fatal("Request to server failed: ", e);
332 }
333 //Ensure that all graph's data sets are removed. (May need to
334 //change later.)
335 main_pane.clear_main_graph();
336 main_pane.clear_indicator_graph();
337 main_dataset = data_builder.last_market_data();
338 link_with_axis(main_dataset, null);
339 main_pane.add_main_data_set(main_dataset);
340 if (! current_upper_indicators.isEmpty()) {
341 // Retrieve the data for the newly requested tradable for the
342 // upper indicators, add it to the upper graph and draw
343 // the new indicator data and the tradable data.
344 count = current_upper_indicators.size();
345 for (int i = 0; i < count; ++i) {
346 current_indicator = (String)
347 current_upper_indicators.elementAt(i);
348 try {
349 data_builder.send_indicator_data_request(((Integer)
350 indicators().get(current_indicator)).
351 intValue(), tradable, current_period_type);
352 } catch (Exception e) {
353 fatal("Exception occurred", e);
354 }
355 dataset = data_builder.last_indicator_data();
356 dataset.set_dates_needed(false);
357 dataset.set_color(
358 conf.indicator_color(current_indicator, true));
359 link_with_axis(dataset, current_indicator);
360 main_pane.add_main_data_set(dataset);
361 }
362 }
363 current_tradable = tradable;
364 set_window_title();
365 if (! current_lower_indicators.isEmpty()) {
366 // Retrieve the indicator data for the newly requested
367 // tradable for the lower indicators and draw it.
368 count = current_lower_indicators.size();
369 for (int i = 0; i < count; ++i) {
370 current_indicator = (String)
371 current_lower_indicators.elementAt(i);
372 if (current_lower_indicators.elementAt(i).equals(Volume)) {
373 // (Nothing to retrieve from server)
374 dataset = data_builder.last_volume();
375 } else if (current_lower_indicators.elementAt(i).equals(
376 Open_interest)) {
377 // (Nothing to retrieve from server)
378 dataset = data_builder.last_open_interest();
379 } else {
380 try {
381 data_builder.send_indicator_data_request(((Integer)
382 indicators().get(current_indicator)).
383 intValue(), tradable, current_period_type);
384 } catch (Exception e) {
385 fatal("Exception occurred", e);
386 }
387 dataset = data_builder.last_indicator_data();
388 }
389 if (dataset != null) {
390 dataset.set_color(
391 conf.indicator_color(current_indicator, false));
392 link_with_axis(dataset, current_indicator);
393 add_indicator_lines(dataset, current_indicator);
394 main_pane.add_indicator_data_set(dataset);
395 }
396 }
397 }
398 main_pane.repaint_graphs();
399 GUI_Utilities.busy_cursor(false, this);
400 }
401 }
402
403 // Implementation
404
405 // Add any extra lines to the indicator graph - specified in the
406 // configuration.
407 protected void add_indicator_lines(DataSet dataset, String indicator) {
408 if (current_lower_indicators.isEmpty()) {
409 return;
410 }
411
412 Vector lines;
413 double d1, d2;
414 Configuration conf = Configuration.instance();
415 lines = conf.vertical_indicator_lines_at(indicator);
416 if (lines != null && lines.size() > 0) {
417 for (int j = 0; j < lines.size(); j += 2) {
418 d1 = ((Float) lines.elementAt(j)).floatValue();
419 d2 = ((Float) lines.elementAt(j+1)).floatValue();
420 dataset.add_vline(new DoublePair(d1, d2));
421 }
422 }
423 lines = conf.horizontal_indicator_lines_at(indicator);
424 if (lines != null && lines.size() > 0) {
425 for (int j = 0; j < lines.size(); j += 2) {
426 d1 = ((Float) lines.elementAt(j)).floatValue();
427 d2 = ((Float) lines.elementAt(j+1)).floatValue();
428 dataset.add_hline(new DoublePair(d1, d2));
429 }
430 }
431 }
432
433 // Initialize components and obtain and display data for `symbol' if
434 // it is not null, etc.
435 private void initialize_GUI_components(String symbol) {
436 // Create the main scroll pane, size it, and center it.
437 main_pane = new MA_ScrollPane(_period_types,
438 MA_ScrollPane.SCROLLBARS_NEVER, this, window_settings != null?
439 window_settings.print_properties(): null);
440 if (window_settings != null && window_count == 1) {
441 main_pane.setSize(window_settings.size().width,
442 window_settings.size().height + 2);
443 setLocation(window_settings.location());
444 current_upper_indicators = window_settings.upper_indicators();
445 current_lower_indicators = window_settings.lower_indicators();
446 replace_indicators = window_settings.replace_indicators();
447 } else {
448 main_pane.setSize(800, 460);
449 current_upper_indicators = new Vector();
450 current_lower_indicators = new Vector();
451 }
452 add(main_pane, "Center");
453 if (symbol != null) {
454 if (data_builder.options().print_on_startup() &&
455 window_count == 1) {
456 print_all_charts();
457 }
458 // Show the graph of the first symbol in the selection list.
459 request_data(symbol);
460 }
461 market_selections = new MarketSelection(this);
462 ma_menu_bar = new MA_MenuBar(this, data_builder, _period_types);
463 setMenuBar(ma_menu_bar);
464
465 // Event listener for close requests
466 addWindowListener(new WindowAdapter() {
467 public void windowClosing(WindowEvent e) { close(); }
468 });
469
470 pack();
471 show();
472 }
473
474 // Set the window title using current_tradable and current_lower_indicators.
475 protected void set_window_title() {
476 if (! current_lower_indicators.isEmpty()) {
477 StringBuffer newtitle = new
478 StringBuffer (current_tradable.toUpperCase() + " - ");
479 int i;
480 for (i = 0; i < current_lower_indicators.size() - 1; ++i) {
481 newtitle.append(current_lower_indicators.elementAt(i));
482 newtitle.append(", ");
483 }
484 newtitle.append(current_lower_indicators.elementAt(i));
485 setTitle(newtitle.toString());
486 }
487 else {
488 setTitle(current_tradable.toUpperCase());
489 }
490 }
491
492 // Notify the user that the symbol chosen does not exist and then
493 // remove the symbol from the selection list.
494 private void handle_nonexistent_sybmol(String symbol) {
495 ErrorBox errorbox = new ErrorBox("",
496 "Symbol " + symbol + " is not in the database.", this_chart);
497 market_selections.remove_selection(symbol);
498 }
499
500 // Save persistent settings as a serialized file.
501 // Precondition: main_pane != null
502 protected void save_settings() {
503 if (serialize_filename != null) {
504 try {
505 FileOutputStream chartfile =
506 new FileOutputStream(serialize_filename);
507 ObjectOutputStream oos = new ObjectOutputStream(chartfile);
508 ChartSettings cs = new ChartSettings(main_pane.getSize(),
509 main_pane.print_properties, getLocation(),
510 current_upper_indicators, current_lower_indicators,
511 replace_indicators);
512 for (int i = 0; i < saved_dialogs.size(); ++i) {
513 Dialog d = (Dialog) saved_dialogs.elementAt(i);
514 WindowSettings ws = new WindowSettings(
515 d.getSize(), d.getLocation());
516 cs.add_window_setting(ws, d.getTitle());
517 }
518 oos.writeObject(cs);
519 oos.flush();
520 oos.close();
521 }
522 catch (IOException e) {
523 System.err.println("Could not save file " + serialize_filename);
524 System.err.println(e);
525 }
526 }
527 }
528
529 // Set replace_indicators to its opposite state.
530 protected void toggle_indicator_replacement() {
531 replace_indicators = ! replace_indicators;
532 }
533
534 // Add a menu item for each indicator to `imenu'.
535 protected void add_indicators(Menu imenu) {
536 MenuItem menu_item;
537 IndicatorListener listener = new IndicatorListener(this);
538 Enumeration ind_keys = ordered_indicator_list.elements();
539 for ( ; ind_keys.hasMoreElements(); ) {
540 menu_item = new MenuItem((String) ind_keys.nextElement());
541 imenu.add(menu_item);
542 menu_item.addActionListener(listener);
543 }
544 }
545
546 // Print the chart for each member of market_selections
547 protected void print_all_charts() {
548 main_pane.print(true);
549 }
550
551 /** Close a window. If this is the last open window, just quit. */
552 protected void close() {
553 if (window_count == 1) { // Close last remaining window, exit.
554 save_settings();
555 data_builder.logout(true, 0);
556 }
557 else { // More than 1 windows remain, close this one.
558 --window_count;
559 data_builder.logout(false, 0);
560 dispose();
561 }
562 }
563
564 /** Quit gracefully, sending a logout request for each open window. */
565 protected void quit(int status) {
566 if (main_pane != null) save_settings();
567 log_out_and_exit(status);
568 }
569
570 /** Log out of all sessions and exit. */
571 protected void log_out_and_exit(int status) {
572 // Log out the corresponding session for all but one window.
573 for (int i = 0; i < window_count - 1; ++i) {
574 data_builder.logout(false, 0);
575 }
576 // Log out the remaining window and exit with `status'.
577 data_builder.logout(true, status);
578 }
579
580 // Print fatal error and exit after saving settings.
581 protected void fatal(String s, Exception e) {
582 System.err.println("Fatal error: " + s);
583 if (current_tradable != null) {
584 System.err.println("Current symbol is " + current_tradable);
585 }
586 if (e != null) {
587 System.err.println("(" + e + ")");
588 e.printStackTrace();
589 }
590 System.err.println("Exiting ...");
591 quit(-1);
592 }
593
594 // Same as `fatal' except `save_settings' is not called.
595 protected void abort(String s, Exception e) {
596 System.err.println("Fatal error: " + s);
597 if (current_tradable != null) {
598 System.err.println("Current symbol is " + current_tradable);
599 }
600 if (e != null) {
601 System.err.println("(" + e + ")");
602 e.printStackTrace();
603 }
604 System.err.println("Exiting ...");
605 log_out_and_exit(-1);
606 }
607
608 // Link `d' with the appropriate indicator group, using `indicator_name'
609 // as a key. If `indicator_name' is null, the group for the main
610 // (upper) graph will be used. If `indicator_name' specifies an
611 // indicator that is not a group member, no action is taken.
612 protected void link_with_axis(DataSet d, String indicator_name) {
613 if (indicator_groups == null) {
614 indicator_groups = (IndicatorGroups)
615 Configuration.instance().indicator_groups().clone();
616 }
617 IndicatorGroup group;
618 if (indicator_name == null) {
619 indicator_name = indicator_groups.Maingroup;
620 }
621 group = indicator_groups.at(indicator_name);
622 if (group != null) {
623 group.attach_data_set(d);
624 }
625 }
626
627 private boolean vector_has(Vector v, String s) {
628 return Utilities.vector_has(v, s);
629 }
630
631 // First period type to be displayed - daily, if it exists
632 String initial_period_type(Vector types) {
633 String result = null;
634 String daily = MA_MenuBar.daily_period.toLowerCase();
635 for (int i = 0; result == null && i < types.size(); ++i) {
636 if (((String) types.elementAt(i)).toLowerCase().equals(daily)) {
637 result = (String) types.elementAt(i);
638 }
639 }
640 if (result == null) result = (String) types.elementAt(0);
641
642 return result;
643 }
644
645 protected DataSetBuilder data_builder;
646
647 private Chart this_chart;
648
649 // # of open windows - so program can exit when last one is closed
650 protected static int window_count = 0;
651
652 // Main window pane
653 MA_ScrollPane main_pane;
654
655 // The menu bar for this chart
656 MA_MenuBar ma_menu_bar;
657
658 // Valid trading period types - static for now, since it is currently
659 // hard-coded in the server
660 protected static Vector _period_types; // Vector of String
661
662 // Table of market indicators
663 protected static Hashtable _indicators; // key: String, value: Integer
664
665 // Has data_builder.send_indicator_list_request been called since the
666 // last call to `indicators'?
667 protected boolean new_indicators = true;
668
669 // Indicators in user-specified order - includes no-upper/lower,
670 // volume, and open-interest indicators
671 private static Vector ordered_indicator_list; // Vector of String
672
673 // Current selected tradable
674 protected String current_tradable;
675
676 // Current selected period type
677 protected String current_period_type;
678
679 // Has the period type just been changed?
680 protected boolean period_type_change;
681
682 // Upper indicators currently selected for display
683 protected Vector current_upper_indicators;
684
685 // Lower indicators currently selected for display
686 protected Vector current_lower_indicators;
687
688 // Should new indicator selections replace, rather than be added to,
689 // existing indicators?
690 boolean replace_indicators;
691
692 protected MarketSelection market_selections;
693
694 protected final String No_upper_indicator = "No upper indicator";
695
696 protected final String No_lower_indicator = "No lower indicator";
697
698 protected final String Volume = "Volume";
699
700 protected final String Open_interest = "Open interest";
701
702 static String serialize_filename;
703
704 static ChartSettings window_settings;
705
706 IndicatorGroups indicator_groups;
707
708 // Dialog windows registered to have their settings saved on exit
709 private Vector saved_dialogs;
710
711 // Saved result of data_builder.last_indicator_list(), used by
712 // `indicators' to compare old list with new list
713 private Vector old_indicators_from_server = null;
714
715 // Did the previously retrieved data from the server contain an
716 // open interest field?
717 private boolean previous_open_interest;
718 }