App-financeta
view release on metacpan or search on metacpan
lib/App/financeta/gui.pm view on Meta::CPAN
package App::financeta::gui;
use strict;
use warnings;
use 5.10.0;
our $VERSION = '0.16';
$VERSION = eval $VERSION;
use App::financeta::mo;
use App::financeta::utils qw(dumper log_filter get_icon_path get_file_path);
use Carp ();
use Log::Any '$log', filter => \&App::financeta::utils::log_filter;
use Try::Tiny;
use File::Spec;
use File::HomeDir;
use File::Path ();
use DateTime;
if ($^O !~ /win32/i) {
eval {
require POE;
require POE::Kernel;
POE::Kernel->import({loop => 'Prima'});
require POE::Session;
} or die "Unable to load POE::Loop::Prima";
}
use Prima qw(
Application Buttons MsgBox Calendar ComboBox Notebooks
Widget::ScrollWidget DetailedList Dialog::ColorDialog
Dialog::FileDialog Dialog::FindDialog ScrollBar
Dialog::PrintDialog Dialog::ImageDialog Dialog::FontDialog
sys::GUIException Utils
);
use Prima::Utils ();
use Capture::Tiny ();
use PDL::Lite;
use PDL::IO::Misc;
use PDL::NiceSlice;
use PDL::Graphics::Gnuplot;
use App::financeta::gui::security_wizard;
use App::financeta::gui::progress_bar;
use App::financeta::gui::editor;
use App::financeta::gui::tradereport;
use App::financeta::indicators;
use App::financeta::data;
use Scalar::Util qw(blessed);
use Browser::Open ();
use YAML::Any ();
use JSON::XS qw(encode_json);
use Template;
$PDL::doubleformat = "%0.6lf";
$| = 1;
has debug => 0;
has timezone => 'America/New_York';
has brand => __PACKAGE__;
has main => (builder => '_build_main');
has tmpdir => ( default => sub {
my $dir = $ENV{TEMP} || $ENV{TMP} || $ENV{APPDATA} if $^O =~ /Win32|Cygwin/i;
$dir //= $ENV{TMPDIR} || File::Spec->tmpdir;
$dir //= '/tmp' unless $^O =~ /Win32|Cygwin/i;
$dir = File::Spec->catdir($dir, $ENV{USER} || getlogin(), 'financeta');
File::Path::make_path($dir) unless -d $dir;
return $dir;
});
has datadir => ( default => sub {
my $dir = $ENV{DATADIR} || File::Spec->catfile(File::HomeDir->my_home, '.financeta');
File::Path::make_path($dir) unless -d $dir;
return $dir;
});
has plot_engine => 'highcharts';
has current => {};
has indicator => (builder => '_build_indicator');
has tab_was_closed => 0;
has editors => {};
has tradereports => {};
has list_sources => ['yahoo', 'gemini'];
has list_sources_pretty => ['Yahoo! Finance', 'Gemini Crypto Exchange'];
lib/App/financeta/gui.pm view on Meta::CPAN
modalResult => mb::Cancel,
default => 0,
enabled => 1,
font => { height => 16, style => fs::Bold },
onClick => sub {
$result = {};
},
);
$w->insert(
Button => name => 'btn_ok',
text => 'OK',
autoHeight => 1,
autoWidth => 1,
origin => [ 150, 20 ],
modalResult => mb::Ok,
default => 1,
enabled => 0,
font => { height => 16, style => fs::Bold },
onClick => sub {
my $btn = shift;
my $owner = $btn->owner;
my $indicators = $self->get_tab_indicators($owner->owner, $result->{tab});
my @inds = ();
if ($indicators) {
my $iref = $indicators->[$result->{indicator_index}]->{indicator};
if ($iref->{func} eq $result->{indicator}) {
$result->{columns} = $indicators->[$result->{indicator_index}]->{columns};
} else {
$log->warn("Cannot find the columns to remove");
}
} else {
$log->warn("Invalid indicators for tab: ", $result->{tab});
}
$log->debug("Result: ", dumper($result));
},
);
my $res = $w->execute();
$w->end_modal;
return ($res == mb::Ok) ? $result : undef;
}
sub run_and_display_indicator {
my ($self, $win, $data, $symbol, $indicators) = @_;
return unless $win;
if (defined $data and defined $symbol and defined $indicators and
ref $indicators eq 'ARRAY') {
my $icount = scalar @$indicators;
foreach my $iref (@$indicators) {
$log->debug("Trying to run indicator for :", dumper($iref));
my $output;
if (exists $iref->{params} and exists $iref->{params}->{CompareWith}) {
# ok this is a security.
# we need to download the data for this and store it
my $bar = App::financeta::gui::progress_bar->new(owner => $win, title => 'Downloading...');
my $current = $self->current;
$iref->{params}->{CompareWith} =~ s/\s//g;
$current->{symbol} = $iref->{params}->{CompareWith};
my $tz = $self->timezone;
unless ($current->{start_date}) {
my $sd = $data->at(0, 0); # time in 0th column
my $dt = DateTime->from_epoch(epoch => $sd, time_zone => $tz);
$current->{start_date} = $dt;
}
unless ($current->{end_date}) {
my $ed = $data->at($data->dim(0) - 1, 0); # time in 0th column
my $dt = DateTime->from_epoch(epoch => $ed, time_zone => $tz);
$current->{end_date} = $dt;
}
my ($data2, $symbol2, $csv2) = $self->download_data($bar, $current);
$bar->close if $bar;
return unless (defined $data2 and defined $symbol2);
$log->debug("Successfully downloaded data for $symbol2");
$iref->{params}->{CompareWith} = $symbol2;
$output = $self->indicator->execute_ohlcv($data, $iref, $data2);
} else {
$output = $self->indicator->execute_ohlcv($data, $iref);
}
unless (defined $output) {
message_box('Indicator Error',
"Unable to run the indicator on data.",
mb::Ok | mb::Error);
return;
}
my ($next_data) = $self->display_data($win, $data, $symbol, $iref, $output);
$icount--;
$data = $next_data if $icount > 0;
}
return 1;
}
return 0;
}
sub add_indicator($$$) {
my ($self, $win, $data, $symbol) = @_;
if ($self->add_indicator_wizard($win)) {
my $iref = $self->current->{indicator};
if ($self->run_and_display_indicator($win, $data, $symbol, [$iref])) {
my ($ndata, $nsymbol, $indicators, $ndhr, $nbs) = $self->get_tab_data($win);
my $type = $self->current->{plot_type} || 'OHLC';
$self->plot_data($win, $ndata, $nsymbol, $type, $indicators, $nbs);
return 1;
}
}
return 0;
}
sub indicator_parameter_wizard {
my ($self, $gbox, $fn_name, $grp, $params) = @_;
if ($gbox) {
# remove the current parameter screen
my @widgets = $gbox->get_widgets;
if (@widgets) {
map { $_->close() } @widgets;
}
} else {
return;
}
# if all are defined create the parameter screen
if (defined $fn_name and defined $grp and defined $params) {
$gbox->text("$fn_name Parameters");
my @origin = $gbox->origin;
my @size = $gbox->size;
$log->debug("Gbox: Origin: @origin Size: @size");
my $num = scalar @$params;
my $sz_x = $size[0] / 2; # label and value
my $sz_y = $size[1] / ($num + 1);
lib/App/financeta/gui.pm view on Meta::CPAN
# there and get that page number
# default headers
my $headers = ['Date', 'Open', 'High', 'Low', 'Close', 'Volume'];
my $existing_indicators = [];
my ($info, $buysells);
for my $idx (0 .. $pc) {
my @wids = $nt->widgets_from_page($idx);
next unless @wids;
my @dls = grep { $_->name eq "tab_$symbol" } @wids;
if (@dls) {
foreach (@dls) {
$log->debug("Found existing " . $_->name . " at $idx");
$headers = $_->headers if defined $_->headers;
push @$existing_indicators, @{$_->{-indicators}} if exists $_->{-indicators};
$info = $_->{-info} if exists $_->{-info};
$buysells = $_->{-buysells} if exists $_->{-buysells};
$nt->delete_widget($_);
}
$pageno = $idx;
last;
}
}
# handle the current indicator first
if ($output and scalar @$output) {
my @cols = ();
foreach my $a (@$output) {
# add the DetailedList column number
push @cols, scalar(@$headers);
# add the header
push @$headers, $a->[0];
# splice the indicator PDL into $data
$data = $data->glue(1, $a->[1]) if ref $a->[1] eq 'PDL';
}
# add the current indicator to the bottom of the list
push @$existing_indicators, {indicator => $iref, data => $output, columns => \@cols};
}
$log->debug("Data dimension: ", dumper([$data->dims]));
$log->debug("Updated headers: ", dumper($headers));
my $items;
if (defined $buysells and ref $buysells eq 'HASH' and
defined $buysells->{buys} and
defined $buysells->{sells}) {
my $buys = $buysells->{buys};
my $sells = $buysells->{sells};
if (ref $buys eq 'PDL' and ref $sells eq 'PDL') {
my $ldata = $data->copy;
$ldata = $ldata->glue(1, $buys);
$ldata = $ldata->glue(1, $sells);
push @$headers, 'Buys', 'Sells' unless grep {/Buys|Sells/} @$headers;
$items = $ldata->transpose->unpdl;
} else {
$log->warn("Buy-sells object is corrupt. Not using.");
$items = $data->transpose->unpdl;
}
} else {
$items = $data->transpose->unpdl;
}
my $tz = $self->timezone;
# reformat
foreach my $arr (@$items) {
my $dt = DateTime->from_epoch(epoch => $arr->[0], time_zone => $tz)->datetime(' ');
$arr->[0] = $dt;
for (my $i = 1; $i < scalar @$arr; ++$i) {
$arr->[$i] = '' if $arr->[$i] =~ /BAD/i;
}
}
$tabsize[0] *= 0.98;
$tabsize[1] *= 0.96;
my $dl = $nt->insert_to_page($pageno, 'DetailedList',
name => "tab_$symbol",
pack => { expand => 1, fill => 'both' },
items => $items,
origin => [ 10, 10 ],
headers => $headers,
hScroll => 1,
growMode => gm::Client | gm::GrowHiX | gm::GrowHiY,
columns => scalar @$headers,
onSort => sub {
my ($p, $col, $dir) = @_;
return if $col != 1;
if ($dir) {
$p->{items} = [
sort {$$a[$col] <=> $$b[$col]}
@{$self->{items}}
];
} else {
$p->{items} = [
sort {$$b[$col] <=> $$a[$col]}
@{$self->{items}}
];
}
$p->clear_event;
},
title => $symbol,
titleSpace => 30,
size => \@tabsize,
visible => 1,
);
$nt->pageIndex($pageno);
$dl->{-pdl} = $data;
$dl->{-symbol} = $symbol;
$dl->{-indicators} = $existing_indicators if defined $existing_indicators;
$dl->{-info} = $info || {};
$dl->{-buysells} = $buysells if defined $buysells;
return wantarray ? ($data) : 1;
}
sub enable_menu_options {
my $self = shift;
my $win = shift || $self->main;
# enable the menu option now that we have something open
$win->menu->security_save->enabled(1);
$win->menu->security_saveas->enabled(1);
$win->menu->security_close->enabled(1);
$win->menu->plot_ohlc->enabled(1);
$win->menu->plot_ohlcv->enabled(1);
$win->menu->plot_close->enabled(1);
$win->menu->plot_closev->enabled(1);
$win->menu->plot_cdl->enabled(1);
$win->menu->plot_cdlv->enabled(1);
$win->menu->plot_using_highcharts->enabled(1);
lib/App/financeta/gui.pm view on Meta::CPAN
my $self = shift;
my $win = $self->main;
# disable the menu option now that we have nothing open
$win->menu->security_save->enabled(0);
$win->menu->security_saveas->enabled(0);
$win->menu->security_close->enabled(0);
$win->menu->plot_ohlc->enabled(0);
$win->menu->plot_ohlcv->enabled(0);
$win->menu->plot_close->enabled(0);
$win->menu->plot_closev->enabled(0);
$win->menu->plot_cdl->enabled(0);
$win->menu->plot_cdlv->enabled(0);
$win->menu->plot_using_highcharts->enabled(0);
$win->menu->plot_using_gnuplot->enabled(0);
$win->menu->add_indicator->enabled(0);
$win->menu->remove_indicator->enabled(0);
$win->menu->edit_rules->enabled(0);
$win->menu->execute_rules->enabled(0);
$win->menu->trade_report->enabled(0);
}
#rudimentary
sub get_model {
my ($self, $data, $symbol, $indicators) = @_;
if (defined $data and defined $symbol) {
my $sd = $data->at(0, 0);
my $ed = $data->at($data->dim(0) - 1, 0);
my $tz = $self->timezone;
my %saved = (
start_date => $sd,
end_date => $ed,
symbol => $symbol,
);
if ($indicators and ref $indicators eq 'ARRAY') {
my $arr = [];
foreach (@$indicators) {
push @$arr, $_->{indicator};
}
$saved{indicators} = $arr;
} elsif ($indicators and ref $indicators eq 'HASH') {
$saved{indicators} = [$indicators->{indicator}];
}
return wantarray ? %saved : \%saved;
}
}
#rudimentary
sub save_current_tab {
my ($self, $win, $save_as, $name) = @_;
return unless $win;
my ($data, $symbol, $indicators) = (defined $name) ?
$self->get_tab_data_by_name($win, $name) :
$self->get_tab_data($win);
# in save-as mode do not get historical file name
my ($info, $tname) = (defined $name) ?
$self->get_tab_info_by_name($win, $name) :
$self->get_tab_info($win);
my $saved = $self->get_model($data, $symbol, $indicators);
return unless $saved;
my $tz = $self->timezone;
$saved->{saved_at} = DateTime->now(time_zone => $tz)->iso8601();
$log->debug("Saving the model: ", dumper($saved));
my $mfile;
if ($info and $info->{filename} and not $save_as) {
$mfile = $info->{filename};
$log->info(sprintf "Saving tab %s to %s", ($name ? $name : ''), $mfile);
} else {
my $dlg = Prima::Dialog::SaveDialog->new(
fileName => "$symbol.yml",
filter => [
['financeta files' => '*.yml'],
['All files' => '*'],
],
filterIndex => 0,
multiSelect => 0,
overwritePrompt => 1,
pathMustExist => 1,
directory => $self->datadir,
);
$mfile = $dlg->fileName if $dlg->execute;
if ($mfile) {
if ($^O !~ /Win32/) {
$mfile = File::Spec->catfile($self->datadir, $mfile) unless ($mfile =~ /^\//);
} else {
$mfile .= '.yml' unless $mfile =~ /\.yml$/; #windows is weird
}
$name //= $tname // '';
$log->info("Saving tab $name to $mfile");
} else {
$name //= $tname // '';
$log->warn("Saving the tab $name was canceled.");
return;
}
}
if ($info and defined $info->{rules}) {
$saved->{rules} = $info->{rules};
} else {
if (exists $self->editors->{$tname}) {
$saved->{rules} = $self->editors->{$tname}->get_text;
}
}
$saved->{old_filename} = $info->{old_filename} if defined $info and defined $info->{old_filename};
$saved->{csv} = $info->{csv} if defined $info and defined $info->{csv};
if ($mfile) {
$saved->{filename} = $mfile;
$log->debug("You have selected $mfile to save the tab info into.");
YAML::Any::DumpFile($mfile, $saved) or message("Unable to save to $mfile");
$self->set_tab_info($win, $saved);
1;
} else {
$log->warn("Saving the tab was canceled.");
}
}
#rudimentary
sub load_new_tab {
my ($self, $win) = @_;
return unless $win;
my $dlg = Prima::Dialog::OpenDialog->new(
filter => [
['financeta files' => '*.yml'],
['All files' => '*'],
],
filterIndex => 0,
fileMustExist => 1,
multiSelect => 0,
directory => $self->datadir,
);
my $mfile = $dlg->fileName if $dlg->execute;
$log->info("requesting file $mfile to be opened");
return unless $mfile;
return unless -e $mfile;
my $saved = YAML::Any::LoadFile($mfile);
return unless $saved;
my $tz = $self->timezone;
my $current = {
start_date => DateTime->from_epoch(epoch => $saved->{start_date}, time_zone => $tz),
end_date => DateTime->from_epoch(epoch => $saved->{end_date}, time_zone => $tz),
symbol => $saved->{symbol},
force_download => 0,
};
$current->{csv} = $saved->{csv} if defined $saved->{csv};
my $bar = App::financeta::gui::progress_bar->new(owner => $win, title => 'Loading...');
my ($data, $symbol, $csv) = $self->download_data($bar, $current);
$log->debug("Loading the data into tab");
$saved->{csv} = $csv if defined $csv;
# overwrite the filename for saving
if (defined $saved->{filename} and $mfile ne $saved->{filename}) {
$saved->{old_filename} = $saved->{filename};
}
$saved->{filename} = $mfile;
$self->display_data($win, $data, $symbol);
$self->enable_menu_options($win);
$self->set_tab_info($win, $saved);
$log->debug("Running the indicators and updating tab");
$bar->close if $bar;
if ($self->run_and_display_indicator($win, $data, $symbol,
$saved->{indicators})) {
# this is specially done
$win->menu->remove_indicator->enabled(1);
}
my ($adata, $asym, $aind, $ahdr, $abysl) = $self->get_tab_data($win);
my $type = $self->current->{plot_type} || 'OHLC';
$self->plot_data($win, $adata, $asym, $type, $aind, $abysl);
}
sub close_current_tab {
my ($self, $win) = @_;
return unless $win;
my @tabs = grep { $_->name =~ /data_tabs/ } $win->get_widgets();
return unless @tabs;
my $nt = $win->data_tabs;
my $idx = $nt->pageIndex;
if ($nt->pageCount == 1) {
foreach my $e (keys %{$self->editors}) {
my $ed = $self->editors->{$e};
$ed->close;
}
$self->editors({});
foreach my $t (keys %{$self->tradereports}) {
my $trw = $self->tradereports->{$t};
$trw->close;
}
$self->tradereports({});
if ($win->{plot}) {
$win->{plot}->close();
}
$nt->close;
$self->disable_menu_options;
} else {
$self->tab_was_closed(1);
# find corresponding editors and close them
my @wids = $win->data_tabs->widgets_from_page($idx);
if (@wids) {
my ($dl) = grep { $_->name =~ /^tab_/i } @wids;
if ($dl and exists $self->editors->{$dl->name}) {
$log->debug("Closing the rules editor for " . $dl->name);
$self->editors->{$dl->name}->close;
( run in 0.714 second using v1.01-cache-2.11-cpan-ceb78f64989 )