After all of our work on station trading, it’s time to take a breather with a far simpler (but still extremely effective) reporting exercise.
In this article, we will:
- Reapproach the concept of price floors and discover some ways that they manifest in the market of EvE Online.
- Establish requisite in-game skill requirements and set reporting goals
- Develop a fast and effective reporting strategy for engaging in near-arbitrage trade.
Before continuing, I would recommend returning to the third article in this series and reading the first few paragraphs for a quick refresher on the context and importance of price floors.
Discovering and Evaluating Price Floors
Before we can get started writing a script, we need to walk through more of EvE Online’s game mechanics to determine if anything resembling a price floor exists and is worth our effort developing a reporting strategy against.
Blueprint Originals and Skill Books
While developing our station trading report, we identified that certain blueprints are subject to price floors. More specifically, ‘unmodified tech 1 (meta 0) blueprint originals’ are exclusively sold by NPC traders at fixed prices, serving as their sole item faucet. While players are free to trade them after purchase, secondary market prices typically do not fall very far since the supply of new blueprints entering the economy completely evaporates the second players are unwilling to pay the fixed NPC rate. The inverse effect also applies; so long as players are willing to pay more than the fixed rate, supply is unlimited, resulting in an effective price ceiling.
The same mechanics also apply to most ‘Skill Books’, which unlike the aforementioned blueprints are consumable single-use items and are thus not frequently resold on the secondary market.
There are some interesting dynamics surrounding these items; some traders will ‘fish’ for low-priced blueprints and skill books, while others might list these items for sale in otherwise dry regions where they are not sold by any local NPCs. These behaviors represent edge cases which don’t require a dedicated reporting strategy; instances of the former will appear in our station trading report if profit margins are substantial enough, while instances of the latter require movement of goods or ‘hauling’ which will be covered more broadly in an upcoming article.
To my knowledge, these two item categories are subject to the only price floors in the entire game which are pegged directly to the in-game currency, ISK. This is in contrast to Old School Runescape where nearly every item in the game can be directly converted to currency in fixed quantities per-unit through the game mechanic of alchemy.
As a result, any other price floors (or ceilings) we discover in EvE Online will involve commodities being pegged to other commodities. This results in comparatively ‘softer’ floors and ceilings than what we’ve seen previously which we will need to keep in-mind as we go over more examples.
Industry
Determining if price floors and ceilings exist requires understanding the incentive structures guiding the behavior of all market participants in a supply chain.
In the case of EvE Online, the entire supply chain encompassing all in-game industry can be broken-up into three distinct process categories:
- Harvesting includes all processes where new material is acquired from the environment. Examples include Mining, PvE, Planetary Industry (R0), Salvaging, and more.
- Manufacturing includes any process where many different types of materials are combined and converted into a single type of material.
- Reprocessing inversely includes any process where any single type of material is converted into many different types of materials.
Note: Logistics and ‘hauling’ are important components of industry, however in this context they represent a cost center and are irrelevant in this discovery process.
Let’s work through each component of EvE Online’s supply chain to see if we can uncover any price floors or ceilings.
Harvesting
As we discussed in our first article on price floors, state-funded agricultural subsidies represent an example of policy which includes the enforcement of price floors. In many countries, farmers plant and harvest crops with the understanding that the government will purchase any surplus at a fixed minimum price, which generally results in supply being greater than what would be produced at market equilibrium without subsidies.
In EvE Online, the concept of subsidies does not exist; there are no guaranteed returns and all players are subject to market rates when exchanging materials for ISK. The result is that (rational) players engage in market-driven behavior, including harvesting materials which maximize profit as a function of time. While specialization (sunk cost) and player enjoyment of specific activities (utility) might influence sub-optimal behavior, the end-result of these conditions is a market where player harvesting actions are not influenced by the presence of price floors.
Manufacturing
Materials are used by players to manufacture ‘useful’ items including ships, modules, ammunition, etc. Like harvesting operations, there are no guaranteed returns when manufacturing products and rational players will produce what the market demands in-order to generate profit.
While manufacturing in EvE Online shares some qualities with Old School Runescape in the vein of skill-locking certain activities, these similarities evaporate once we consider EvE’s mechanic of material efficiency. In Old School Runescape, two different players performing the same manufacturing activity will (almost) always use the same input materials to produce the same output. This is in-contrast to EvE Online where two different players manufacturing identical products can face vastly different material costs depending on a huge number of esoteric variables, not limited to but including manufacturing facility type, installed modules, system security level, system cost index, blueprint quality, and type/quality of cybernetic implants installed in a character’s head. The end-result is that in EvE Online players who invest more toward their overall manufacturing context and infrastructure will use proportionally fewer input materials to obtain the same output.
So, how does the concept of material efficiency in manufacturing combined with natural market incentives influence the prevalence of price floors and ceilings in EvE Online?
In circumstances where volatility is low and demand is consistent for a given product, price will drop as manufacturers invest in infrastructure to reduce material costs in-order to stay competitive and improve profit margins. The logical extreme of this outcome is an equilibrium price matching the material costs associated with manufacturing the item at maximum material efficiency. If manufacturers can reliably and consistently sell a lot of their product, the investment in infrastructure to improve material efficiency can be justified and will be pursued.
Inversely, if volatility is high and demand is inconsistent for the same product, manufacturers will be less likely to invest in infrastructure which will result in higher material costs and correspondingly higher prices for the product. The logical extreme of this outcome is an equilibrium price pegged to the material costs associated with manufacturing the item in the most materially-inefficient manner possible. If manufacturers cannot reliably move volume for a particular product, they will not maintain manufacturing infrastructure for that product and instead choose to manufacture it in an ad-hoc and materially-inefficient manner any time a bid order appears which exceeds these material costs in price.
So, we can say that the price floor for any manufactured product is the material cost associated with manufacturing the product with maximum material efficiency. Inversely, the price ceiling is the material cost associated with manufacturing the product with no/minimum material efficiency. Since the underlying materials used in manufacturing are subject to their own changes in price, all price floors and ceilings for manufactured goods are soft and subject to change in-accordance with the broader economy.
So, we’ve identified both a price floor and ceiling! Now, what can we do with this information?
This price ceiling is of particular importance to players interested in manufacturing; as price for a product approaches this value, players possessing the minimum manufacturing requirements can begin to profitably produce it. While this might seem like a fairly simple problem to report against, in-reality there are a lot of different factors influencing effective engagement:
- The ‘minimum manufacturing requirements’ represent fixed costs of manufacturing and vary wildly between different products. ‘tech 1’ items possess the lowest barrier to entry and correspondingly feature some of the lowest fixed costs, often just requiring a blueprint and requisite skills. More advanced ‘tech 2’ and ‘tech 3’ products involve higher fixed costs. In-order for a new (rational) market participant to enter the field of manufacturing, these fixed costs in-addition to the operating costs of materials need to be offset by projected earnings.
- Manufacturing is not an instantaneous procedure and time is an important factor to manage when performing production runs. While concurrency is possible, there are limits per-character. These constraints present opportunity cost dilemmas which manufacturers must consider before deciding to manufacture different products. This manufacturing delta also forces players to consider how the market is changing since the market will change between the start and end of their production runs.
- Manufacturing incurs fewer fees (system cost index) the less populated/utilized a system is. While this might appear to increase profit margins, it does so at the cost of logistics overhead if materials and products are purchased and/or sold in densely-populated systems such as Jita.
There are probably many other factors I’m missing, but the point should be clear that against the backdrop of all trading strategies we’ve covered in this series so far, manufacturing in EvE Online presents the biggest ball of wax. While generating a report which ranks products by their potential profitability while adequately encompassing all of these different variables would be a fun and complex exercise in data analytics, we’ll couch this idea for now. If we write an article on this in the future, it will be linked here.
The best publicly-available tool I’m aware of for manufacturing industrialists would be FuzzySteve’s blueprint calculator which does an excellent job at spitting-out profit projections given any single commodity type and any combination of available manufacturing infrastructure. It won’t tell you what to manufacture the same way my reports tell traders what to buy and sell, but it will help any industrialist make informed decisions before buying blueprints and committing to production runs.
Next, let’s move-on to the manufacturing price floor. As the market price for a product falls toward and approaches this value, players possessing the most materially-efficient manufacturing infrastructure will eventually see their profits approach zero. Below this point, new supply will dry-up entirely as manufacturers recognize that their raw materials are worth more than the end product. Thus, this means that this is the price at which any ordinary trading player should consider purchasing the product for subsequent re-sale in much the same way that our report against price floors resulting from the mechanism of alchemy in Old School Runescape functions.
Notwithstanding known factors including buy/sell deltas and taxes, the biggest problem with this approach is the fact that some products simply aren’t worth their material cost. A trader operating under this principle might accidentally purchase a completely useless item, inheriting the mistake of a manufacturer who didn’t do their research before initiating a production run. The solution to this problem will arrive as we delve into the mechanics of reprocessing.
Reprocessing
While manufacturing involves combining multiple inputs into a single output, reprocessing involves breaking-down a single item type into multiple different outputs.
There are two different types of reprocessing which both share the same interface:
- Ore reprocessing or ‘refining’ involves the conversion of raw harvested ores into their constituent minerals.
- Scrapmetal processing involves the conversion of manufactured products back into their constituent materials.
So, where does reprocessing fit into our discovery and review of price floors and ceilings?
Ore reprocessing serves as a necessary link between harvesting and manufacturing. Like manufacturing, ore reprocessing benefits from infrastructural investments made toward improving material efficiency. Players with minimal skills can attain about 50% ore reprocessing efficiency, meaning that half of the ore’s constituent minerals are recovered during a reprocessing operation. This rate can rise to as high as 90.6% for maximally-efficient players who are heavily invested in their reprocessing infrastructure. This spread results in price floor and ceiling behavior largely resembling that of manufacturing, with prices for ores and their constituent materials being pegged to one another.
Scrapmetal processing on the other hand allows players to convert finished products back into their constituent materials. This effectively serves as the direct inverse of manufacturing and presents a direct solution to the aforementioned issue of unwanted/useless goods; if a manufacturer produces a run of goods which fail to sell, they can be scrapped back into their constituent materials at an efficiency ranging from 50-55%, providing us with a much harder price floor for all manufactured products.
So, how can we interact with these price floors and ceilings? Is reprocessing in EvE Online as big of a ball of wax as manufacturing?
While reprocessing and manufacturing share the concept of material efficiency, the former is substantially less complicated than the latter:
- Reprocessing operations are executed instantaneously; there is no need to fret over production runs, concurrency, or really any adverse condition that involves time.
- System cost index does not apply and costs associated with reprocessing do not scale with local player population.
- All mechanisms associated with blueprints do not apply
Furthermore, when we specifically compare scrapmetal processing with ore reprocessing, the mechanics become even less complicated:
- While ore reprocessing with high material efficiency requires training a lot of different skills, scrapmetal processing is governed by only a small handful of skills which can be easily attained by most players.
- System security level has no bearing on material efficiency
- There are no implants, modules, or other game mechanisms which can push the material efficiency rating any higher than 55%.
As a result of all of these factors, scrapmetal processing and (to a lesser extent) ore reprocessing both provide excellent and accessible engagement potential with a price floor. With a very small investment in some basic skills, we can purchase under-priced manufactured products, reprocess them into scrap, and immediately sell the scrap material back to the market for profit, all in a matter of seconds with virtually no risk. In-summary, we have discovered a market opportunity which more closely resembles arbitrage than anything else we’ve covered in this series so far.
Prerequisites and Goals
With this idea in-mind, let’s establish some prerequisites and set our reporting goals.
Skill Requirements
For a refresher on the mechanics of skill training and an introduction to core trade-related skills, see the prerequisite section of our last article on Station Trading.
For trading against this price floor, we don’t actually need all of our trading skills maxed-out. Since we will only be clearing existing market orders placed by other traders, Trade V and Accounting V are all that we really need to minimize our effective sales tax rate when we sell reprocessed materials back to the market.
If you have a specific trade hub or station in-mind to use for reprocessing purposes, validate that the station provides 50% base material efficiency. Some NPC stations only provide 30% material efficiency, which will render most scrapmetal processing unprofitable. Jita provides 50% by-default.
Next, each level of the Scrapmetal Processing skill will increase our yield by 1%, providing us with the maximum 55% material efficiency. This skill along with all of its prerequisites should take about a month in real-time to train.
Equipment taxes for reprocessing sits at around 5% of material value when using NPC stations. This tax can be eliminated by either having high corporation ratings (~6.7) with the NPC station, or by using player-owned stations with a 0% tax rate for reprocessing. Alternatively, investing in the Connections skill can marginally reduce this fee, albeit not nearly as much as the aforementioned options.
Ore reprocessing is not the main focus of our report since yields can go much higher than 55%, with the highest yields requiring a lot of investment and null-security infrastructure to support. In any case, having some ore reprocessing skills trained to improve yields might be useful if other players are especially clueless when setting prices for their ore.
Super simple, right? Now, let’s get into our reporting goals!
Reporting Goals
As a recap from our third article, when trading against price floors our general goal is to take advantage of circumstances where other market actors fail to recognize that the floor exists. In the case of reprocessing, this includes any circumstance where an item has been listed for sale at a price less than its material scrap value. For instance, this can easily occur when players are undercutting each other on the market and fail to recognize that their Ask price has dipped below the product’s material scrap value.
With ‘Source’ defined as the item we are reprocessing and ‘Product’ defined as the materials we are creating through reprocessing, profit can be calculated as:
Volume * (Product Bid Price – Source Ask Price – Fees)
There are some extra considerations we need to make when determining which Bid, Ask, and Volume values to use in this formula. When dealing with hard price floors, we could simply take the lowest Ask order at face value for price and volume. Since our price floor is soft, we need to also consider whether or not the resulting volume of reprocessed material we obtain can be absorbed by corresponding Bid orders on the market. This can be achieved by matching pairs of minimum source Ask and maximum product Bid orders which can both sustain at least a single reprocessing cycle. Volume then becomes the maximum number of reprocessing cycles these orders can sustain before one of these orders becomes saturated, resulting in a price change necessitating recalculation.
We’ll otherwise need to report ROI, which will be especially important for traders who might not yet have requisite standings or skills for 55% material efficiency.
With our reporting goals set, let’s get started with some scripting!
Writing our Script
Compared to our massive script for station trading, this example will be a pretty breezy SQLite exercise. Since this report involves instantaneous trading without concern for time deltas, there is no need to break-out any regression modeling; we’re essentially just going to stuff a formula table full of current market data and sort the results by profitability.
Let’s start by importing our modules and setting some global variables:
### modules
import sqlite3
import pandas as pd
import os
import time
### define regions to report against
regionlist = ['TheForge', 'Domain', 'Essence', 'Metropolis', 'SinqLaison', 'Heimatar', 'TashMurkon']
### define database location where source data is stored
databaselocation = 'marketdata.sqlite'
### change working directory to script location
pathtorunfile = os.path.dirname(__file__)
os.chdir(pathtorunfile)
## start timer
starttime = time.time()
Next, let’s get into our runtime, setup a temporary scratch database and attach our ESI database as read-only:
### runtime
if __name__ == '__main__':
for region in regionlist:
## create in-memory database to temporarily store tables used for report generation
tempdb = sqlite3.connect(":memory:")
cur = tempdb.cursor()
## attach primary database (READ-ONLY)
cur.execute(f"ATTACH 'file:{databaselocation}' AS marketdatadb;")
Since we’re only concerned with the present state of the market, let’s grab the latest timestamp from our orderbook data which we will use later-on:
## extract current unix time from order book data
OrderbookUnixTime = pd.read_sql(f"SELECT DISTINCT max(timestamp) AS maxtimestamp FROM {region};", tempdb)
OrderbookUnixTime = int((OrderbookUnixTime['maxtimestamp'].to_list())[0])
Next, we’ll need to get started on our formula table. For our low-effort processing report for Old School Runescape, I had to manually create a formula table to define inputs, outputs and quantities. Needless to say, doing this for EvE Online would be a nightmare. Fortunately, FuzzySteve comes to our rescue again with their static data export tables. Our database building script now also maintains a copy of the formula table “industryActivityMaterials” which maps items to the base quantity of all their comprised materials. Here is a snippet of this table structure:
typeID materialTypeID quantity
18 34 175
18 36 70
19 34 48000
19 37 1000
19 38 160
19 39 80
19 40 40
20 36 60
20 37 120
...
This snippet tells us that the base composition of TypeID 18 includes 175 units of TypeID 34 and 70 units of TypeID 36. The table goes-on to cover the base composition of over 9,000 different items.
Let’s put this data to use: Since the maximum material efficiency offered by scrapmetal processing is 55%, we can multiply all material quantity values by 0.55 to obtain the actual quantity of each material we will obtain when scrapping the corresponding source item.
### define our formula table "ScrapRates"
## calculate scrapmetal reprocessing product quantities (55% of base material costs assuming max skills)
cur.execute("CREATE TABLE IndustryMaterialsAdj AS SELECT typeID AS Source_TypeID, materialTypeID AS Product_TypeID, (quantity * 0.55) AS Product_Quantity FROM IndustryMaterials;")
While building this script, I learned that certain items can only be reprocessed in quantities greater than one, including most ores, missiles, etc. This value is called ‘portionSize’ and is provided by the invTypes table. Furthermore, the base material quantities defined by the industryActivityMaterials table correspond directly with a ‘portionSize’ of the source item.
In the case of the above example involving TypeID 18, since ‘portionSize’ is 100, we actually need 100 units of this TypeID in-order to obtain the aforementioned quantities of its constituent TypeIDs 34 and 36, sans material efficiency losses. While ‘portionSize’ is 1 for most items, it is important to ensure that this value is included in our calculations in-order to prevent over-estimating profit potential for affected item types.
So, let’s join these with our current formula table to create ScrapRates:
## create "ScrapRates" table by appending portionSize and typeName from InvTypes table. This is the formula table which defines our source-to-product ratios
cur.execute("CREATE TABLE ScrapRates AS SELECT Source_TypeID, portionSize AS Source_Quantity, Product_TypeID, Product_Quantity, typeName AS name FROM IndustryMaterialsAdj LEFT JOIN InvTypes ON Source_TypeID = typeID")
## purge unneeded tables
cur.execute("DROP TABLE IndustryMaterialsAdj;")
With our formula table ready to use, let’s pull some Bid/Buy orders from our ESI database using the latest timestamp. Details for each command are included in the comments highlighted in bold:
### process and convert Buy orders from the order book
## create table containing all current Buy orders
cur.execute(f"CREATE TABLE CurrentBuyOrders AS SELECT type_id AS Product_TypeID, price AS Product_Price, volume_remain AS Product_Volume_Remain FROM {region} WHERE timestamp = {OrderbookUnixTime} AND is_buy_order = 1;")
## select all distinct combinations of product type_id and product_quantity from ScrapRates
cur.execute("CREATE TABLE ScrapRatesDistinct AS SELECT DISTINCT Product_TypeID, Product_Quantity FROM ScrapRates")
## append the Buy order with the highest price to ScrapRatesDistinct which possesses a sufficient order volume remaining to capture the generated product material.
cur.execute("CREATE TABLE ScrapRatesDistinctProducts AS SELECT ScrapRatesDistinct.Product_TypeID, Product_Quantity, max(Product_Price) AS Product_Price, Product_Volume_Remain FROM ScrapRatesDistinct, CurrentBuyOrders WHERE ScrapRatesDistinct.Product_TypeID = CurrentBuyOrders.Product_TypeID AND Product_Volume_Remain > Product_Quantity GROUP BY ScrapRatesDistinct.Product_TypeID, Product_Quantity;")
## purge unneeded tables
cur.executescript("DROP TABLE CurrentBuyOrders; DROP TABLE ScrapRatesDistinct")
By cross-referencing our order book with our new formula table, we can address our soft price floor concern by only selecting the highest-priced Bid/Buy orders which possess sufficient remaining volume to absorb the total output quantity of at least a single reprocessing operation.
We’ll take a similar approach with Ask/Sell orders in the next block:
### process and convert Sell orders from the order book
##create table containing all current Sell orders
cur.execute(f"CREATE TABLE CurrentSellOrders AS SELECT type_id AS Source_TypeID, price AS Source_Price, volume_remain AS Source_Volume_Remain FROM {region} WHERE timestamp = {OrderbookUnixTime} AND is_buy_order = 0;")
## select all distinct source type_id and source_quantity from ScrapRates
cur.execute("CREATE TABLE ScrapRatesDistinct AS SELECT DISTINCT Source_TypeID, Source_Quantity FROM ScrapRates")
## append the Sell order with the lowest price to ScrapRatesDistinctSources which possesses a sufficient order volume remaining to capture the required source material
cur.execute("CREATE TABLE ScrapRatesDistinctSources AS SELECT ScrapRatesDistinct.Source_TypeID, Source_Quantity, min(Source_Price) AS Source_Price, Source_Volume_Remain FROM ScrapRatesDistinct, CurrentSellOrders WHERE ScrapRatesDistinct.Source_TypeID = CurrentSellOrders.Source_TypeID AND Source_Volume_Remain > Source_Quantity GROUP BY ScrapRatesDistinct.Source_TypeID, Source_Quantity;")
## purge unneeded tables
cur.executescript("DROP TABLE CurrentSellOrders; DROP TABLE ScrapRatesDistinct")
Since some items have a portionSize greater than 1, we need to ensure that any Ask/Sell orders that we include in our report possess sufficient remaining volume to permit at least a single reprocessing operation to occur.
With our CurrentSellOrders, CurrentBuyOrders, and ScrapRates tables all prepared, it is time to bring each of these together to create a single formula table which we can use to obtain the values outlined by our goal. Let’s start with ScrapRates and CurrentBuyOrders:
### merge Buy, Sell, and ScrapRates data into single formula table
## left join Buy data with ScrapRates, matching product type_id and quantity
cur.execute("CREATE TABLE FormulaTableProduct AS SELECT * FROM ScrapRatesDistinctProducts LEFT JOIN ScrapRates ON ScrapRates.Product_TypeID = ScrapRatesDistinctProducts.Product_TypeID AND ScrapRates.Product_Quantity = ScrapRatesDistinctProducts.Product_Quantity;")
## calculate maximum product cycles which can be absorbed by the prevailing Buy price
cur.execute("ALTER TABLE FormulaTableProduct ADD COLUMN MaxProductCycle;")
cur.execute("UPDATE FormulaTableProduct SET MaxProductCycle = Product_Volume_Remain / Product_Quantity;")
## calculate revenue per product per cycle
cur.execute("ALTER TABLE FormulaTableProduct ADD COLUMN ProductCycleRevenue;")
cur.execute("UPDATE FormulaTableProduct SET ProductCycleRevenue = Product_Quantity * Product_Price")
## take revenue sum and product per cycle min for each Source ID
cur.execute("CREATE TABLE FormulaTableProductSum AS SELECT name, Source_TypeID, Product_TypeID, Product_Price, min(MaxProductCycle) AS MaxProductCycle, sum(ProductCycleRevenue) AS ProductCycleRevenue FROM FormulaTableProduct GROUP BY Source_TypeID;")
The resulting FormulaTableProductSum contains 9113 rows, each containing values representing a unique item, the revenue attained by reprocessing the item, and the maximum number of reprocessing cycles that the market can sustain for the resulting materials at current prices.
Let’s continue by joining CurrentSellOrders with ForrmulaTableSum:
## inner join Sell data with our formula table
cur.execute("CREATE TABLE FormulaTableSourceProduct AS SELECT name, FormulaTableProductSum.Source_TypeID, Source_Price, Source_Quantity, Source_Volume_Remain, Product_TypeID, Product_Price, MaxProductCycle, ProductCycleRevenue FROM FormulaTableProductSum INNER JOIN ScrapRatesDistinctSources ON FormulaTableProductSum.Source_TypeID = ScrapRatesDistinctSources.Source_TypeID;")
## drop tables that we no longer need
cur.executescript("DROP TABLE FormulaTableProductSum; DROP TABLE FormulaTableProduct;")
### run more calculations against our formula table
cur.execute("ALTER TABLE FormulaTableSourceProduct ADD COLUMN MaxSourceCycle;")
cur.execute("UPDATE FormulaTableSourceProduct SET MaxSourceCycle = Source_Volume_Remain / Source_Quantity;")
cur.execute("ALTER TABLE FormulaTableSourceProduct ADD COLUMN SourceCycleCost;")
cur.execute("UPDATE FormulaTableSourceProduct SET SourceCycleCost = Source_Price * Source_Quantity;")
cur.execute("UPDATE FormulaTableSourceProduct SET ProductCycleRevenue = ProductCycleRevenue * 0.964;")
Our formula table now possesses the costs associated with purchasing the required units of the source material to complete one reprocessing run along with the maximum reprocessing cycles the supply-side can sustain. We’ve also adjusted ProductCycleRevenue to account for a typical sales tax rate of 3.6% which will be incurred when reprocessed materials are sold back to the market.
We now have everything we need to calculate profit and ROI, which can be achieved in a long one-liner:
### generate final output
cur.execute("CREATE TABLE Profit AS SELECT name, Source_TypeID AS type_id, Source_Price AS Price, round(min(MaxProductCycle, (Source_Volume_Remain / Source_Quantity)) - 0.5) * Source_Quantity AS Volume, round(ProductCycleRevenue - SourceCycleCost) * round(min(MaxProductCycle, (Source_Volume_Remain / Source_Quantity)) - 0.5) AS Profit, round(((ProductCycleRevenue - SourceCycleCost) / SourceCycleCost) * 100) AS pctROI FROM FormulaTableSourceProduct WHERE Profit > 0 ORDER BY Profit DESC;")
### export
ResultData = pd.read_sql("SELECT name, type_id, CAST(Price AS INT) AS Price, CAST(Volume AS INT) AS Volume, CAST(Profit AS INT) AS Profit, CAST(pctROI AS INT) AS pctROI FROM Profit;", tempdb)
ResultData.to_csv(f'reprocessing{region}.csv', index=False)
Volume is effectively the lowest common denominator between available inputs and outputs, with rounded values first being subtracted by 0.5 to force the rounding operation to round-down, since discrete reprocessing cycles cannot be completed in fractions.
Without any complicated regression calculations to run, printing our script runtime reveals an extremely fast execution time of ~5 seconds for all regions. Our report results also look great, with the first dozen or-so entries for The Forge providing an opportunity to make a quick 30 million ISK. While these returns do not present the same profit potential as station trading, they present a far better risk profile and shorter turnaround time since we aren’t waiting for our orders to be executed by other players.
Fully-automated reports for each region are now published as ‘Reprocessing’ on the Projects page and a copy of this new script can be downloaded here.
Closing Notes
While I was pleasantly surprised by the efficacy of this reporting strategy, there are a few avenues we can take toward future improvement.
Recency
Right now, the fully-automated reports published on the Projects page for EvE Online are updated once every hour. Reports such as this one are more similar to our Old School Runescape reports which rely heavily on recency and thus are better suited to faster refresh intervals.
While the order book data published by ESI refreshes every 5 minutes, for now I’ll be maintaining my current refresh interval. If this changes in the future, I’ll be sure to update this article and the pages where the reports are published. In the meantime, I would invite anybody who desires a faster refresh interval to self-host this report generation script alongside the database generation script published two articles ago. There are certainly improvements which can be made to my code and I’m happy to receive feedback from anybody interested in using my tools.
My advice for players using the automated reprocessing reports published on this site would be to double-check in-game prices before executing orders, since a lot can change on the market in the span of just an hour.
Patient vs. Impatient Reprocessing
The reporting strategy covered here operates under the assumption that traders will instantly-purchase and instantly-sell all reprocessing inputs and outputs by clearing existing orders. While this approach has its benefits over originating orders, including reduced fees and the elimination of time as a variable, close readers might recognize that I’ve never encouraged this type of trade strategy before in any of my earlier writings.
Generally speaking, clearing orders means paying a premium for instant supply or liquidity. Station Trading in-particular would almost always result in losses if all transactions were instantly executed.
Reprocessing is particularly intriguing as it appears to be the only trade scenario in EvE Online where profit can be attained solely by clearing existing orders, representing the closest in-game analogue to arbitrage. In a sense, the report published in this article represents an ‘impatient’ approach to reprocessing.
The ‘patient’ approach to reprocessing would instead involve originating orders and patiently waiting for them to be cleared by other players. It wouldn’t make much sense originating Sell orders in this context since the markets for scrap materials/minerals generally have pretty tight spreads. Originating buy orders on the other hand isn’t a terrible idea, as this can substantially increase profit margins for traders who are willing to deal with time deltas, the consumption of open order slots, and a marginal introduction of risk.
In the future, I might consider writing this alternative ‘patient’ version of the reprocessing script. If this comes to fruition, I’ll be sure to paste a link here.
Conclusion
By taking a broad view of industry and supply chains in EvE Online, we identified a handful of price floors and ceilings manifesting as a result of underlying game mechanics meshed with free market conditions. We used one of these price floors to generate a useful report and also couched a few ideas for potential future reporting.
In our next article, we will perform broad macroeconomic review and analysis of the various regions of New Eden in-order to set important groundwork before delving into interregional trade. Stay tuned, and thank you for reading!
*Update: “Virtual Markets, Part Ten: Interregional Trade Dynamics” is now live!