X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~yarrgweb/git?p=ypp-sc-tools.db-live.git;a=blobdiff_plain;f=yarrg%2Fweb%2Froutetrade;h=eadc473d4b23dac47885d569b89a79d12a741ae5;hp=9c2a5a423fdda83fe833c43ceb2ba02462df6a79;hb=880b063ca880504e85bbeba2e37fc8faa187947e;hpb=b54e4d479114f7641bddf8a47d075639323620d2 diff --git a/yarrg/web/routetrade b/yarrg/web/routetrade index 9c2a5a4..eadc473 100644 --- a/yarrg/web/routetrade +++ b/yarrg/web/routetrade @@ -32,6 +32,19 @@ This Mason component is the core trade planner for a specific route. +========== TODO ========== +16:36 alpha,byrne,papaya,turtle,jorvik,luthien is my example + +use POST for update. Hrrm. + +LATER OR NOT AT ALL + +adjustable potential cost of losses (rather than fixed 1e-BIG per league) + +max volume/mass + +========== TODO ========== + <%args> $dbh @@ -39,8 +52,15 @@ $dbh @archipelagoes $qa +<&| script &> + da_pageload= Date.now(); + + <%perl> +my $now= time; +my $loss_per_league= 1e-7; + my @flow_conds; my @query_params; @@ -98,40 +118,92 @@ my $stmt= " SELECT sell_islands.islandname org_name, sell_islands.islandid org_id, sell.price org_price, - sum(sell.qty) org_qty, + sell.qty org_qty_stall, + sell_stalls.stallname org_stallname, + sell.stallid org_stallid, + sell_uploads.timestamp org_timestamp, buy_islands.islandname dst_name, buy_islands.islandid dst_id, buy.price dst_price, - sum(buy.qty) dst_qty, + buy.qty dst_qty_stall, + buy_stalls.stallname dst_stallname, + buy.stallid dst_stallid, + buy_uploads.timestamp dst_timestamp, +".($qa->{ShowStalls} ? " + sell.qty org_qty_agg, + buy.qty dst_qty_agg, +" : " + (SELECT sum(qty) FROM sell AS sell_agg + WHERE sell_agg.commodid = commods.commodid + AND sell_agg.islandid = sell.islandid + AND sell_agg.price = sell.price) org_qty_agg, + (SELECT sum(qty) FROM buy AS buy_agg + WHERE buy_agg.commodid = commods.commodid + AND buy_agg.islandid = buy.islandid + AND buy_agg.price = buy.price) dst_qty_agg, +")." commods.commodname commodname, commods.commodid commodid, commods.unitmass unitmass, commods.unitvolume unitvolume, - buy.price - sell.price unitprofit, - min(sell.qty,buy.qty) max_qty, - min(sell.qty,buy.qty) * (buy.price-sell.price) max_profit + dist dist, + buy.price - sell.price unitprofit FROM commods - JOIN buy on commods.commodid = buy.commodid - JOIN sell on commods.commodid = sell.commodid - JOIN islands as sell_islands on sell.islandid = sell_islands.islandid - JOIN islands as buy_islands on buy.islandid = buy_islands.islandid + JOIN sell ON commods.commodid = sell.commodid + JOIN buy ON commods.commodid = buy.commodid + JOIN islands AS sell_islands ON sell.islandid = sell_islands.islandid + JOIN islands AS buy_islands ON buy.islandid = buy_islands.islandid + JOIN uploads AS sell_uploads ON sell.islandid = sell_uploads.islandid + JOIN uploads AS buy_uploads ON buy.islandid = buy_uploads.islandid + JOIN stalls AS sell_stalls ON sell.stallid = sell_stalls.stallid + JOIN stalls AS buy_stalls ON buy.stallid = buy_stalls.stallid + JOIN dists ON aiid = sell.islandid AND biid = buy.islandid WHERE ( ".join(" OR ", @flow_conds)." ) AND buy.price > sell.price - GROUP BY commods.commodid, org_id, org_price, dst_id, dst_price - ORDER BY org_name, dst_name, max_profit DESC, commodname, - org_price, dst_price DESC + ORDER BY org_name, dst_name, commodname, unitprofit DESC, + org_price, dst_price DESC, + org_stallname, dst_stallname "; my $sth= $dbh->prepare($stmt); $sth->execute(@query_params); my @flows; -my @columns= qw(org_name dst_name commodname - org_price org_qty dst_price dst_qty - max_qty max_profit); +my @cols= ({ NoSort => 1 }); + +my $addcols= sub { + my $base= shift @_; + foreach my $name (@_) { + my $col= { Name => $name, %$base }; + $col->{Numeric}=1 if !$col->{Text}; + push @cols, $col; + } +}; + +if ($qa->{ShowStalls}) { + $addcols->({ Text => 1 }, qw( + org_name org_stallname + dst_name dst_stallname + )); +} else { + $addcols->({Text => 1 }, qw( + org_name dst_name + )); +} +$addcols->({ Text => 1 }, qw(commodname)); +$addcols->({ DoReverse => 1 }, + qw( org_price org_qty_agg dst_price dst_qty_agg + )); +$addcols->({ DoReverse => 1, SortColKey => 'MarginSortKey' }, + qw( Margin + )); +$addcols->({ DoReverse => 1 }, + qw( unitprofit MaxQty + MaxCapital MaxProfit + )); @@ -142,19 +214,133 @@ my @columns= qw(org_name dst_name commodname % } +<& dumptable:start, qa => $qa, sth => $sth &> % { -<& dumptable:start, sth => $sth &> -% my $flow; -% while ($flow= $sth->fetchrow_hashref()) { -% $flow->{Ix}= @flows; -% $flow->{Var}= "f$flow->{Ix}"; -% push @flows, $flow; -<& dumptable:row, sth => $sth, row => $flow &> -% } -<& dumptable:end &> +% my $got; +% while ($got= $sth->fetchrow_hashref()) { +<%perl> + + my $f= $flows[$#flows]; + if ( !$f || + $qa->{ShowStalls} || + grep { $f->{$_} ne $got->{$_} } + qw(org_id org_price dst_id dst_price commodid) + ) { + # Make a new flow rather than adding to the existing one + + $f= { + Ix => scalar(@flows), + Var => "f".@flows, + %$got + }; + $f->{"org_stallid"}= $f->{"dst_stallid"}= 'all' + if !$qa->{ShowStalls}; + push @flows, $f; + } + foreach my $od (qw(org dst)) { + $f->{"${od}Stalls"}{ + $got->{"${od}_stallname"} + } = + $got->{"${od}_qty_stall"} + ; + } + + +<& dumptable:row, qa => $qa, sth => $sth, row => $f &> +% } +<& dumptable:end, qa => $qa &> % } -% if (!$specific || $confusing || @islandids<=1) { +<%perl> +foreach my $f (@flows) { + + $f->{MaxQty}= $f->{'org_qty_agg'} < $f->{'dst_qty_agg'} + ? $f->{'org_qty_agg'} : $f->{'dst_qty_agg'}; + $f->{MaxProfit}= $f->{MaxQty} * $f->{'unitprofit'}; + $f->{MaxCapital}= $f->{MaxQty} * $f->{'org_price'}; + + $f->{MarginSortKey}= sprintf "%d", + $f->{'dst_price'} * 10000 / $f->{'org_price'}; + $f->{Margin}= sprintf "%3.1f%%", + $f->{'dst_price'} * 100.0 / $f->{'org_price'} - 100.0; + + $f->{ExpectedUnitProfit}= + $f->{'dst_price'} * (1.0 - $loss_per_league) ** $f->{'dist'} + - $f->{'org_price'}; + + my @uid= $f->{commodid}; + foreach my $od (qw(org dst)) { + push @uid, + $f->{"${od}_id"}, + $f->{"${od}_price"}; + push @uid, + $f->{"${od}_stallid"} + if $qa->{ShowStalls}; + } + $f->{UidLong}= join '_', @uid; + + my $base= 31; + my $cmpu= ''; + map { + my $uue= $_; + my $first= $base; + do { + my $this= $uue % $base; +print STDERR "uue=$uue this=$this "; + $uue -= $this; + $uue /= $base; + $this += $first; + $first= 0; + $cmpu .= chr($this + ($this < 26 ? ord('a') : + $this < 52 ? ord('A')-26 + : ord('0')-52)); +print STDERR " uue=$uue this=$this cmpu=$cmpu\n"; +die "$cmpu $uue ?" if length $cmpu > 20; + } while ($uue); + $cmpu; + } @uid; + $f->{UidShort}= $cmpu; + + if ($qa->{'debug'}) { + my @outuid; + $_= $f->{UidShort}; + my $mul; + while (m/./) { + my $v= m/^[a-z]/ ? ord($&)-ord('a') : + m/^[A-Z]/ ? ord($&)-ord('A')+26 : + m/^[0-9]/ ? ord($&)-ord('0')+52 : + die "$_ ?"; + if ($v >= $base) { + push @outuid, 0; + $v -= $base; + $mul= 1; +#print STDERR "(next)\n"; + } + die "$f->{UidShort} $_ ?" unless defined $mul; + $outuid[$#outuid] += $v * $mul; + +#print STDERR "$f->{UidShort} $_ $& v=$v mul=$mul ord()=".ord($&). +# "[vs.".ord('a').",".ord('A').",".ord('0')."]". +# " outuid=@outuid\n"; + + $mul *= $base; + s/^.//; + } + my $recons_long= join '_', @outuid; + $f->{UidLong} eq $recons_long or + die "$f->{UidLong} = $f->{UidShort} = $recons_long ?"; + } + + if (defined $qa->{"R$f->{UidShort}"} && + !defined $qa->{"T$f->{UidShort}"}) { + $f->{Suppress}= 1; + } + +} + + +% my $optimise= $specific && !$confusing && @islandids>1; +% if (!$optimise) {

% if (@islandids<=1) { @@ -177,22 +363,33 @@ Maximize totalprofit: ".(join " + - ", map { "$_->{unit_profit} $_->{Var}" } @flows)." + ", map { + sprintf "%.20f %s", $_->{ExpectedUnitProfit}, $_->{Var} + } @flows)." Subject To "; my %avail_csts; foreach my $flow (@flows) { + if ($flow->{Suppress}) { + $cplex .= " + $flow->{Var} = 0 +"; + next; + } foreach my $od (qw(org dst)) { - my $cstname= join '_', + my $cstname= join '_', ( 'avail', $flow->{'commodid'}, $od, $flow->{"${od}_id"}, - $flow->{"${od}_price"}; + $flow->{"${od}_price"}, + $flow->{"${od}_stallid"}, + ); + push @{ $avail_csts{$cstname}{Flows} }, $flow->{Var}; - $avail_csts{$cstname}{Qty}= $flow->{"${od}_qty"}; + $avail_csts{$cstname}{Qty}= $flow->{"${od}_qty_agg"}; } } foreach my $cstname (sort keys %avail_csts) { @@ -242,23 +439,247 @@ if ($qa->{'debug'}) { my ($ix, $qty) = m/^\s*\d+\s+f(\d+)\s+\S+\s+(\d+)\s/ or die "$_ ?"; my $flow= $flows[$ix] or die; - $flow->{Opt_qty}= $qty; - $flow->{Opt_profit}= $flow->{'unitprofit'} * $qty; + $flow->{OptQty}= $qty; + $flow->{OptProfit}= $flow->{'unitprofit'} * $qty; + $flow->{OptCapital}= $flow->{OptQty} * $flow->{'org_price'}; } print "\n" if $qa->{'debug'}; pipethrough_run_finish($output, 'glpsol'); die unless $found_section; }; -print join ' ', map { $_->{Optimal} } @flows; +$addcols->({ DoReverse => 1 }, qw( + OptQty + )); +$addcols->({ Total => 0, DoReverse => 1 }, qw( + OptCapital OptProfit + )); -push @columns, qw(Opt_qty Opt_profit); + +% } # ========== OPTIMISATION ========== + +% my %ts_sortkeys; +% { +% my $cdspan= $qa->{ShowStalls} ? ' colspan=2' : ''; +% my $cdstall= $qa->{ShowStalls} ? 'Stall' : ''; + +++<% $qa->{ShowStalls} ? '' : '' %> ++++++% if ($optimise) { ++% } + + + +% foreach my $col (@cols) { + + +
+>Collect +>Deliver + +Collect +Deliver +Profit +Max +% if ($optimise) { +Planned +% } + +
+Island <% $cdstall %> +Island <% $cdstall %> +Commodity +Price +Qty +Price +Qty +Margin +Unit +Qty +Capital +Profit +% if ($optimise) { +Qty +Capital +Profit +% } +% } + +
+% } + +% foreach my $flowix (0..$#flows) { +% my $flow= $flows[$flowix]; +% my $rowid= "id_row_$flow->{UidShort}"; +
{UidShort} %> value=""> + {UidShort} %> value="" + <% $flow->{Suppress} ? '' : 'checked' %> > +% foreach my $ci (1..$#cols) { +% my $col= $cols[$ci]; +% my $v= $flow->{$col->{Name}}; +% $col->{Total} += $v if defined $col->{Total}; +% $v='' if !$col->{Text} && !$v; +% my $sortkey= $col->{SortColKey} ? +% $flow->{$col->{SortColKey}} : $v; +% $ts_sortkeys{$ci}{$rowid}= $sortkey; +{Text} ? '' : 'align=right' %>><% $v |h %> +% } +% } +
+Total +% foreach my $ci (3..$#cols) { +% my $col= $cols[$ci]; + +% if (defined $col->{Total}) { +<% $col->{Total} |h %> +% } +% } +
+ +<& tabsort, cols => \@cols, table => 'trades', rowclass => 'datarow', + throw => 'trades_sort', tbrow => 'trades_total' &> +<&| script &> + ts_sortkeys= <% to_json_protecttags(\%ts_sortkeys) %>; + function all_onload() { + ts_onload__trades(); + } + window.onload= all_onload; + + + + +% if ($optimise) { # ========== TRADING PLAN ========== +% +% my $iquery= $dbh->prepare('SELECT islandname FROM islands +% WHERE islandid = ?'); +% my %da_ages; +% +

Voyage trading plan

+ +% foreach my $i (0..$#islandids) { + + +% } +% $total += $t->{Total}; +% my $span= 0 + keys %{ $t->{Stalls} }; +% my $td= "td rowspan=$span"; + +<<% $td %>><% $collectdeliver %> +<<% $td %>><% $t->{'commodname'} |h %> +% +% my @stalls= sort keys %{ $t->{Stalls} }; +% my $pstall= sub { +% my $name= $stalls[$_[0]]; + +% $pstall->($stallix); +% } +% +% $dline ^= 1; +% } +% }; +% my $show_total= sub { +% my ($totaldesc)= @_; +% if (defined $total) { + +
+% $iquery->execute($islandids[$i]); +% my ($islandname) = $iquery->fetchrow_array(); +% if (!$i) { +Start at <% $islandname |h %> +% } else { +Sail to <% $islandname |h %> +% } + +<%perl> + my $age_reported= 0; + my %flowlists; + foreach my $od (qw(org dst)) { + foreach my $f (@flows) { + next if $f->{Suppress}; + next unless $f->{"${od}_id"} == $islandids[$i]; + next unless $f->{OptQty}; + my $price= $f->{"${od}_price"}; + my $stallname= $f->{"${od}_stallname"}; + my $todo= \$flowlists{$od}{ + $f->{'commodname'}, + (sprintf "%07d", ($od eq 'dst' ? + 9999999-$price : $price)), + $stallname + }; + $$todo= { + Qty => 0, + orgArbitrage => 0, + dstArbitrage => 0, + } unless $$todo; + $$todo->{'commodname'}= $f->{'commodname'}; + $$todo->{'stallname'}= $stallname; + $$todo->{Price}= $price; + $$todo->{Timestamp}= $f->{"${od}_timestamp"}; + $$todo->{Qty} += $f->{OptQty}; + $$todo->{Total}= $$todo->{Price} * $$todo->{Qty}; + $$todo->{Stalls}= $f->{"${od}Stalls"}; + if ($f->{'org_id'} == $f->{'dst_id'}) { + $$todo->{"${od}Arbitrage"}= 1; + } + } + } + + my $total; + my $dline= 0; + my $show_flows= sub { + my ($od,$arbitrage,$collectdeliver) = @_; +% +% my $todo= $flowlists{$od}; +% return unless $todo; +% foreach my $tkey (sort keys %$todo) { +% my $t= $todo->{$tkey}; +% next if $t->{"${od}Arbitrage"} != $arbitrage; +% if (!$age_reported++) { +% my $age= $now - $t->{Timestamp}; +% my $cellid= "da_${i}"; +% $da_ages{$cellid}= $age; +\ +(Data age: <% prettyprint_age($age) %>) +% } elsif (!defined $total) { +% $total= 0; +
<% $name |h %> +% }; +% +% $pstall->(0); +<<% $td %> align=right><% $t->{Price} |h %> poe ea. +<<% $td %> align=right><% $t->{Qty} |h %> unit(s) +<<% $td %> align=right><% $t->{Total} |h %> total +% +% foreach my $stallix (1..$#stalls) { +
+<% $totaldesc %> +<% $total |h %> total +% } +% $total= undef; +% $dline= 0; +<%perl> + }; -<& dumptable:literal, cols => \@columns, rows => \@flows &> + $show_flows->('dst',0,'Deliver'); $show_total->('Proceeds'); + $show_flows->('org',1,'Collect'); $show_total->('(Arbitrage) outlay'); + $show_flows->('dst',1,'Deliver'); $show_total->('(Arbitrage) proceeds'); + $show_flows->('org',0,'Collect'); $show_total->('Outlay'); -% } # ========== OPTIMISATION ========== +} + +
+<& query_age:dataages, id2age => \%da_ages &> +% +% } # ========== TRADING PLAN ========== <%init> use CommodsWeb;