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 )