-
Notifications
You must be signed in to change notification settings - Fork 613
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PDF import from tastytrade and tastyworks #3493
base: master
Are you sure you want to change the base?
PDF import from tastytrade and tastyworks #3493
Conversation
Hello @stockmarketpotato I have had a little look at the source. We always try (95% all cases) to generate a unified source in the PDF importers. For this we have developed the Contributing Rules. We ask you to read them again and adjust your source code. A good idea is, replace all I think the For taxes and fees, use the V-Bank Importer's identification of the All in all, this is a good pull request and is not meant to be a criticism. 👍🏻 I would check again after your customization. Regards |
Dear Alex,
Sorry, I was a bit too sloppy with the rules. The implementation puts more focus on functionality due to a tight time budget on my side. I will read the Contributing rules and implement the proposed structure.
It depends on the relevance of options trading for PP and whether it is useful to other importers.
I have no experience with Generic Types in Java yet. This is a good chance to learn about it and reduce some of the redundancies in the code.
Thank you 😊
I will try to provide the updated code by the end of CW33. BR |
Indeed, would be interesting to hear @buchen's opinion on whether option trading support might be added to PP (of course, support for short trades is on critical path for this). In either case, in the name of #3303 , I'd suggest to keep OccOsiSymbology.java separate for possible future reuse. This would be one small step towards growing something new cool in PP (like that option support). Of course, very small step, but dozens of such steps done by different people might lead somewhere. Whereas merging it into the importer won't lead anywhere by code duplication (next guy who'll need to deal with option symbols of course won't fish for it in another obscure importer, just write duplicate code again). |
Hello @stockmarketpotato
No problem... Sometimes I catch myself and have to do it again. The good thing about it is that through these repetitions of the source a quick understanding comes and then global changes can also be implemented very quickly. For example with function outsourcing.
The idea of implementing options has been around for a long time, but it's just not that easy to implement. We have been thinking about how best to implement this for several months now and are in the process of identifying the problems and collecting them analytically. Including options simply with "Put &Call" is simply a whole new booking type with a cross behavior and must be considered separately. (Reference account <-> securities account)
I can understand that... 😄 but you can do it. 💪 💯 Regards... and many thx |
I agree. If it is not specific to a one importer, place it where it is reusable. About options in general: As I do not fully understand the implications, my strategy at the moment is: do not prevent the way options are currently maintained, but also do not introduce partial functionality that might required incompatible migration in the future. Personally, I just do not understand enough of the financial mathematics involved. |
I'd prefer to keep the options. The options are added as a Security, like any other, and they do not need any special treatment. Quotes can be obtained from Yahoo using the OCC symbol.
Ok, I'll leave it in the utilities folder for now. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about some suggested changes?
That could make the source code clearer, right?
name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/pdf/TastytradePDFExtractor.java
Outdated
Show resolved
Hide resolved
t.setShares(asShares(v.get("shares"), "en", "US")); | ||
v.put("currency", "USD"); | ||
Money tax = Money.of("USD", asAmount(v.get("tax"), "en", "US")); | ||
ExtractorUtils.checkAndSetTax(tax, t, type.getCurrentContext()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ExtractorUtils.checkAndSetTax(tax, t, type.getCurrentContext());
Maybe you use this method in the same example... see in the other PDF importer
(addTaxesSectionsTransaction / addFeesSectionsTransaction)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you elaborate this a bit more?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could simply look at the other PDF importers and adopt their structure. You will find that they are always structured in the same way in order to achieve standardization. :-)
pdfTransaction.subject(() -> { | ||
AccountTransaction entry = new AccountTransaction(); | ||
entry.setType(AccountTransaction.Type.DIVIDENDS); | ||
entry.setCurrencyCode("USD"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it's better they extract the currency globally from the document instead of setting it manually over and over again.
final DocumentType type = new DocumentType("XXXXXXX", (context, lines) -> {
Pattern pCurrency = Pattern.compile("^.* XXXXXX$, (?<currency>[\\w]{3})$");
for (String line : lines)
{
Matcher mCurrency = pCurrency .matcher(line);
if (mCurrency.matches())
context.put("currency", mCurrency.group("currency"));
break;
}
});
this.addDocumentTyp(type);
After that you can pick them up in the individual sections
.assign((t, v) -> {
Map<String, String> context = type.getCurrentContext();
v.put("currency", context.get("currency"));
...
})
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The currency is always USD. IMHO no change is required.
Is there a way to place it by default in ParsedData?
Is it necessary to always set the currency in the transaction?
Money fee = Money.of("USD", asAmount(v.get("commfee"), "en", "US") | ||
+ asAmount(v.get("tranfee"), "en", "US") | ||
+ asAmount(v.get("addlfee"), "en", "US")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be more helpful to calculate an amount via the Money.class.
This way we have the check that the currencies are also correct to each other.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The currency is always USD. Internally, Money uses a long
member to represent the total amount. The method asAmount(..)
will return a long
. That means using the Money class should not make any difference, computation-wise. But it would be more clear and readable though. The current version is just shorter.
We will do whatever you prefer.
.match("^(?<description>.*).*$") | ||
.match("(^OPTION EXPIRATION.*$|^.*[\\s]+[A-Z0-9]+[\\s]+[\\d]+[\\s]+ASSIGNED.*)") | ||
.match("Security Number:[\\s]+(?<cusip>[A-Z0-9]+)") | ||
.assign((t, v) -> { v.put("currency", "USD"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've converted the file and changed the git configuration as mention on Stackoverflow.
How can I check the line endings?
if (v.get("type").equals("EXPIRED")) { | ||
setTypeAndExpirationNote(t, v); // TODO | ||
} else { | ||
t.setNote("Assigned: Buy To Close"); | ||
t.setType(PortfolioTransaction.Type.BUY); // Assignment means this was a short position | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this function you already set the type "Buy" above.
So if the type should change, then you could, if you want, use the following code
Maybe it's better to use a separate section like
.section("type").optional()
.match("^XXXXX (?<type>YYYYYYY)$")
.assign((t, v) -> {
if ("YYYYYYY".equals(v.get("type")))
t.setType(PortfolioTransaction.Type.SELL);
})
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Depending on the type (expiration or assignment), different transactions are possible.
- Long and short positions can expire "worthless" out of the money. That means whenever we see an expiration transaction in the Account Statement, we need to check whether it was a long or a short position at expiration.
- Only short positions can be assigned, which happens when the contract expires in the money. So we do not need to check the quantity.
Here is a hopefully more readable and understandable version.
if (v.get("type").equals("EXPIRED")) { | |
setTypeAndExpirationNote(t, v); // TODO | |
} else { | |
t.setNote("Assigned: Buy To Close"); | |
t.setType(PortfolioTransaction.Type.BUY); // Assignment means this was a short position | |
} | |
// Expiration means it can be a long or short position that expired OTM | |
if (v.get("type").equals("EXPIRED")) { | |
BigDecimal quantity = ExtractorUtils.convertToNumberBigDecimal(v.get("quantity"), Values.Amount, "en", "US"); | |
if (quantity.signum() == 1) { | |
t.setType(PortfolioTransaction.Type.BUY); | |
t.setNote("Expired: Buy To Close"); | |
} | |
else if (quantity.signum() == -1) { | |
t.setType(PortfolioTransaction.Type.SELL); | |
t.setNote("Expired: Sell To Close"); | |
} | |
// Assignment means this was a short position | |
} else if (v.get("type").equals("ASG")) { | |
t.setNote("Assigned: Buy To Close"); | |
t.setType(PortfolioTransaction.Type.BUY); | |
} |
Double.parseDouble(v.get("strike").toString())); | ||
v.put("tickerSymbol", o.getOccKey()); | ||
v.put("name", o.getName()); | ||
t.setShares(asShares(v.get("quantity").replace("-", ""), "en", "US") * 100); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is better to use BigDecimal for the calculation of the quantity...
t.setShares(asShares(v.get("quantity").replace("-", ""), "en", "US") * 100); | |
BigDecimal shares = asBigDecimal(v.get("shares")); | |
t.setShares(Values.Share.factorize(shares.doubleValue() * 100)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done ✔
@Nirus2000 thanks for your detailed suggestions. I will implement them all. About this ^M thing, I have no clue what's going on there 🦧 |
you need to upload in UNIX style. |
private LocalDateTime asDate(String dateString) | ||
{ | ||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M/d/yy").withLocale(Locale.US); | ||
return LocalDate.parse(dateString, formatter).atStartOfDay(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can remove this part if you add the date format in ExtractorUtils.java.
see --> DATE_FORMATTER_US ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah! Did not see this before.
Done ✔
BigDecimal quantity = ExtractorUtils.convertToNumberBigDecimal(v.get("quantity"), | ||
Values.Amount, "en", "US"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
change to this like the other override commands
@Override
protected BigDecimal asExchangeRate(String value)
{
return ExtractorUtils.convertToNumberBigDecimal(value, Values.Share, "en", "US");
}
Hello @stockmarketpotato Especially the comment from the main developer should make it clear to you that we don't want to process any options at the moment. What do you think about it? Regards |
Hi Alex, I must have misunderstood the comment. It was not that clear to me. Especially since IBFlex importer does in fact process and import option trades Line 737 in 5519b5c
What we can do is separate the options transactions in the importer, e.g. in separate functions or generics, and integrate them at a later point in time. What do you think? Cheers |
Hi @Nirus2000, @buchen, it's been some time since there has been any activity or updates from my end, and I appreciate your patience and the time you've already invested in reviewing it. I wanted to propose removing all parts related to stock options to potentially make it more aligned with the project's goals. Would this change address any concerns and make the PR more acceptable? I value your feedback and am open to any suggestions you might have. Looking forward to hearing from you. BR |
Hi, is there any news on this topic? :) |
@seg-on , have you been able to compile the branch and run it for your documents? |
t.setShares(asShares(v.get("shares"), "en", "US")); | ||
v.put("currency", "USD"); | ||
Money tax = Money.of("USD", asAmount(v.get("tax"), "en", "US")); | ||
ExtractorUtils.checkAndSetTax(tax, t, type.getCurrentContext()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could simply look at the other PDF importers and adopt their structure. You will find that they are always structured in the same way in order to achieve standardization. :-)
@stockmarketpotato Sorry, but I'm not a Java programmer. I can't help you. :/ |
Issue: https://forum.portfolio-performance.info/t/pdf-import-from-tastytrade/24238
PDF import for tastytrade Trade Confirmations and Account Statements including support for Options trading and option's symbols following the OCC symbology standard.
Imports